/** * @file ConfirmModal.tsx * @description Centered confirmation dialog for destructive or irreversible * actions (delete webhook, remove alert rule, etc.). Replaces `window.confirm` * with themed UI that matches the dashboard and distinguishes a loading (`busy`) * confirm button from one refused outright (`disabled`). * * ## Dismissal * Clicking the backdrop, pressing Escape, or clicking the X cancels. The confirm * button can be styled non-destructive for neutral confirmations. * * ## Accessibility * Focus moves to Cancel on open (safer default), Tab cycles within the dialog, * Escape cancels, and focus restores to the previously focused element on close * (the shared `hooks/useDialog.ts` contract). * * @author Nguyễn Ngọc Trí Vĩ */ import { useId, useRef, type ReactNode } from "react"; import { useDialog } from "../hooks/useDialog"; import { AlertTriangle, X } from "lucide-react"; /** Props for {@link ConfirmModal}. */ export interface ConfirmModalProps { /** When false, nothing is rendered. */ open: boolean; /** Dialog heading. */ title: string; /** Optional supporting message below the title. */ message?: string; /** Primary action label (e.g. "Delete"). */ confirmLabel: string; /** Secondary cancel label. */ cancelLabel: string; /** When true (default), confirm button uses red destructive styling. */ destructive?: boolean; /** Disables confirm while an async delete is in flight. */ busy?: boolean; /** * Disables confirm because the action is not permitted right now (a blocker, an * unmet checkbox, facts that failed to load). Distinct from `busy`: nothing is * in flight, so callers must not conflate the two — passing a refusal as `busy` * makes a blocked action read as perpetually loading. */ disabled?: boolean; /** Optional action-specific facts shown before the confirmation controls. */ children?: ReactNode; /** Called when the user confirms. */ onConfirm: () => void; /** Called on cancel, backdrop click, Escape, or X. */ onCancel: () => void; } /** * Modal confirmation overlay. * @param props See {@link ConfirmModalProps}. */ export function ConfirmModal({ open, title, message, confirmLabel, cancelLabel, destructive = true, busy = false, disabled = false, children, onConfirm, onCancel, }: ConfirmModalProps) { const titleId = useId(); const messageId = useId(); const panelRef = useRef(null); const cancelRef = useRef(null); useDialog(open, panelRef, onCancel, cancelRef); if (!open) return null; return (
e.stopPropagation()} role="dialog" aria-modal="true" aria-labelledby={titleId} aria-describedby={message ? messageId : undefined} >
{destructive && (
)}

{title}

{message && (

{message}

)} {children}
); }