1 import { useState } from "react";
2 import {
3 useTheme,
4 ACCENT_COLORS,
5 RADIUS_STYLES,
6 type ThemeMode,
7 type AccentColor,
8 type RadiusStyle,
9 type DensityStyle
10 } from "@/contexts/theme-context";
11 import {
12 Dialog,
13 DialogContent,
14 DialogDescription,
15 DialogHeader,
16 DialogTitle,
17 } from "@/components/ui/dialog";
18 import { Button } from "@/components/ui/button";
19 import {
20 Palette,
21 Sun,
22 Moon,
23 Monitor,
24 Sparkles,
25 Check,
26 RotateCcw,
27 Sliders,
28 Layers
29 } from "lucide-react";
30 import { cn } from "@/lib/utils";
31
32 interface ThemeCustomizerModalProps {
33 open: boolean;
34 onOpenChange: (open: boolean) => void;
35 }
36
37 export default function ThemeCustomizerModal({ open, onOpenChange }: ThemeCustomizerModalProps) {
38 const {
39 theme,
40 setTheme,
41 accent,
42 setAccent,
43 radius,
44 setRadius,
45 density,
46 setDensity,
47 resetTheme
48 } = useTheme();
49
50 const themeModes: { id: ThemeMode; label: string; icon: any; desc: string }[] = [
51 { id: "dark", label: "Dark Mode", icon: Moon, desc: "Slate grey background" },
52 { id: "amoled", label: "AMOLED Black", icon: Sparkles, desc: "Pure pitch black (#000)" },
53 { id: "light", label: "Glossy White", icon: Sun, desc: "Porcelain glossy white & glass" },
54 { id: "system", label: "System Sync", icon: Monitor, desc: "Follow OS appearance" },
55 ];
56
57 return (
58 <Dialog open={open} onOpenChange={onOpenChange}>
59 <DialogContent className="max-w-md sm:max-w-lg p-6 rounded-3xl border border-border/60 bg-card/95 backdrop-blur-xl shadow-2xl">
60 <DialogHeader className="space-y-1 text-left pb-2">
61 <div className="flex items-center gap-2 text-primary font-bold text-xs uppercase tracking-wider">
62 <Palette className="h-4 w-4" /> Interface Personalization
63 </div>
64 <DialogTitle className="text-xl font-bold">Theme & Style Customizer</DialogTitle>
65 <DialogDescription className="text-xs text-muted-foreground">
66 Tune your color scheme, UI geometry, and density in real-time. Preferences are saved automatically.
67 </DialogDescription>
68 </DialogHeader>
69
70 <div className="space-y-6 pt-2">
71 {/* Theme Modes */}
72 <div className="space-y-2">
73 <label className="text-xs font-bold text-foreground flex items-center gap-1.5">
74 <Moon className="h-3.5 w-3.5 text-primary" /> Appearance Mode
75 </label>
76 <div className="grid grid-cols-2 gap-2.5">
77 {themeModes.map((m) => {
78 const Icon = m.icon;
79 const isSelected = theme === m.id;
80 return (
81 <button
82 key={m.id}
83 onClick={() => setTheme(m.id)}
84 className={cn(
85 "flex items-center gap-3 p-3 rounded-2xl border text-left transition-all",
86 isSelected
87 ? "border-primary bg-primary/10 text-primary font-semibold ring-1 ring-primary/40 shadow-sm"
88 : "border-border/50 bg-background/50 hover:bg-muted/40 hover:border-border text-muted-foreground"
89 )}
90 >
91 <div className={cn(
92 "h-8 w-8 rounded-xl flex items-center justify-center shrink-0",
93 isSelected ? "bg-primary text-primary-foreground" : "bg-muted text-foreground"
94 )}>
95 <Icon className="h-4 w-4" />
96 </div>
97 <div className="overflow-hidden">
98 <div className="text-xs font-bold truncate text-foreground">{m.label}</div>
99 <div className="text-[10px] text-muted-foreground truncate">{m.desc}</div>
100 </div>
101 </button>
102 );
103 })}
104 </div>
105 </div>
106
107 {/* Accent Colors */}
108 <div className="space-y-2">
109 <div className="flex items-center justify-between">
110 <label className="text-xs font-bold text-foreground flex items-center gap-1.5">
111 <Sliders className="h-3.5 w-3.5 text-primary" /> Primary Accent Tone
112 </label>
113 <span className="text-[11px] font-semibold text-primary capitalize">
114 {ACCENT_COLORS[accent]?.label || accent}
115 </span>
116 </div>
117 <div className="grid grid-cols-4 sm:grid-cols-7 gap-2">
118 {(Object.keys(ACCENT_COLORS) as AccentColor[]).map((key) => {
119 const color = ACCENT_COLORS[key];
120 const isSelected = accent === key;
121 return (
122 <button
123 key={key}
124 onClick={() => setAccent(key)}
125 className={cn(
126 "flex flex-col items-center justify-center p-2 rounded-2xl border transition-all group",
127 isSelected
128 ? "border-primary bg-primary/10 ring-2 ring-primary/50 shadow-sm"
129 : "border-border/40 hover:border-border bg-background/40"
130 )}
131 title={color.label}
132 >
133 <div
134 className="h-6 w-6 rounded-full flex items-center justify-center shadow-sm transition-transform group-hover:scale-110"
135 style={{ backgroundColor: color.bgHex }}
136 >
137 {isSelected && <Check className="h-3.5 w-3.5 text-white stroke-[3]" />}
138 </div>
139 <span className="text-[9px] mt-1 font-medium capitalize text-muted-foreground group-hover:text-foreground">
140 {key}
141 </span>
142 </button>
143 );
144 })}
145 </div>
146 </div>
147
148 {/* Border Radius Geometry */}
149 <div className="space-y-2">
150 <label className="text-xs font-bold text-foreground flex items-center gap-1.5">
151 <Layers className="h-3.5 w-3.5 text-primary" /> UI Corner Geometry
152 </label>
153 <div className="grid grid-cols-3 gap-2">
154 {(Object.keys(RADIUS_STYLES) as RadiusStyle[]).map((rKey) => {
155 const isSelected = radius === rKey;
156 return (
157 <button
158 key={rKey}
159 onClick={() => setRadius(rKey)}
160 className={cn(
161 "p-2.5 text-center rounded-2xl border transition-all text-xs font-semibold",
162 isSelected
163 ? "border-primary bg-primary/10 text-primary ring-1 ring-primary/40"
164 : "border-border/50 hover:bg-muted/40 text-muted-foreground hover:text-foreground"
165 )}
166 >
167 {rKey === "rounded" && "Modern Soft"}
168 {rKey === "tech" && "Sleek Tech"}
169 {rKey === "sharp" && "Sharp Box"}
170 </button>
171 );
172 })}
173 </div>
174 </div>
175
176 {/* Density Selection */}
177 <div className="space-y-2">
178 <label className="text-xs font-bold text-foreground">Content Spacing & Density</label>
179 <div className="grid grid-cols-2 gap-2">
180 {(["comfortable", "compact"] as DensityStyle[]).map((d) => {
181 const isSelected = density === d;
182 return (
183 <button
184 key={d}
185 onClick={() => setDensity(d)}
186 className={cn(
187 "p-2.5 rounded-2xl border text-center text-xs font-semibold capitalize transition-all",
188 isSelected
189 ? "border-primary bg-primary/10 text-primary ring-1 ring-primary/40"
190 : "border-border/50 hover:bg-muted/40 text-muted-foreground hover:text-foreground"
191 )}
192 >
193 {d === "comfortable" ? "Comfortable (Relaxed)" : "Compact (High Tech Density)"}
194 </button>
195 );
196 })}
197 </div>
198 </div>
199 </div>
200
201 {/* Footer Actions */}
202 <div className="flex items-center justify-between pt-4 border-t border-border/50 mt-4">
203 <Button
204 variant="ghost"
205 size="sm"
206 onClick={resetTheme}
207 className="text-xs text-muted-foreground hover:text-foreground gap-1.5 h-9 rounded-2xl"
208 >
209 <RotateCcw className="h-3.5 w-3.5" /> Reset Default
210 </Button>
211
212 <Button
213 onClick={() => onOpenChange(false)}
214 size="sm"
215 className="text-xs px-5 h-9 rounded-2xl font-bold"
216 >
217 Done
218 </Button>
219 </div>
220 </DialogContent>
221 </Dialog>
222 );
223 }