'use client'; import { useState, useEffect, useRef } from 'react'; type ModalSize = 'sm' | 'md' | 'lg' | 'xl' | 'full'; const SIZE_CLASSES: Record = { sm: 'max-w-md', md: 'max-w-2xl', lg: 'max-w-4xl', xl: 'max-w-5xl', full: 'max-w-7xl', }; interface ModalProps { open: boolean; onClose: () => void; title: string; description?: string; children?: React.ReactNode; variant?: 'default' | 'danger'; confirmLabel?: string; cancelLabel?: string; onConfirm?: () => void | Promise; loading?: boolean; size?: ModalSize; } export function Modal({ open, onClose, title, description, children, variant = 'default', confirmLabel, cancelLabel = 'Cancel', onConfirm, loading, size = 'sm', }: ModalProps) { const overlayRef = useRef(null); const firstFocusRef = useRef(null); const [processing, setProcessing] = useState(false); async function handleConfirm() { if (!onConfirm || processing) return; setProcessing(true); try { await onConfirm(); } catch { // Let consumer handle errors via their own toast — just stop processing } finally { setProcessing(false); } } useEffect(() => { if (open) { firstFocusRef.current?.focus(); document.body.style.overflow = 'hidden'; } return () => { document.body.style.overflow = ''; }; }, [open]); useEffect(() => { function handleKey(e: KeyboardEvent) { if (e.key === 'Escape' && open) onClose(); } window.addEventListener('keydown', handleKey); return () => window.removeEventListener('keydown', handleKey); }, [open, onClose]); if (!open) return null; return (
{ if (e.target === overlayRef.current) onClose(); }} >
{description && (

{description}

)} {children &&
{children}
} {(onConfirm || cancelLabel) && (
{onConfirm && ( )}
)}
); }