@shohojdhara/atomix 0.3.5 → 0.3.6

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 (173) hide show
  1. package/README.md +101 -199
  2. package/atomix.config.ts +241 -0
  3. package/dist/atomix.css +260 -179
  4. package/dist/atomix.css.map +1 -1
  5. package/dist/atomix.min.css +250 -179
  6. package/dist/atomix.min.css.map +1 -1
  7. package/dist/charts.js +61 -66
  8. package/dist/charts.js.map +1 -1
  9. package/dist/core.js +47 -31
  10. package/dist/core.js.map +1 -1
  11. package/dist/forms.js +47 -31
  12. package/dist/forms.js.map +1 -1
  13. package/dist/heavy.js +47 -31
  14. package/dist/heavy.js.map +1 -1
  15. package/dist/index.d.ts +1841 -1633
  16. package/dist/index.esm.js +4975 -4113
  17. package/dist/index.esm.js.map +1 -1
  18. package/dist/index.js +5151 -4290
  19. package/dist/index.js.map +1 -1
  20. package/dist/index.min.js +1 -1
  21. package/dist/index.min.js.map +1 -1
  22. package/dist/theme.d.ts +1572 -1442
  23. package/dist/theme.js +4816 -4080
  24. package/dist/theme.js.map +1 -1
  25. package/package.json +6 -20
  26. package/src/components/Accordion/Accordion.stories.tsx +50 -17
  27. package/src/components/AtomixGlass/AtomixGlass.tsx +65 -31
  28. package/src/components/AtomixGlass/AtomixGlassContainer.tsx +11 -4
  29. package/src/components/AtomixGlass/stories/AtomixGlass.stories.tsx +1 -32
  30. package/src/components/AtomixGlass/stories/Examples.stories.tsx +2 -2
  31. package/src/components/AtomixGlass/stories/shared-components.tsx +0 -31
  32. package/src/components/Avatar/Avatar.stories.tsx +7 -0
  33. package/src/components/Badge/Badge.stories.tsx +91 -13
  34. package/src/components/Block/Block.stories.tsx +7 -23
  35. package/src/components/Breadcrumb/Breadcrumb.stories.tsx +7 -0
  36. package/src/components/Button/Button.stories.tsx +141 -22
  37. package/src/components/Button/ButtonGroup.stories.tsx +315 -0
  38. package/src/components/Button/ButtonGroup.tsx +67 -0
  39. package/src/components/Button/index.ts +2 -0
  40. package/src/components/Callout/Callout.stories.tsx +8 -6
  41. package/src/components/Card/Card.stories.tsx +82 -28
  42. package/src/components/Chart/AnimatedChart.tsx +0 -1
  43. package/src/components/Chart/AreaChart.tsx +0 -1
  44. package/src/components/Chart/BarChart.tsx +0 -1
  45. package/src/components/Chart/BubbleChart.tsx +0 -1
  46. package/src/components/Chart/CandlestickChart.tsx +0 -1
  47. package/src/components/Chart/Chart.stories.tsx +5 -7
  48. package/src/components/Chart/Chart.tsx +0 -16
  49. package/src/components/Chart/ChartRenderer.tsx +1 -1
  50. package/src/components/Chart/DonutChart.tsx +0 -1
  51. package/src/components/Chart/FunnelChart.tsx +0 -1
  52. package/src/components/Chart/GaugeChart.tsx +0 -1
  53. package/src/components/Chart/HeatmapChart.tsx +0 -1
  54. package/src/components/Chart/LineChart.tsx +0 -1
  55. package/src/components/Chart/MultiAxisChart.tsx +0 -1
  56. package/src/components/Chart/PieChart.tsx +0 -1
  57. package/src/components/Chart/RadarChart.tsx +0 -1
  58. package/src/components/Chart/ScatterChart.tsx +0 -1
  59. package/src/components/Chart/WaterfallChart.tsx +0 -1
  60. package/src/components/ColorModeToggle/ColorModeToggle.stories.tsx +7 -0
  61. package/src/components/DataTable/DataTable.stories.tsx +23 -16
  62. package/src/components/DatePicker/DatePicker.stories.tsx +27 -19
  63. package/src/components/Dropdown/Dropdown.stories.tsx +11 -19
  64. package/src/components/EdgePanel/EdgePanel.stories.tsx +1 -0
  65. package/src/components/Footer/Footer.stories.tsx +8 -6
  66. package/src/components/Footer/FooterLink.tsx +9 -2
  67. package/src/components/Form/Checkbox.stories.tsx +7 -0
  68. package/src/components/Form/Form.stories.tsx +7 -0
  69. package/src/components/Form/FormGroup.stories.tsx +9 -1
  70. package/src/components/Form/Input.stories.tsx +69 -16
  71. package/src/components/Form/Radio.stories.tsx +9 -1
  72. package/src/components/Form/Select.stories.tsx +9 -1
  73. package/src/components/Form/Textarea.stories.tsx +10 -2
  74. package/src/components/Hero/Hero.stories.tsx +7 -0
  75. package/src/components/List/List.stories.tsx +7 -0
  76. package/src/components/Messages/Messages.stories.tsx +8 -7
  77. package/src/components/Modal/Modal.stories.tsx +17 -6
  78. package/src/components/Navigation/Menu/Menu.stories.tsx +7 -0
  79. package/src/components/Navigation/Nav/Nav.stories.tsx +7 -0
  80. package/src/components/Navigation/Navbar/Navbar.stories.tsx +1 -0
  81. package/src/components/Navigation/SideMenu/SideMenu.stories.tsx +1 -1
  82. package/src/components/Pagination/Pagination.stories.tsx +188 -111
  83. package/src/components/Pagination/Pagination.tsx +83 -3
  84. package/src/components/PhotoViewer/PhotoViewer.stories.tsx +10 -5
  85. package/src/components/Popover/Popover.stories.tsx +191 -115
  86. package/src/components/ProductReview/ProductReview.stories.tsx +80 -58
  87. package/src/components/Progress/Progress.stories.tsx +79 -49
  88. package/src/components/Rating/Rating.stories.tsx +109 -84
  89. package/src/components/River/River.stories.tsx +194 -114
  90. package/src/components/SectionIntro/SectionIntro.stories.tsx +19 -9
  91. package/src/components/Slider/Slider.stories.tsx +7 -0
  92. package/src/components/Spinner/Spinner.stories.tsx +15 -11
  93. package/src/components/Steps/Steps.stories.tsx +132 -98
  94. package/src/components/Tabs/Tabs.stories.tsx +163 -112
  95. package/src/components/Testimonial/Testimonial.stories.tsx +114 -68
  96. package/src/components/Todo/Todo.stories.tsx +38 -12
  97. package/src/components/Toggle/Toggle.stories.tsx +61 -28
  98. package/src/components/Tooltip/Tooltip.stories.tsx +318 -200
  99. package/src/components/Upload/Upload.stories.tsx +122 -84
  100. package/src/components/VideoPlayer/VideoPlayer.stories.tsx +7 -24
  101. package/src/components/index.ts +1 -0
  102. package/src/lib/composables/useAtomixGlass.ts +2 -3
  103. package/src/lib/composables/useNavbar.ts +0 -10
  104. package/src/lib/config/loader.ts +2 -1
  105. package/src/lib/constants/components.ts +10 -0
  106. package/src/lib/hooks/useComponentCustomization.ts +1 -1
  107. package/src/lib/theme/README.md +174 -0
  108. package/src/lib/theme/adapters/index.ts +31 -0
  109. package/src/lib/theme/adapters/themeAdapter.ts +287 -0
  110. package/src/lib/theme/config/__tests__/configLoader.test.ts +207 -0
  111. package/src/lib/theme/config/configLoader.ts +254 -0
  112. package/src/lib/theme/config/loader.ts +37 -48
  113. package/src/lib/theme/config/types.ts +2 -2
  114. package/src/lib/theme/config/validator.ts +15 -91
  115. package/src/lib/theme/{constants.ts → constants/constants.ts} +0 -18
  116. package/src/lib/theme/constants/index.ts +8 -0
  117. package/src/lib/theme/core/ThemeRegistry.ts +19 -6
  118. package/src/lib/theme/core/__tests__/createTheme.test.ts +132 -0
  119. package/src/lib/theme/core/composeTheme.ts +155 -0
  120. package/src/lib/theme/core/createTheme.ts +94 -0
  121. package/src/lib/theme/{createTheme.ts → core/createThemeObject.ts} +10 -6
  122. package/src/lib/theme/core/index.ts +5 -19
  123. package/src/lib/theme/devtools/Comparator.tsx +346 -22
  124. package/src/lib/theme/devtools/IMPROVEMENTS.md +139 -38
  125. package/src/lib/theme/devtools/Inspector.tsx +335 -51
  126. package/src/lib/theme/devtools/LiveEditor.tsx +478 -107
  127. package/src/lib/theme/devtools/Preview.tsx +471 -221
  128. package/src/lib/theme/{core → devtools}/ThemeValidator.ts +1 -1
  129. package/src/lib/theme/devtools/index.ts +14 -4
  130. package/src/lib/theme/devtools/useHistory.ts +130 -0
  131. package/src/lib/theme/errors/index.ts +12 -0
  132. package/src/lib/theme/generators/cssFile.ts +79 -0
  133. package/src/lib/theme/generators/generateCSS.ts +89 -0
  134. package/src/lib/theme/{generateCSSVariables.ts → generators/generateCSSVariables.ts} +3 -13
  135. package/src/lib/theme/generators/index.ts +19 -0
  136. package/src/lib/theme/i18n/rtl.ts +5 -6
  137. package/src/lib/theme/index.ts +120 -15
  138. package/src/lib/theme/runtime/ThemeApplicator.ts +52 -111
  139. package/src/lib/theme/{ThemeContext.tsx → runtime/ThemeContext.tsx} +1 -1
  140. package/src/lib/theme/runtime/ThemeErrorBoundary.tsx +1 -1
  141. package/src/lib/theme/runtime/ThemeProvider.tsx +456 -179
  142. package/src/lib/theme/runtime/index.ts +1 -2
  143. package/src/lib/theme/runtime/useTheme.ts +1 -2
  144. package/src/lib/theme/test/testTheme.ts +385 -0
  145. package/src/lib/theme/tokens/index.ts +12 -0
  146. package/src/lib/theme/tokens/tokens.ts +721 -0
  147. package/src/lib/theme/types.ts +6 -42
  148. package/src/lib/theme/{utils.ts → utils/domUtils.ts} +2 -2
  149. package/src/lib/theme/utils/index.ts +11 -0
  150. package/src/lib/theme/utils/injectCSS.ts +90 -0
  151. package/src/lib/theme/utils/themeHelpers.ts +78 -0
  152. package/src/lib/theme/{themeUtils.ts → utils/themeUtils.ts} +1 -1
  153. package/src/lib/theme-tools.ts +7 -8
  154. package/src/lib/types/components.ts +40 -130
  155. package/src/lib/utils/componentUtils.ts +1 -1
  156. package/src/styles/01-settings/_settings.design-tokens.scss +4 -1
  157. package/src/styles/02-tools/_tools.button.scss +66 -79
  158. package/src/styles/06-components/_components.atomix-glass.scss +13 -3
  159. package/src/styles/06-components/_components.pagination.scss +88 -0
  160. package/scripts/sync-theme-config.js +0 -309
  161. package/src/lib/theme/composeTheme.ts +0 -370
  162. package/src/lib/theme/core/ThemeCache.ts +0 -283
  163. package/src/lib/theme/core/ThemeEngine.test.ts +0 -146
  164. package/src/lib/theme/core/ThemeEngine.ts +0 -665
  165. package/src/lib/theme/createThemeFromConfig.ts +0 -132
  166. package/src/lib/theme/devtools/CLI.ts +0 -364
  167. package/src/lib/theme/runtime/ThemeManager.test.ts +0 -192
  168. package/src/lib/theme/runtime/ThemeManager.ts +0 -446
  169. package/src/styles/03-generic/_generated-root.css +0 -26
  170. package/src/themes/README.md +0 -442
  171. package/src/themes/themes.config.js +0 -68
  172. /package/src/lib/theme/{cssVariableMapper.ts → adapters/cssVariableMapper.ts} +0 -0
  173. /package/src/lib/theme/{errors.ts → errors/errors.ts} +0 -0
