'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 evidence drawer, the terminal filter/inspector sheets and the mobile "More" menu. * `side="auto"` (default): right-side panel ≥ lg, bottom sheet below. Focus trap, Esc, backdrop click, body scroll lock, * `aria-modal`. Renders nothing when closed (server/client markup match: mount it always, drive `open`). */ export function Sheet({ open, onClose, title, eyebrow, children, side = 'auto', width = 'lg:w-[26rem]', className, footer, labelledBy, id, }: { open: boolean; onClose: () => void; title?: ReactNode; eyebrow?: ReactNode; children: ReactNode; side?: 'auto' | 'right' | 'bottom'; /** Tailwind width class for the right-side panel (`lg:w-[26rem]` for `auto`, `w-[…]` when `side="right"`). */ width?: string; className?: string; footer?: ReactNode; labelledBy?: string; id?: string; }) { 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'; const bottom = side === 'bottom'; return (
); }