3import { useState, useEffect } from 'react';
4import { Icon } from '@/contexts/IconContext';
5import { Theme } from '@/lib/themes';
7interface CustomThemeCreatorProps {
8 onSave: (theme: Theme) => void;
10 existingTheme?: Theme;
13export default function CustomThemeCreator({ onSave, onClose, existingTheme }: CustomThemeCreatorProps) {
14 const [themeName, setThemeName] = useState(existingTheme?.name || '');
15 const [themeDescription, setThemeDescription] = useState(existingTheme?.description || '');
16 const [colors, setColors] = useState({
17 bg: existingTheme?.colors.bg || '#f1f5f8',
18 surface: existingTheme?.colors.surface || '#ffffff',
19 surface2: existingTheme?.colors.surface2 || '#f7fafc',
20 text: existingTheme?.colors.text || '#0f1724',
21 muted: existingTheme?.colors.muted || '#6b7280',
22 border: existingTheme?.colors.border || '#e6eef6',
23 brand: existingTheme?.colors.brand || '#00483d',
24 brand2: existingTheme?.colors.brand2 || '#00946b',
25 warn: existingTheme?.colors.warn || '#b26a00',
26 danger: existingTheme?.colors.danger || '#b00020',
27 success: existingTheme?.colors.success || '#00946b',
28 info: existingTheme?.colors.info || '#0066cc',
32 { key: 'bg', label: 'Background', description: 'Page background color' },
33 { key: 'surface', label: 'Surface', description: 'Card/panel background' },
34 { key: 'surface2', label: 'Surface 2', description: 'Subtle inset surface' },
35 { key: 'text', label: 'Text', description: 'Primary text color' },
36 { key: 'muted', label: 'Muted Text', description: 'Secondary text color' },
37 { key: 'border', label: 'Border', description: 'Border and divider color' },
38 { key: 'brand', label: 'Brand Primary', description: 'Main brand color' },
39 { key: 'brand2', label: 'Brand Secondary', description: 'Accent brand color' },
40 { key: 'success', label: 'Success', description: 'Success state color' },
41 { key: 'info', label: 'Info', description: 'Info state color' },
42 { key: 'warn', label: 'Warning', description: 'Warning state color' },
43 { key: 'danger', label: 'Danger', description: 'Error/danger state color' },
46 const handleColorChange = (key: string, value: string) => {
47 setColors(prev => ({ ...prev, [key]: value }));
50 const handleSave = () => {
51 if (!themeName.trim()) {
52 alert('Please enter a theme name');
56 const customTheme: Theme = {
57 id: existingTheme?.id || `custom-${Date.now()}`,
59 description: themeDescription,
62 brand2: colors.brand2.startsWith('#') ? colors.brand2 : `${colors.brand2}1f`,
65 shadow: `0 18px 48px ${hexToRgba(colors.brand, 0.15)}`,
66 shadowSm: `0 8px 20px ${hexToRgba(colors.brand, 0.1)}`,
72 focus: `0 0 0 4px ${hexToRgba(colors.brand, 0.2)}`,
78 const hexToRgba = (hex: string, alpha: number): string => {
79 const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
80 if (!result) return `rgba(0, 0, 0, ${alpha})`;
82 return `rgba(${parseInt(result[1], 16)}, ${parseInt(result[2], 16)}, ${parseInt(result[3], 16)}, ${alpha})`;
86 <div className="fixed inset-0 bg-black/50 backdrop-blur-sm flex items-center justify-center z-50 p-4">
87 <div className="bg-surface rounded-2xl shadow-theme-2xl max-w-5xl w-full max-h-[90vh] overflow-hidden animate-slide-in">
89 <div className="gradient-brand text-surface px-6 py-4 flex items-center justify-between">
91 <h2 className="text-2xl font-bold">Custom Theme Creator</h2>
92 <p className="text-sm opacity-90">Design your own unique color theme</p>
96 className="hover:opacity-80 transition p-2"
99 <Icon name="close" size={28} />
104 <div className="p-6 overflow-y-auto max-h-[calc(90vh-180px)]">
106 <div className="mb-6">
107 <label className="block text-sm font-semibold text-text mb-2">
113 onChange={(e) => setThemeName(e.target.value)}
114 placeholder="My Custom Theme"
115 className="w-full px-4 py-2 border border-border rounded-lg focus:outline-none focus:ring-2 focus:ring-brand bg-surface text-text"
119 <div className="mb-6">
120 <label className="block text-sm font-semibold text-text mb-2">
125 value={themeDescription}
126 onChange={(e) => setThemeDescription(e.target.value)}
127 placeholder="A beautiful custom theme for my workspace"
128 className="w-full px-4 py-2 border border-border rounded-lg focus:outline-none focus:ring-2 focus:ring-brand bg-surface text-text"
133 <div className="mb-6">
134 <h3 className="text-lg font-bold text-text mb-4 flex items-center gap-2">
135 <Icon name="palette" size={24} />
138 <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
139 {colorFields.map((field) => (
140 <div key={field.key} className="card p-4">
141 <label className="block text-sm font-semibold text-text mb-2">
144 <p className="text-xs text-muted mb-3">{field.description}</p>
146 <div className="flex gap-2">
148 <div className="relative flex-1">
151 value={colors[field.key as keyof typeof colors]}
152 onChange={(e) => handleColorChange(field.key, e.target.value)}
153 className="w-full h-10 rounded-lg cursor-pointer border-2 border-border"
155 background: colors[field.key as keyof typeof colors],
163 value={colors[field.key as keyof typeof colors]}
164 onChange={(e) => handleColorChange(field.key, e.target.value)}
165 placeholder="#000000"
166 className="w-24 px-2 py-1 border border-border rounded-lg text-sm font-mono focus:outline-none focus:ring-2 focus:ring-brand bg-surface text-text"
175 <div className="mb-6">
176 <h3 className="text-lg font-bold text-text mb-4 flex items-center gap-2">
177 <Icon name="visibility" size={24} />
181 className="rounded-xl p-6 border-2"
183 background: colors.bg,
184 borderColor: colors.border,
189 className="rounded-lg p-6 border shadow-lg mb-4"
191 background: colors.surface,
192 borderColor: colors.border,
196 className="text-xl font-bold mb-2"
197 style={{ color: colors.text }}
202 className="text-sm mb-4"
203 style={{ color: colors.muted }}
205 This is how your theme will look in the application
208 {/* Preview Buttons */}
209 <div className="flex gap-2 flex-wrap">
211 className="px-4 py-2 rounded-lg font-semibold text-white"
212 style={{ background: colors.brand }}
217 className="px-4 py-2 rounded-lg font-semibold text-white"
218 style={{ background: colors.success }}
223 className="px-4 py-2 rounded-lg font-semibold text-white"
224 style={{ background: colors.info }}
229 className="px-4 py-2 rounded-lg font-semibold text-white"
230 style={{ background: colors.warn }}
235 className="px-4 py-2 rounded-lg font-semibold text-white"
236 style={{ background: colors.danger }}
243 {/* Preview Stats */}
244 <div className="grid grid-cols-3 gap-4">
245 {[colors.success, colors.info, colors.warn].map((color, idx) => (
248 className="rounded-lg p-4 border"
250 background: colors.surface,
251 borderColor: colors.border,
255 className="text-sm mb-1"
256 style={{ color: colors.muted }}
261 className="text-2xl font-bold"
274 <div className="border-t border-border px-6 py-4 flex justify-end gap-3 bg-surface-2">
277 className="px-6 py-2 bg-surface-2 text-text rounded-lg hover:opacity-90 transition font-semibold border border-border"
283 className="btn btn-gradient px-6 py-2 rounded-lg font-semibold"
285 <Icon name="save" size={18} className="inline mr-2" />