@@ -2,12 +2,14 @@
2
2
  * Theme Live Editor Component
3
3
  *
4
4
  * React component for live editing themes in development
5
+ * Enhanced with undo/redo, keyboard shortcuts, resizable layout, and better color pickers
5
6
  */
6
7
 
7
- import React, { useState, useCallback } from 'react';
8
+ import React, { useState, useCallback, useEffect, useRef } from 'react';
8
9
  import type { Theme } from '../types';
9
- import { createTheme } from '../createTheme';
10
+ import { createThemeObject } from '../core/createThemeObject';
10
11
  import { ThemePreview } from './Preview';
12
+ import { useHistory } from './useHistory';
11
13
 
12
14
  /**
13
15
  * Live editor props
@@ -23,6 +25,92 @@ export interface ThemeLiveEditorProps {
23
25
  style?: React.CSSProperties;
24
26
  }
25
27
 
28
+ /**
29
+ * Color format type
30
+ */
31
+ type ColorFormat = 'hex' | 'rgb' | 'rgba' | 'hsl' | 'hsla';
32
+
33
+ /**
34
+ * Convert color to different formats
35
+ */
36
+ function convertColorFormat(color: string, format: ColorFormat): string {
37
+ // Remove whitespace
38
+ color = color.trim();
39
+
40
+ // If already in target format, return as is
41
+ if (format === 'hex' && color.startsWith('#')) return color;
42
+ if (format === 'rgb' && color.startsWith('rgb(')) return color;
43
+ if (format === 'rgba' && color.startsWith('rgba(')) return color;
44
+ if (format === 'hsl' && color.startsWith('hsl(')) return color;
45
+ if (format === 'hsla' && color.startsWith('hsla(')) return color;
46
+
47
+ // Create a temporary element to parse color
48
+ const temp = document.createElement('div');
49
+ temp.style.color = color;
50
+ document.body.appendChild(temp);
51
+ const computed = window.getComputedStyle(temp).color;
52
+ document.body.removeChild(temp);
53
+
54
+ // Parse rgb values
55
+ const rgbMatch = computed.match(/\d+/g);
56
+ if (!rgbMatch || rgbMatch.length < 3) return color;
57
+
58
+ const r = parseInt(rgbMatch[0], 10);
59
+ const g = parseInt(rgbMatch[1] || '0', 10);
60
+ const b = parseInt(rgbMatch[2] || '0', 10);
61
+ const a = rgbMatch[3] ? parseFloat(rgbMatch[3]) : 1;
62
+
63
+ switch (format) {
64
+ case 'hex':
65
+ return `#${[r, g, b].map(x => x.toString(16).padStart(2, '0')).join('')}`;
66
+ case 'rgb':
67
+ return `rgb(${r}, ${g}, ${b})`;
68
+ case 'rgba':
69
+ return `rgba(${r}, ${g}, ${b}, ${a})`;
70
+ case 'hsl':
71
+ case 'hsla':
72
+ // Convert RGB to HSL (simplified)
73
+ const hsl = rgbToHsl(r, g, b);
74
+ return format === 'hsl'
75
+ ? `hsl(${hsl.h}, ${hsl.s}%, ${hsl.l}%)`
76
+ : `hsla(${hsl.h}, ${hsl.s}%, ${hsl.l}%, ${a})`;
77
+ default:
78
+ return color;
79
+ }
80
+ }
81
+
82
+ /**
83
+ * Convert RGB to HSL
84
+ */
85
+ function rgbToHsl(r: number, g: number, b: number): { h: number; s: number; l: number } {
86
+ r /= 255;
87
+ g /= 255;
88
+ b /= 255;
89
+
90
+ const max = Math.max(r, g, b);
91
+ const min = Math.min(r, g, b);
92
+ let h = 0;
93
+ let s = 0;
94
+ const l = (max + min) / 2;
95
+
96
+ if (max !== min) {
97
+ const d = max - min;
98
+ s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
99
+
100
+ switch (max) {
101
+ case r: h = ((g - b) / d + (g < b ? 6 : 0)) / 6; break;
102
+ case g: h = ((b - r) / d + 2) / 6; break;
103
+ case b: h = ((r - g) / d + 4) / 6; break;
104
+ }
105
+ }
106
+
107
+ return {
108
+ h: Math.round(h * 360),
109
+ s: Math.round(s * 100),
110
+ l: Math.round(l * 100),
111
+ };
112
+ }
113
+
26
114
  /**
27
115
  * Theme Live Editor Component
28
116
  *
@@ -34,30 +122,98 @@ export const ThemeLiveEditor: React.FC<ThemeLiveEditorProps> = ({
34
122
  className,
35
123
  style,
36
124
  }) => {
37
- const [theme, setTheme] = useState<Theme>(initialTheme);
125
+ const {
126
+ state: theme,
127
+ setState: setThemeHistory,
128
+ undo,
129
+ redo,
130
+ canUndo,
131
+ canRedo,
132
+ } = useHistory<Theme>({
133
+ initialState: initialTheme,
134
+ maxHistorySize: 50,
135
+ });
136
+
38
137
  const [jsonInput, setJsonInput] = useState<string>(JSON.stringify(initialTheme, null, 2));
39
138
  const [error, setError] = useState<string | null>(null);
40
139
  const [editMode, setEditMode] = useState<'visual' | 'json'>('visual');
41
-
42
- const updateTheme = useCallback((newTheme: Theme) => {
43
- setTheme(newTheme);
44
- setJsonInput(JSON.stringify(newTheme, null, 2));
140
+ const [colorFormat, setColorFormat] = useState<ColorFormat>('hex');
141
+ const [resizerPosition, setResizerPosition] = useState<number>(50); // Percentage
142
+ const [isResizing, setIsResizing] = useState(false);
143
+ const editorRef = useRef<HTMLDivElement>(null);
144
+ const previewRef = useRef<HTMLDivElement>(null);
145
+
146
+ // Load saved layout preference
147
+ useEffect(() => {
148
+ const saved = localStorage.getItem('atomix-editor-layout');
149
+ if (saved) {
150
+ const position = parseFloat(saved);
151
+ if (!isNaN(position) && position > 0 && position < 100) {
152
+ setResizerPosition(position);
153
+ }
154
+ }
155
+ }, []);
156
+
157
+ // Save layout preference
158
+ useEffect(() => {
159
+ localStorage.setItem('atomix-editor-layout', resizerPosition.toString());
160
+ }, [resizerPosition]);
161
+
162
+ const updateTheme = useCallback((newTheme: Theme, addToHistory = true) => {
163
+ if (addToHistory) {
164
+ setThemeHistory(newTheme);
165
+ } else {
166
+ // Direct update without history (for JSON editor typing)
167
+ setJsonInput(JSON.stringify(newTheme, null, 2));
168
+ }
45
169
  onChange?.(newTheme);
46
170
  setError(null);
47
- }, [onChange]);
171
+ }, [onChange, setThemeHistory]);
172
+
173
+ // Sync JSON input with theme history
174
+ useEffect(() => {
175
+ setJsonInput(JSON.stringify(theme, null, 2));
176
+ }, [theme]);
48
177
 
49
178
  const handleJsonChange = useCallback((value: string) => {
50
179
  setJsonInput(value);
51
180
  try {
52
181
  const parsed = JSON.parse(value);
53
- const newTheme = createTheme(parsed);
54
- setTheme(newTheme);
55
- onChange?.(newTheme);
56
- setError(null);
182
+ const newTheme = createThemeObject(parsed);
183
+ updateTheme(newTheme, false); // Don't add to history on every keystroke
57
184
  } catch (err) {
58
185
  setError(err instanceof Error ? err.message : 'Invalid JSON');
59
186
  }
60
- }, [onChange]);
187
+ }, [updateTheme]);
188
+
189
+ // Debounced JSON update to history
190
+ const jsonUpdateTimeoutRef = useRef<NodeJS.Timeout>();
191
+ useEffect(() => {
192
+ if (error) return;
193
+
194
+ try {
195
+ const parsed = JSON.parse(jsonInput);
196
+ const newTheme = createThemeObject(parsed);
197
+
198
+ // Clear existing timeout
199
+ if (jsonUpdateTimeoutRef.current) {
200
+ clearTimeout(jsonUpdateTimeoutRef.current);
201
+ }
202
+
203
+ // Add to history after 1 second of no typing
204
+ jsonUpdateTimeoutRef.current = setTimeout(() => {
205
+ setThemeHistory(newTheme);
206
+ }, 1000);
207
+ } catch {
208
+ // Invalid JSON, don't update
209
+ }
210
+
211
+ return () => {
212
+ if (jsonUpdateTimeoutRef.current) {
213
+ clearTimeout(jsonUpdateTimeoutRef.current);
214
+ }
215
+ };
216
+ }, [jsonInput, error, setThemeHistory]);
61
217
 
62
218
  const handleColorChange = useCallback((path: string, value: string | number) => {
63
219
  const newTheme = { ...theme };
@@ -78,7 +234,7 @@ export const ThemeLiveEditor: React.FC<ThemeLiveEditorProps> = ({
78
234
  if (lastKey) {
79
235
  current[lastKey] = value;
80
236
  }
81
- updateTheme(createTheme(newTheme));
237
+ updateTheme(createThemeObject(newTheme));
82
238
  }, [theme, updateTheme]);
83
239
 
84
240
  const exportTheme = useCallback(() => {
@@ -96,114 +252,234 @@ export const ThemeLiveEditor: React.FC<ThemeLiveEditorProps> = ({
96
252
  navigator.clipboard?.writeText(jsonInput);
97
253
  }, [jsonInput]);
98
254
 
255
+ // Keyboard shortcuts
256
+ useEffect(() => {
257
+ const handleKeyDown = (e: KeyboardEvent) => {
258
+ const isMac = navigator.platform.toUpperCase().indexOf('MAC') >= 0;
259
+ const ctrlKey = isMac ? e.metaKey : e.ctrlKey;
260
+
261
+ if (ctrlKey && e.key === 'z' && !e.shiftKey) {
262
+ e.preventDefault();
263
+ if (canUndo) undo();
264
+ } else if (ctrlKey && (e.key === 'y' || (e.key === 'z' && e.shiftKey))) {
265
+ e.preventDefault();
266
+ if (canRedo) redo();
267
+ } else if (ctrlKey && e.key === 's') {
268
+ e.preventDefault();
269
+ exportTheme();
270
+ } else if (ctrlKey && e.key === '/') {
271
+ e.preventDefault();
272
+ setEditMode(prev => prev === 'visual' ? 'json' : 'visual');
273
+ } else if (e.key === 'Escape') {
274
+ setError(null);
275
+ }
276
+ };
277
+
278
+ window.addEventListener('keydown', handleKeyDown);
279
+ return () => window.removeEventListener('keydown', handleKeyDown);
280
+ }, [canUndo, canRedo, undo, redo, exportTheme]);
281
+
282
+ // Resizer handlers
283
+ const handleResizeStart = useCallback((e: React.MouseEvent) => {
284
+ e.preventDefault();
285
+ setIsResizing(true);
286
+ }, []);
287
+
288
+ useEffect(() => {
289
+ if (!isResizing) return;
290
+
291
+ const handleMouseMove = (e: MouseEvent) => {
292
+ if (!editorRef.current || !previewRef.current) return;
293
+
294
+ const container = editorRef.current.parentElement;
295
+ if (!container) return;
296
+
297
+ const containerRect = container.getBoundingClientRect();
298
+ const newPosition = ((e.clientX - containerRect.left) / containerRect.width) * 100;
299
+
300
+ // Constrain between 20% and 80%
301
+ const constrainedPosition = Math.max(20, Math.min(80, newPosition));
302
+ setResizerPosition(constrainedPosition);
303
+ };
304
+
305
+ const handleMouseUp = () => {
306
+ setIsResizing(false);
307
+ };
308
+
309
+ window.addEventListener('mousemove', handleMouseMove);
310
+ window.addEventListener('mouseup', handleMouseUp);
311
+
312
+ return () => {
313
+ window.removeEventListener('mousemove', handleMouseMove);
314
+ window.removeEventListener('mouseup', handleMouseUp);
315
+ };
316
+ }, [isResizing]);
317
+
318
+ const getColorValue = useCallback((color: string): string => {
319
+ return convertColorFormat(color, colorFormat);
320
+ }, [colorFormat]);
321
+
99
322
  return (
100
323
  <div className={`atomix-theme-live-editor ${className || ''}`} style={style}>
101
324
  <div className="editor-header">
102
325
  <h2>Live Theme Editor</h2>
103
326
  <div className="editor-controls">
104
- <button
105
- className={`mode-button ${editMode === 'visual' ? 'active' : ''}`}
106
- onClick={() => setEditMode('visual')}
107
- >
108
- Visual
109
- </button>
110
- <button
111
- className={`mode-button ${editMode === 'json' ? 'active' : ''}`}
112
- onClick={() => setEditMode('json')}
113
- >
114
- JSON
115
- </button>
116
- <button className="export-button" onClick={exportTheme}>
117
- Export
118
- </button>
119
- <button className="copy-button" onClick={copyToClipboard}>
120
- Copy JSON
121
- </button>
327
+ <div className="history-controls">
328
+ <button
329
+ className="history-button"
330
+ onClick={undo}
331
+ disabled={!canUndo}
332
+ title="Undo (Ctrl+Z)"
333
+ >
334
+ Undo
335
+ </button>
336
+ <button
337
+ className="history-button"
338
+ onClick={redo}
339
+ disabled={!canRedo}
340
+ title="Redo (Ctrl+Shift+Z)"
341
+ >
342
+ Redo
343
+ </button>
344
+ </div>
345
+ <div className="mode-controls">
346
+ <button
347
+ className={`mode-button ${editMode === 'visual' ? 'active' : ''}`}
348
+ onClick={() => setEditMode('visual')}
349
+ title="Visual Editor (Ctrl+/)"
350
+ >
351
+ Visual
352
+ </button>
353
+ <button
354
+ className={`mode-button ${editMode === 'json' ? 'active' : ''}`}
355
+ onClick={() => setEditMode('json')}
356
+ title="JSON Editor (Ctrl+/)"
357
+ >
358
+ JSON
359
+ </button>
360
+ </div>
361
+ <div className="action-controls">
362
+ <button className="export-button" onClick={exportTheme} title="Export (Ctrl+S)">
363
+ Export
364
+ </button>
365
+ <button className="copy-button" onClick={copyToClipboard} title="Copy JSON">
366
+ Copy JSON
367
+ </button>
368
+ </div>
122
369
  </div>
123
370
  </div>
124
371
 
125
- <div className="editor-content">
126
- <div className="editor-panel">
372
+ <div className="editor-content" ref={editorRef}>
373
+ <div
374
+ className="editor-panel"
375
+ style={{ width: `${resizerPosition}%` }}
376
+ >
127
377
  {editMode === 'visual' ? (
128
378
  <div className="visual-editor">
129
- <h3>Colors</h3>
130
-
131
- {/* Primary Color */}
132
- <div className="editor-field">
133
- <label>Primary Color</label>
134
- <div className="color-input-group">
135
- <input
136
- type="color"
137
- value={theme.palette.primary.main}
138
- onChange={(e) => handleColorChange('palette.primary.main', e.target.value)}
139
- />
140
- <input
141
- type="text"
142
- value={theme.palette.primary.main}
143
- onChange={(e) => handleColorChange('palette.primary.main', e.target.value)}
144
- placeholder="#7AFFD7"
145
- />
379
+ <div className="editor-section">
380
+ <h3>Colors</h3>
381
+ <div className="color-format-selector">
382
+ <label>Color Format:</label>
383
+ <select
384
+ value={colorFormat}
385
+ onChange={(e) => setColorFormat(e.target.value as ColorFormat)}
386
+ >
387
+ <option value="hex">HEX</option>
388
+ <option value="rgb">RGB</option>
389
+ <option value="rgba">RGBA</option>
390
+ <option value="hsl">HSL</option>
391
+ <option value="hsla">HSLA</option>
392
+ </select>
393
+ </div>
394
+
395
+ {/* Primary Color */}
396
+ <div className="editor-field">
397
+ <label>Primary Color</label>
398
+ <div className="color-input-group">
399
+ <input
400
+ type="color"
401
+ value={theme.palette.primary.main}
402
+ onChange={(e) => handleColorChange('palette.primary.main', e.target.value)}
403
+ />
404
+ <input
405
+ type="text"
406
+ value={getColorValue(theme.palette.primary.main)}
407
+ onChange={(e) => {
408
+ const converted = convertColorFormat(e.target.value, 'hex');
409
+ handleColorChange('palette.primary.main', converted);
410
+ }}
411
+ placeholder="#7AFFD7"
412
+ />
413
+ </div>
414
+ </div>
415
+
416
+ {/* Secondary Color */}
417
+ <div className="editor-field">
418
+ <label>Secondary Color</label>
419
+ <div className="color-input-group">
420
+ <input
421
+ type="color"
422
+ value={theme.palette.secondary.main}
423
+ onChange={(e) => handleColorChange('palette.secondary.main', e.target.value)}
424
+ />
425
+ <input
426
+ type="text"
427
+ value={getColorValue(theme.palette.secondary.main)}
428
+ onChange={(e) => {
429
+ const converted = convertColorFormat(e.target.value, 'hex');
430
+ handleColorChange('palette.secondary.main', converted);
431
+ }}
432
+ placeholder="#FF5733"
433
+ />
434
+ </div>
435
+ </div>
436
+
437
+ {/* Background Colors */}
438
+ <h3>Background</h3>
439
+ <div className="editor-field">
440
+ <label>Default Background</label>
441
+ <div className="color-input-group">
442
+ <input
443
+ type="color"
444
+ value={theme.palette.background.default}
445
+ onChange={(e) => handleColorChange('palette.background.default', e.target.value)}
446
+ />
447
+ <input
448
+ type="text"
449
+ value={getColorValue(theme.palette.background.default)}
450
+ onChange={(e) => {
451
+ const converted = convertColorFormat(e.target.value, 'hex');
452
+ handleColorChange('palette.background.default', converted);
453
+ }}
454
+ />
455
+ </div>
146
456
  </div>
