"use client"; import type { ReactNode } from "react"; import * as Dialog from "@radix-ui/react-dialog"; import { X } from "lucide-react"; const SIZE: Record = { sm: "max-w-md", md: "max-w-lg", lg: "max-w-2xl", xl: "max-w-4xl", }; /** * Token-skinned dialog built on Radix — gives focus-trap, Escape, scroll-lock, * `aria-modal`, and focus restoration for free, so the app stops hand-rolling * these per modal. Visual layer stays 100% ours (tokens + tailwindcss-animate). */ export function Modal({ open, onClose, title, description, size = "md", children, footer, closeOnOutside = true, dismissable = true, }: { open: boolean; onClose: () => void; title: ReactNode; description?: ReactNode; size?: keyof typeof SIZE; children: ReactNode; footer?: ReactNode; /** allow closing by clicking the backdrop */ closeOnOutside?: boolean; /** allow Escape / close button (set false while a blocking op runs) */ dismissable?: boolean; }) { return ( !o && dismissable && onClose()}> (!closeOnOutside || !dismissable) && e.preventDefault()} onEscapeKeyDown={(e) => !dismissable && e.preventDefault()} className={`fixed left-1/2 top-1/2 z-[101] w-[calc(100vw-2rem)] ${SIZE[size]} -translate-x-1/2 -translate-y-1/2 overflow-hidden rounded-2xl border border-edge bg-panel shadow-lg data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=open]:fade-in-0 data-[state=closed]:fade-out-0 data-[state=open]:zoom-in-95 data-[state=closed]:zoom-out-95`} >
{title} {description && ( {description} )}
{dismissable && ( )}
{children}
{footer && (
{footer}
)}
); }