@shohojdhara/atomix 0.4.7 → 0.4.9

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 (176) hide show
  1. package/atomix.config.ts +58 -1
  2. package/dist/atomix.css +172 -157
  3. package/dist/atomix.css.map +1 -1
  4. package/dist/atomix.min.css +4 -4
  5. package/dist/atomix.min.css.map +1 -1
  6. package/dist/charts.d.ts +33 -0
  7. package/dist/charts.js +1274 -164
  8. package/dist/charts.js.map +1 -1
  9. package/dist/core.d.ts +33 -10
  10. package/dist/core.js +1099 -83
  11. package/dist/core.js.map +1 -1
  12. package/dist/forms.d.ts +33 -0
  13. package/dist/forms.js +2106 -1050
  14. package/dist/forms.js.map +1 -1
  15. package/dist/heavy.d.ts +42 -1
  16. package/dist/heavy.js +1663 -638
  17. package/dist/heavy.js.map +1 -1
  18. package/dist/index.d.ts +442 -270
  19. package/dist/index.esm.js +1947 -680
  20. package/dist/index.esm.js.map +1 -1
  21. package/dist/index.js +1982 -712
  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/package.json +6 -3
  26. package/scripts/atomix-cli.js +136 -1827
  27. package/scripts/cli/__tests__/basic.test.js +3 -2
  28. package/scripts/cli/__tests__/clean.test.js +278 -0
  29. package/scripts/cli/__tests__/component-validator.test.js +433 -0
  30. package/scripts/cli/__tests__/generator.test.js +613 -0
  31. package/scripts/cli/__tests__/glass-motion.test.js +256 -0
  32. package/scripts/cli/__tests__/integration.test.js +719 -108
  33. package/scripts/cli/__tests__/migrate.test.js +74 -0
  34. package/scripts/cli/__tests__/security.test.js +206 -0
  35. package/scripts/cli/__tests__/test-setup.js +3 -1
  36. package/scripts/cli/__tests__/theme-bridge.test.js +507 -0
  37. package/scripts/cli/__tests__/token-provider.test.js +361 -0
  38. package/scripts/cli/__tests__/utils.test.js +5 -5
  39. package/scripts/cli/commands/benchmark.js +105 -0
  40. package/scripts/cli/commands/build-theme.js +115 -0
  41. package/scripts/cli/commands/clean.js +109 -0
  42. package/scripts/cli/commands/doctor.js +88 -0
  43. package/scripts/cli/commands/generate.js +218 -0
  44. package/scripts/cli/commands/init.js +73 -0
  45. package/scripts/cli/commands/migrate.js +106 -0
  46. package/scripts/cli/commands/sync-tokens.js +206 -0
  47. package/scripts/cli/commands/theme-bridge.js +248 -0
  48. package/scripts/cli/commands/tokens.js +157 -0
  49. package/scripts/cli/commands/validate.js +194 -0
  50. package/scripts/cli/internal/ai-engine.js +156 -0
  51. package/scripts/cli/internal/compiler.js +114 -0
  52. package/scripts/cli/internal/component-validator.js +443 -0
  53. package/scripts/cli/internal/config-loader.js +162 -0
  54. package/scripts/cli/internal/filesystem.js +158 -0
  55. package/scripts/cli/internal/generator.js +430 -0
  56. package/scripts/cli/internal/glass-generator.js +398 -0
  57. package/scripts/cli/internal/hook-generator.js +369 -0
  58. package/scripts/cli/internal/hooks.js +61 -0
  59. package/scripts/cli/internal/itcss-generator.js +565 -0
  60. package/scripts/cli/internal/motion-generator.js +679 -0
  61. package/scripts/cli/internal/template-engine.js +301 -0
  62. package/scripts/cli/internal/theme-bridge.js +664 -0
  63. package/scripts/cli/internal/tokens/engine.js +122 -0
  64. package/scripts/cli/internal/tokens/provider.js +34 -0
  65. package/scripts/cli/internal/tokens/providers/figma.js +50 -0
  66. package/scripts/cli/internal/tokens/providers/style-dictionary.js +48 -0
  67. package/scripts/cli/internal/tokens/providers/w3c.js +48 -0
  68. package/scripts/cli/internal/tokens/token-provider.js +443 -0
  69. package/scripts/cli/internal/tokens/token-validator.js +513 -0
  70. package/scripts/cli/internal/validator.js +276 -0
  71. package/scripts/cli/internal/wizard.js +115 -0
  72. package/scripts/cli/mappings.js +23 -0
  73. package/scripts/cli/migration-tools.js +164 -94
  74. package/scripts/cli/plugins/style-dictionary.js +46 -0
  75. package/scripts/cli/templates/README.md +525 -95
  76. package/scripts/cli/templates/common-templates.js +40 -14
  77. package/scripts/cli/templates/components/react-component.ts +282 -0
  78. package/scripts/cli/templates/config/project-config.ts +112 -0
  79. package/scripts/cli/templates/hooks/use-component.ts +477 -0
  80. package/scripts/cli/templates/index.js +19 -4
  81. package/scripts/cli/templates/index.ts +171 -0
  82. package/scripts/cli/templates/next-templates.js +72 -0
  83. package/scripts/cli/templates/react-templates.js +70 -126
  84. package/scripts/cli/templates/scss-templates.js +35 -35
  85. package/scripts/cli/templates/stories/storybook-story.ts +241 -0
  86. package/scripts/cli/templates/styles/scss-component.ts +255 -0
  87. package/scripts/cli/templates/tests/vitest-test.ts +229 -0
  88. package/scripts/cli/templates/token-templates.js +337 -1
  89. package/scripts/cli/templates/tokens/token-generators.ts +1088 -0
  90. package/scripts/cli/templates/types/component-types.ts +145 -0
  91. package/scripts/cli/templates/utils/testing-utils.ts +144 -0
  92. package/scripts/cli/templates/vanilla-templates.js +39 -0
  93. package/scripts/cli/token-manager.js +8 -2
  94. package/scripts/cli/utils/cache-manager.js +240 -0
  95. package/scripts/cli/utils/detector.js +46 -0
  96. package/scripts/cli/utils/diagnostics.js +289 -0
  97. package/scripts/cli/utils/error.js +89 -0
  98. package/scripts/cli/utils/helpers.js +67 -0
  99. package/scripts/cli/utils/logger.js +75 -0
  100. package/scripts/cli/utils/security.js +302 -0
  101. package/scripts/cli/utils/telemetry.js +115 -0
  102. package/scripts/cli/utils/validation.js +37 -0
  103. package/scripts/cli/utils.js +28 -341
  104. package/src/components/Accordion/Accordion.stories.tsx +0 -18
  105. package/src/components/Accordion/Accordion.test.tsx +0 -17
  106. package/src/components/Accordion/Accordion.tsx +0 -4
  107. package/src/components/AtomixGlass/AtomixGlass.test.tsx +37 -3
  108. package/src/components/AtomixGlass/AtomixGlass.tsx +143 -31
  109. package/src/components/AtomixGlass/AtomixGlassContainer.tsx +129 -31
  110. package/src/components/AtomixGlass/PerformanceDashboard.tsx +219 -0
  111. package/src/components/AtomixGlass/README.md +25 -10
  112. package/src/components/AtomixGlass/__snapshots__/AtomixGlass.test.tsx.snap +216 -0
  113. package/src/components/AtomixGlass/animation-system.ts +578 -0
  114. package/src/components/AtomixGlass/shader-utils.ts +4 -1
  115. package/src/components/AtomixGlass/stories/Overview.stories.tsx +157 -6
  116. package/src/components/AtomixGlass/stories/Phase1-Animation.stories.tsx +653 -0
  117. package/src/components/AtomixGlass/stories/Phase1-Test.stories.tsx +95 -0
  118. package/src/components/AtomixGlass/stories/Playground.stories.tsx +51 -51
  119. package/src/components/AtomixGlass/stories/shared-components.tsx +6 -0
  120. package/src/components/Avatar/Avatar.tsx +1 -1
  121. package/src/components/Button/Button.stories.disabled-link.tsx +10 -0
  122. package/src/components/Button/Button.stories.tsx +10 -0
  123. package/src/components/Button/Button.test.tsx +16 -11
  124. package/src/components/Button/Button.tsx +4 -4
  125. package/src/components/Card/Card.tsx +1 -1
  126. package/src/components/Dropdown/Dropdown.tsx +12 -12
  127. package/src/components/Form/Select.tsx +62 -3
  128. package/src/components/Modal/Modal.tsx +14 -3
  129. package/src/components/Navigation/Navbar/Navbar.tsx +44 -0
  130. package/src/components/Slider/Slider.stories.tsx +3 -3
  131. package/src/components/Slider/Slider.tsx +38 -0
  132. package/src/components/Steps/Steps.tsx +3 -3
  133. package/src/components/Tabs/Tabs.tsx +77 -8
  134. package/src/components/Testimonial/Testimonial.tsx +1 -1
  135. package/src/components/TypedButton/TypedButton.stories.tsx +59 -0
  136. package/src/components/TypedButton/TypedButton.tsx +39 -0
  137. package/src/components/TypedButton/index.ts +2 -0
  138. package/src/components/VideoPlayer/VideoPlayer.tsx +11 -4
  139. package/src/lib/composables/index.ts +4 -7
  140. package/src/lib/composables/types.ts +45 -0
  141. package/src/lib/composables/useAccordion.ts +0 -7
  142. package/src/lib/composables/useAtomixGlass.ts +148 -6
  143. package/src/lib/composables/useAtomixGlassStyles.ts +9 -7
  144. package/src/lib/composables/useChartExport.ts +3 -13
  145. package/src/lib/composables/useDropdown.ts +66 -0
  146. package/src/lib/composables/useFocusTrap.ts +80 -0
  147. package/src/lib/composables/usePerformanceMonitor.ts +448 -0
  148. package/src/lib/composables/useResponsiveGlass.presets.ts +192 -0
  149. package/src/lib/composables/useResponsiveGlass.ts +441 -0
  150. package/src/lib/composables/useTooltip.ts +16 -0
  151. package/src/lib/composables/useTypedButton.ts +66 -0
  152. package/src/lib/config/index.ts +62 -5
  153. package/src/lib/constants/components.ts +62 -7
  154. package/src/lib/theme/devtools/__tests__/useHistory.test.tsx +150 -0
  155. package/src/lib/theme/tokens/centralized-tokens.ts +120 -0
  156. package/src/lib/theme/utils/__tests__/domUtils.test.ts +101 -0
  157. package/src/lib/types/components.ts +37 -11
  158. package/src/lib/types/glass.ts +35 -0
  159. package/src/lib/types/index.ts +1 -0
  160. package/src/lib/utils/displacement-generator.ts +1 -1
  161. package/src/styles/01-settings/_settings.testtypecheck.scss +53 -0
  162. package/src/styles/01-settings/_settings.typedbutton.scss +53 -0
  163. package/src/styles/06-components/_components.atomix-glass.scss +17 -21
  164. package/src/styles/06-components/_components.edge-panel.scss +1 -5
  165. package/src/styles/06-components/_components.modal.scss +1 -4
  166. package/src/styles/06-components/_components.navbar.scss +1 -1
  167. package/src/styles/06-components/_components.testbutton.scss +212 -0
  168. package/src/styles/06-components/_components.testtypecheck.scss +212 -0
  169. package/src/styles/06-components/_components.tooltip.scss +9 -5
  170. package/src/styles/06-components/_components.typedbutton.scss +212 -0
  171. package/src/styles/99-utilities/_index.scss +1 -0
  172. package/src/styles/99-utilities/_utilities.text.scss +1 -1
  173. package/src/styles/99-utilities/_utilities.touch-target.scss +36 -0
  174. package/scripts/cli/component-generator.js +0 -564
  175. package/scripts/cli/interactive-init.js +0 -357
  176. package/src/styles/06-components/old.chart.styles.scss +0 -2788
