"use client"; import { useEffect, useRef } from "react"; type Props = { open: boolean; onCancel: () => void; onConfirm: () => void; title: string; message: React.ReactNode; confirmLabel?: string; destructive?: boolean; pending?: boolean; }; /** * Centered modal confirmation dialog. Uses the native element * so we get Esc-to-close, focus trapping, and the ::backdrop pseudo for * the scrim — no a11y tax we'd pay rolling our own. Backdrop click * cancels. */ export default function ConfirmDialog({ open, onCancel, onConfirm, title, message, confirmLabel = "Confirm", destructive = false, pending = false, }: Props) { const ref = useRef(null); useEffect(() => { const dialog = ref.current; if (!dialog) return; if (open && !dialog.open) { dialog.showModal(); } else if (!open && dialog.open) { dialog.close(); } }, [open]); // Lock body scroll while open. Native doesn't do this in all // browsers (notably iOS Safari), so background scroll can leak through // when scrolling on the dialog content. We restore the previous value // on close so other modals stacking later don't regress this. useEffect(() => { if (!open) return; const previous = document.body.style.overflow; document.body.style.overflow = "hidden"; return () => { document.body.style.overflow = previous; }; }, [open]); return ( { // Click on the dialog background (not the inner form) cancels. if (e.target === ref.current) onCancel(); }} className="m-auto w-[min(92vw,440px)] rounded-2xl bg-white p-0 ring-1 ring-zinc-200/60 backdrop:bg-zinc-900/50 backdrop:backdrop-blur-sm" >
{ e.preventDefault(); onConfirm(); }} className="flex flex-col gap-4 p-6" >

{title}

{message}
); }