@shohojdhara/atomix 0.3.13 → 0.3.15

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 (249) hide show
  1. package/CHANGELOG.md +39 -0
  2. package/README.md +2 -0
  3. package/build-tools/EXAMPLES.md +372 -0
  4. package/build-tools/README.md +242 -0
  5. package/build-tools/__tests__/error-handler.test.js +230 -0
  6. package/build-tools/__tests__/index.test.js +141 -0
  7. package/build-tools/__tests__/rollup-plugin.test.js +194 -0
  8. package/build-tools/__tests__/utils.test.js +161 -0
  9. package/build-tools/__tests__/vite-plugin.test.js +129 -0
  10. package/build-tools/__tests__/webpack-loader.test.js +190 -0
  11. package/build-tools/error-handler.js +308 -0
  12. package/build-tools/index.d.ts +43 -0
  13. package/build-tools/index.js +88 -0
  14. package/build-tools/package.json +67 -0
  15. package/build-tools/rollup-plugin.js +236 -0
  16. package/build-tools/types.d.ts +163 -0
  17. package/build-tools/utils.js +203 -0
  18. package/build-tools/vite-plugin.js +161 -0
  19. package/build-tools/webpack-loader.js +123 -0
  20. package/dist/atomix.css +298 -167
  21. package/dist/atomix.css.map +1 -1
  22. package/dist/atomix.min.css +3 -3
  23. package/dist/atomix.min.css.map +1 -1
  24. package/dist/build-tools/EXAMPLES.md +372 -0
  25. package/dist/build-tools/README.md +242 -0
  26. package/dist/build-tools/__tests__/error-handler.test.js +230 -0
  27. package/dist/build-tools/__tests__/index.test.js +141 -0
  28. package/dist/build-tools/__tests__/rollup-plugin.test.js +194 -0
  29. package/dist/build-tools/__tests__/utils.test.js +161 -0
  30. package/dist/build-tools/__tests__/vite-plugin.test.js +129 -0
  31. package/dist/build-tools/__tests__/webpack-loader.test.js +190 -0
  32. package/dist/build-tools/error-handler.js +308 -0
  33. package/dist/build-tools/index.d.ts +43 -0
  34. package/dist/build-tools/index.js +88 -0
  35. package/dist/build-tools/package.json +67 -0
  36. package/dist/build-tools/rollup-plugin.js +236 -0
  37. package/dist/build-tools/types.d.ts +163 -0
  38. package/dist/build-tools/utils.js +203 -0
  39. package/dist/build-tools/vite-plugin.js +161 -0
  40. package/dist/build-tools/webpack-loader.js +123 -0
  41. package/dist/charts.d.ts +2 -2
  42. package/dist/charts.js +87 -58
  43. package/dist/charts.js.map +1 -1
  44. package/dist/core.d.ts +42 -12
  45. package/dist/core.js +175 -135
  46. package/dist/core.js.map +1 -1
  47. package/dist/forms.d.ts +30 -16
  48. package/dist/forms.js +146 -131
  49. package/dist/forms.js.map +1 -1
  50. package/dist/heavy.d.ts +2 -2
  51. package/dist/heavy.js +151 -118
  52. package/dist/heavy.js.map +1 -1
  53. package/dist/index.d.ts +130 -106
  54. package/dist/index.esm.js +1083 -465
  55. package/dist/index.esm.js.map +1 -1
  56. package/dist/index.js +1102 -483
  57. package/dist/index.js.map +1 -1
  58. package/dist/index.min.js +1 -1
  59. package/dist/index.min.js.map +1 -1
  60. package/dist/theme.d.ts +27 -2
  61. package/dist/theme.js +721 -108
  62. package/dist/theme.js.map +1 -1
  63. package/package.json +23 -8
  64. package/scripts/atomix-cli.js +749 -1153
  65. package/scripts/cli/__tests__/README.md +81 -0
  66. package/scripts/cli/__tests__/basic.test.js +115 -0
  67. package/scripts/cli/__tests__/component-generator.test.js +332 -0
  68. package/scripts/cli/__tests__/integration.test.js +327 -0
  69. package/scripts/cli/__tests__/test-setup.js +133 -0
  70. package/scripts/cli/__tests__/token-manager.test.js +251 -0
  71. package/scripts/cli/__tests__/utils.test.js +78 -118
  72. package/scripts/cli/component-generator.js +564 -0
  73. package/scripts/cli/dependency-checker.js +355 -0
  74. package/scripts/cli/documentation-sync.js +542 -0
  75. package/scripts/cli/interactive-init.js +129 -292
  76. package/scripts/cli/mappings.js +211 -0
  77. package/scripts/cli/migration-tools.js +95 -288
  78. package/scripts/cli/template-manager.js +105 -0
  79. package/scripts/cli/templates/README.md +123 -0
  80. package/scripts/cli/templates/common-templates.js +636 -0
  81. package/scripts/cli/templates/composable-templates.js +171 -0
  82. package/scripts/cli/templates/config-templates.js +126 -0
  83. package/scripts/cli/templates/index.js +102 -0
  84. package/scripts/cli/templates/project-templates.js +342 -0
  85. package/scripts/cli/templates/react-templates.js +331 -0
  86. package/scripts/cli/templates/scss-templates.js +155 -0
  87. package/scripts/cli/templates/storybook-templates.js +236 -0
  88. package/scripts/cli/templates/testing-templates.js +224 -0
  89. package/scripts/cli/templates/testing-utils.js +278 -0
  90. package/scripts/cli/templates/token-templates.js +447 -0
  91. package/scripts/cli/templates/types-templates.js +147 -0
  92. package/scripts/cli/templates.js +35 -0
  93. package/scripts/cli/theme-bridge.js +28 -16
  94. package/scripts/cli/token-manager.js +432 -247
  95. package/scripts/cli/utils.js +37 -26
  96. package/src/components/Accordion/Accordion.stories.tsx +369 -870
  97. package/src/components/Accordion/Accordion.test.tsx +57 -0
  98. package/src/components/Accordion/Accordion.tsx +4 -0
  99. package/src/components/AtomixGlass/AtomixGlass.tsx +80 -39
  100. package/src/components/AtomixGlass/AtomixGlassContainer.tsx +103 -81
  101. package/src/components/AtomixGlass/__snapshots__/AtomixGlass.test.tsx.snap +8 -7
  102. package/src/components/AtomixGlass/glass-utils.ts +2 -2
  103. package/src/components/AtomixGlass/shader-utils.ts +5 -0
  104. package/src/components/AtomixGlass/stories/Customization.stories.tsx +131 -0
  105. package/src/components/AtomixGlass/stories/Examples.stories.tsx +2965 -2861
  106. package/src/components/AtomixGlass/stories/Modes.stories.tsx +1 -1
  107. package/src/components/AtomixGlass/stories/Overview.stories.tsx +348 -0
  108. package/src/components/AtomixGlass/stories/Performance.stories.tsx +103 -0
  109. package/src/components/AtomixGlass/stories/Playground.stories.tsx +73 -59
  110. package/src/components/AtomixGlass/stories/{ShaderVariants.stories.tsx → Shaders.stories.tsx} +1 -1
  111. package/src/components/AtomixGlass/stories/shared-components.tsx +90 -190
  112. package/src/components/Avatar/Avatar.stories.tsx +239 -27
  113. package/src/components/Badge/Badge.stories.tsx +132 -373
  114. package/src/components/Badge/Badge.test.tsx +51 -0
  115. package/src/components/Badge/Badge.tsx +20 -1
  116. package/src/components/Block/Block.stories.tsx +26 -17
  117. package/src/components/Breadcrumb/Breadcrumb.stories.tsx +141 -23
  118. package/src/components/Breadcrumb/Breadcrumb.tsx +2 -2
  119. package/src/components/Button/Button.stories.tsx +463 -1126
  120. package/src/components/Button/Button.test.tsx +107 -0
  121. package/src/components/Button/Button.tsx +50 -54
  122. package/src/components/Button/ButtonGroup.stories.tsx +373 -217
  123. package/src/components/Button/README.md +5 -0
  124. package/src/components/Callout/Callout.stories.tsx +299 -644
  125. package/src/components/Callout/Callout.test.tsx +10 -10
  126. package/src/components/Callout/Callout.tsx +7 -7
  127. package/src/components/Callout/README.md +9 -8
  128. package/src/components/Card/Card.stories.tsx +248 -68
  129. package/src/components/Card/Card.tsx +2 -2
  130. package/src/components/Chart/Chart.stories.tsx +156 -14
  131. package/src/components/Chart/Chart.tsx +1 -1
  132. package/src/components/ColorModeToggle/ColorModeToggle.stories.tsx +151 -69
  133. package/src/components/Countdown/Countdown.stories.tsx +115 -8
  134. package/src/components/DataTable/DataTable.stories.tsx +346 -146
  135. package/src/components/DataTable/DataTable.tsx +14 -12
  136. package/src/components/DatePicker/DatePicker.stories.tsx +325 -1066
  137. package/src/components/Dropdown/Dropdown.stories.tsx +157 -37
  138. package/src/components/EdgePanel/EdgePanel.stories.tsx +230 -21
  139. package/src/components/Footer/Footer.stories.tsx +392 -328
  140. package/src/components/Form/Checkbox.stories.tsx +143 -9
  141. package/src/components/Form/Checkbox.test.tsx +63 -0
  142. package/src/components/Form/Checkbox.tsx +90 -52
  143. package/src/components/Form/Form.stories.tsx +121 -22
  144. package/src/components/Form/FormGroup.stories.tsx +128 -5
  145. package/src/components/Form/Input.stories.tsx +28 -16
  146. package/src/components/Form/Input.test.tsx +59 -0
  147. package/src/components/Form/Input.tsx +97 -95
  148. package/src/components/Form/Radio.stories.tsx +232 -97
  149. package/src/components/Form/Radio.tsx +2 -2
  150. package/src/components/Form/Select.stories.tsx +144 -12
  151. package/src/components/Form/Select.tsx +2 -2
  152. package/src/components/Form/Textarea.stories.tsx +171 -13
  153. package/src/components/Form/Textarea.test.tsx +45 -0
  154. package/src/components/Form/Textarea.tsx +88 -86
  155. package/src/components/Hero/Hero.stories.tsx +333 -32
  156. package/src/components/List/List.stories.tsx +143 -5
  157. package/src/components/Modal/Modal.stories.tsx +185 -46
  158. package/src/components/Navigation/Navbar/Navbar.stories.tsx +5 -5
  159. package/src/components/Navigation/Navbar/Navbar.tsx +1 -1
  160. package/src/components/Navigation/README.md +1 -1
  161. package/src/components/Pagination/Pagination.stories.tsx +5 -2
  162. package/src/components/Pagination/Pagination.tsx +1 -1
  163. package/src/components/PhotoViewer/PhotoViewer.stories.tsx +10 -10
  164. package/src/components/Popover/Popover.stories.tsx +449 -99
  165. package/src/components/ProductReview/ProductReview.tsx +1 -1
  166. package/src/components/Progress/Progress.stories.tsx +167 -5
  167. package/src/components/Progress/Progress.tsx +46 -46
  168. package/src/components/Rating/Rating.stories.tsx +4 -4
  169. package/src/components/Rating/Rating.tsx +8 -8
  170. package/src/components/River/River.stories.tsx +1 -1
  171. package/src/components/SectionIntro/SectionIntro.stories.tsx +240 -48
  172. package/src/components/Slider/Slider.stories.tsx +63 -63
  173. package/src/components/Spinner/Spinner.stories.tsx +104 -10
  174. package/src/components/Spinner/Spinner.test.tsx +35 -0
  175. package/src/components/Spinner/Spinner.tsx +9 -2
  176. package/src/components/Steps/Steps.stories.tsx +172 -43
  177. package/src/components/Tabs/Tabs.stories.tsx +136 -10
  178. package/src/components/Testimonial/Testimonial.stories.tsx +121 -4
  179. package/src/components/Todo/Todo.stories.tsx +198 -9
  180. package/src/components/Toggle/Toggle.stories.tsx +153 -43
  181. package/src/components/Toggle/Toggle.test.tsx +91 -0
  182. package/src/components/Toggle/Toggle.tsx +44 -27
  183. package/src/components/Tooltip/Tooltip.stories.tsx +194 -104
  184. package/src/components/Tooltip/Tooltip.tsx +1 -1
  185. package/src/components/Upload/Upload.stories.tsx +113 -24
  186. package/src/layouts/Grid/Grid.stories.tsx +49 -49
  187. package/src/layouts/MasonryGrid/MasonryGrid.stories.tsx +2 -2
  188. package/src/lib/README.md +2 -2
  189. package/src/lib/__tests__/theme-tools.test.ts +193 -0
  190. package/src/lib/composables/index.ts +2 -2
  191. package/src/lib/composables/useAccordion.ts +12 -3
  192. package/src/lib/composables/useAtomixGlass.ts +28 -56
  193. package/src/lib/composables/useBreadcrumb.ts +2 -2
  194. package/src/lib/composables/useCallout.ts +7 -7
  195. package/src/lib/composables/useChartExport.ts +2 -7
  196. package/src/lib/composables/useDataTable.ts +46 -29
  197. package/src/lib/composables/useNavbar.ts +1 -1
  198. package/src/lib/constants/components.ts +10 -33
  199. package/src/lib/storybook/InteractiveDemo.tsx +113 -0
  200. package/src/lib/storybook/PreviewContainer.tsx +36 -0
  201. package/src/lib/storybook/VariantsGrid.tsx +21 -0
  202. package/src/lib/storybook/index.ts +3 -0
  203. package/src/lib/theme/core/createThemeObject.ts +9 -5
  204. package/src/lib/theme/devtools/CLI.ts +155 -0
  205. package/src/lib/theme/devtools/DesignTokensCustomizer.stories.tsx +213 -0
  206. package/src/lib/theme/devtools/DesignTokensCustomizer.tsx +566 -0
  207. package/src/lib/theme/devtools/LiveEditor.tsx +2 -1
  208. package/src/lib/theme/devtools/index.ts +3 -0
  209. package/src/lib/theme/errors/errors.ts +8 -0
  210. package/src/lib/theme/runtime/ThemeProvider.tsx +117 -57
  211. package/src/lib/theme/runtime/__tests__/ThemeProvider.integration.test.tsx +305 -0
  212. package/src/lib/theme/runtime/__tests__/ThemeProvider.test.tsx +588 -0
  213. package/src/lib/theme/utils/__tests__/themeValidation.test.ts +264 -0
  214. package/src/lib/theme/utils/index.ts +1 -0
  215. package/src/lib/theme/utils/themeValidation.ts +501 -0
  216. package/src/lib/theme-tools.ts +32 -3
  217. package/src/lib/types/components.ts +82 -27
  218. package/src/lib/utils/__tests__/csv.test.ts +45 -0
  219. package/src/lib/utils/csv.ts +17 -0
  220. package/src/lib/utils/dataTableExport.ts +1 -10
  221. package/src/lib/utils/themeNaming.ts +1 -1
  222. package/src/styles/01-settings/_index.scss +2 -1
  223. package/src/styles/01-settings/_settings.accordion.scss +28 -7
  224. package/src/styles/01-settings/_settings.colors.scss +11 -11
  225. package/src/styles/01-settings/_settings.typography.scss +5 -5
  226. package/src/styles/02-tools/_tools.utility-api.scss +14 -0
  227. package/src/styles/06-components/_components.accordion.scss +56 -14
  228. package/src/styles/06-components/_components.callout.scss +29 -33
  229. package/src/styles/06-components/_components.checkbox.scss +23 -17
  230. package/src/styles/06-components/_index.scss +1 -1
  231. package/src/styles/99-utilities/_index.scss +2 -0
  232. package/src/styles/99-utilities/_utilities.display.scss +14 -3
  233. package/src/styles/99-utilities/_utilities.flex.scss +10 -10
  234. package/src/styles/99-utilities/_utilities.scss +3 -1
  235. package/src/styles/99-utilities/_utilities.text-gradient.scss +45 -0
  236. package/src/styles/99-utilities/_utilities.text.scss +28 -8
  237. package/themes/dark-complementary/README.md +98 -0
  238. package/themes/dark-complementary/index.scss +158 -0
  239. package/themes/default-light/README.md +81 -0
  240. package/themes/default-light/index.scss +154 -0
  241. package/themes/high-contrast/README.md +105 -0
  242. package/themes/high-contrast/index.scss +172 -0
  243. package/themes/test-theme/README.md +38 -0
  244. package/themes/test-theme/index.scss +47 -0
  245. package/scripts/cli/__tests__/cli-commands.test.js +0 -204
  246. package/scripts/cli/__tests__/vitest.config.js +0 -26
  247. package/src/components/AtomixGlass/stories/AtomixGlass.stories.tsx +0 -1438
  248. package/src/lib/composables/useButton.ts +0 -93
  249. package/src/lib/composables/useCheckbox.ts +0 -70