147
457
  </div>
148
458
 
149
- {/* Secondary Color */}
150
- <div className="editor-field">
151
- <label>Secondary Color</label>
152
- <div className="color-input-group">
153
- <input
154
- type="color"
155
- value={theme.palette.secondary.main}
156
- onChange={(e) => handleColorChange('palette.secondary.main', e.target.value)}
157
- />
459
+ {/* Typography */}
460
+ <div className="editor-section">
461
+ <h3>Typography</h3>
462
+ <div className="editor-field">
463
+ <label>Font Family</label>
158
464
  <input
159
465
  type="text"
160
- value={theme.palette.secondary.main}
161
- onChange={(e) => handleColorChange('palette.secondary.main', e.target.value)}
162
- placeholder="#FF5733"
466
+ value={theme.typography.fontFamily}
467
+ onChange={(e) => handleColorChange('typography.fontFamily', e.target.value)}
468
+ placeholder="Inter, sans-serif"
163
469
  />
164
470
  </div>
165
- </div>
166
471
 
167
- {/* Background Colors */}
168
- <h3>Background</h3>
169
- <div className="editor-field">
170
- <label>Default Background</label>
171
- <div className="color-input-group">
472
+ <div className="editor-field">
473
+ <label>Base Font Size (px)</label>
172
474
  <input
