alexui 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (124) hide show
  1. package/README.md +57 -0
  2. package/components/ActionTable.tsx +307 -0
  3. package/components/AlertBanner.tsx +124 -0
  4. package/components/AnimatedAccordion.tsx +95 -0
  5. package/components/Autocomplete.tsx +144 -0
  6. package/components/Avatar.tsx +123 -0
  7. package/components/Badge.tsx +80 -0
  8. package/components/Breadcrumb.tsx +74 -0
  9. package/components/Calendar.tsx +340 -0
  10. package/components/Card3D.tsx +117 -0
  11. package/components/Carousel3D.tsx +193 -0
  12. package/components/CascadeSelect.tsx +232 -0
  13. package/components/ChartShowcase.tsx +700 -0
  14. package/components/Checkbox.tsx +212 -0
  15. package/components/ChipsInput.tsx +152 -0
  16. package/components/CircularKnob.tsx +240 -0
  17. package/components/CodeVisualizer.tsx +67 -0
  18. package/components/Collapsible.tsx +72 -0
  19. package/components/ColorThemeManager.tsx +458 -0
  20. package/components/CommandMenu.tsx +191 -0
  21. package/components/ConfirmDialog.tsx +152 -0
  22. package/components/ContextMenu.tsx +192 -0
  23. package/components/DashboardLayout.tsx +115 -0
  24. package/components/DatePicker.tsx +108 -0
  25. package/components/Divider.tsx +67 -0
  26. package/components/Dock.tsx +93 -0
  27. package/components/DragDropLists.tsx +160 -0
  28. package/components/Drawer.tsx +161 -0
  29. package/components/DropdownPlus.tsx +304 -0
  30. package/components/EmptyState.tsx +49 -0
  31. package/components/ErrorPage.tsx +62 -0
  32. package/components/FileDropzone.tsx +206 -0
  33. package/components/ForgotPassword.tsx +137 -0
  34. package/components/FormField.tsx +81 -0
  35. package/components/GlassButton.tsx +56 -0
  36. package/components/GlassCard.tsx +82 -0
  37. package/components/GlassInput.tsx +96 -0
  38. package/components/GlassmorphicModal.tsx +108 -0
  39. package/components/GlowInput.tsx +111 -0
  40. package/components/GlowSelect.tsx +203 -0
  41. package/components/GlowTextArea.tsx +105 -0
  42. package/components/HorizontalTimeline.tsx +121 -0
  43. package/components/HoverCard.tsx +105 -0
  44. package/components/ImageLightbox.tsx +259 -0
  45. package/components/InputGroup.tsx +118 -0
  46. package/components/InputOTP.tsx +147 -0
  47. package/components/InteractiveNavbar.tsx +266 -0
  48. package/components/InteractiveSidebar.tsx +211 -0
  49. package/components/Kbd.tsx +51 -0
  50. package/components/LiteYouTube.tsx +118 -0
  51. package/components/LoaderCollection.tsx +368 -0
  52. package/components/LoginForm.tsx +192 -0
  53. package/components/MagneticButton.tsx +101 -0
  54. package/components/MaskedInput.tsx +79 -0
  55. package/components/MentionInput.tsx +413 -0
  56. package/components/MorphingSwitch.tsx +86 -0
  57. package/components/MultiSelect.tsx +158 -0
  58. package/components/NumberInput.tsx +203 -0
  59. package/components/Panel.tsx +104 -0
  60. package/components/PasswordInput.tsx +203 -0
  61. package/components/Popover.tsx +91 -0
  62. package/components/PricingTable.tsx +113 -0
  63. package/components/ProgressBar.tsx +152 -0
  64. package/components/RadioButton.tsx +211 -0
  65. package/components/Rating.tsx +82 -0
  66. package/components/ResizablePanel.tsx +114 -0
  67. package/components/ScrollPanel.tsx +103 -0
  68. package/components/SettingsPage.tsx +154 -0
  69. package/components/SignupForm.tsx +182 -0
  70. package/components/Skeleton.tsx +41 -0
  71. package/components/Slider.tsx +95 -0
  72. package/components/SlidingTabs.tsx +54 -0
  73. package/components/SortableList.tsx +91 -0
  74. package/components/SpeedDial.tsx +134 -0
  75. package/components/Spinner.tsx +40 -0
  76. package/components/Stepper.tsx +124 -0
  77. package/components/TabMenu.tsx +72 -0
  78. package/components/TableControls.tsx +77 -0
  79. package/components/TablePagination.tsx +88 -0
  80. package/components/TextEditor.tsx +329 -0
  81. package/components/TextReveal.tsx +99 -0
  82. package/components/ThemeSwitcher.tsx +133 -0
  83. package/components/TimelineGSAP.tsx +164 -0
  84. package/components/ToastSystem.tsx +110 -0
  85. package/components/ToggleButton.tsx +79 -0
  86. package/components/Tooltip.tsx +121 -0
  87. package/components/Tree.tsx +138 -0
  88. package/dist/commands/add.d.ts +7 -0
  89. package/dist/commands/add.js +110 -0
  90. package/dist/commands/init.d.ts +5 -0
  91. package/dist/commands/init.js +76 -0
  92. package/dist/commands/list.d.ts +1 -0
  93. package/dist/commands/list.js +32 -0
  94. package/dist/index.d.ts +2 -0
  95. package/dist/index.js +60 -0
  96. package/dist/registry.d.ts +6 -0
  97. package/dist/registry.js +38 -0
  98. package/dist/tui/browse.d.ts +3 -0
  99. package/dist/tui/browse.js +139 -0
  100. package/dist/tui/format.d.ts +11 -0
  101. package/dist/tui/format.js +52 -0
  102. package/dist/tui/main.d.ts +1 -0
  103. package/dist/tui/main.js +86 -0
  104. package/dist/tui/panels.d.ts +9 -0
  105. package/dist/tui/panels.js +50 -0
  106. package/dist/tui/theme.d.ts +28 -0
  107. package/dist/tui/theme.js +76 -0
  108. package/dist/types.d.ts +28 -0
  109. package/dist/types.js +1 -0
  110. package/dist/utils/config.d.ts +6 -0
  111. package/dist/utils/config.js +24 -0
  112. package/dist/utils/copy.d.ts +9 -0
  113. package/dist/utils/copy.js +43 -0
  114. package/dist/utils/cwd.d.ts +6 -0
  115. package/dist/utils/cwd.js +30 -0
  116. package/dist/utils/deps.d.ts +1 -0
  117. package/dist/utils/deps.js +19 -0
  118. package/dist/utils/project.d.ts +5 -0
  119. package/dist/utils/project.js +30 -0
  120. package/dist/utils/theme.d.ts +1 -0
  121. package/dist/utils/theme.js +24 -0
  122. package/package.json +52 -0
  123. package/registry.json +1133 -0
  124. package/templates/theme.css +81 -0