@@ -0,0 +1,513 @@
1
+ /**
2
+ * Token Validator System
3
+ * Validates design tokens for consistency, accessibility, and best practices
4
+ */
5
+
6
+ import { logger } from '../../utils/logger.js';
7
+ import { AtomixCLIError } from '../../utils/error.js';
8
+
9
+ /**
10
+ * Validation rule severity levels
11
+ */
12
+ export const SEVERITY = {
13
+ ERROR: 'error',
14
+ WARNING: 'warning',
15
+ INFO: 'info'
16
+ };
17
+
18
+ /**
19
+ * Built-in validation rules for design tokens
20
+ */
21
+ export const VALIDATION_RULES = {
22
+ /**
23
+ * Color contrast validation
24
+ * Ensures text colors have sufficient contrast with backgrounds
25
+ */
26
+ COLOR_CONTRAST: {
27
+ name: 'color-contrast',
28
+ description: 'Ensures color contrast meets WCAG AA standards',
29
+ severity: SEVERITY.ERROR,
30
+ validate: (tokens) => {
31
+ const issues = [];
32
+ const colorTokens = tokens.color || {};
33
+
34
+ // Check common text/background combinations
35
+ const textColors = ['text', 'foreground', 'primary', 'secondary'];
36
+ const backgroundColors = ['background', 'bg', 'surface', 'card'];
37
+
38
+ for (const textKey of textColors) {
39
+ for (const bgKey of backgroundColors) {
40
+ const textColor = findTokenByPartialKey(colorTokens, textKey);
41
+ const bgColor = findTokenByPartialKey(colorTokens, bgKey);
42
+
43
+ if (textColor && bgColor) {
44
+ const contrast = calculateContrast(textColor.value, bgColor.value);
45
+ if (contrast < 4.5) {
46
+ issues.push({
47
+ rule: 'color-contrast',
48
+ message: `Insufficient contrast ratio (${contrast.toFixed(2)}:1) between ${textKey} and ${bgKey}`,
49
+ suggestion: 'Adjust colors to meet WCAG AA standard (4.5:1 minimum)',
50
+ severity: SEVERITY.ERROR
51
+ });
52
+ }
53
+ }
54
+ }
55
+ }
56
+
57
+ return issues;
58
+ }
59
+ },
60
+
61
+ /**
62
+ * Semantic naming validation
63
+ * Ensures tokens use semantic names instead of literal values
64
+ */
65
+ SEMANTIC_NAMING: {
66
+ name: 'semantic-naming',
67
+ description: 'Ensures tokens use semantic names',
68
+ severity: SEVERITY.WARNING,
69
+ validate: (tokens) => {
70
+ const issues = [];
71
+
72
+ // Check for non-semantic color names
73
+ const literalColorNames = ['blue', 'red', 'green', 'yellow', 'orange', 'purple', 'pink'];
74
+ const semanticPrefixes = ['brand', 'primary', 'secondary', 'accent', 'success', 'warning', 'error', 'info'];
75
+ const colorTokens = tokens.color || {};
76
+
77
+ for (const [tokenName, tokenData] of Object.entries(colorTokens)) {
78
+ const nameLower = tokenName.toLowerCase();
79
+
80
+ // Skip if token has semantic prefix
81
+ const hasSemanticPrefix = semanticPrefixes.some(prefix => nameLower.startsWith(prefix));
82
+ if (hasSemanticPrefix) continue;
83
+
84
+ for (const literalName of literalColorNames) {
85
+ // Check if literal color name appears but not as part of a semantic name
86
+ if (nameLower.includes(literalName) && !nameLower.includes('brand')) {
87
+ issues.push({
88
+ rule: 'semantic-naming',
89
+ message: `Token "color.${tokenName}" uses literal color name`,
90
+ suggestion: `Consider using semantic names like "primary", "accent", "success" instead of "${literalName}"`,
91
+ severity: SEVERITY.WARNING
92
+ });
93
+ break; // Only report once per token
94
+ }
95
+ }
96
+ }
97
+
98
+ return issues;
99
+ }
100
+ },
101
+
102
+ /**
103
+ * Spacing scale consistency
104
+ * Ensures spacing tokens follow a consistent scale
105
+ */
106
+ SPACING_SCALE: {
107
+ name: 'spacing-scale',
108
+ description: 'Ensures spacing tokens follow a consistent scale',
109
+ severity: SEVERITY.INFO,
110
+ validate: (tokens) => {
111
+ const issues = [];
112
+ const spacingTokens = tokens.spacing || {};
113
+
114
+ const values = Object.values(spacingTokens).map(t =>
115
+ typeof t === 'object' ? t.value : t
116
+ ).filter(v => typeof v === 'string');
117
+
118
+ // Parse spacing values
119
+ const numericValues = values.map(v => {
120
+ const match = v.match(/^([\d.]+)(px|rem|em)$/);
121
+ return match ? parseFloat(match[1]) : null;
122
+ }).filter(v => v !== null);
123
+
124
+ if (numericValues.length > 0) {
125
+ // Check for consistent increments (e.g., multiples of 4 or 8)
126
+ const hasConsistentScale = numericValues.every(v => v % 4 === 0 || v % 0.25 === 0);
127
+
128
+ if (!hasConsistentScale) {
129
+ issues.push({
130
+ rule: 'spacing-scale',
131
+ message: 'Spacing values do not follow a consistent scale',
132
+ suggestion: 'Use a consistent scale (e.g., 4px or 8px base unit)',
133
+ severity: SEVERITY.INFO
134
+ });
135
+ }
136
+ }
137
+
138
+ return issues;
139
+ }
140
+ },
141
+
142
+ /**
143
+ * No hardcoded colors in components
144
+ * Ensures components reference tokens instead of hardcoded values
145
+ */
146
+ NO_HARDCODED_COLORS: {
147
+ name: 'no-hardcoded-colors',
148
+ description: 'Prevents hardcoded colors in component code',
149
+ severity: SEVERITY.ERROR,
150
+ validate: (tokens, context = {}) => {
151
+ const issues = [];
152
+ const { codeContent = '' } = context;
153
+
154
+ // Check for hex colors
155
+ const hexColorRegex = /#([A-Fa-f0-9]{3}|[A-Fa-f0-9]{6})\b/g;
156
+ const hexMatches = codeContent.match(hexColorRegex) || [];
157
+
158
+ // Check for RGB/RGBA colors
159
+ const rgbRegex = /rgba?\(\s*\d+\s*,\s*\d+\s*,\s*\d+\s*(,\s*[\d.]+\s*)?\)/gi;
160
+ const rgbMatches = codeContent.match(rgbRegex) || [];
161
+
162
+ // Check for HSL/HSLA colors
163
+ const hslRegex = /hsla?\(\s*\d+\s*,\s*[\d.]+%?\s*,\s*[\d.]+%?\s*(,\s*[\d.]+\s*)?\)/gi;
164
+ const hslMatches = codeContent.match(hslRegex) || [];
165
+
166
+ const allMatches = [...hexMatches, ...rgbMatches, ...hslMatches];
167
+
168
+ if (allMatches.length > 0) {
169
+ issues.push({
170
+ rule: 'no-hardcoded-colors',
171
+ message: `Found ${allMatches.length} hardcoded color value(s): ${allMatches.slice(0, 5).join(', ')}`,
172
+ suggestion: 'Replace hardcoded colors with design tokens (e.g., var(--color-primary))',
173
+ severity: SEVERITY.ERROR,
174
+ matches: allMatches
175
+ });
176
+ }
177
+
178
+ return issues;
179
+ }
180
+ },
181
+
182
+ /**
183
+ * Token completeness validation
184
+ * Ensures required token categories exist
185
+ */
186
+ TOKEN_COMPLETENESS: {
187
+ name: 'token-completeness',
188
+ description: 'Ensures all required token categories are present',
189
+ severity: SEVERITY.WARNING,
190
+ validate: (tokens) => {
191
+ const issues = [];
192
+ const requiredCategories = ['color', 'spacing', 'typography'];
193
+ const optionalCategories = ['shadow', 'radius', 'animation', 'breakpoint'];
194
+
195
+ for (const category of requiredCategories) {
196
+ if (!tokens[category] || Object.keys(tokens[category]).length === 0) {
197
+ issues.push({
198
+ rule: 'token-completeness',
199
+ message: `Missing required token category: "${category}"`,
200
+ suggestion: `Add ${category} tokens to ensure design consistency`,
201
+ severity: SEVERITY.WARNING
202
+ });
203
+ }
204
+ }
205
+
206
+ // Check for optional but recommended categories
207
+ for (const category of optionalCategories) {
208
+ if (!tokens[category]) {
209
+ issues.push({
210
+ rule: 'token-completeness',
211
+ message: `Missing optional token category: "${category}"`,
212
+ suggestion: `Consider adding ${category} tokens for enhanced theming`,
213
+ severity: SEVERITY.INFO
214
+ });
215
+ }
216
+ }
217
+
218
+ return issues;
219
+ }
220
+ },
221
+
222
+ /**
223
+ * Duplicate token detection
224
+ * Identifies tokens with identical values that might be redundant
225
+ */
226
+ DUPLICATE_DETECTION: {
227
+ name: 'duplicate-detection',
228
+ description: 'Detects potentially redundant duplicate tokens',
229
+ severity: SEVERITY.INFO,
230
+ validate: (tokens) => {
231
+ const issues = [];
232
+ const valueMap = new Map();
233
+
234
+ // Flatten all tokens
235
+ for (const [category, categoryTokens] of Object.entries(tokens)) {
236
+ for (const [tokenName, tokenData] of Object.entries(categoryTokens)) {
237
+ const value = typeof tokenData === 'object' ? tokenData.value : tokenData;
238
+ const key = `${value}`;
239
+
240
+ if (!valueMap.has(key)) {
241
+ valueMap.set(key, []);
242
+ }
243
+ valueMap.get(key).push(`${category}.${tokenName}`);
244
+ }
245
+ }
246
+
247
+ // Find duplicates
248
+ for (const [value, tokenNames] of valueMap.entries()) {
249
+ if (tokenNames.length > 1) {
250
+ issues.push({
251
+ rule: 'duplicate-detection',
252
+ message: `Multiple tokens share the same value "${value}": ${tokenNames.join(', ')}`,
253
+ suggestion: 'Consider consolidating duplicate tokens or documenting their distinct purposes',
254
+ severity: SEVERITY.INFO
255
+ });
256
+ }
257
+ }
258
+
259
+ return issues;
260
+ }
261
+ },
262
+
263
+ /**
264
+ * Typography scale validation
265
+ * Ensures typography follows a harmonious scale
266
+ */
267
+ TYPOGRAPHY_SCALE: {
268
+ name: 'typography-scale',
269
+ description: 'Ensures typography follows a harmonious scale',
270
+ severity: SEVERITY.INFO,
271
+ validate: (tokens) => {
272
+ const issues = [];
273
+ const typeTokens = tokens.typography || {};
274
+
275
+ const fontSizes = Object.entries(typeTokens)
276
+ .filter(([key]) => key.includes('size') || key.includes('heading'))
277
+ .map(([, data]) => {
278
+ const value = typeof data === 'object' ? data.value : data;
279
+ const match = String(value).match(/^([\d.]+)(px|rem|em)$/);
280
+ return match ? parseFloat(match[1]) : null;
281
+ })
282
+ .filter(v => v !== null);
283
+
284
+ if (fontSizes.length >= 2) {
285
+ // Check for reasonable scale ratios (between 1.2 and 1.5 is ideal)
286
+ fontSizes.sort((a, b) => a - b);
287
+ const ratios = [];
288
+
289
+ for (let i = 1; i < fontSizes.length; i++) {
290
+ ratios.push(fontSizes[i] / fontSizes[i - 1]);
291
+ }
292
+
293
+ const avgRatio = ratios.reduce((a, b) => a + b, 0) / ratios.length;
294
+
295
+ if (avgRatio < 1.1 || avgRatio > 1.6) {
296
+ issues.push({
297
+ rule: 'typography-scale',
298
+ message: `Typography scale ratio (${avgRatio.toFixed(2)}) may create visual inconsistency`,
299
+ suggestion: 'Consider using a modular scale (1.2-1.5 ratio) for harmonious typography',
300
+ severity: SEVERITY.INFO
301
+ });
302
+ }
303
+ }
304
+
305
+ return issues;
306
+ }
307
+ }
308
+ };
309
+
310
+ /**
311
+ * Helper function to find token by partial key match
312
+ * @private
313
+ */
314
+ function findTokenByPartialKey(tokens, partialKey) {
315
+ for (const [key, value] of Object.entries(tokens)) {
316
+ if (key.toLowerCase().includes(partialKey.toLowerCase())) {
317
+ return typeof value === 'object' ? value : { value };
318
+ }
319
+ }
320
+ return null;
321
+ }
322
+
323
+ /**
324
+ * Calculate contrast ratio between two colors
325
+ * Uses WCAG 2.0 formula
326
+ * @private
327
+ */
328
+ function calculateContrast(color1, color2) {
329
+ // Simple implementation - in production, use a proper color library
330
+ const lum1 = getLuminance(color1);
331
+ const lum2 = getLuminance(color2);
332
+
333
+ const lighter = Math.max(lum1, lum2);
334
+ const darker = Math.min(lum1, lum2);
335
+
336
+ return (lighter + 0.05) / (darker + 0.05);
337
+ }
338
+
339
+ /**
340
+ * Get relative luminance of a color
341
+ * @private
342
+ */
343
+ function getLuminance(color) {
344
+ // Simplified - assumes hex color input
345
+ let hex = color.replace('#', '');
346
+
347
+ if (hex.length === 3) {
348
+ hex = hex.split('').map(c => c + c).join('');
349
+ }
350
+
351
+ const r = parseInt(hex.substr(0, 2), 16) / 255;
352
+ const g = parseInt(hex.substr(2, 2), 16) / 255;
353
+ const b = parseInt(hex.substr(4, 2), 16) / 255;
354
+
355
+ const a = [r, g, b].map(v => {
356
+ return v <= 0.03928 ? v / 12.92 : Math.pow((v + 0.055) / 1.055, 2.4);
357
+ });
358
+
359
+ return 0.2126 * a[0] + 0.7152 * a[1] + 0.0722 * a[2];
360
+ }
361
+
362
+ /**
363
+ * TokenValidator class
364
+ */
365
+ export class TokenValidator {
366
+ constructor(options = {}) {
367
+ this.rules = new Map();
368
+ this.enabledRules = options.enabledRules || Object.keys(VALIDATION_RULES);
369
+
370
+ // Register built-in rules
371
+ for (const [key, rule] of Object.entries(VALIDATION_RULES)) {
372
+ this.registerRule(key, rule);
373
+ }
374
+ }
375
+
376
+ /**
377
+ * Register a custom validation rule
378
+ * @param {string} name - Rule name
379
+ * @param {Object} rule - Rule definition
380
+ */
381
+ registerRule(name, rule) {
382
+ if (!rule.name || !rule.validate || !rule.severity) {
383
+ throw new Error(`Invalid rule "${name}": must have name, validate, and severity`);
384
+ }
385
+ this.rules.set(name, rule);
386
+ }
387
+
388
+ /**
389
+ * Enable or disable a rule
390
+ * @param {string} ruleName - Rule to toggle
391
+ * @param {boolean} enabled - Whether to enable
392
+ */
393
+ toggleRule(ruleName, enabled) {
394
+ if (enabled) {
395
+ if (!this.enabledRules.includes(ruleName)) {
396
+ this.enabledRules.push(ruleName);
397
+ }
398
+ } else {
399
+ this.enabledRules = this.enabledRules.filter(r => r !== ruleName);
400
+ }
401
+ }
402
+
403
+ /**
404
+ * Validate tokens against all enabled rules
405
+ * @param {Object} tokens - Tokens to validate
406
+ * @param {Object} context - Additional context for validation
407
+ * @returns {Object} Validation results
408
+ */
409
+ validate(tokens, context = {}) {
410
+ const results = {
411
+ valid: true,
412
+ issues: [],
413
+ summary: {
414
+ errors: 0,
415
+ warnings: 0,
416
+ info: 0
417
+ }
418
+ };
419
+
420
+ for (const ruleName of this.enabledRules) {
421
+ const rule = this.rules.get(ruleName);
422
+
423
+ if (!rule) {
424
+ logger.debug(`Rule "${ruleName}" not found, skipping`);
425
+ continue;
426
+ }
427
+
428
+ try {
429
+ const issues = rule.validate(tokens, context);
430
+
431
+ if (issues.length > 0) {
432
+ results.valid = false;
433
+ results.issues.push(...issues);
434
+
435
+ // Update summary
436
+ for (const issue of issues) {
437
+ if (issue.severity === SEVERITY.ERROR) {
438
+ results.summary.errors++;
439
+ } else if (issue.severity === SEVERITY.WARNING) {
440
+ results.summary.warnings++;
441
+ } else {
442
+ results.summary.info++;
443
+ }
444
+ }
445
+ }
446
+ } catch (error) {
447
+ logger.warn(`Rule "${ruleName}" failed: ${error.message}`);
448
+ results.issues.push({
449
+ rule: ruleName,
450
+ message: `Validation rule error: ${error.message}`,
451
+ severity: SEVERITY.WARNING
452
+ });
453
+ }
454
+ }
455
+
456
+ return results;
457
+ }
458
+
459
+ /**
460
+ * Validate component code against token rules
461
+ * @param {string} codeContent - Component code
462
+ * @param {Object} tokens - Design tokens
463
+ * @returns {Object} Validation results
464
+ */
465
+ validateComponent(codeContent, tokens = {}) {
466
+ return this.validate(tokens, { codeContent });
467
+ }
468
+
469
+ /**
470
+ * Get detailed report of validation results
471
+ * @param {Object} results - Validation results
472
+ * @returns {string} Formatted report
473
+ */
474
+ getReport(results) {
475
+ const lines = [
476
+ '\nšŸ” Token Validation Report',
477
+ '='.repeat(50),
478
+ `Status: ${results.valid ? 'āœ… PASSED' : 'āŒ FAILED'}`,
479
+ '',
480
+ 'Summary:',
481
+ ` Errors: ${results.summary.errors}`,
482
+ ` Warnings: ${results.summary.warnings}`,
483
+ ` Info: ${results.summary.info}`,
484
+ ''
485
+ ];
486
+
487
+ if (results.issues.length > 0) {
488
+ lines.push('Issues:');
489
+
490
+ for (const issue of results.issues) {
491
+ const icon = issue.severity === SEVERITY.ERROR ? 'āŒ' :
492
+ issue.severity === SEVERITY.WARNING ? 'āš ļø' : 'ā„¹ļø';
493
+
494
+ lines.push(` ${icon} [${issue.rule}] ${issue.message}`);
495
+
496
+ if (issue.suggestion) {
497
+ lines.push(` šŸ’” ${issue.suggestion}`);
498
+ }
499
+ }
500
+ }
501
+
502
+ lines.push('='.repeat(50));
503
+
504
+ return lines.join('\n');
505
+ }
506
+ }
507
+
508
+ /**
509
+ * Create a singleton validator instance
510
+ */
511
+ export const tokenValidator = new TokenValidator();
512
+
513
+ export default tokenValidator;