/* ───────────────────────────────────────────────────────────────────────── PixelModal — dialog with title bar (pixel surface = old-school window). ───────────────────────────────────────────────────────────────────────── */ import React, { forwardRef, useCallback, useId, useRef, useState } from 'react'; import { Surface, cn, surfaceClasses, useEffectiveSurface, CloseIcon, } from '../common'; import { usePxlKitLocale } from '../locale'; import { PixelPortal } from '../overlay-foundation/PixelPortal'; import { OverlayBackdrop } from './_internal/OverlayBackdrop'; import { useFocusTrap } from '../hooks/useFocusTrap'; import { useScrollLock } from '../hooks/useScrollLock'; import { useEscape } from '../hooks/useEscape'; import { useReducedMotion } from '../hooks/useReducedMotion'; /** Public prop bag for {@link PixelModal}. */ export interface PixelModalProps { /** Whether the modal is currently visible. */ open: boolean; /** Modal title shown in the header. */ title: string; /** Modal body content. */ children: React.ReactNode; /** Called when the user requests to close the modal. */ onClose: () => void; /** Width preset. Default `'md'`. */ size?: 'sm' | 'md' | 'lg' | 'xl' | 'full'; /** Visual surface override. Falls back to nearest `` surface. */ surface?: Surface; /** Optional override for the close button's accessible label. */ closeLabel?: string; /** Optional footer node, rendered at the bottom separated by a surface-aware divider. */ footer?: React.ReactNode; /** Optional description, wired via `aria-describedby` for AT users. */ description?: React.ReactNode; /** * When provided, the close button awaits this promise (and shows a loading * state) before the consumer-controlled `onClose` is invoked. Lets callers * persist or animate-out before unmounting. */ asyncClose?: () => Promise; /** Optional portal container override. Defaults to `document.body`. */ container?: HTMLElement | null; } export const PixelModal = forwardRef(function PixelModal({ open, title, children, onClose, size = 'md', surface: surfaceProp, closeLabel = 'Close', footer, description, asyncClose, container, }, forwardedRef) { const surface = useEffectiveSurface(surfaceProp); const s = surfaceClasses(surface); const { upper } = usePxlKitLocale(); const titleId = useId(); const descId = useId(); const dialogRef = useRef(null); const [closing, setClosing] = useState(false); const reducedMotion = useReducedMotion(); const pulseClass = reducedMotion ? '' : 'animate-pulse'; const setRefs = useCallback((node: HTMLDivElement | null) => { (dialogRef as React.MutableRefObject).current = node; if (typeof forwardedRef === 'function') forwardedRef(node); else if (forwardedRef && typeof forwardedRef === 'object') { (forwardedRef as React.MutableRefObject).current = node; } }, [forwardedRef]); useFocusTrap(open, dialogRef); useScrollLock(open); useEscape(() => { if (!closing) void handleClose(); }, open); async function handleClose() { if (asyncClose) { try { setClosing(true); await asyncClose(); } finally { setClosing(false); onClose(); } return; } onClose(); } if (!open) return null; const maxW = size === 'sm' ? 'max-w-sm' : size === 'lg' ? 'max-w-2xl' : size === 'xl' ? 'max-w-4xl' : size === 'full' ? 'max-w-[95vw] max-h-[95vh] overflow-y-auto' : 'max-w-md'; const dividerClass = surface === 'pixel' ? 'border-t-2 border-retro-border' : 'border-t border-retro-border'; return (
{ if (!closing) void handleClose(); }} />
{surface === 'pixel' ? ( <>

{upper(title)}

{description &&

{description}

} {children}
{footer && (
{footer}
)} ) : ( <>

{upper(title)}

{description &&

{description}

}
{children}
{footer && (
{footer}
)} )}
); }); PixelModal.displayName = 'PixelModal';