@shohojdhara/atomix 0.3.13 → 0.3.14

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 (151) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/README.md +2 -0
  3. package/dist/atomix.css +95 -77
  4. package/dist/atomix.css.map +1 -1
  5. package/dist/atomix.min.css +2 -2
  6. package/dist/atomix.min.css.map +1 -1
  7. package/dist/charts.d.ts +1 -1
  8. package/dist/charts.js +1 -1
  9. package/dist/core.d.ts +41 -11
  10. package/dist/core.js +39 -23
  11. package/dist/core.js.map +1 -1
  12. package/dist/forms.d.ts +28 -11
  13. package/dist/forms.js +8 -5
  14. package/dist/forms.js.map +1 -1
  15. package/dist/heavy.d.ts +1 -1
  16. package/dist/heavy.js +15 -6
  17. package/dist/heavy.js.map +1 -1
  18. package/dist/index.d.ts +122 -46
  19. package/dist/index.esm.js +849 -182
  20. package/dist/index.esm.js.map +1 -1
  21. package/dist/index.js +854 -186
  22. package/dist/index.js.map +1 -1
  23. package/dist/index.min.js +1 -1
  24. package/dist/index.min.js.map +1 -1
  25. package/dist/theme.d.ts +27 -2
  26. package/dist/theme.js +721 -108
  27. package/dist/theme.js.map +1 -1
  28. package/package.json +1 -1
  29. package/scripts/atomix-cli.js +610 -1111
  30. package/scripts/cli/component-generator.js +610 -0
  31. package/scripts/cli/documentation-sync.js +542 -0
  32. package/scripts/cli/interactive-init.js +84 -288
  33. package/scripts/cli/mappings.js +211 -0
  34. package/scripts/cli/migration-tools.js +95 -288
  35. package/scripts/cli/template-manager.js +107 -0
  36. package/scripts/cli/templates/README.md +123 -0
  37. package/scripts/cli/templates/composable-templates.js +149 -0
  38. package/scripts/cli/templates/config-templates.js +126 -0
  39. package/scripts/cli/templates/index.js +95 -0
  40. package/scripts/cli/templates/project-templates.js +214 -0
  41. package/scripts/cli/templates/react-templates.js +261 -0
  42. package/scripts/cli/templates/scss-templates.js +156 -0
  43. package/scripts/cli/templates/storybook-templates.js +236 -0
  44. package/scripts/cli/templates/testing-templates.js +45 -0
  45. package/scripts/cli/templates/token-templates.js +447 -0
  46. package/scripts/cli/templates/types-templates.js +133 -0
  47. package/scripts/cli/templates-original-backup.js +1655 -0
  48. package/scripts/cli/templates.js +35 -0
  49. package/scripts/cli/templates_backup.js +684 -0
  50. package/scripts/cli/theme-bridge.js +20 -14
  51. package/scripts/cli/token-manager.js +150 -77
  52. package/scripts/cli/utils.js +37 -25
  53. package/src/components/Accordion/Accordion.stories.tsx +5 -5
  54. package/src/components/Accordion/Accordion.test.tsx +57 -0
  55. package/src/components/Accordion/Accordion.tsx +4 -0
  56. package/src/components/AtomixGlass/stories/AtomixGlass.stories.tsx +1 -1
  57. package/src/components/AtomixGlass/stories/Examples.stories.tsx +37 -37
  58. package/src/components/AtomixGlass/stories/Playground.stories.tsx +50 -51
  59. package/src/components/Avatar/Avatar.stories.tsx +26 -26
  60. package/src/components/Badge/Badge.stories.tsx +31 -31
  61. package/src/components/Badge/Badge.test.tsx +51 -0
  62. package/src/components/Badge/Badge.tsx +20 -1
  63. package/src/components/Block/Block.stories.tsx +5 -5
  64. package/src/components/Breadcrumb/Breadcrumb.stories.tsx +1 -1
  65. package/src/components/Breadcrumb/Breadcrumb.tsx +2 -2
  66. package/src/components/Button/Button.stories.tsx +13 -13
  67. package/src/components/Button/Button.tsx +4 -4
  68. package/src/components/Button/ButtonGroup.stories.tsx +2 -2
  69. package/src/components/Button/README.md +5 -0
  70. package/src/components/Callout/Callout.stories.tsx +11 -11
  71. package/src/components/Callout/Callout.test.tsx +10 -10
  72. package/src/components/Callout/Callout.tsx +7 -7
  73. package/src/components/Callout/README.md +9 -8
  74. package/src/components/Card/Card.tsx +2 -2
  75. package/src/components/Chart/Chart.stories.tsx +6 -6
  76. package/src/components/Chart/Chart.tsx +1 -1
  77. package/src/components/ColorModeToggle/ColorModeToggle.stories.tsx +1 -1
  78. package/src/components/DataTable/DataTable.tsx +14 -12
  79. package/src/components/DatePicker/DatePicker.stories.tsx +6 -6
  80. package/src/components/Dropdown/Dropdown.stories.tsx +4 -4
  81. package/src/components/Form/Checkbox.stories.tsx +3 -3
  82. package/src/components/Form/Checkbox.tsx +4 -2
  83. package/src/components/Form/Form.stories.tsx +3 -3
  84. package/src/components/Form/FormGroup.stories.tsx +1 -1
  85. package/src/components/Form/Input.stories.tsx +28 -16
  86. package/src/components/Form/Input.test.tsx +59 -0
  87. package/src/components/Form/Input.tsx +97 -95
  88. package/src/components/Form/Radio.stories.tsx +94 -94
  89. package/src/components/Form/Radio.tsx +2 -2
  90. package/src/components/Form/Select.stories.tsx +4 -4
  91. package/src/components/Form/Select.tsx +2 -2
  92. package/src/components/Form/Textarea.stories.tsx +22 -7
  93. package/src/components/Form/Textarea.test.tsx +45 -0
  94. package/src/components/Form/Textarea.tsx +88 -86
  95. package/src/components/List/List.stories.tsx +2 -2
  96. package/src/components/Modal/Modal.stories.tsx +4 -4
  97. package/src/components/Navigation/Navbar/Navbar.stories.tsx +5 -5
  98. package/src/components/Navigation/Navbar/Navbar.tsx +1 -1
  99. package/src/components/Navigation/README.md +1 -1
  100. package/src/components/Pagination/Pagination.stories.tsx +5 -2
  101. package/src/components/Pagination/Pagination.tsx +1 -1
  102. package/src/components/PhotoViewer/PhotoViewer.stories.tsx +10 -10
  103. package/src/components/Popover/Popover.stories.tsx +1 -1
  104. package/src/components/ProductReview/ProductReview.tsx +1 -1
  105. package/src/components/Progress/Progress.tsx +46 -46
  106. package/src/components/Rating/Rating.stories.tsx +4 -4
  107. package/src/components/Rating/Rating.tsx +8 -8
  108. package/src/components/Slider/Slider.stories.tsx +63 -63
  109. package/src/components/Spinner/Spinner.stories.tsx +2 -2
  110. package/src/components/Spinner/Spinner.test.tsx +35 -0
  111. package/src/components/Spinner/Spinner.tsx +9 -2
  112. package/src/components/Testimonial/Testimonial.stories.tsx +1 -1
  113. package/src/components/Toggle/Toggle.stories.tsx +32 -9
  114. package/src/components/Toggle/Toggle.test.tsx +91 -0
  115. package/src/components/Toggle/Toggle.tsx +44 -27
  116. package/src/components/Tooltip/Tooltip.tsx +1 -1
  117. package/src/layouts/Grid/Grid.stories.tsx +49 -49
  118. package/src/layouts/MasonryGrid/MasonryGrid.stories.tsx +2 -2
  119. package/src/lib/composables/useAccordion.ts +12 -3
  120. package/src/lib/composables/useBreadcrumb.ts +2 -2
  121. package/src/lib/composables/useCallout.ts +7 -7
  122. package/src/lib/composables/useNavbar.ts +1 -1
  123. package/src/lib/constants/components.ts +1 -1
  124. package/src/lib/storybook/InteractiveDemo.tsx +113 -0
  125. package/src/lib/storybook/PreviewContainer.tsx +36 -0
  126. package/src/lib/storybook/VariantsGrid.tsx +21 -0
  127. package/src/lib/storybook/index.ts +3 -0
  128. package/src/lib/theme/core/createThemeObject.ts +9 -5
  129. package/src/lib/theme/devtools/CLI.ts +155 -0
  130. package/src/lib/theme/devtools/DesignTokensCustomizer.stories.tsx +213 -0
  131. package/src/lib/theme/devtools/DesignTokensCustomizer.tsx +566 -0
  132. package/src/lib/theme/devtools/LiveEditor.tsx +2 -1
  133. package/src/lib/theme/devtools/index.ts +3 -0
  134. package/src/lib/theme/errors/errors.ts +8 -0
  135. package/src/lib/theme/runtime/ThemeProvider.tsx +117 -57
  136. package/src/lib/theme/runtime/__tests__/ThemeProvider.integration.test.tsx +305 -0
  137. package/src/lib/theme/runtime/__tests__/ThemeProvider.test.tsx +588 -0
  138. package/src/lib/theme/utils/__tests__/themeValidation.test.ts +264 -0
  139. package/src/lib/theme/utils/index.ts +1 -0
  140. package/src/lib/theme/utils/themeValidation.ts +501 -0
  141. package/src/lib/theme-tools.ts +32 -3
  142. package/src/lib/types/components.ts +81 -26
  143. package/src/lib/utils/themeNaming.ts +1 -1
  144. package/src/styles/06-components/_components.callout.scss +29 -33
  145. package/src/styles/06-components/_index.scss +1 -1
  146. package/src/styles/99-utilities/_utilities.display.scss +14 -3
  147. package/src/styles/99-utilities/_utilities.flex.scss +10 -10
  148. package/src/styles/99-utilities/_utilities.text.scss +28 -8
  149. package/scripts/cli/__tests__/cli-commands.test.js +0 -204
  150. package/scripts/cli/__tests__/utils.test.js +0 -201
  151. package/scripts/cli/__tests__/vitest.config.js +0 -26
