1"use client";
2
3import { useEffect, type ReactNode } from "react";
4import { cn } from "@/lib/utils";
5
6export interface ConfirmDialogProps {
7 open: boolean;
8 title: string;
9 description?: ReactNode;
10 confirmLabel?: string;
11 cancelLabel?: string;
12
13 destructive?: boolean;
14 busy?: boolean;
15 onConfirm: () => void;
16 onClose: () => void;
17}
18
19
23export function ConfirmDialog({
24 open,
25 title,
26 description,
27 confirmLabel = "Confirm",
28 cancelLabel = "Cancel",
29 destructive = false,
30 busy = false,
31 onConfirm,
32 onClose
33}: ConfirmDialogProps) {
34 useEffect(() => {
35 if (!open) return;
36 const onKey = (e: KeyboardEvent) => {
37 if (e.key === "Escape") onClose();
38 };
39 window.addEventListener("keydown", onKey);
40 return () => window.removeEventListener("keydown", onKey);
41 }, [open, onClose]);
42
43 if (!open) return null;
44
45 return (
46 <div className="fixed inset-0 z-[90] flex items-center justify-center p-4">
47 <div className="absolute inset-0 bg-black/40" onClick={onClose} aria-hidden />
48 <div
49 role="dialog"
50 aria-modal="true"
51 aria-labelledby="confirm-dialog-title"
52 className="relative w-full max-w-sm rounded-lg border border-border bg-card p-5 shadow-xl"
53 >
54 <h2 id="confirm-dialog-title" className="text-base font-semibold">
55 {title}
56 </h2>
57 {description ? (
58 <div className="mt-2 text-sm text-muted-foreground">{description}</div>
59 ) : null}
60 <div className="mt-5 flex justify-end gap-2">
61 <button
62 type="button"
63 onClick={onClose}
64 disabled={busy}
65 className="rounded-md border border-border bg-background px-3 py-1.5 text-sm font-medium hover:bg-accent disabled:opacity-50"
66 >
67 {cancelLabel}
68 </button>
69 <button
70 type="button"
71 onClick={onConfirm}
72 disabled={busy}
73 className={cn(
74 "rounded-md px-3 py-1.5 text-sm font-medium disabled:opacity-50",
75
76
77
78 destructive ? "bg-red-600 text-white hover:bg-red-700" : "bg-primary text-primary-foreground hover:opacity-90"
79 )}
80 >
81 {busy ? "Working…" : confirmLabel}
82 </button>
83 </div>
84 </div>
85 </div>
86 );
87}
88