11import { useEffect , useRef } from "react" ;
2- import { AlertTriangle , X , Loader2 } from "lucide-react" ;
2+ import { AlertTriangle , X , Loader2 , CheckCircle2 , Info , AlertCircle , LucideIcon } from "lucide-react" ;
3+
4+ type ModalVariant = "danger" | "success" | "warning" | "info" ;
35
46interface ConfirmModalProps {
57 isOpen : boolean ;
@@ -10,9 +12,37 @@ interface ConfirmModalProps {
1012 onConfirm : ( ) => void ;
1113 onCancel : ( ) => void ;
1214 isLoading ?: boolean ;
13- danger ?: boolean ;
15+ variant ?: ModalVariant ;
16+ icon ?: LucideIcon ;
1417}
1518
19+ const VARIANT_CONFIG = {
20+ danger : {
21+ bg : "bg-[var(--cd-danger-subtle)]" ,
22+ text : "text-[var(--cd-danger)]" ,
23+ btn : "bg-[var(--cd-danger)] hover:bg-[var(--cd-danger-text)]" ,
24+ icon : AlertTriangle ,
25+ } ,
26+ success : {
27+ bg : "bg-[var(--cd-success-subtle)]" ,
28+ text : "text-[var(--cd-success)]" ,
29+ btn : "bg-[var(--cd-success)] hover:bg-[var(--cd-success-text)]" ,
30+ icon : CheckCircle2 ,
31+ } ,
32+ warning : {
33+ bg : "bg-[var(--cd-warning-subtle)]" ,
34+ text : "text-[var(--cd-warning)]" ,
35+ btn : "bg-[var(--cd-warning)] hover:bg-[var(--cd-warning-text)]" ,
36+ icon : AlertCircle ,
37+ } ,
38+ info : {
39+ bg : "bg-[var(--cd-primary-subtle)]" ,
40+ text : "text-[var(--cd-primary)]" ,
41+ btn : "bg-[var(--cd-primary)] hover:bg-[var(--cd-primary-text)]" ,
42+ icon : Info ,
43+ } ,
44+ } ;
45+
1646export default function ConfirmModal ( {
1747 isOpen,
1848 title,
@@ -22,11 +52,13 @@ export default function ConfirmModal({
2252 onConfirm,
2353 onCancel,
2454 isLoading = false ,
25- danger = false ,
55+ variant = "info" ,
56+ icon : CustomIcon ,
2657} : ConfirmModalProps ) {
2758 const cancelRef = useRef < HTMLButtonElement > ( null ) ;
59+ const config = VARIANT_CONFIG [ variant ] ;
60+ const Icon = CustomIcon || config . icon ;
2861
29- // Focus cancel on open & close on Escape
3062 useEffect ( ( ) => {
3163 if ( ! isOpen ) return ;
3264 cancelRef . current ?. focus ( ) ;
@@ -40,77 +72,58 @@ export default function ConfirmModal({
4072 if ( ! isOpen ) return null ;
4173
4274 return (
43- < div
44- className = "fixed inset-0 z-50 flex items-center justify-center"
45- aria-modal = "true"
46- role = "dialog"
47- >
75+ < div className = "fixed inset-0 z-50 flex items-center justify-center p-4" >
4876 { /* Backdrop */ }
4977 < div
50- className = "absolute inset-0 bg-black/30 backdrop-blur-[2px]"
78+ className = "absolute inset-0 bg-black/40 backdrop-blur-[2px]"
5179 onClick = { ( ) => ! isLoading && onCancel ( ) }
5280 />
5381
5482 { /* Dialog */ }
5583 < div
56- className = "relative z-10 w-full max-w-md mx-4 rounded-2xl shadow-2xl border overflow-hidden animate-in fade-in zoom-in-95 duration-150"
84+ className = "relative z-10 w-full max-w-md rounded-2xl shadow-2xl border overflow-hidden animate-in fade-in zoom-in-95 duration-150"
5785 style = { { backgroundColor : "var(--cd-surface)" , borderColor : "var(--cd-border)" } }
5886 >
59- { /* Close */ }
6087 < button
6188 onClick = { onCancel }
6289 disabled = { isLoading }
6390 className = "absolute top-4 right-4 p-1.5 rounded-lg text-[var(--cd-text-muted)] hover:bg-[var(--cd-hover)] hover:text-[var(--cd-text)] transition disabled:opacity-40"
64- aria-label = "Close"
6591 >
6692 < X size = { 16 } />
6793 </ button >
6894
69- < div className = "p-6" >
70- { /* Icon + Title */ }
71- < div className = "flex items-start gap-4" >
72- < div
73- className = { `shrink-0 w-11 h-11 rounded-xl flex items-center justify-center ${
74- danger ? "bg-[var(--cd-danger-subtle)]" : "bg-[var(--cd-primary-subtle)]"
75- } `}
76- >
77- < AlertTriangle
78- size = { 22 }
79- className = { danger ? "text-[var(--cd-danger)]" : "text-[var(--cd-primary)]" }
80- />
95+ < div className = "p-6 pt-8" >
96+ < div className = "flex flex-col items-center text-center gap-4" >
97+ < div className = { `shrink-0 w-14 h-14 rounded-2xl flex items-center justify-center ${ config . bg } ` } >
98+ < Icon size = { 28 } className = { config . text } />
8199 </ div >
82- < div className = "flex flex-col gap-2 min-w-0" >
83- < h3 className = "text-lg font-bold text-[var(--cd-text)] leading-tight" > { title } </ h3 >
84- < p className = "text-sm text-[var(--cd-text-2)] leading-relaxed" > { message } </ p >
100+
101+ < div className = "flex flex-col gap-2" >
102+ < h3 className = "text-xl font-bold text-[var(--cd-text)] leading-tight" > { title } </ h3 >
103+ < p className = "text-[var(--cd-text-2)] leading-relaxed" > { message } </ p >
85104 </ div >
86105 </ div >
87106
88- { /* Actions */ }
89- < div className = "flex items-center justify-end gap-3 mt-8" >
107+ < div className = "flex items-center gap-3 mt-8" >
90108 < button
91109 ref = { cancelRef }
92110 onClick = { onCancel }
93111 disabled = { isLoading }
94- className = "px-5 py-2.5 rounded-xl border border-[var(--cd-border)] text-sm font-bold text-[var(--cd-text-2)] hover:bg-[var(--cd-hover)] hover:text-[var(--cd-text)] transition-all active:scale-95 disabled:opacity-40"
112+ className = "flex-1 px-5 py-3 rounded-xl border border-[var(--cd-border)] text-sm font-bold text-[var(--cd-text-2)] hover:bg-[var(--cd-hover)] hover:text-[var(--cd-text)] transition-all active:scale-95 disabled:opacity-40"
95113 >
96114 { cancelLabel }
97115 </ button >
98116 < button
99117 onClick = { onConfirm }
100118 disabled = { isLoading }
101- className = { `flex items-center gap-2 px-5 py-2.5 rounded-xl text-sm font-bold text-white transition-all active:scale-95 shadow-sm disabled:opacity-60 ${
102- danger
103- ? "bg-[var(--cd-danger)] hover:opacity-90 shadow-[var(--cd-danger-subtle)]"
104- : "bg-[var(--cd-primary)] hover:opacity-90 shadow-[var(--cd-primary-subtle)]"
105- } `}
119+ className = { `flex-1 flex items-center justify-center gap-2 px-5 py-3 rounded-xl text-sm font-bold text-white transition-all active:scale-95 shadow-sm disabled:opacity-60 ${ config . btn } ` }
106120 >
107- { isLoading && < Loader2 size = { 15 } className = "animate-spin" /> }
121+ { isLoading && < Loader2 size = { 16 } className = "animate-spin" /> }
108122 { confirmLabel }
109123 </ button >
110124 </ div >
111125 </ div >
112126 </ div >
113-
114127 </ div >
115128 ) ;
116- }
129+ }
0 commit comments