import { type MouseEvent, type ReactNode, type RefObject, useEffect, useId, useRef, } from "react"; import { createPortal } from "react-dom"; import { useFocusTrap } from "../../hooks/useFocusTrap"; export type DialogSize = "sm" | "md" | "lg" | "xl"; export interface DialogProps { open: boolean; title: ReactNode; children: ReactNode; description?: ReactNode; footer?: ReactNode; onOpenChange?: (open: boolean) => void; initialFocusRef?: RefObject; closeOnOverlayClick?: boolean; closeOnEscape?: boolean; showCloseButton?: boolean; closeLabel?: string; size?: DialogSize; className?: string; panelClassName?: string; overlayClassName?: string; labelledById?: string; describedById?: string; } const sizeClasses: Record = { sm: "max-w-md", md: "max-w-2xl", lg: "max-w-4xl", xl: "max-w-6xl", }; let bodyScrollLockCount = 0; let previousBodyOverflow = ""; function cx(...classes: Array): string { return classes.filter(Boolean).join(" "); } function lockBodyScroll(ownerDocument: Document): () => void { const { body } = ownerDocument; if (!body) { return () => undefined; } if (bodyScrollLockCount === 0) { previousBodyOverflow = body.style.overflow; body.style.overflow = "hidden"; } bodyScrollLockCount += 1; let released = false; return () => { if (released) { return; } released = true; bodyScrollLockCount = Math.max(0, bodyScrollLockCount - 1); if (bodyScrollLockCount === 0) { body.style.overflow = previousBodyOverflow; previousBodyOverflow = ""; } }; } export function Dialog({ open, title, children, description, footer, onOpenChange, initialFocusRef, closeOnOverlayClick = true, closeOnEscape = true, showCloseButton = true, closeLabel = "Close dialog", size = "md", className, panelClassName, overlayClassName, labelledById, describedById, }: DialogProps) { const generatedId = useId(); const panelRef = useRef(null); const titleId = labelledById ?? `${generatedId}-title`; const descriptionId = description ? describedById ?? `${generatedId}-description` : undefined; useFocusTrap(panelRef, { enabled: open, initialFocus: initialFocusRef, onEscapeKey: closeOnEscape ? () => onOpenChange?.(false) : undefined, restoreFocus: true, preventScroll: true, }); useEffect(() => { if (!open || typeof document === "undefined") { return undefined; } return lockBodyScroll(document); }, [open]); if (!open || typeof document === "undefined") { return null; } const handleOverlayMouseDown = (event: MouseEvent) => { if (closeOnOverlayClick && event.target === event.currentTarget) { onOpenChange?.(false); } }; return createPortal(

{title}

{description ? (
{description}
) : null}
{showCloseButton ? ( ) : null}
{children}
{footer ? (
{footer}
) : null}
, document.body, ); }