@@ -0,0 +1,72 @@
1
+ import React, { useState } from 'react';
2
+ import { motion, AnimatePresence } from 'framer-motion';
3
+ import { ChevronDown } from 'lucide-react';
4
+
5
+ export interface CollapsibleProps {
6
+ trigger: React.ReactNode;
7
+ children: React.ReactNode;
8
+ defaultOpen?: boolean;
9
+ open?: boolean;
10
+ onOpenChange?: (open: boolean) => void;
11
+ disabled?: boolean;
12
+ className?: string;
13
+ }
14
+
15
+ export const Collapsible: React.FC<CollapsibleProps> = ({
16
+ trigger,
17
+ children,
18
+ defaultOpen = false,
19
+ open: controlledOpen,
20
+ onOpenChange,
21
+ disabled = false,
22
+ className = ''
23
+ }) => {
24
+ const [internalOpen, setInternalOpen] = useState(defaultOpen);
25
+ const isControlled = controlledOpen !== undefined;
26
+ const isOpen = isControlled ? controlledOpen : internalOpen;
27
+
28
+ const setOpen = (next: boolean) => {
29
+ if (disabled) return;
30
+ if (!isControlled) setInternalOpen(next);
31
+ onOpenChange?.(next);
32
+ };
33
+
34
+ return (
35
+ <div className={`w-full ${className}`}>
36
+ <button
37
+ type="button"
38
+ onClick={() => setOpen(!isOpen)}
39
+ disabled={disabled}
40
+ aria-expanded={isOpen}
41
+ className={`w-full flex items-center justify-between gap-3 text-left transition-colors rounded-xl ${
42
+ disabled ? 'opacity-50 cursor-not-allowed' : 'cursor-pointer hover:bg-bg-app/40'
43
+ }`}
44
+ >
45
+ <span className="flex-1 min-w-0">{trigger}</span>
46
+ <motion.span
47
+ animate={{ rotate: isOpen ? 180 : 0 }}
48
+ transition={{ type: 'spring', stiffness: 350, damping: 24 }}
49
+ className="text-text-muted flex-shrink-0"
50
+ >
51
+ <ChevronDown className="w-4 h-4" />
52
+ </motion.span>
53
+ </button>
54
+
55
+ <AnimatePresence initial={false}>
56
+ {isOpen && (
57
+ <motion.div
58
+ initial={{ height: 0, opacity: 0 }}
59
+ animate={{ height: 'auto', opacity: 1 }}
60
+ exit={{ height: 0, opacity: 0 }}
61
+ transition={{ type: 'spring', stiffness: 380, damping: 30 }}
62
+ className="overflow-hidden"
63
+ >
64
+ <div className="pt-3 text-sm text-text-muted leading-relaxed">
65
+ {children}
66
+ </div>
67
+ </motion.div>
68
+ )}
69
+ </AnimatePresence>
70
+ </div>
71
+ );
72
+ };
@@ -0,0 +1,458 @@
1
+ import React, { useState, useEffect, useCallback, useRef } from 'react';
2
+ import { createPortal } from 'react-dom';
3
+ import { motion, AnimatePresence } from 'framer-motion';
4
+ import { X, Sparkles, Download, RotateCcw, Check, Palette } from 'lucide-react';
5
+
6
+ export interface ColorPalette {
7
+ accent: string;
8
+ success: string;
9
+ warning: string;
10
+ error: string;
11
+ info: string;
12
+ }
13
+
14
+ export interface ColorTheme {
15
+ id: string;
16
+ name: string;
17
+ light: ColorPalette;
18
+ dark: ColorPalette;
19
+ }
20
+
21
+ // 10 Curated Premium Themes
22
+ export const PRESETS_THEMES: ColorTheme[] = [
23
+ {
24
+ id: 'default',
25
+ name: 'Default Indigo',
26
+ light: { accent: '#6366f1', success: '#10b981', warning: '#f59e0b', error: '#ef4444', info: '#3b82f6' },
27
+ dark: { accent: '#818cf8', success: '#34d399', warning: '#fbbf24', error: '#f87171', info: '#60a5fa' }
28
+ },
29
+ {
30
+ id: 'emerald',
31
+ name: 'Emerald Forest',
32
+ light: { accent: '#059669', success: '#10b981', warning: '#d97706', error: '#dc2626', info: '#2563eb' },
33
+ dark: { accent: '#34d399', success: '#34d399', warning: '#fbbf24', error: '#f87171', info: '#60a5fa' }
34
+ },
35
+ {
36
+ id: 'cyberpunk',
37
+ name: 'Cyberpunk Pink',
38
+ light: { accent: '#db2777', success: '#10b981', warning: '#ea580c', error: '#dc2626', info: '#8b5cf6' },
39
+ dark: { accent: '#f472b6', success: '#34d399', warning: '#fbbf24', error: '#f87171', info: '#c084fc' }
40
+ },
41
+ {
42
+ id: 'ocean',
43
+ name: 'Ocean Blue',
44
+ light: { accent: '#2563eb', success: '#10b981', warning: '#f59e0b', error: '#ef4444', info: '#0ea5e9' },
45
+ dark: { accent: '#60a5fa', success: '#34d399', warning: '#fbbf24', error: '#f87171', info: '#38bdf8' }
46
+ },
47
+ {
48
+ id: 'sunset',
49
+ name: 'Sunset Amber',
50
+ light: { accent: '#ea580c', success: '#10b981', warning: '#f59e0b', error: '#ef4444', info: '#3b82f6' },
51
+ dark: { accent: '#fb923c', success: '#34d399', warning: '#fbbf24', error: '#f87171', info: '#60a5fa' }
52
+ },
53
+ {
54
+ id: 'violet',
55
+ name: 'Midnight Violet',
56
+ light: { accent: '#7c3aed', success: '#059669', warning: '#ea580c', error: '#dc2626', info: '#2563eb' },
57
+ dark: { accent: '#a78bfa', success: '#34d399', warning: '#fb923c', error: '#f87171', info: '#60a5fa' }
58
+ },
59
+ {
60
+ id: 'teal',
61
+ name: 'Teal Breeze',
62
+ light: { accent: '#0d9488', success: '#10b981', warning: '#d97706', error: '#e11d48', info: '#0284c7' },
63
+ dark: { accent: '#2dd4bf', success: '#34d399', warning: '#fbbf24', error: '#fda4af', info: '#38bdf8' }
64
+ },
65
+ {
66
+ id: 'crimson',
67
+ name: 'Crimson Blood',
68
+ light: { accent: '#dc2626', success: '#10b981', warning: '#d97706', error: '#b91c1c', info: '#2563eb' },
69
+ dark: { accent: '#f87171', success: '#34d399', warning: '#fbbf24', error: '#ef4444', info: '#60a5fa' }
70
+ },
71
+ {
72
+ id: 'neon',
73
+ name: 'Glow Neon',
74
+ light: { accent: '#65a30d', success: '#10b981', warning: '#f59e0b', error: '#ef4444', info: '#3b82f6' },
75
+ dark: { accent: '#a3e635', success: '#34d399', warning: '#fbbf24', error: '#f87171', info: '#60a5fa' }
76
+ },
77
+ {
78
+ id: 'slate',
79
+ name: 'Monochrome Slate',
80
+ light: { accent: '#475569', success: '#10b981', warning: '#f59e0b', error: '#ef4444', info: '#3b82f6' },
81
+ dark: { accent: '#94a3b8', success: '#34d399', warning: '#fbbf24', error: '#f87171', info: '#60a5fa' }
82
+ }
83
+ ];
84
+
85
+ // Helper functions for dynamic theme calculations
86
+ const darkenHex = (hex: string, percent: number) => {
87
+ const num = parseInt(hex.replace("#", ""), 16);
88
+ const amt = Math.round(2.55 * percent);
89
+ const R = (num >> 16) - amt;
90
+ const G = (num >> 8 & 0x00FF) - amt;
91
+ const B = (num & 0x0000FF) - amt;
92
+ return "#" + (0x1000000 + (R < 255 ? R < 0 ? 0 : R : 255) * 0x10000 + (G < 255 ? G < 0 ? 0 : G : 255) * 0x100 + (B < 255 ? B < 0 ? 0 : B : 255)).toString(16).slice(1);
93
+ };
94
+
95
+ const hexToRgba = (hex: string, alpha: number) => {
96
+ const r = parseInt(hex.slice(1, 3), 16);
97
+ const g = parseInt(hex.slice(3, 5), 16);
98
+ const b = parseInt(hex.slice(5, 7), 16);
99
+ return `rgba(${r}, ${g}, ${b}, ${alpha})`;
100
+ };
101
+
102
+ export interface ColorThemeManagerProps {
103
+ isOpen: boolean;
104
+ onClose: () => void;
105
+ selectedThemeId: string;
106
+ setSelectedThemeId: (id: string) => void;
107
+ onThemeChange?: () => void;
108
+ }
109
+
110
+ export const ColorThemeManager: React.FC<ColorThemeManagerProps> = ({
111
+ isOpen,
112
+ onClose,
113
+ selectedThemeId,
114
+ setSelectedThemeId,
115
+ onThemeChange,
116
+ }) => {
117
+ const [customColors, setCustomColors] = useState<ColorPalette>({
118
+ accent: '#6366f1',
119
+ success: '#10b981',
120
+ warning: '#f59e0b',
121
+ error: '#ef4444',
122
+ info: '#3b82f6'
123
+ });
124
+
125
+ // Track if current browser dark mode is active
126
+ const [isDark, setIsDark] = useState(false);
127
+
128
+ useEffect(() => {
129
+ // Initial load
130
+ const storedTheme = localStorage.getItem('alexui-theme-id') || 'default';
131
+ setSelectedThemeId(storedTheme);
132
+
133
+ const storedCustom = localStorage.getItem('alexui-custom-colors');
134
+ if (storedCustom) {
135
+ try {
136
+ setCustomColors(JSON.parse(storedCustom));
137
+ } catch (e) {
138
+ console.error(e);
139
+ }
140
+ }
141
+
142
+ const checkDarkMode = () => {
143
+ setIsDark(document.documentElement.classList.contains('dark'));
144
+ };
145
+
146
+ checkDarkMode();
147
+
148
+ // Listen for theme changes from switcher
149
+ const observer = new MutationObserver(checkDarkMode);
150
+ observer.observe(document.documentElement, { attributes: true, attributeFilter: ['class'] });
151
+
152
+ return () => observer.disconnect();
153
+ }, []);
154
+
155
+ const applyColors = useCallback((palette: ColorPalette) => {
156
+ const root = document.documentElement;
157
+ root.style.setProperty('--color-accent', palette.accent);
158
+ root.style.setProperty('--color-accent-hover', darkenHex(palette.accent, 10));
159
+ root.style.setProperty('--color-accent-glow', hexToRgba(palette.accent, 0.18));
160
+ root.style.setProperty('--color-success', palette.success);
161
+ root.style.setProperty('--color-error', palette.error);
162
+ root.style.setProperty('--color-warning', palette.warning);
163
+ root.style.setProperty('--color-info', palette.info);
164
+ }, []);
165
+
166
+ const onThemeChangeRef = useRef(onThemeChange);
167
+ useEffect(() => {
168
+ onThemeChangeRef.current = onThemeChange;
169
+ }, [onThemeChange]);
170
+
171
+ // Update styles when theme, mode, or custom color changes
172
+ useEffect(() => {
173
+ if (selectedThemeId === 'custom') {
174
+ applyColors(customColors);
175
+ localStorage.setItem('alexui-theme-id', 'custom');
176
+ localStorage.setItem('alexui-custom-colors', JSON.stringify(customColors));
177
+ } else {
178
+ const theme = PRESETS_THEMES.find(t => t.id === selectedThemeId) || PRESETS_THEMES[0];
179
+ const palette = isDark ? theme.dark : theme.light;
180
+ applyColors(palette);
181
+ localStorage.setItem('alexui-theme-id', selectedThemeId);
182
+ }
183
+ onThemeChangeRef.current?.();
184
+ }, [selectedThemeId, customColors, isDark, applyColors]);
185
+
186
+ const handleCustomColorChange = (key: keyof ColorPalette, value: string) => {
187
+ setCustomColors(prev => ({ ...prev, [key]: value }));
188
+ setSelectedThemeId('custom');
189
+ };
190
+
191
+ const handleResetCustom = () => {
192
+ const defaultCustom = {
193
+ accent: '#6366f1',
194
+ success: '#10b981',
195
+ warning: '#f59e0b',
196
+ error: '#ef4444',
197
+ info: '#3b82f6'
198
+ };
199
+ setCustomColors(defaultCustom);
200
+ setSelectedThemeId('default');
201
+ localStorage.removeItem('alexui-custom-colors');
202
+ };
203
+
204
+ const handleDownloadCSS = () => {
205
+ const activePalette = selectedThemeId === 'custom'
206
+ ? customColors
207
+ : (isDark
208
+ ? (PRESETS_THEMES.find(t => t.id === selectedThemeId)?.dark || PRESETS_THEMES[0].dark)
209
+ : (PRESETS_THEMES.find(t => t.id === selectedThemeId)?.light || PRESETS_THEMES[0].light)
210
+ );
211
+
212
+ const cssContent = `:root {
213
+ --color-accent: ${activePalette.accent};
214
+ --color-accent-hover: ${darkenHex(activePalette.accent, 10)};
215
+ --color-accent-glow: ${hexToRgba(activePalette.accent, 0.18)};
216
+ --color-success: ${activePalette.success};
217
+ --color-error: ${activePalette.error};
218
+ --color-warning: ${activePalette.warning};
219
+ --color-info: ${activePalette.info};
220
+ }`;
221
+
222
+ const blob = new Blob([cssContent], { type: 'text/css' });
223
+ const url = URL.createObjectURL(blob);
224
+ const link = document.createElement('a');
225
+ link.href = url;
226
+ link.download = `alexui-theme-${selectedThemeId}.css`;
227
+ document.body.appendChild(link);
228
+ link.click();
229
+ document.body.removeChild(link);
230
+ URL.revokeObjectURL(url);
231
+ };
232
+
233
+ // Close on Escape key press
234
+ useEffect(() => {
235
+ const handleKeyDown = (e: KeyboardEvent) => {
236
+ if (e.key === 'Escape') onClose();
237
+ };
238
+ if (isOpen) {
239
+ window.addEventListener('keydown', handleKeyDown);
240
+ document.body.style.overflow = 'hidden';
241
+ }
242
+ return () => {
243
+ window.removeEventListener('keydown', handleKeyDown);
244
+ document.body.style.overflow = '';
245
+ };
246
+ }, [isOpen, onClose]);
247
+
248
+ if (typeof document === 'undefined') return null;
249
+
250
+ return createPortal(
251
+ <AnimatePresence>
252
+ {isOpen && (
253
+ <div className="fixed inset-0 z-50 flex items-center justify-center p-4">
254
+
255
+ {/* Backdrop blur layer */}
256
+ <motion.div
257
+ initial={{ opacity: 0 }}
258
+ animate={{ opacity: 1 }}
259
+ exit={{ opacity: 0 }}
260
+ onClick={onClose}
261
+ className="fixed inset-0 bg-black/40 backdrop-blur-md cursor-pointer"
262
+ />
263
+
264
+ {/* Modal Container */}
265
+ <motion.div
266
+ initial={{ scale: 0.95, opacity: 0, y: 15 }}
267
+ animate={{
268
+ scale: 1,
269
+ opacity: 1,
270
+ y: 0,
271
+ transition: { type: 'spring', stiffness: 350, damping: 25 }
272
+ }}
273
+ exit={{
274
+ scale: 0.95,
275
+ opacity: 0,
276
+ y: 15,
277
+ transition: { duration: 0.2 }
278
+ }}
279
+ className="relative w-full max-w-2xl rounded-2xl glass bg-bg-card/90 shadow-2xl border border-border-app z-10 overflow-hidden flex flex-col max-h-[90vh]"
280
+ >
281
+ {/* Header */}
282
+ <div className="flex items-center justify-between p-5 border-b border-border-app/50">
283
+ <div className="flex items-center gap-2 text-accent">
284
+ <Palette className="w-5 h-5" />
285
+ <h3 className="font-extrabold text-lg text-text-main font-display">
286
+ Personalizar Temas de Color
287
+ </h3>
288
+ </div>
289
+ <button
290
+ onClick={onClose}
291
+ className="p-1 rounded-lg hover:bg-bg-app text-text-muted hover:text-text-main transition-colors cursor-pointer"
292
+ aria-label="Cerrar personalizador"
293
+ >
294
+ <X className="w-5 h-5" />
295
+ </button>
296
+ </div>
297
+
298
+ {/* Scrollable Content */}
299
+ <div className="p-6 overflow-y-auto flex flex-col gap-6">
300
+
301
+ {/* Presets Grid */}
302
+ <div>
303
+ <h4 className="text-xs font-bold uppercase tracking-wider text-text-muted mb-3 font-display">
304
+ Temas Predefinidos (10 Paletas Premium)
305
+ </h4>
306
+ <div className="grid grid-cols-2 sm:grid-cols-5 gap-3">
307
+ {PRESETS_THEMES.map((theme) => {
308
+ const palette = isDark ? theme.dark : theme.light;
309
+ const isSelected = selectedThemeId === theme.id;
310
+ return (
311
+ <button
312
+ key={theme.id}
313
+ onClick={() => setSelectedThemeId(theme.id)}
314
+ className={`p-3 rounded-xl bg-bg-app border text-left flex flex-col gap-2 items-center justify-center transition-all duration-300 cursor-pointer ${
315
+ isSelected
316
+ ? 'border-accent ring-2 ring-accent/20 bg-accent/5'
317
+ : 'border-border-app hover:border-border-app/80 hover:bg-bg-card'
318
+ }`}
319
+ >
320
+ <span className="text-[10px] font-bold text-text-main truncate w-full text-center">
321
+ {theme.name}
322
+ </span>
323
+ {/* Color preview circles */}
324
+ <div className="flex gap-0.5 items-center justify-center">
325
+ <div className="w-3.5 h-3.5 rounded-full border border-black/10" style={{ backgroundColor: palette.accent }} title="Acento" />
326
+ <div className="w-2.5 h-2.5 rounded-full border border-black/10" style={{ backgroundColor: palette.success }} title="Éxito" />
327
+ <div className="w-2.5 h-2.5 rounded-full border border-black/10" style={{ backgroundColor: palette.error }} title="Error" />
328
+ </div>
329
+ </button>
330
+ );
331
+ })}
332
+ </div>
333
+ </div>
334
+
335
+ {/* Custom Theme Editor */}
336
+ <div className="border-t border-border-app/50 pt-5">
337
+ <div className="flex items-center justify-between mb-4">
338
+ <div className="flex items-center gap-2">
339
+ <Sparkles className="w-4 h-4 text-accent animate-pulse" />
340
+ <h4 className="text-xs font-bold uppercase tracking-wider text-text-muted font-display">
341
+ Creador de Tema Customizado (En Tiempo Real)
342
+ </h4>
343
+ </div>
344
+ {selectedThemeId === 'custom' && (
345
+ <button
346
+ onClick={handleResetCustom}
347
+ className="flex items-center gap-1 text-[10px] font-bold text-red-500 hover:text-red-600 transition-colors cursor-pointer"
348
+ >
349
+ <RotateCcw className="w-3.5 h-3.5" />
350
+ <span>Reiniciar</span>
351
+ </button>
352
+ )}
353
+ </div>
354
+
355
+ <div className="bg-bg-app/40 rounded-2xl p-4 border border-border-app/40 grid grid-cols-1 sm:grid-cols-5 gap-4">
356
+ {/* Accent Color */}
357
+ <div className="flex flex-col items-center gap-2">
358
+ <span className="text-[10px] font-bold text-text-muted font-mono">Acento</span>
359
+ <label className="relative w-12 h-12 rounded-full border-2 border-border-app cursor-pointer overflow-hidden shadow-sm hover:scale-105 transition-transform flex items-center justify-center" style={{ backgroundColor: customColors.accent }}>
360
+ <input
361
+ type="color"
362
+ value={customColors.accent}
363
+ onChange={(e) => handleCustomColorChange('accent', e.target.value)}
364
+ className="absolute inset-0 opacity-0 cursor-pointer w-full h-full"
365
+ />
366
+ {selectedThemeId === 'custom' && <Check className="w-4 h-4 text-white drop-shadow-[0_1px_2px_rgba(0,0,0,0.5)]" />}
367
+ </label>
368
+ <span className="text-[10px] font-mono font-medium text-text-main uppercase">{customColors.accent}</span>
369
+ </div>
370
+
371
+ {/* Success Color */}
372
+ <div className="flex flex-col items-center gap-2">
373
+ <span className="text-[10px] font-bold text-text-muted font-mono">Éxito</span>
374
+ <label className="relative w-12 h-12 rounded-full border-2 border-border-app cursor-pointer overflow-hidden shadow-sm hover:scale-105 transition-transform flex items-center justify-center" style={{ backgroundColor: customColors.success }}>
375
+ <input
376
+ type="color"
377
+ value={customColors.success}
378
+ onChange={(e) => handleCustomColorChange('success', e.target.value)}
379
+ className="absolute inset-0 opacity-0 cursor-pointer w-full h-full"
380
+ />
381
+ {selectedThemeId === 'custom' && <Check className="w-4 h-4 text-white drop-shadow-[0_1px_2px_rgba(0,0,0,0.5)]" />}
382
+ </label>
383
+ <span className="text-[10px] font-mono font-medium text-text-main uppercase">{customColors.success}</span>
384
+ </div>
385
+
386
+ {/* Warning Color */}
387
+ <div className="flex flex-col items-center gap-2">
388
+ <span className="text-[10px] font-bold text-text-muted font-mono">Advertencia</span>
389
+ <label className="relative w-12 h-12 rounded-full border-2 border-border-app cursor-pointer overflow-hidden shadow-sm hover:scale-105 transition-transform flex items-center justify-center" style={{ backgroundColor: customColors.warning }}>
390
+ <input
391
+ type="color"
392
+ value={customColors.warning}
393
+ onChange={(e) => handleCustomColorChange('warning', e.target.value)}
394
+ className="absolute inset-0 opacity-0 cursor-pointer w-full h-full"
395
+ />
396
+ {selectedThemeId === 'custom' && <Check className="w-4 h-4 text-white drop-shadow-[0_1px_2px_rgba(0,0,0,0.5)]" />}
397
+ </label>
398
+ <span className="text-[10px] font-mono font-medium text-text-main uppercase">{customColors.warning}</span>
399
+ </div>
400
+
401
+ {/* Error Color */}
402
+ <div className="flex flex-col items-center gap-2">
403
+ <span className="text-[10px] font-bold text-text-muted font-mono">Error</span>
404
+ <label className="relative w-12 h-12 rounded-full border-2 border-border-app cursor-pointer overflow-hidden shadow-sm hover:scale-105 transition-transform flex items-center justify-center" style={{ backgroundColor: customColors.error }}>
405
+ <input
406
+ type="color"
407
+ value={customColors.error}
408
+ onChange={(e) => handleCustomColorChange('error', e.target.value)}
409
+ className="absolute inset-0 opacity-0 cursor-pointer w-full h-full"
410
+ />
411
+ {selectedThemeId === 'custom' && <Check className="w-4 h-4 text-white drop-shadow-[0_1px_2px_rgba(0,0,0,0.5)]" />}
412
+ </label>
413
+ <span className="text-[10px] font-mono font-medium text-text-main uppercase">{customColors.error}</span>
414
+ </div>
415
+
416
+ {/* Info Color */}
417
+ <div className="flex flex-col items-center gap-2">
418
+ <span className="text-[10px] font-bold text-text-muted font-mono">Información</span>
419
+ <label className="relative w-12 h-12 rounded-full border-2 border-border-app cursor-pointer overflow-hidden shadow-sm hover:scale-105 transition-transform flex items-center justify-center" style={{ backgroundColor: customColors.info }}>
420
+ <input
421
+ type="color"
422
+ value={customColors.info}
423
+ onChange={(e) => handleCustomColorChange('info', e.target.value)}
424
+ className="absolute inset-0 opacity-0 cursor-pointer w-full h-full"
425
+ />
426
+ {selectedThemeId === 'custom' && <Check className="w-4 h-4 text-white drop-shadow-[0_1px_2px_rgba(0,0,0,0.5)]" />}
427
+ </label>
428
+ <span className="text-[10px] font-mono font-medium text-text-main uppercase">{customColors.info}</span>
429
+ </div>
430
+ </div>
431
+ </div>
432
+
433
+ </div>
434
+
435
+ {/* Footer */}
436
+ <div className="p-5 border-t border-border-app/50 bg-bg-app/20 flex justify-between gap-3">
437
+ <button
438
+ onClick={handleDownloadCSS}
439
+ className="flex items-center gap-2 px-4 py-2.5 rounded-xl border border-border-app bg-bg-card hover:border-accent text-xs font-semibold hover:text-accent transition-all cursor-pointer"
440
+ >
441
+ <Download className="w-4 h-4" />
442
+ <span>Descargar CSS del Tema</span>
443
+ </button>
444
+
445
+ <button
446
+ onClick={onClose}
447
+ className="px-5 py-2.5 rounded-xl bg-accent hover:bg-accent-hover text-white text-xs font-bold transition-all cursor-pointer shadow-md"
448
+ >
449
+ Listo, aplicar
450
+ </button>
451
+ </div>
452
+ </motion.div>
453
+ </div>
454
+ )}
455
+ </AnimatePresence>,
456
+ document.body
457
+ );
458
+ };