import { createContext, forwardRef, useCallback, useContext, useId, useLayoutEffect, useRef, useState, type HTMLAttributes, type ReactNode, } from "react"; import { createPortal } from "react-dom"; import { X } from "lucide-react"; import { Button } from "../components/ui/button"; import "./WorkAreaPanel.css"; import { AdministrationHeader } from "./administration/AdministrationPage"; function classes(base: string, className?: string) { return className ? `${base} ${className}` : base; } const WorkAreaPanelHostContext = createContext(null); interface WorkAreaPanelHostProviderProps { host: HTMLElement | null; children: ReactNode; } export function WorkAreaPanelHostProvider({ host, children, }: WorkAreaPanelHostProviderProps) { return ( {children} ); } export interface WorkAreaPanelHostProps extends HTMLAttributes {} /** Hosts modeless management panels inside the central application work area. */ export const WorkAreaPanelHost = forwardRef( function WorkAreaPanelHost({ className, children, ...props }, forwardedRef) { const [host, setHost] = useState(null); const assignRef = useCallback((node: HTMLDivElement | null) => { setHost(node); if (typeof forwardedRef === "function") forwardedRef(node); else if (forwardedRef) forwardedRef.current = node; }, [forwardedRef]); return (
{children}
); }, ); /** * Keeps a modeless work-area panel keyboard-reachable without trapping focus. * Only a panel containing focus responds to Escape, so stacked panels close one * at a time and focus returns to the control that opened the panel. */ export function useModelessPanel(open: boolean, onClose: () => void, busy = false) { const panelRef = useRef(null); const stateRef = useRef({ busy, onClose }); stateRef.current = { busy, onClose }; useLayoutEffect(() => { if (!open) return; const origin = document.activeElement instanceof HTMLElement ? document.activeElement : null; const focusTimer = window.setTimeout(() => { const panel = panelRef.current; const target = panel?.querySelector("[data-work-area-panel-close]") ?? panel; target?.focus(); }, 0); const onKeyDown = (event: KeyboardEvent) => { const panel = panelRef.current; if ( event.key !== "Escape" || event.defaultPrevented || stateRef.current.busy || !panel?.contains(document.activeElement) ) return; event.preventDefault(); stateRef.current.onClose(); }; document.addEventListener("keydown", onKeyDown); return () => { const activeElement = document.activeElement; const shouldRestoreFocus = activeElement === document.body || Boolean(activeElement && panelRef.current?.contains(activeElement)); window.clearTimeout(focusTimer); document.removeEventListener("keydown", onKeyDown); if (shouldRestoreFocus) { window.setTimeout(() => { if (origin?.isConnected) origin.focus(); }, 0); } }; }, [open]); return panelRef; } export interface WorkAreaPanelProps { open: boolean; mode?: "panel" | "page"; ariaLabel?: string; eyebrow?: ReactNode; title: ReactNode; description?: ReactNode; onClose: () => void; closeLabel?: string; children: ReactNode; bodyClassName?: string; footer?: ReactNode; footerClassName?: string; busy?: boolean; width?: "standard" | "wide" | "expanded"; className?: string; catalogMarkers?: boolean; } /** * Shared presentational shell for management, history, review and edit panels * that live inside the application's central work area. */ export const WorkAreaPanel = forwardRef( function WorkAreaPanel( { open, mode = "panel", ariaLabel, eyebrow, title, description, onClose, closeLabel = "Close panel", children, bodyClassName, footer, footerClassName, busy = false, width = "standard", className, catalogMarkers = false, }, forwardedRef, ) { const panelHost = useContext(WorkAreaPanelHostContext); const titleId = useId(); const descriptionId = useId(); const panelRef = useModelessPanel(mode === "panel" && open, onClose, busy); const assignRef = (node: HTMLElement | null) => { panelRef.current = node; if (typeof forwardedRef === "function") forwardedRef(node); else if (forwardedRef) forwardedRef.current = node; }; if (!open) return null; const panel = mode === "page" ? (
{children}
{footer ? (
{footer}
) : null}
) : ( ); return mode === "panel" && panelHost ? createPortal(panel, panelHost) : panel; }, );