@@ -13,6 +13,9 @@ import { createTokens } from '../tokens/tokens';
13
13
  import { getLogger } from '../errors';
14
14
  import { createTheme } from '../core';
15
15
  import { injectCSS, removeCSS } from '../utils/injectCSS';
16
+ import { validateAndMergeTokens } from '../utils/themeValidation';
17
+
18
+ const logger = getLogger();
16
19
  import {
17
20
  isServer,
18
21
  createLocalStorageAdapter,
@@ -102,9 +105,18 @@ export const ThemeProvider: React.FC<ThemeProviderProps> = ({
102
105
  });
103
106
 
104
107
  const [activeTokens, setActiveTokens] = useState<DesignTokens | null>(() => {
105
- // If defaultTheme is DesignTokens, store them
108
+ // If defaultTheme is DesignTokens, validate and store them
106
109
  if (defaultTheme && typeof defaultTheme !== 'string') {
107
- return createTokens(defaultTheme);
110
+ const { tokens, validation } = validateAndMergeTokens(defaultTheme);
111
+ if (validation.valid) {
112
+ return tokens;
113
+ } else {
114
+ logger.warn('Invalid default theme tokens, using defaults', {
115
+ errors: validation.errors,
116
+ warnings: validation.warnings,
117
+ });
118
+ return createTokens({}); // Use defaults if validation fails
119
+ }
108
120
  }
109
121
  return null;
110
122
  });
@@ -120,8 +132,8 @@ export const ThemeProvider: React.FC<ThemeProviderProps> = ({
120
132
  // Handle initial DesignTokens defaultTheme
121
133
  useEffect(() => {
122
134
  if (defaultTheme && typeof defaultTheme !== 'string' && activeTokens && !isServer()) {
123
- // If defaultTheme is DesignTokens, inject CSS on mount
124
- const css = createTheme(defaultTheme);
135
+ // If defaultTheme is DesignTokens, inject CSS on mount (tokens are already validated)
136
+ const css = createTheme(activeTokens);
125
137
  injectCSS(css, 'theme-tokens-theme');
126
138
  }
127
139
  }, [defaultTheme, activeTokens]); // Run when defaultTheme or activeTokens change
@@ -191,9 +203,52 @@ export const ThemeProvider: React.FC<ThemeProviderProps> = ({
191
203
  return;
192
204
  }
193
205
 
194
- // For DesignTokens, create CSS and inject it
195
- const { createTheme } = await import('../core');
196
- const css = createTheme(theme);
206
+ // Validate and merge DesignTokens
207
+ const { tokens: validatedTokens, validation } = validateAndMergeTokens(theme);
208
+
209
+ if (!validation.valid) {
210
+ const errorMsg = `Invalid DesignTokens provided: ${validation.errors.join(', ')}`;
211
+ const validationError = new Error(errorMsg);
212
+ logger.error('Theme validation failed', validationError, {
213
+ errors: validation.errors,
214
+ warnings: validation.warnings,
215
+ });
216
+
217
+ // Check if we should fallback to default theme
218
+ const shouldFallback = options?.fallbackOnError !== false; // Default to true
219
+ if (shouldFallback) {
220
+ logger.warn('Falling back to default theme due to validation errors');
221
+ // Use default tokens instead
222
+ const { tokens: defaultTokens } = validateAndMergeTokens({});
223
+ const css = createTheme(defaultTokens);
224
+ const themeId = 'tokens-theme-fallback';
225
+
226
+ // Check if aborted before state update
227
+ if (abortController.signal.aborted) {
228
+ return;
229
+ }
230
+
231
+ // Remove any previously loaded theme CSS
232
+ removeCSS(`theme-${currentTheme}`);
233
+
234
+ // Inject new theme CSS
235
+ injectCSS(css, `theme-${themeId}`);
236
+
237
+ // Store default tokens
238
+ setActiveTokens(defaultTokens);
239
+ setCurrentTheme(themeId);
240
+ handleThemeChange(defaultTokens);
241
+ handleError(validationError, themeId);
242
+ setIsLoading(false);
243
+ return;
244
+ } else {
245
+ // No fallback, throw the error
246
+ throw validationError;
247
+ }
248
+ }
249
+
250
+ // For valid DesignTokens, create CSS and inject it
251
+ const css = createTheme(validatedTokens);
197
252
  const themeId = 'tokens-theme';
198
253
 
199
254
  // Check if aborted after async operation
@@ -207,17 +262,16 @@ export const ThemeProvider: React.FC<ThemeProviderProps> = ({
207
262
  // Inject new theme CSS
208
263
  injectCSS(css, `theme-${themeId}`);
209
264
 
210
- // Store tokens for reference
211
- const fullTokens = createTokens(theme);
265
+ // Store validated tokens for reference
212
266
 
213
267
  // Check if aborted before state update
214
268
  if (abortController.signal.aborted) {
215
269
  return;
216
270
  }
217
271
 
218
- setActiveTokens(fullTokens);
272
+ setActiveTokens(validatedTokens);
219
273
  setCurrentTheme(themeId);
220
- handleThemeChange(fullTokens);
274
+ handleThemeChange(validatedTokens);
221
275
  setIsLoading(false);
222
276
  return;
223
277
  }
@@ -243,65 +297,71 @@ export const ThemeProvider: React.FC<ThemeProviderProps> = ({
243
297
  }
244
298
 
245
299
  // Load CSS theme
246
- const themeLoadPromise = new Promise<void>(async (resolve, reject) => {
247
- try {
248
- // Check if aborted
249
- if (abortController.signal.aborted) {
250
- resolve();
251
- return;
252
- }
253
-
254
- const themeMetadata = themes[theme];
255
-
256
- if (themeMetadata) {
257
- // Build CSS path using utility function
258
- const cssPath = buildThemePath(
259
- theme,
260
- basePath,
261
- useMinified,
262
- cdnPath
263
- );
264
-
300
+ const themeLoadPromise = new Promise<void>((resolve, reject) => {
301
+ // Handle the async operations inside the promise without making the executor async
302
+ const loadTheme = async () => {
303
+ try {
265
304
  // Check if aborted
266
305
  if (abortController.signal.aborted) {
267
306
  resolve();
268
307
  return;
269
308
  }
270
309
 
271
- // Load CSS file (using loadThemeCSS from domUtils)
272
- const { loadThemeCSS } = await import('../utils/domUtils');
273
- await loadThemeCSS(cssPath, `theme-${theme}`);
274
-
275
- // Check if aborted after async operation
310
+ const themeMetadata = themes[theme];
311
+
312
+ if (themeMetadata) {
313
+ // Build CSS path using utility function
314
+ const cssPath = buildThemePath(
315
+ theme,
316
+ basePath,
317
+ useMinified,
318
+ cdnPath
319
+ );
320
+
321
+ // Check if aborted
322
+ if (abortController.signal.aborted) {
323
+ resolve();
324
+ return;
325
+ }
326
+
327
+ // Load CSS file (using loadThemeCSS from domUtils)
328
+ const { loadThemeCSS } = await import('../utils/domUtils');
329
+ await loadThemeCSS(cssPath, `theme-${theme}`);
330
+
331
+ // Check if aborted after async operation
332
+ if (abortController.signal.aborted) {
333
+ resolve();
334
+ return;
335
+ }
336
+
337
+ // Remove any previously loaded theme CSS
338
+ removeCSS(`theme-${String(currentTheme)}`);
339
+
340
+ loadedThemesRef.current.add(theme);
341
+
342
+ setCurrentTheme(theme);
343
+ setActiveTokens(null);
344
+ handleThemeChange(theme);
345
+ resolve();
346
+ } else {
347
+ throw new Error(`Theme metadata not found for theme: ${theme}`);
348
+ }
349
+ } catch (err) {
350
+ // Don't reject if aborted
276
351
  if (abortController.signal.aborted) {
277
352
  resolve();
278
353
  return;
279
354
  }
280
355
 
281
- // Remove any previously loaded theme CSS
282
- removeCSS(`theme-${String(currentTheme)}`);
283
-
284
- loadedThemesRef.current.add(theme);
285
-
286
- setCurrentTheme(theme);
287
- setActiveTokens(null);
288
- handleThemeChange(theme);
289
- resolve();
290
- } else {
291
- throw new Error(`Theme metadata not found for theme: ${theme}`);
292
- }
293
- } catch (err) {
294
- // Don't reject if aborted
295
- if (abortController.signal.aborted) {
296
- resolve();
297
- return;
356
+ const error = err instanceof Error ? err : new Error(String(err));
357
+ setError(error);
358
+ handleError(error, String(theme));
359
+ reject(error);
298
360
  }
361
+ };
299
362
 
300
- const error = err instanceof Error ? err : new Error(String(err));
301
- setError(error);
302
- handleError(error, String(theme));
303
- reject(error);
304
- }
363
+ // Start the async operation
364
+ loadTheme();
305
365
  });
306
366
 
307
367
  themePromisesRef.current[theme] = themeLoadPromise;
@@ -0,0 +1,305 @@
1
+ /**
2
+ * Theme Provider Integration Tests
3
+ *
4
+ * Integration tests that render components with themes and verify styling application.
5
+ */
6
+
7
+ import React from 'react';
8
+ import { describe, it, expect, beforeEach, vi, afterEach } from 'vitest';
9
+ import { render, screen, waitFor } from '@testing-library/react';
10
+ import { ThemeProvider } from '../ThemeProvider';
11
+ import { useTheme } from '../useTheme';
12
+ import { createTokens, defaultTokens } from '../../tokens/tokens';
13
+ import type { ThemeMetadata, DesignTokens } from '../../types';
14
+
15
+ // Mock DOM utilities
16
+ vi.mock('../../utils/domUtils', () => ({
17
+ isServer: vi.fn(() => false),
18
+ createLocalStorageAdapter: vi.fn(() => ({
19
+ getItem: vi.fn(() => null),
20
+ setItem: vi.fn(),
21
+ removeItem: vi.fn(),
22
+ isAvailable: vi.fn(() => true),
23
+ })),
24
+ applyThemeAttributes: vi.fn(),
25
+ buildThemePath: vi.fn((theme, basePath) => `${basePath}${theme}.css`),
26
+ loadThemeCSS: vi.fn(() => Promise.resolve()),
27
+ }));
28
+
29
+ // Mock CSS injection utilities
30
+ vi.mock('../../utils/injectCSS', () => ({
31
+ injectCSS: vi.fn(),
32
+ removeCSS: vi.fn(),
33
+ }));
34
+
35
+ // Mock theme creation
36
+ vi.mock('../../core', () => ({
37
+ createTheme: vi.fn((tokens: DesignTokens) => {
38
+ const css = Object.entries(tokens)
39
+ .map(([key, value]) => `--atomix-${key}: ${value};`)
40
+ .join('\n');
41
+ return `:root {\n${css}\n}`;
42
+ }),
43
+ }));
44
+
45
+ // Mock validation
46
+ vi.mock('../../utils/themeValidation', () => ({
47
+ validateAndMergeTokens: vi.fn((tokens?: Partial<DesignTokens>) => ({
48
+ tokens: tokens ? createTokens(tokens) : defaultTokens,
49
+ validation: { valid: true, errors: [], warnings: [] },
50
+ })),
51
+ }));
52
+
53
+ // Mock logger
54
+ vi.mock('../../errors', () => ({
55
+ getLogger: vi.fn(() => ({
56
+ warn: vi.fn(),
57
+ error: vi.fn(),
58
+ })),
59
+ }));
60
+
61
+ // Sample component that uses CSS variables
62
+ const StyledComponent: React.FC = () => {
63
+ const style = {
64
+ color: 'var(--atomix-primary-text-emphasis)',
65
+ backgroundColor: 'var(--atomix-primary-bg-subtle)',
66
+ border: '1px solid var(--atomix-primary-border-subtle)',
67
+ padding: 'var(--atomix-spacing-4)',
68
+ borderRadius: 'var(--atomix-border-radius)',
69
+ };
70
+
71
+ return (
72
+ <div data-testid="styled-component" style={style}>
73
+ Styled Component
74
+ </div>
75
+ );
76
+ };
77
+
78
+ // Component that uses theme context
79
+ const ThemeAwareComponent: React.FC = () => {
80
+ const { theme, activeTokens } = useTheme();
81
+
82
+ return (
83
+ <div>
84
+ <div data-testid="theme-name">{theme}</div>
85
+ <div data-testid="has-tokens">{activeTokens ? 'has-tokens' : 'no-tokens'}</div>
86
+ <StyledComponent />
87
+ </div>
88
+ );
89
+ };
90
+
91
+ // Mock theme metadata
92
+ const mockThemes: Record<string, ThemeMetadata> = {
93
+ 'default-light': {
94
+ name: 'Default Light',
95
+ class: 'theme-default-light',
96
+ description: 'Default light theme',
97
+ supportsDarkMode: false,
98
+ status: 'stable',
99
+ },
100
+ 'dark-complementary': {
101
+ name: 'Dark Complementary',
102
+ class: 'theme-dark-complementary',
103
+ description: 'Dark theme with complementary colors',
104
+ supportsDarkMode: true,
105
+ status: 'stable',
106
+ },
107
+ 'high-contrast': {
108
+ name: 'High Contrast',
109
+ class: 'theme-high-contrast',
110
+ description: 'High contrast theme for accessibility',
111
+ supportsDarkMode: true,
112
+ status: 'stable',
113
+ a11y: {
114
+ contrastTarget: 7,
115
+ modes: ['dark', 'light'],
116
+ },
117
+ },
118
+ };
119
+
120
+ describe('ThemeProvider Integration', () => {
121
+ beforeEach(() => {
122
+ vi.clearAllMocks();
123
+ // Clear any injected styles
124
+ document.head.innerHTML = '';
125
+ });
126
+
127
+ afterEach(() => {
128
+ vi.restoreAllMocks();
129
+ document.head.innerHTML = '';
130
+ });
131
+
132
+ describe('CSS Variable Application to Components', () => {
133
+ it('should apply theme attributes to document element for string themes', async () => {
134
+ await render(
135
+ <ThemeProvider themes={mockThemes} defaultTheme="dark-complementary">
136
+ <ThemeAwareComponent />
137
+ </ThemeProvider>
138
+ );
139
+
140
+ expect(screen.getByTestId('theme-name')).toHaveTextContent('dark-complementary');
141
+ expect(screen.getByTestId('has-tokens')).toHaveTextContent('no-tokens');
142
+
143
+ const { applyThemeAttributes } = await import('../../utils/domUtils');
144
+ expect(applyThemeAttributes).toHaveBeenCalledWith('dark-complementary', 'data-theme');
145
+ });
146
+
147
+ it('should apply DesignTokens and inject CSS on mount', async () => {
148
+ const customTokens: DesignTokens = {
149
+ ...defaultTokens,
150
+ 'primary-text-emphasis': '#ff0000',
151
+ };
152
+
153
+ await render(
154
+ <ThemeProvider defaultTheme={customTokens}>
155
+ <ThemeAwareComponent />
156
+ </ThemeProvider>
157
+ );
158
+
159
+ // Check that theme is set
160
+ expect(screen.getByTestId('theme-name')).toHaveTextContent('tokens-theme');
161
+ expect(screen.getByTestId('has-tokens')).toHaveTextContent('has-tokens');
162
+
163
+ // Check that CSS was injected
164
+ const { injectCSS } = await import('../../utils/injectCSS');
165
+ expect(injectCSS).toHaveBeenCalledWith(
166
+ expect.stringContaining('--atomix-primary-text-emphasis: #ff0000'),
167
+ 'theme-tokens-theme'
168
+ );
169
+ });
170
+ });
171
+
172
+ describe('Component Styling Verification', () => {
173
+ it('should render components with theme context', () => {
174
+ render(
175
+ <ThemeProvider>
176
+ <ThemeAwareComponent />
177
+ </ThemeProvider>
178
+ );
179
+
180
+ const component = screen.getByTestId('styled-component');
181
+ expect(component).toBeInTheDocument();
182
+ expect(component).toHaveTextContent('Styled Component');
183
+ expect(screen.getByTestId('theme-name')).toHaveTextContent('default');
184
+ });
185
+
186
+ it('should apply different DesignTokens themes to components', async () => {
187
+ const customTokens = createTokens({
188
+ 'primary-text-emphasis': '#ff0000',
189
+ 'primary-bg-subtle': '#f0f0f0',
190
+ });
191
+
192
+ render(
193
+ <ThemeProvider defaultTheme={customTokens}>
194
+ <ThemeAwareComponent />
195
+ </ThemeProvider>
196
+ );
197
+
198
+ expect(screen.getByTestId('theme-name')).toHaveTextContent('tokens-theme');
199
+ expect(screen.getByTestId('has-tokens')).toHaveTextContent('has-tokens');
200
+
201
+ // CSS injection should have occurred
202
+ const { injectCSS } = await import('../../utils/injectCSS');
203
+ expect(injectCSS).toHaveBeenCalledWith(
204
+ expect.stringContaining('--atomix-primary-text-emphasis: #ff0000'),
205
+ 'theme-tokens-theme'
206
+ );
207
+ });
208
+ });
209
+
210
+ describe('Theme Persistence Integration', () => {
211
+ it('should restore theme from localStorage on mount', async () => {
212
+ const { createLocalStorageAdapter } = await import('../../utils/domUtils');
213
+ const mockStorage = {
214
+ getItem: vi.fn(() => 'dark-complementary'),
215
+ setItem: vi.fn(),
216
+ removeItem: vi.fn(),
217
+ isAvailable: vi.fn(() => true),
218
+ };
219
+ createLocalStorageAdapter.mockReturnValue(mockStorage);
220
+
221
+ render(
222
+ <ThemeProvider themes={mockThemes} enablePersistence={true}>
223
+ <ThemeAwareComponent />
224
+ </ThemeProvider>
225
+ );
226
+
227
+ expect(screen.getByTestId('theme-name')).toHaveTextContent('dark-complementary');
228
+ });
229
+
230
+ it('should persist theme changes to localStorage', async () => {
231
+ const { createLocalStorageAdapter } = await import('../../utils/domUtils');
232
+ const mockStorage = {
233
+ getItem: vi.fn(() => null),
234
+ setItem: vi.fn(),
235
+ removeItem: vi.fn(),
236
+ isAvailable: vi.fn(() => true),
237
+ };
238
+ createLocalStorageAdapter.mockReturnValue(mockStorage);
239
+
240
+ render(
241
+ <ThemeProvider themes={mockThemes} enablePersistence={true}>
242
+ <ThemeAwareComponent />
243
+ </ThemeProvider>
244
+ );
245
+
246
+ expect(mockStorage.setItem).toHaveBeenCalledWith('atomix-theme', 'default');
247
+ });
248
+ });
249
+
250
+ describe('Error Handling Integration', () => {
251
+ it('should continue rendering components even when theme loading fails', async () => {
252
+ const { loadThemeCSS } = await import('../../utils/domUtils');
253
+ loadThemeCSS.mockRejectedValueOnce(new Error('CSS load failed'));
254
+
255
+ render(
256
+ <ThemeProvider themes={mockThemes}>
257
+ <ThemeAwareComponent />
258
+ </ThemeProvider>
259
+ );
260
+
261
+ // Component should still render
262
+ expect(screen.getByTestId('styled-component')).toBeInTheDocument();
263
+ expect(screen.getByTestId('theme-name')).toHaveTextContent('default');
264
+ });
265
+ });
266
+
267
+ describe('Accessibility Integration', () => {
268
+ it('should apply high contrast theme correctly', async () => {
269
+ // High contrast tokens would have better contrast ratios
270
+ const highContrastTokens = createTokens({
271
+ 'primary-text-emphasis': '#000000',
272
+ 'primary-bg-subtle': '#ffffff',
273
+ 'secondary-text-emphasis': '#000000',
274
+ 'secondary-bg-subtle': '#ffffff',
275
+ });
276
+
277
+ render(
278
+ <ThemeProvider defaultTheme={highContrastTokens}>
279
+ <ThemeAwareComponent />
280
+ </ThemeProvider>
281
+ );
282
+
283
+ const { injectCSS } = await import('../../utils/injectCSS');
284
+ expect(injectCSS).toHaveBeenCalledWith(
285
+ expect.stringContaining('--atomix-primary-text-emphasis: #000000'),
286
+ 'theme-tokens-theme'
287
+ );
288
+ });
289
+
290
+ it('should support data attributes for theme switching', async () => {
291
+ render(
292
+ <ThemeProvider
293
+ themes={mockThemes}
294
+ dataAttribute="data-bs-theme"
295
+ defaultTheme="high-contrast"
296
+ >
297
+ <ThemeAwareComponent />
298
+ </ThemeProvider>
299
+ );
300
+
301
+ const { applyThemeAttributes } = await import('../../utils/domUtils');
302
+ expect(applyThemeAttributes).toHaveBeenCalledWith('high-contrast', 'data-bs-theme');
303
+ });
304
+ });
305
+ });