'use client'; import { X } from 'lucide-react'; import { type ReactNode, useEffect, useRef } from 'react'; import { cn } from '@/lib/cn'; /** * Sheet / drawer primitive shared by the event drawer, filter sheets and the mobile "More" menu. * `side="auto"` (default): right-side panel ≥ lg, bottom sheet below. Focus trap, Esc, backdrop click, body scroll lock. */ export function Sheet({ open, onClose, title, eyebrow, children, side = 'auto', width = 'lg:w-[30rem]', className, footer, id, headerExtra, }: { open: boolean; onClose: () => void; title?: ReactNode; eyebrow?: ReactNode; children: ReactNode; side?: 'auto' | 'right' | 'bottom'; width?: string; className?: string; footer?: ReactNode; id?: string; headerExtra?: ReactNode; }) { const panel = useRef(null); const closeBtn = useRef(null); const restore = useRef(null); useEffect(() => { if (!open) return; restore.current = document.activeElement as HTMLElement | null; const prevOverflow = document.body.style.overflow; document.body.style.overflow = 'hidden'; const t = setTimeout(() => closeBtn.current?.focus(), 20); const onKey = (e: KeyboardEvent) => { if (e.key === 'Escape') { e.stopPropagation(); onClose(); } else if (e.key === 'Tab' && panel.current) { const nodes = panel.current.querySelectorAll('a[href], button:not([disabled]), input, select, textarea, [tabindex]:not([tabindex="-1"])'); if (!nodes.length) return; const first = nodes[0] as HTMLElement; const last = nodes[nodes.length - 1] as HTMLElement; if (e.shiftKey && document.activeElement === first) { e.preventDefault(); last.focus(); } else if (!e.shiftKey && document.activeElement === last) { e.preventDefault(); first.focus(); } } }; document.addEventListener('keydown', onKey); return () => { clearTimeout(t); document.removeEventListener('keydown', onKey); document.body.style.overflow = prevOverflow; restore.current?.focus?.(); }; }, [open, onClose]); if (!open) return null; const right = side === 'right'; return (
); }