@@ -0,0 +1,566 @@
1
+ /**
2
+ * Design Tokens Customizer Component
3
+ *
4
+ * Interactive theme customizer that allows real-time editing of DesignTokens
5
+ * with live preview and export functionality.
6
+ */
7
+
8
+ import React, { useState, useCallback, useEffect, useMemo } from 'react';
9
+ import type { DesignTokens } from '../tokens/tokens';
10
+ import { defaultTokens, createTokens } from '../tokens/tokens';
11
+ import { createTheme } from '../core/createTheme';
12
+ import { createThemeObject } from '../core/createThemeObject';
13
+ import { generateCSSVariables } from '../generators/generateCSSVariables';
14
+ import { ThemePreview } from './Preview';
15
+
16
+ /**
17
+ * Customizer props
18
+ */
19
+ export interface DesignTokensCustomizerProps {
20
+ /** Initial DesignTokens to customize */
21
+ initialTokens?: Partial<DesignTokens>;
22
+ /** Callback when tokens change */
23
+ onTokensChange?: (tokens: DesignTokens) => void;
24
+ /** CSS class name */
25
+ className?: string;
26
+ /** Inline styles */
27
+ style?: React.CSSProperties;
28
+ }
29
+
30
+ /**
31
+ * Color format type
32
+ */
33
+ type ColorFormat = 'hex' | 'rgb' | 'rgba' | 'hsl' | 'hsla';
34
+
35
+ /**
36
+ * Token category for organization
37
+ */
38
+ type TokenCategory = 'colors' | 'typography' | 'spacing' | 'shadows' | 'borders' | 'transitions' | 'zindex' | 'breakpoints';
39
+
40
+ /**
41
+ * Design Tokens Customizer Component
42
+ */
43
+ export const DesignTokensCustomizer: React.FC<DesignTokensCustomizerProps> = ({
44
+ initialTokens = {},
45
+ onTokensChange,
46
+ className,
47
+ style,
48
+ }) => {
49
+ // Current tokens state
50
+ const [tokens, setTokens] = useState<DesignTokens>(() => createTokens(initialTokens));
51
+ const [colorFormat, setColorFormat] = useState<ColorFormat>('hex');
52
+ const [activeCategory, setActiveCategory] = useState<TokenCategory>('colors');
53
+ const [cssPreview, setCssPreview] = useState<string>('');
54
+
55
+ // Generate CSS when tokens change
56
+ useEffect(() => {
57
+ const css = createTheme(tokens, { selector: ':root', prefix: 'atomix-preview' });
58
+ setCssPreview(css);
59
+ onTokensChange?.(tokens);
60
+ }, [tokens, onTokensChange]);
61
+
62
+ // Create theme object for preview
63
+ const previewTheme = useMemo(() => {
64
+ return createThemeObject({
65
+ palette: {
66
+ primary: { main: tokens['primary'] },
67
+ secondary: { main: tokens['secondary'] },
68
+ error: { main: tokens['error'] },
69
+ warning: { main: tokens['warning'] },
70
+ info: { main: tokens['info'] },
71
+ success: { main: tokens['success'] },
72
+ background: {
73
+ default: '#ffffff',
74
+ subtle: tokens['secondary-bg-subtle'] || '#f3f4f6',
75
+ },
76
+ text: {
77
+ primary: tokens['primary-text-emphasis'] || '#111827',
78
+ secondary: tokens['secondary-text-emphasis'] || '#374151',
79
+ disabled: tokens['disabled-text-emphasis'] || '#9ca3af',
80
+ },
81
+ },
82
+ typography: {
83
+ fontFamily: tokens['body-font-family'] || '"Roboto", sans-serif',
84
+ fontSize: parseInt(tokens['body-font-size'] || '16'),
85
+ },
86
+ spacing: (factor: number) => `${0.25 * factor}rem`, // Simplified spacing
87
+ });
88
+ }, [tokens]);
89
+
90
+ // Update token value
91
+ const updateToken = useCallback((key: keyof DesignTokens, value: string) => {
92
+ setTokens(prev => ({
93
+ ...prev,
94
+ [key]: value,
95
+ }));
96
+ }, []);
97
+
98
+ // Convert color format
99
+ const convertColorFormat = useCallback((color: string, format: ColorFormat): string => {
100
+ // Parse current color
101
+ const temp = document.createElement('div');
102
+ temp.style.color = color;
103
+ document.body.appendChild(temp);
104
+ const computed = window.getComputedStyle(temp).color;
105
+ document.body.removeChild(temp);
106
+
107
+ const rgbMatch = computed.match(/\d+/g);
108
+ if (!rgbMatch || rgbMatch.length < 3) return color;
109
+
110
+ const r = parseInt(rgbMatch[0] || '0', 10);
111
+ const g = parseInt(rgbMatch[1] || '0', 10);
112
+ const b = parseInt(rgbMatch[2] || '0', 10);
113
+ const a = rgbMatch[3] ? parseFloat(rgbMatch[3]) : 1;
114
+
115
+ switch (format) {
116
+ case 'hex':
117
+ return `#${[r, g, b].map(x => x.toString(16).padStart(2, '0')).join('')}`;
118
+ case 'rgb':
119
+ return `rgb(${r}, ${g}, ${b})`;
120
+ case 'rgba':
121
+ return `rgba(${r}, ${g}, ${b}, ${a})`;
122
+ case 'hsl':
123
+ case 'hsla':
124
+ {
125
+ const hsl = rgbToHsl(r, g, b);
126
+ return format === 'hsl'
127
+ ? `hsl(${hsl.h}, ${hsl.s}%, ${hsl.l}%)`
128
+ : `hsla(${hsl.h}, ${hsl.s}%, ${hsl.l}%, ${a})`;
129
+ }
130
+ default:
131
+ return color;
132
+ }
133
+ }, []);
134
+
135
+ // RGB to HSL conversion
136
+ function rgbToHsl(r: number, g: number, b: number): { h: number; s: number; l: number } {
137
+ r /= 255;
138
+ g /= 255;
139
+ b /= 255;
140
+
141
+ const max = Math.max(r, g, b);
142
+ const min = Math.min(r, g, b);
143
+ let h = 0;
144
+ let s = 0;
145
+ const l = (max + min) / 2;
146
+
147
+ if (max !== min) {
148
+ const d = max - min;
149
+ s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
150
+
151
+ switch (max) {
152
+ case r: h = ((g - b) / d + (g < b ? 6 : 0)) / 6; break;
153
+ case g: h = ((b - r) / d + 2) / 6; break;
154
+ case b: h = ((r - g) / d + 4) / 6; break;
155
+ }
156
+ }
157
+
158
+ return {
159
+ h: Math.round(h * 360),
160
+ s: Math.round(s * 100),
161
+ l: Math.round(l * 100),
162
+ };
163
+ }
164
+
165
+ // Export functions
166
+ const exportTokens = useCallback(() => {
167
+ const dataStr = JSON.stringify(tokens, null, 2);
168
+ const dataUri = 'data:application/json;charset=utf-8,' + encodeURIComponent(dataStr);
169
+ const exportFileDefaultName = 'design-tokens.json';
170
+
171
+ const linkElement = document.createElement('a');
172
+ linkElement.setAttribute('href', dataUri);
173
+ linkElement.setAttribute('download', exportFileDefaultName);
174
+ linkElement.click();
175
+ }, [tokens]);
176
+
177
+ const exportTheme = useCallback(() => {
178
+ const themeCss = createTheme(tokens);
179
+ const dataUri = 'data:text/css;charset=utf-8,' + encodeURIComponent(themeCss);
180
+ const exportFileDefaultName = 'theme.css';
181
+
182
+ const linkElement = document.createElement('a');
183
+ linkElement.setAttribute('href', dataUri);
184
+ linkElement.setAttribute('download', exportFileDefaultName);
185
+ linkElement.click();
186
+ }, [tokens]);
187
+
188
+ const copyToClipboard = useCallback(() => {
189
+ navigator.clipboard?.writeText(JSON.stringify(tokens, null, 2));
190
+ }, [tokens]);
191
+
192
+ // Reset to defaults
193
+ const resetToDefaults = useCallback(() => {
194
+ setTokens(defaultTokens);
195
+ }, []);
196
+
197
+ // Load tokens from file
198
+ const loadTokensFromFile = useCallback((event: React.ChangeEvent<HTMLInputElement>) => {
199
+ const file = event.target.files?.[0];
200
+ if (!file) return;
201
+
202
+ const reader = new FileReader();
203
+ reader.onload = (e) => {
204
+ try {
205
+ const content = e.target?.result as string;
206
+ const parsedTokens = JSON.parse(content);
207
+ const mergedTokens = createTokens(parsedTokens);
208
+ setTokens(mergedTokens);
209
+ } catch (error) {
210
+ console.error('Failed to load tokens:', error);
211
+ alert('Invalid JSON file. Please select a valid design tokens JSON file.');
212
+ }
213
+ };
214
+ reader.readAsText(file);
215
+ }, []);
216
+
217
+ // Token categories with their keys
218
+ const tokenCategories = {
219
+ colors: {
220
+ label: 'Colors',
221
+ tokens: [
222
+ // Base colors
223
+ 'primary', 'secondary', 'success', 'info', 'warning', 'error', 'light', 'dark',
224
+ // RGB versions
225
+ 'primary-rgb', 'secondary-rgb', 'success-rgb', 'info-rgb', 'warning-rgb', 'error-rgb', 'light-rgb', 'dark-rgb',
226
+ // Gray scale
227
+ 'gray-1', 'gray-2', 'gray-3', 'gray-4', 'gray-5', 'gray-6', 'gray-7', 'gray-8', 'gray-9', 'gray-10',
228
+ // Primary scale
229
+ 'primary-1', 'primary-2', 'primary-3', 'primary-4', 'primary-5', 'primary-6', 'primary-7', 'primary-8', 'primary-9', 'primary-10',
230
+ // Text emphasis
231
+ 'primary-text-emphasis', 'secondary-text-emphasis', 'tertiary-text-emphasis', 'disabled-text-emphasis',
232
+ 'invert-text-emphasis', 'brand-text-emphasis', 'error-text-emphasis', 'success-text-emphasis',
233
+ 'warning-text-emphasis', 'info-text-emphasis', 'light-text-emphasis', 'dark-text-emphasis',
234
+ // Background subtle
235
+ 'primary-bg-subtle', 'secondary-bg-subtle', 'tertiary-bg-subtle', 'invert-bg-subtle',
236
+ 'brand-bg-subtle', 'error-bg-subtle', 'success-bg-subtle', 'warning-bg-subtle', 'info-bg-subtle',
237
+ 'light-bg-subtle', 'dark-bg-subtle',
238
+ // Border subtle
239
+ 'primary-border-subtle', 'secondary-border-subtle', 'success-border-subtle', 'error-border-subtle',
240
+ 'warning-border-subtle', 'info-border-subtle', 'brand-border-subtle', 'light-border-subtle', 'dark-border-subtle',
241
+ // Hover states
242
+ 'primary-hover', 'secondary-hover', 'light-hover', 'dark-hover', 'error-hover', 'success-hover',
243
+ 'warning-hover', 'info-hover',
244
+ // Gradients
245
+ 'primary-gradient', 'secondary-gradient', 'light-gradient', 'dark-gradient', 'success-gradient',
246
+ 'info-gradient', 'warning-gradient', 'error-gradient', 'gradient',
247
+ ],
248
+ },
249
+ typography: {
250
+ label: 'Typography',
251
+ tokens: [
252
+ 'font-sans-serif', 'font-monospace', 'body-font-family', 'body-font-size', 'body-font-weight',
253
+ 'body-line-height', 'body-color', 'body-bg', 'heading-color',
254
+ 'font-size-xl', 'font-size-2xl', 'display-1',
255
+ 'font-weight-light', 'font-weight-normal', 'font-weight-medium', 'font-weight-semibold',
256
+ 'font-weight-bold', 'font-weight-heavy', 'font-weight-black',
257
+ 'line-height-base', 'line-height-sm', 'line-height-lg',
258
+ 'letter-spacing-h1', 'letter-spacing-h2', 'letter-spacing-h3', 'letter-spacing-h4',
259
+ 'letter-spacing-h5', 'letter-spacing-h6',
260
+ 'link-color', 'link-color-rgb', 'link-decoration', 'link-hover-color', 'link-hover-color-rgb',
261
+ 'highlight-bg', 'code-color',
262
+ ],
263
+ },
264
+ spacing: {
265
+ label: 'Spacing',
266
+ tokens: [
267
+ 'spacing-0', 'spacing-1', 'spacing-px-6', 'spacing-2', 'spacing-px-10', 'spacing-3', 'spacing-px-14',
268
+ 'spacing-4', 'spacing-5', 'spacing-px-22', 'spacing-6', 'spacing-7', 'spacing-px-30', 'spacing-8',
269
+ 'spacing-9', 'spacing-10', 'spacing-11', 'spacing-12', 'spacing-14', 'spacing-16', 'spacing-20',
270
+ 'spacing-24', 'spacing-28', 'spacing-32', 'spacing-36', 'spacing-40', 'spacing-44', 'spacing-48',
271
+ 'spacing-52', 'spacing-56', 'spacing-60', 'spacing-64', 'spacing-72', 'spacing-80', 'spacing-90', 'spacing-200',
272
+ ],
273
+ },
274
+ shadows: {
275
+ label: 'Shadows',
276
+ tokens: [
277
+ 'box-shadow', 'box-shadow-xs', 'box-shadow-sm', 'box-shadow-lg', 'box-shadow-xl', 'box-shadow-inset',
278
+ ],
279
+ },
280
+ borders: {
281
+ label: 'Borders',
282
+ tokens: [
283
+ 'border-width', 'border-style', 'border-color', 'border-color-translucent',
284
+ 'border-radius', 'border-radius-sm', 'border-radius-lg', 'border-radius-xl', 'border-radius-xxl',
285
+ 'border-radius-2xl', 'border-radius-3xl', 'border-radius-4xl', 'border-radius-pill',
286
+ 'focus-border-color', 'focus-ring-width', 'focus-ring-offset', 'focus-ring-opacity',
287
+ 'form-valid-color', 'form-valid-border-color', 'form-invalid-color', 'form-invalid-border-color',
288
+ ],
289
+ },
290
+ transitions: {
291
+ label: 'Transitions',
292
+ tokens: [
293
+ 'transition-duration-fast', 'transition-duration-base', 'transition-duration-slow', 'transition-duration-slower',
294
+ 'easing-base', 'easing-ease-in-out', 'easing-ease-out', 'easing-ease-in', 'easing-ease-linear',
295
+ 'transition-fast', 'transition-base', 'transition-slow',
296
+ ],
297
+ },
298
+ zindex: {
299
+ label: 'Z-Index',
300
+ tokens: [
301
+ 'z-n1', 'z-0', 'z-1', 'z-2', 'z-3', 'z-4', 'z-5', 'z-dropdown', 'z-sticky', 'z-fixed',
302
+ 'z-modal', 'z-popover', 'z-tooltip', 'z-drawer',
303
+ ],
304
+ },
305
+ breakpoints: {
306
+ label: 'Breakpoints',
307
+ tokens: [
308
+ 'breakpoint-xs', 'breakpoint-sm', 'breakpoint-md', 'breakpoint-lg', 'breakpoint-xl', 'breakpoint-xxl',
309
+ ],
310
+ },
311
+ };
312
+
313
+ return (
314
+ <div className={`design-tokens-customizer ${className || ''}`} style={style}>
315
+ <div className="customizer-header">
316
+ <h2>Interactive Theme Customizer</h2>
317
+ <div className="customizer-controls">
318
+ <select
319
+ value={colorFormat}
320
+ onChange={(e) => setColorFormat(e.target.value as ColorFormat)}
321
+ >
322
+ <option value="hex">HEX</option>
323
+ <option value="rgb">RGB</option>
324
+ <option value="rgba">RGBA</option>
325
+ <option value="hsl">HSL</option>
326
+ <option value="hsla">HSLA</option>
327
+ </select>
328
+ <button onClick={resetToDefaults}>Reset to Defaults</button>
329
+ <label className="file-input-button">
330
+ Load Tokens
331
+ <input
332
+ type="file"
333
+ accept=".json"
334
+ onChange={loadTokensFromFile}
335
+ style={{ display: 'none' }}
336
+ />
337
+ </label>
338
+ <button onClick={copyToClipboard}>Copy Tokens</button>
339
+ <button onClick={exportTokens}>Export Tokens</button>
340
+ <button onClick={exportTheme}>Export Theme CSS</button>
341
+ </div>
342
+ </div>
343
+
344
+ <div className="customizer-content">
345
+ <div className="customizer-sidebar">
346
+ {Object.entries(tokenCategories).map(([key, category]) => (
347
+ <button
348
+ key={key}
349
+ className={`category-button ${activeCategory === key ? 'active' : ''}`}
350
+ onClick={() => setActiveCategory(key as TokenCategory)}
351
+ >
352
+ {category.label}
353
+ </button>
354
+ ))}
355
+ </div>
356
+
357
+ <div className="customizer-editor">
358
+ <h3>{tokenCategories[activeCategory].label}</h3>
359
+ <div className="tokens-grid">
360
+ {tokenCategories[activeCategory].tokens.map((tokenKey) => {
361
+ const value = tokens[tokenKey as keyof DesignTokens] || '';
362
+ const isColor = tokenKey.includes('color') || tokenKey.includes('bg') || tokenKey.includes('gradient') ||
363
+ ['primary', 'secondary', 'success', 'info', 'warning', 'error', 'light', 'dark'].includes(tokenKey) ||
364
+ tokenKey.match(/^(gray|primary|red|green|blue|yellow)-\d+$/);
365
+
366
+ return (
367
+ <div key={tokenKey} className="token-item">
368
+ <label>{tokenKey}</label>
369
+ {isColor ? (
370
+ <div className="color-input-group">
371
+ <input
372
+ type="color"
373
+ value={value.startsWith('#') ? value : convertColorFormat(value, 'hex')}
374
+ onChange={(e) => updateToken(tokenKey as keyof DesignTokens, e.target.value)}
375
+ />
376
+ <input
377
+ type="text"
378
+ value={convertColorFormat(value, colorFormat)}
379
+ onChange={(e) => {
380
+ const converted = convertColorFormat(e.target.value, 'hex');
381
+ updateToken(tokenKey as keyof DesignTokens, converted);
382
+ }}
383
+ />
384
+ </div>
385
+ ) : (
386
+ <input
387
+ type="text"
388
+ value={value}
389
+ onChange={(e) => updateToken(tokenKey as keyof DesignTokens, e.target.value)}
390
+ />
391
+ )}
392
+ </div>
393
+ );
394
+ })}
395
+ </div>
396
+ </div>
397
+
398
+ <div className="customizer-preview">
399
+ <h3>Live Preview</h3>
400
+ <style>{cssPreview}</style>
401
+ <ThemePreview
402
+ theme={previewTheme}
403
+ showDetails={false}
404
+ showPalette={true}
405
+ showTypography={true}
406
+ showSpacing={true}
407
+ />
408
+ </div>
409
+ </div>
410
+
411
+ <style>{`
412
+ .design-tokens-customizer {
413
+ display: flex;
414
+ flex-direction: column;
415
+ height: 100vh;
416
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
417
+ }
418
+
419
+ .customizer-header {
420
+ display: flex;
421
+ justify-content: space-between;
422
+ align-items: center;
423
+ padding: 16px 24px;
424
+ border-bottom: 1px solid #e0e0e0;
425
+ background: #f5f5f5;
426
+ }
427
+
428
+ .customizer-header h2 {
429
+ margin: 0;
430
+ font-size: 24px;
431
+ color: #333;
432
+ }
433
+
434
+ .customizer-controls {
435
+ display: flex;
436
+ gap: 8px;
437
+ align-items: center;
438
+ }
439
+
440
+ .customizer-controls select,
441
+ .customizer-controls button {
442
+ padding: 8px 12px;
443
+ border: 1px solid #e0e0e0;
444
+ border-radius: 4px;
445
+ background: white;
446
+ cursor: pointer;
447
+ }
448
+
449
+ .customizer-controls button:hover,
450
+ .file-input-button:hover {
451
+ background: #f0f0f0;
452
+ }
453
+
454
+ .file-input-button {
455
+ padding: 8px 12px;
456
+ border: 1px solid #e0e0e0;
457
+ border-radius: 4px;
458
+ background: white;
459
+ cursor: pointer;
460
+ font-size: 14px;
461
+ display: inline-block;
462
+ }
463
+
464
+ .customizer-content {
465
+ display: flex;
466
+ flex: 1;
467
+ overflow: hidden;
468
+ }
469
+
470
+ .customizer-sidebar {
471
+ width: 200px;
472
+ border-right: 1px solid #e0e0e0;
473
+ padding: 16px;
474
+ background: #fafafa;
475
+ overflow-y: auto;
476
+ }
477
+
478
+ .category-button {
479
+ display: block;
480
+ width: 100%;
481
+ padding: 12px;
482
+ margin-bottom: 8px;
483
+ border: none;
484
+ background: white;
485
+ border-radius: 4px;
486
+ cursor: pointer;
487
+ text-align: left;
488
+ font-size: 14px;
489
+ }
490
+
491
+ .category-button:hover,
492
+ .category-button.active {
493
+ background: #e0e0e0;
494
+ }
495
+
496
+ .customizer-editor {
497
+ flex: 1;
498
+ padding: 24px;
499
+ overflow-y: auto;
500
+ }
501
+
502
+ .customizer-editor h3 {
503
+ margin-top: 0;
504
+ margin-bottom: 16px;
505
+ color: #333;
506
+ }
507
+
508
+ .tokens-grid {
509
+ display: grid;
510
+ grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
511
+ gap: 16px;
512
+ }
513
+
514
+ .token-item {
515
+ display: flex;
516
+ flex-direction: column;
517
+ gap: 8px;
518
+ }
519
+
520
+ .token-item label {
521
+ font-size: 14px;
522
+ font-weight: 500;
523
+ color: #666;
524
+ }
525
+
526
+ .color-input-group {
527
+ display: flex;
528
+ gap: 8px;
529
+ align-items: center;
530
+ }
531
+
532
+ .color-input-group input[type="color"] {
533
+ width: 50px;
534
+ height: 40px;
535
+ border: 1px solid #e0e0e0;
536
+ border-radius: 4px;
537
+ cursor: pointer;
538
+ }
539
+
540
+ .color-input-group input[type="text"],
541
+ .token-item input[type="text"] {
542
+ flex: 1;
543
+ padding: 8px 12px;
544
+ border: 1px solid #e0e0e0;
545
+ border-radius: 4px;
546
+ font-size: 14px;
547
+ font-family: 'Monaco', 'Menlo', monospace;
548
+ }
549
+
550
+ .customizer-preview {
551
+ width: 400px;
552
+ border-left: 1px solid #e0e0e0;
553
+ padding: 24px;
554
+ overflow-y: auto;
555
+ background: #fafafa;
556
+ }
557
+
558
+ .customizer-preview h3 {
559
+ margin-top: 0;
560
+ margin-bottom: 16px;
561
+ color: #333;
562
+ }
563
+ `}</style>
564
+ </div>
565
+ );
566
+ };
@@ -68,12 +68,13 @@ function convertColorFormat(color: string, format: ColorFormat): string {
68
68
  case 'rgba':
69
69
  return `rgba(${r}, ${g}, ${b}, ${a})`;
70
70
  case 'hsl':
71
- case 'hsla':
71
+ case 'hsla': {
72
72
  // Convert RGB to HSL (simplified)
73
73
  const hsl = rgbToHsl(r, g, b);
74
74
  return format === 'hsl'
75
75
  ? `hsl(${hsl.h}, ${hsl.s}%, ${hsl.l}%)`
76
76
  : `hsla(${hsl.h}, ${hsl.s}%, ${hsl.l}%, ${a})`;
77
+ }
77
78
  default:
78
79
  return color;
79
80
  }
