'use client'; import { useCallback, useEffect, useRef, type PointerEvent, type ReactNode } from 'react'; import { X } from 'lucide-react'; import { usePathname } from 'next/navigation'; import type { PublicPortfolio } from '@/server/content'; import { useDismissCaseStudy } from './PortfolioNavigation'; import { CaseStudyShare } from './CaseStudyShare'; import { useDrawerDrag } from './useDrawerDrag'; import styles from './CaseStudyDrawer.module.css'; export function CaseStudyDrawer({ children, portfolio }: { children: ReactNode; portfolio: PublicPortfolio }) { const copy = portfolio.messages.projects; const pathname = usePathname(); const dismissRoute = useDismissCaseStudy(); const dialogRef = useRef(null); const sheetRef = useRef(null); const scrollRef = useRef(null); const backdropPressed = useRef(false); const closing = useRef(false); const returnFocusRef = useRef(null); const backgroundScrollY = useRef(0); const finishDismiss = useCallback(() => { const dialog = dialogRef.current; if (dialog?.open) dialog.close(); returnFocusRef.current?.focus({ preventScroll: true }); dismissRoute(); requestAnimationFrame(() => { requestAnimationFrame(() => { window.scrollTo({ top: backgroundScrollY.current, behavior: 'instant' }); returnFocusRef.current?.focus({ preventScroll: true }); }); }); }, [dismissRoute]); const requestDismiss = useCallback((animate = true) => { if (closing.current) return; closing.current = true; const dialog = dialogRef.current; const sheet = sheetRef.current; const reduceMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches; if (!animate || reduceMotion || !dialog || !sheet) { finishDismiss(); return; } dialog.dataset.state = 'closing'; sheet.style.transition = 'transform 180ms var(--motion-ease-out)'; sheet.style.transform = 'translateY(100%)'; window.setTimeout(finishDismiss, 180); }, [finishDismiss]); const dragHandlers = useDrawerDrag({ sheetRef, onDismiss: requestDismiss }); useEffect(() => { const dialog = dialogRef.current; if (!dialog) return; const root = document.documentElement; const previousOverflow = root.style.overflow; const skipMovement = root.dataset.caseStudyInput === 'keyboard'; const recordedScrollY = Number(root.dataset.caseStudyScrollY); const activeElement = document.activeElement; returnFocusRef.current = activeElement instanceof HTMLElement && activeElement !== document.body ? activeElement : null; backgroundScrollY.current = Number.isFinite(recordedScrollY) ? recordedScrollY : window.scrollY; delete root.dataset.caseStudyInput; dialog.dataset.motion = skipMovement ? 'none' : 'standard'; dialog.showModal(); root.style.overflow = 'hidden'; requestAnimationFrame(() => { dialog.dataset.state = 'open'; dialog.querySelector('[data-case-study-initial-focus]')?.focus(); }); return () => { root.style.overflow = previousOverflow; if (dialog.open) dialog.close(); if (returnFocusRef.current) { const restoreScroll = () => window.scrollTo({ top: backgroundScrollY.current, behavior: 'instant', }); restoreScroll(); window.setTimeout(restoreScroll, 120); window.setTimeout(restoreScroll, 600); } }; }, []); useEffect(() => { scrollRef.current?.scrollTo({ top: 0, behavior: 'auto' }); requestAnimationFrame(() => { dialogRef.current?.querySelector('[data-case-study-initial-focus]')?.focus(); }); }, [pathname]); const handleBackdropDown = (event: PointerEvent) => { backdropPressed.current = event.target === event.currentTarget; }; const handleBackdropUp = (event: PointerEvent) => { if (backdropPressed.current && event.target === event.currentTarget) requestDismiss(); backdropPressed.current = false; }; return ( { event.preventDefault(); requestDismiss(false); }} onPointerDown={handleBackdropDown} onPointerUp={handleBackdropUp} >
{copy.drawerLabel}
{children}
); }