"use client";
import { useEffect, useRef, useCallback } from "react";
import Alert from "./Alert";
interface ConfirmDialogProps {
open: boolean;
title: string;
message: React.ReactNode;
confirmLabel?: string;
confirmingLabel?: string;
error?: string | null;
busy?: boolean;
onConfirm: () => void;
onCancel: () => void;
}
export default function ConfirmDialog({
open,
title,
message,
confirmLabel = "Delete",
confirmingLabel = "Deleting...",
error,
busy,
onConfirm,
onCancel,
}: ConfirmDialogProps) {
const dialogRef = useRef<HTMLDivElement>(null);
const previousFocusRef = useRef<HTMLElement | null>(null);
useEffect(() => {
if (open) {
previousFocusRef.current = document.activeElement as HTMLElement;
// Focus the dialog panel so keyboard events work immediately
dialogRef.current?.focus();
} else if (previousFocusRef.current) {
previousFocusRef.current.focus();
previousFocusRef.current = null;
}
}, [open]);
const handleKeyDown = useCallback(
(e: React.KeyboardEvent) => {
if (e.key === "Enter" && !busy) {
e.preventDefault();
onConfirm();
return;
}
if (e.key === "Escape" && !busy) {
e.stopPropagation();
onCancel();
return;
}
if (e.key === "Tab") {
const focusable = dialogRef.current?.querySelectorAll<HTMLElement>(
'button:not([disabled]), [href], input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"])'
);
if (!focusable || focusable.length === 0) return;
const first = focusable[0];
const last = focusable[focusable.length - 1];
if (e.shiftKey) {
if (document.activeElement === first || document.activeElement === dialogRef.current) {
e.preventDefault();
last.focus();
}
} else {
if (document.activeElement === last) {
e.preventDefault();
first.focus();
}
}
}
},
[busy, onConfirm, onCancel]
);
if (!open) return null;
return (
<div
className="fixed inset-0 z-50 flex items-center justify-center bg-black/50"
role="dialog"
aria-modal="true"
aria-labelledby="confirm-dialog-title"
onKeyDown={handleKeyDown}
>
<div
ref={dialogRef}
tabIndex={-1}
className="mx-4 w-full max-w-md rounded-lg border border-[var(--border)] bg-[var(--card)] p-6 shadow-lg outline-none"
>
<h3 id="confirm-dialog-title" className="text-lg font-semibold text-[var(--card-foreground)]">{title}</h3>
<div className="mt-2 text-sm text-[var(--muted-foreground)]">{message}</div>
{error && <Alert className="mt-3">{error}</Alert>}
<div className="mt-4 flex items-center justify-end gap-3">
<button
type="button"
onClick={onCancel}
disabled={busy}
className="rounded-md border border-[var(--border)] px-4 py-2 text-sm text-[var(--foreground)] hover:bg-[var(--muted)] transition-colors"
>
Cancel
</button>
<button
type="button"
onClick={onConfirm}
disabled={busy}
className="rounded-md bg-red-600 px-4 py-2 text-sm font-medium text-white hover:bg-red-700 transition-colors disabled:opacity-50"
>
{busy ? confirmingLabel : confirmLabel}
</button>
</div>
</div>
</div>
);
}