import { Sheet, SheetContent } from "@repo/shadcn-ui/components"; // hooks/use-pinned-preview-sheet.ts import * as React from "react"; import { createPortal } from 'react-dom'; type UsePinnedPreviewSheetOptions = { persistKey?: string; // clave localStorage para “pin” widthClass?: string; // ancho del panel: p. ej. "w-[500px]" onOpenChange?: (open: boolean) => void; title?: string | ((item: T | null) => string); // Título del Sheet (no anclado) }; export type PinnedPreviewSheet = { /** Estado */ isOpen: boolean; isPinned: boolean; item: T | null; open: (item: T) => void; close: () => void; togglePin: () => void; /** Añade margen al contenedor de la lista cuando está anclado */ listRightMarginClass: string; /** Renderiza el panel (Sheet o aside) */ Preview: React.FC<{ children: (ctx: { item: T; isPinned: boolean; close: () => void; togglePin: () => void; }) => React.ReactNode }>; }; export function usePinnedPreviewSheet({ persistKey = "preview-pin", widthClass = "w-[500px]", onOpenChange, title, }: UsePinnedPreviewSheetOptions = {}): PinnedPreviewSheet { const [isOpen, setOpen] = React.useState(false); const [item, setItem] = React.useState(null); const [isPinned, setPinned] = React.useState(() => { try { return localStorage.getItem(persistKey) === "1"; } catch { return false; } }); // recordar/restaurar foco para accesibilidad const lastFocused = React.useRef(null); const rememberFocus = () => { lastFocused.current = document.activeElement as HTMLElement | null; }; const restoreFocus = () => { lastFocused.current?.focus?.(); }; const open = React.useCallback((next: T) => { rememberFocus(); setItem(next); setOpen(true); onOpenChange?.(true); }, [onOpenChange]); const close = React.useCallback(() => { if (isPinned) return; setOpen(false); onOpenChange?.(false); setTimeout(restoreFocus, 0); }, [isPinned, onOpenChange]); const togglePin = React.useCallback(() => { setPinned((p) => { const n = !p; try { localStorage.setItem(persistKey, n ? "1" : "0"); } catch { } return n; }); }, [persistKey]); // Bloquea scroll solo en modo Sheet React.useEffect(() => { if (isOpen && !isPinned) { const prev = document.body.style.overflow; document.body.style.overflow = "hidden"; return () => { document.body.style.overflow = prev; }; } }, [isOpen, isPinned]); // Cerrar con ESC solo en modo Sheet React.useEffect(() => { if (!isOpen || isPinned) return; const onKey = (e: KeyboardEvent) => { if (e.key === "Escape") close(); }; window.addEventListener("keydown", onKey); return () => window.removeEventListener("keydown", onKey); }, [isOpen, isPinned, close]); const listRightMarginClass = isPinned ? "mr-[500px]" : ""; // ajusta si cambias widthClass const HeaderTitle = React.useMemo(() => { if (!item) return ""; if (typeof title === "function") return title(item); return title ?? ""; }, [item, title]); const Preview: PinnedPreviewSheet["Preview"] = React.useCallback(({ children }) => { if (!item) return null; // Panel anclado: aside estático sin overlay if (isPinned) { return createPortal( , document.body ); } // Panel no anclado: Sheet de shadcn/ui (con overlay y accesibilidad) return createPortal( (o ? setOpen(true) : close())}> {children({ item, isPinned: false, close, togglePin })} , document.body ); }, [item, isPinned, isOpen, widthClass, close, togglePin]); return { isOpen, isPinned, item, open, close, togglePin, listRightMarginClass, Preview }; }