@@ -17,6 +17,9 @@ export type { ThemeComparatorProps } from './Comparator';
17
17
  export { ThemeLiveEditor } from './LiveEditor';
18
18
  export type { ThemeLiveEditorProps } from './LiveEditor';
19
19
 
20
+ export { DesignTokensCustomizer } from './DesignTokensCustomizer';
21
+ export type { DesignTokensCustomizerProps } from './DesignTokensCustomizer';
22
+
20
23
  // Validator (devtools only)
21
24
  export { ThemeValidator } from './ThemeValidator';
22
25
  export type {
@@ -29,6 +29,14 @@ export enum ThemeErrorCode {
29
29
  INVALID_THEME_NAME = 'INVALID_THEME_NAME',
30
30
  /** CSS injection failed */
31
31
  CSS_INJECTION_FAILED = 'CSS_INJECTION_FAILED',
32
+ /** Invalid color format */
33
+ INVALID_COLOR_FORMAT = 'INVALID_COLOR_FORMAT',
34
+ /** Missing required token */
35
+ MISSING_REQUIRED_TOKEN = 'MISSING_REQUIRED_TOKEN',
36
+ /** Accessibility contrast violation */
37
+ CONTRAST_VIOLATION = 'CONTRAST_VIOLATION',
38
+ /** Invalid token type */
39
+ INVALID_TOKEN_TYPE = 'INVALID_TOKEN_TYPE',
32
40
  /** Unknown error */
33
41
  UNKNOWN_ERROR = 'UNKNOWN_ERROR',
34
42
  }