173
- type="color"
174
- value={theme.palette.background.default}
175
- onChange={(e) => handleColorChange('palette.background.default', e.target.value)}
176
- />
177
- <input
178
- type="text"
179
- value={theme.palette.background.default}
180
- onChange={(e) => handleColorChange('palette.background.default', e.target.value)}
475
+ type="number"
476
+ value={theme.typography.fontSize}
477
+ onChange={(e) => handleColorChange('typography.fontSize', parseInt(e.target.value))}
478
+ min="10"
479
+ max="24"
181
480
  />
182
481
  </div>
183
482
  </div>
184
-
185
- {/* Typography */}
186
- <h3>Typography</h3>
187
- <div className="editor-field">
188
- <label>Font Family</label>
189
- <input
190
- type="text"
191
- value={theme.typography.fontFamily}
192
- onChange={(e) => handleColorChange('typography.fontFamily', e.target.value)}
193
- placeholder="Inter, sans-serif"
194
- />
195
- </div>
196
-
197
- <div className="editor-field">
198
- <label>Base Font Size (px)</label>
199
- <input
200
- type="number"
201
- value={theme.typography.fontSize}
202
- onChange={(e) => handleColorChange('typography.fontSize', parseInt(e.target.value))}
203
- min="10"
204
- max="24"
205
- />
206
- </div>
207
483
  </div>
