👁️
1import { AlertTriangle } from "lucide-react";
2import { useEffect, useId, useState } from "react";
3
4interface DeleteDeckDialogProps {
5 deckName: string;
6 isOpen: boolean;
7 onClose: () => void;
8 onConfirm: () => void;
9 isDeleting?: boolean;
10}
11
12export function DeleteDeckDialog({
13 deckName,
14 isOpen,
15 onClose,
16 onConfirm,
17 isDeleting = false,
18}: DeleteDeckDialogProps) {
19 const [confirmText, setConfirmText] = useState("");
20 const titleId = useId();
21 const inputId = useId();
22
23 const isMatch = confirmText === deckName;
24
25 useEffect(() => {
26 if (!isOpen) {
27 setConfirmText("");
28 }
29 }, [isOpen]);
30
31 useEffect(() => {
32 const handleKeyDown = (e: KeyboardEvent) => {
33 if (e.key === "Escape" && !isDeleting) {
34 onClose();
35 }
36 };
37
38 if (isOpen) {
39 document.addEventListener("keydown", handleKeyDown);
40 return () => document.removeEventListener("keydown", handleKeyDown);
41 }
42 }, [isOpen, isDeleting, onClose]);
43
44 if (!isOpen) return null;
45
46 const handleSubmit = (e: React.FormEvent) => {
47 e.preventDefault();
48 if (isMatch && !isDeleting) {
49 onConfirm();
50 }
51 };
52
53 return (
54 <>
55 {/* Backdrop */}
56 <div
57 className="fixed inset-0 bg-black/50 z-40"
58 onClick={isDeleting ? undefined : onClose}
59 aria-hidden="true"
60 />
61
62 {/* Dialog */}
63 <div className="fixed inset-0 z-50 flex items-center justify-center p-4 pointer-events-none">
64 <div
65 role="alertdialog"
66 aria-modal="true"
67 aria-labelledby={titleId}
68 className="bg-white dark:bg-zinc-900 rounded-lg shadow-2xl max-w-md w-full pointer-events-auto border border-gray-300 dark:border-zinc-600"
69 >
70 {/* Header */}
71 <div className="flex items-center gap-3 p-6 border-b border-gray-200 dark:border-zinc-700">
72 <div className="p-2 bg-red-100 dark:bg-red-900/30 rounded-full">
73 <AlertTriangle className="w-5 h-5 text-red-600 dark:text-red-400" />
74 </div>
75 <h2
76 id={titleId}
77 className="text-xl font-bold text-gray-900 dark:text-white"
78 >
79 Delete deck
80 </h2>
81 </div>
82
83 {/* Body */}
84 <form onSubmit={handleSubmit} className="p-6 space-y-4">
85 <p className="text-gray-600 dark:text-zinc-300">
86 This action <strong>cannot</strong> be undone. This will
87 permanently delete the deck and all its cards.
88 </p>
89
90 <div>
91 <label
92 htmlFor={inputId}
93 className="block text-sm text-gray-700 dark:text-zinc-300 mb-2"
94 >
95 Please type{" "}
96 <span className="font-mono font-semibold text-gray-900 dark:text-white bg-gray-100 dark:bg-zinc-800 px-1.5 py-0.5 rounded">
97 {deckName}
98 </span>{" "}
99 to confirm.
100 </label>
101 <input
102 id={inputId}
103 type="text"
104 value={confirmText}
105 onChange={(e) => setConfirmText(e.target.value)}
106 disabled={isDeleting}
107 autoComplete="off"
108 className="w-full px-4 py-2 bg-white dark:bg-zinc-800 border border-gray-300 dark:border-zinc-600 rounded-lg text-gray-900 dark:text-white focus:outline-none focus:ring-2 focus:ring-red-500 focus:border-transparent disabled:opacity-50 disabled:cursor-not-allowed"
109 placeholder="Deck name"
110 />
111 </div>
112
113 {/* Footer */}
114 <div className="flex items-center justify-end gap-3 pt-2">
115 <button
116 type="button"
117 onClick={onClose}
118 disabled={isDeleting}
119 className="px-4 py-2 bg-gray-200 dark:bg-zinc-800 hover:bg-gray-300 dark:hover:bg-zinc-700 text-gray-900 dark:text-white rounded-lg transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
120 >
121 Cancel
122 </button>
123 <button
124 type="submit"
125 disabled={!isMatch || isDeleting}
126 className="px-4 py-2 bg-red-600 hover:bg-red-700 disabled:bg-red-600/50 disabled:cursor-not-allowed text-white rounded-lg transition-colors"
127 >
128 {isDeleting ? "Deleting..." : "Delete this deck"}
129 </button>
130 </div>
131 </form>
132 </div>
133 </div>
134 </>
135 );
136}