import { useEffect, useId, useRef } from 'react'; import type { ReactNode } from 'react'; import { OVERLAY_Z_CONFIRM } from '../../lib/overlayLayers'; import { __ } from '../../lib/i18n'; export type ConfirmDialogProps = { open: boolean; /** `confirm` shows primary + cancel; `alert` shows a single dismiss button. */ type?: 'confirm' | 'alert'; title: string; children: ReactNode; confirmLabel?: string; cancelLabel?: string; dismissLabel?: string; variant?: 'default' | 'danger'; busy?: boolean; onClose: () => void; onConfirm?: () => void | Promise; }; /** * Accessible modal for confirmations and alerts (replaces `window.confirm` / `window.alert`). */ export function ConfirmDialog({ open, type = 'confirm', title, children, confirmLabel = __('Confirm', 'sikshya'), cancelLabel = __('Cancel', 'sikshya'), dismissLabel = __('OK', 'sikshya'), variant = 'default', busy = false, onClose, onConfirm, }: ConfirmDialogProps) { const titleId = useId(); const panelRef = useRef(null); const previouslyFocused = useRef(null); useEffect(() => { if (!open) { return; } previouslyFocused.current = document.activeElement as HTMLElement | null; const t = window.setTimeout(() => { panelRef.current?.querySelector('button:not([disabled])')?.focus(); }, 0); return () => window.clearTimeout(t); }, [open]); useEffect(() => { if (!open) { return; } const onKey = (e: KeyboardEvent) => { if (e.key === 'Escape' && !busy) { e.preventDefault(); onClose(); } }; document.addEventListener('keydown', onKey); return () => document.removeEventListener('keydown', onKey); }, [open, busy, onClose]); useEffect(() => { if (!open && previouslyFocused.current?.focus) { previouslyFocused.current.focus(); } }, [open]); if (!open) { return null; } const isAlert = type === 'alert'; const danger = variant === 'danger'; const primaryBtn = danger && !isAlert ? 'bg-red-600 text-white hover:bg-red-700 focus:ring-red-500/40 dark:bg-red-600 dark:hover:bg-red-500' : [ // Fallback first: if `brand` tokens are not present in the compiled CSS // the button should still remain visible (avoid white-on-white). 'bg-slate-900 text-white hover:bg-slate-800 focus:ring-slate-500/40 dark:bg-slate-100 dark:text-slate-900 dark:hover:bg-white', // Preferred brand styling (overrides fallback when available). 'bg-brand-600 hover:bg-brand-700 focus:ring-brand-500/40 dark:bg-brand-500 dark:hover:bg-brand-400', ].join(' '); return (
) : ( <> )}
); }