208
484
  ) : (
209
485
  <div className="json-editor">
@@ -215,13 +491,23 @@ export const ThemeLiveEditor: React.FC<ThemeLiveEditorProps> = ({
215
491
  {error && (
216
492
  <div className="error-message">
217
493
  ❌ {error}
494
+ <button className="error-dismiss" onClick={() => setError(null)}>×</button>
218
495
  </div>
219
496
  )}
220
497
  </div>
221
498
  )}
222
499
  </div>
223
500
 
224
- <div className="preview-panel">
501
+ <div
502
+ className={`resizer ${isResizing ? 'resizing' : ''}`}
503
+ onMouseDown={handleResizeStart}
504
+ />
505
+
506
+ <div
507
+ className="preview-panel"
508
+ ref={previewRef}
509
+ style={{ width: `${100 - resizerPosition}%` }}
510
+ >
225
511
  <h3>Live Preview</h3>
226
512
  <ThemePreview
227
513
  theme={theme}
@@ -257,10 +543,19 @@ export const ThemeLiveEditor: React.FC<ThemeLiveEditorProps> = ({
257
543
  }
258
544
 
259
545
  .editor-controls {
546
+ display: flex;
547
+ gap: 12px;
548
+ align-items: center;
549
+ }
550
+
551
+ .history-controls,
552
+ .mode-controls,
553
+ .action-controls {
260
554
  display: flex;
261
555
  gap: 8px;
262
556
  }
263
557
 
558
+ .history-button,
264
559
  .mode-button,
265
560
  .export-button,
266
561
  .copy-button {
@@ -273,12 +568,18 @@ export const ThemeLiveEditor: React.FC<ThemeLiveEditorProps> = ({
273
568
  transition: all 0.2s;
274
569
  }
275
570
 
571
+ .history-button:hover:not(:disabled),
276
572
  .mode-button:hover,
277
573
  .export-button:hover,
278
574
  .copy-button:hover {
279
575
  background: #f5f5f5;
280
576
  }
281
577
 
578
+ .history-button:disabled {
579
+ opacity: 0.5;
580
+ cursor: not-allowed;
581
+ }
582
+
282
583
  .mode-button.active {
283
584
  background: #2196f3;
284
585
  color: white;
@@ -298,16 +599,38 @@ export const ThemeLiveEditor: React.FC<ThemeLiveEditorProps> = ({
298
599
  }
299
600
 
300
601
  .editor-content {
301
- display: grid;
302
- grid-template-columns: 1fr 1fr;
303
- gap: 24px;
304
- padding: 24px;
602
+ display: flex;
603
+ position: relative;
305
604
  min-height: 600px;
306
605
  }
307
606
 
308
607
  .editor-panel,
309
608
  .preview-panel {
310
609
  overflow-y: auto;
610
+ padding: 24px;
611
+ }
612
+
613
+ .resizer {
614
+ width: 4px;
615
+ background: #e0e0e0;
616
+ cursor: col-resize;
617
+ position: relative;
618
+ flex-shrink: 0;
619
+ transition: background 0.2s;
620
+ }
621
+
622
+ .resizer:hover,
623
+ .resizer.resizing {
624
+ background: #2196f3;
625
+ }
626
+
627
+ .resizer::before {
628
+ content: '';
629
+ position: absolute;
630
+ left: -2px;
631
+ right: -2px;
632
+ top: 0;
633
+ bottom: 0;
311
634
  }
312
635
 
313
636
  .editor-panel h3,
@@ -320,11 +643,39 @@ export const ThemeLiveEditor: React.FC<ThemeLiveEditorProps> = ({
320
643
  }
321
644
 
322
645
  .visual-editor {
646
+ display: flex;
647
+ flex-direction: column;
648
+ gap: 24px;
649
+ }
650
+
651
+ .editor-section {
323
652
  display: flex;
324
653
  flex-direction: column;
325
654
  gap: 16px;
326
655
  }
327
656
 
657
+ .color-format-selector {
658
+ display: flex;
659
+ align-items: center;
660
+ gap: 8px;
661
+ margin-bottom: 8px;
662
+ }
663
+
664
+ .color-format-selector label {
665
+ font-size: 14px;
666
+ font-weight: 500;
667
+ color: #666;
668
+ }
669
+
670
+ .color-format-selector select {
671
+ padding: 6px 12px;
672
+ border: 1px solid #e0e0e0;
673
+ border-radius: 4px;
674
+ font-size: 14px;
675
+ background: white;
676
+ cursor: pointer;
677
+ }
678
+
328
679
  .editor-field {
329
680
  display: flex;
330
681
  flex-direction: column;
@@ -386,14 +737,34 @@ export const ThemeLiveEditor: React.FC<ThemeLiveEditorProps> = ({
386
737
  border-radius: 4px;
387
738
  margin-top: 8px;
388
739
  font-size: 14px;
740
+ display: flex;
741
+ justify-content: space-between;
742
+ align-items: center;
743
+ }
744
+
745
+ .error-dismiss {
746
+ background: none;
747
+ border: none;
748
+ color: #d32f2f;
749
+ font-size: 20px;
750
+ cursor: pointer;
751
+ padding: 0;
752
+ width: 24px;
753
+ height: 24px;
754
+ display: flex;
755
+ align-items: center;
756
+ justify-content: center;
757
+ }
758
+
759
+ .error-dismiss:hover {
760
+ background: rgba(211, 47, 47, 0.1);
761
+ border-radius: 50%;
389
762
  }
390
763
 
391
764
  .preview-panel {
392
765
  border-left: 1px solid #e0e0e0;
393
- padding-left: 24px;
394
766
  }
395
767
  `}</style>
396
768
  </div>
397
769
  );
398
770
  };
399
-