@shohojdhara/atomix 0.3.15 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (245) hide show
  1. package/build-tools/index.d.ts +31 -30
  2. package/build-tools/package.json +4 -21
  3. package/dist/atomix.css +20924 -2611
  4. package/dist/atomix.css.map +1 -1
  5. package/dist/atomix.min.css +76 -2
  6. package/dist/atomix.min.css.map +1 -1
  7. package/dist/build-tools/index.d.ts +31 -30
  8. package/dist/build-tools/package.json +4 -21
  9. package/dist/charts.js.map +1 -1
  10. package/dist/core.js.map +1 -1
  11. package/dist/forms.js.map +1 -1
  12. package/dist/heavy.js.map +1 -1
  13. package/dist/index.d.ts +144 -18
  14. package/dist/index.esm.js +110 -55
  15. package/dist/index.esm.js.map +1 -1
  16. package/dist/index.js +110 -55
  17. package/dist/index.js.map +1 -1
  18. package/dist/index.min.js +1 -1
  19. package/dist/index.min.js.map +1 -1
  20. package/dist/layout.js.map +1 -1
  21. package/dist/theme.d.ts +9 -9
  22. package/dist/theme.js.map +1 -1
  23. package/package.json +1 -1
  24. package/src/components/Accordion/Accordion.stories.tsx +32 -23
  25. package/src/components/Accordion/Accordion.test.tsx +70 -50
  26. package/src/components/Accordion/Accordion.tsx +99 -94
  27. package/src/components/AtomixGlass/AtomixGlass.test.tsx +1 -1
  28. package/src/components/AtomixGlass/GlassFilter.tsx +9 -16
  29. package/src/components/AtomixGlass/glass-utils.ts +4 -3
  30. package/src/components/AtomixGlass/shader-utils.ts +128 -52
  31. package/src/components/AtomixGlass/stories/Playground.stories.tsx +1 -1
  32. package/src/components/AtomixGlass/stories/Shaders.stories.tsx +1 -1
  33. package/src/components/Avatar/Avatar.stories.tsx +45 -62
  34. package/src/components/Avatar/Avatar.tsx +58 -56
  35. package/src/components/Badge/Badge.stories.tsx +20 -9
  36. package/src/components/Badge/Badge.test.tsx +41 -41
  37. package/src/components/Badge/Badge.tsx +64 -62
  38. package/src/components/Block/Block.stories.tsx +14 -4
  39. package/src/components/Breadcrumb/Breadcrumb.stories.tsx +9 -8
  40. package/src/components/Breadcrumb/Breadcrumb.tsx +62 -60
  41. package/src/components/Button/Button.stories.tsx +13 -22
  42. package/src/components/Button/Button.test.tsx +97 -81
  43. package/src/components/Button/Button.tsx +46 -14
  44. package/src/components/Button/ButtonGroup.stories.tsx +37 -32
  45. package/src/components/Button/ButtonGroup.tsx +4 -15
  46. package/src/components/Callout/Callout.stories.tsx +109 -16
  47. package/src/components/Card/Card.stories.tsx +67 -36
  48. package/src/components/Card/Card.tsx +30 -14
  49. package/src/components/Chart/AreaChart.tsx +1 -1
  50. package/src/components/Chart/CandlestickChart.tsx +23 -16
  51. package/src/components/Chart/Chart.stories.tsx +4 -9
  52. package/src/components/Chart/Chart.tsx +40 -44
  53. package/src/components/Chart/ChartRenderer.tsx +39 -12
  54. package/src/components/Chart/ChartToolbar.tsx +21 -5
  55. package/src/components/Chart/DonutChart.tsx +1 -1
  56. package/src/components/Chart/FunnelChart.tsx +4 -1
  57. package/src/components/Chart/GaugeChart.tsx +3 -1
  58. package/src/components/Chart/HeatmapChart.tsx +50 -37
  59. package/src/components/Chart/LineChart.tsx +3 -2
  60. package/src/components/Chart/MultiAxisChart.tsx +24 -16
  61. package/src/components/Chart/RadarChart.tsx +19 -17
  62. package/src/components/Chart/ScatterChart.tsx +29 -21
  63. package/src/components/ColorModeToggle/ColorModeToggle.stories.tsx +6 -2
  64. package/src/components/ColorModeToggle/ColorModeToggle.tsx +15 -3
  65. package/src/components/Countdown/Countdown.stories.tsx +7 -7
  66. package/src/components/DataTable/DataTable.stories.tsx +43 -38
  67. package/src/components/DataTable/DataTable.test.tsx +26 -148
  68. package/src/components/DataTable/DataTable.tsx +485 -456
  69. package/src/components/DatePicker/DatePicker.stories.tsx +32 -47
  70. package/src/components/DatePicker/DatePicker.tsx +31 -26
  71. package/src/components/Dropdown/Dropdown.stories.tsx +2 -5
  72. package/src/components/Dropdown/Dropdown.tsx +313 -299
  73. package/src/components/EdgePanel/EdgePanel.stories.tsx +6 -19
  74. package/src/components/EdgePanel/EdgePanel.tsx +1 -3
  75. package/src/components/Footer/Footer.stories.tsx +21 -16
  76. package/src/components/Footer/Footer.tsx +130 -128
  77. package/src/components/Footer/FooterLink.tsx +2 -2
  78. package/src/components/Form/Checkbox.test.tsx +49 -49
  79. package/src/components/Form/Checkbox.tsx +108 -100
  80. package/src/components/Form/Form.stories.tsx +2 -10
  81. package/src/components/Form/Input.stories.tsx +22 -39
  82. package/src/components/Form/Input.test.tsx +38 -44
  83. package/src/components/Form/Radio.stories.tsx +6 -12
  84. package/src/components/Form/Radio.tsx +68 -66
  85. package/src/components/Form/Select.tsx +184 -182
  86. package/src/components/Form/Textarea.test.tsx +27 -32
  87. package/src/components/Hero/Hero.stories.tsx +56 -23
  88. package/src/components/Hero/Hero.tsx +201 -55
  89. package/src/components/Icon/index.ts +7 -1
  90. package/src/components/List/List.tsx +19 -23
  91. package/src/components/Modal/Modal.stories.tsx +2 -1
  92. package/src/components/Modal/Modal.tsx +130 -127
  93. package/src/components/Navigation/Menu/MegaMenu.tsx +70 -70
  94. package/src/components/Navigation/Nav/NavDropdown.tsx +1 -5
  95. package/src/components/Navigation/SideMenu/SideMenu.stories.tsx +128 -28
  96. package/src/components/Navigation/SideMenu/SideMenu.tsx +5 -7
  97. package/src/components/Navigation/SideMenu/SideMenuItem.tsx +4 -5
  98. package/src/components/Pagination/Pagination.stories.tsx +7 -4
  99. package/src/components/Pagination/Pagination.tsx +199 -202
  100. package/src/components/PhotoViewer/PhotoViewer.tsx +4 -1
  101. package/src/components/Popover/Popover.stories.tsx +99 -192
  102. package/src/components/Popover/Popover.tsx +41 -37
  103. package/src/components/Progress/Progress.stories.tsx +35 -44
  104. package/src/components/River/River.stories.tsx +2 -1
  105. package/src/components/SectionIntro/SectionIntro.stories.tsx +71 -71
  106. package/src/components/Slider/Slider.stories.tsx +12 -4
  107. package/src/components/Spinner/Spinner.stories.tsx +3 -1
  108. package/src/components/Spinner/Spinner.test.tsx +23 -23
  109. package/src/components/Spinner/Spinner.tsx +43 -46
  110. package/src/components/Steps/Steps.stories.tsx +8 -6
  111. package/src/components/Tabs/Tabs.stories.tsx +12 -9
  112. package/src/components/Tabs/Tabs.tsx +74 -72
  113. package/src/components/Toggle/Toggle.stories.tsx +27 -13
  114. package/src/components/Toggle/Toggle.test.tsx +65 -70
  115. package/src/components/Toggle/Toggle.tsx +4 -1
  116. package/src/components/Tooltip/Tooltip.stories.tsx +24 -20
  117. package/src/components/Tooltip/Tooltip.tsx +104 -106
  118. package/src/components/Upload/Upload.stories.tsx +129 -127
  119. package/src/components/Upload/Upload.tsx +287 -283
  120. package/src/components/VideoPlayer/VideoPlayer.tsx +6 -1
  121. package/src/components/index.ts +13 -2
  122. package/src/layouts/Grid/Grid.stories.tsx +9 -3
  123. package/src/layouts/MasonryGrid/MasonryGrid.tsx +5 -1
  124. package/src/lib/__tests__/theme-tools.test.ts +32 -6
  125. package/src/lib/composables/shared-mouse-tracker.ts +13 -14
  126. package/src/lib/composables/useAtomixGlass.ts +106 -49
  127. package/src/lib/composables/useChartExport.ts +1 -1
  128. package/src/lib/composables/useDataTable.ts +29 -17
  129. package/src/lib/composables/useHero.ts +58 -14
  130. package/src/lib/composables/useHeroBackgroundSlider.ts +2 -9
  131. package/src/lib/composables/useInput.ts +10 -8
  132. package/src/lib/composables/useSideMenu.ts +6 -5
  133. package/src/lib/composables/useTooltip.ts +1 -2
  134. package/src/lib/composables/useVideoPlayer.ts +44 -35
  135. package/src/lib/config/index.ts +154 -154
  136. package/src/lib/constants/cssVariables.ts +29 -29
  137. package/src/lib/hooks/__tests__/useComponentCustomization.test.ts +2 -6
  138. package/src/lib/hooks/index.ts +1 -1
  139. package/src/lib/hooks/useComponentCustomization.ts +11 -17
  140. package/src/lib/hooks/usePerformanceMonitor.ts +6 -7
  141. package/src/lib/patterns/__tests__/slots.test.ts +1 -1
  142. package/src/lib/patterns/index.ts +1 -1
  143. package/src/lib/patterns/slots.tsx +8 -13
  144. package/src/lib/storybook/InteractiveDemo.tsx +13 -18
  145. package/src/lib/storybook/PreviewContainer.tsx +1 -1
  146. package/src/lib/storybook/VariantsGrid.tsx +3 -7
  147. package/src/lib/storybook/index.ts +1 -1
  148. package/src/lib/theme/adapters/cssVariableMapper.ts +47 -74
  149. package/src/lib/theme/adapters/index.ts +3 -9
  150. package/src/lib/theme/adapters/themeAdapter.ts +41 -26
  151. package/src/lib/theme/config/index.ts +1 -1
  152. package/src/lib/theme/config/types.ts +2 -2
  153. package/src/lib/theme/config/validator.ts +10 -5
  154. package/src/lib/theme/constants/constants.ts +2 -2
  155. package/src/lib/theme/constants/index.ts +1 -2
  156. package/src/lib/theme/core/__tests__/createTheme.test.ts +20 -22
  157. package/src/lib/theme/core/composeTheme.ts +32 -26
  158. package/src/lib/theme/core/createTheme.ts +1 -1
  159. package/src/lib/theme/core/createThemeObject.ts +308 -301
  160. package/src/lib/theme/core/index.ts +3 -3
  161. package/src/lib/theme/devtools/CLI.ts +106 -104
  162. package/src/lib/theme/devtools/Comparator.tsx +50 -32
  163. package/src/lib/theme/devtools/DesignTokensCustomizer.stories.tsx +50 -48
  164. package/src/lib/theme/devtools/DesignTokensCustomizer.tsx +257 -63
  165. package/src/lib/theme/devtools/Inspector.tsx +75 -60
  166. package/src/lib/theme/devtools/LiveEditor.tsx +97 -76
  167. package/src/lib/theme/devtools/Preview.tsx +150 -106
  168. package/src/lib/theme/devtools/ThemeValidator.ts +29 -21
  169. package/src/lib/theme/devtools/index.ts +3 -9
  170. package/src/lib/theme/devtools/useHistory.ts +23 -21
  171. package/src/lib/theme/errors/errors.ts +12 -11
  172. package/src/lib/theme/errors/index.ts +2 -7
  173. package/src/lib/theme/generators/generateCSS.ts +9 -13
  174. package/src/lib/theme/generators/generateCSSNested.ts +1 -6
  175. package/src/lib/theme/generators/generateCSSVariables.ts +673 -630
  176. package/src/lib/theme/generators/index.ts +1 -4
  177. package/src/lib/theme/i18n/index.ts +1 -1
  178. package/src/lib/theme/i18n/rtl.ts +13 -13
  179. package/src/lib/theme/index.ts +7 -16
  180. package/src/lib/theme/runtime/ThemeApplicator.ts +4 -4
  181. package/src/lib/theme/runtime/ThemeContext.tsx +1 -1
  182. package/src/lib/theme/runtime/ThemeErrorBoundary.tsx +19 -23
  183. package/src/lib/theme/runtime/ThemeProvider.tsx +230 -239
  184. package/src/lib/theme/runtime/__tests__/ThemeProvider.integration.test.tsx +1 -1
  185. package/src/lib/theme/runtime/__tests__/ThemeProvider.test.tsx +24 -29
  186. package/src/lib/theme/runtime/index.ts +2 -5
  187. package/src/lib/theme/runtime/useTheme.ts +18 -18
  188. package/src/lib/theme/runtime/useThemeTokens.ts +22 -22
  189. package/src/lib/theme/test/testTheme.ts +15 -16
  190. package/src/lib/theme/tokens/index.ts +2 -7
  191. package/src/lib/theme/tokens/tokens.ts +25 -24
  192. package/src/lib/theme/types.ts +428 -411
  193. package/src/lib/theme/utils/__tests__/themeValidation.test.ts +3 -3
  194. package/src/lib/theme/utils/componentTheming.ts +18 -18
  195. package/src/lib/theme/utils/domUtils.ts +277 -289
  196. package/src/lib/theme/utils/index.ts +1 -2
  197. package/src/lib/theme/utils/injectCSS.ts +10 -14
  198. package/src/lib/theme/utils/naming.ts +20 -16
  199. package/src/lib/theme/utils/themeHelpers.ts +10 -12
  200. package/src/lib/theme/utils/themeUtils.ts +85 -86
  201. package/src/lib/theme/utils/themeValidation.ts +82 -33
  202. package/src/lib/theme-tools.ts +8 -6
  203. package/src/lib/types/components.ts +172 -71
  204. package/src/lib/types/partProps.ts +1 -1
  205. package/src/lib/utils/__tests__/csv.test.ts +1 -1
  206. package/src/lib/utils/componentUtils.ts +8 -12
  207. package/src/lib/utils/csv.ts +3 -1
  208. package/src/lib/utils/dataTableExport.ts +1 -5
  209. package/src/lib/utils/fontPreloader.ts +10 -19
  210. package/src/lib/utils/icons.ts +4 -1
  211. package/src/lib/utils/index.ts +2 -6
  212. package/src/lib/utils/memoryMonitor.ts +10 -8
  213. package/src/lib/utils/themeNaming.ts +2 -2
  214. package/src/styles/01-settings/_index.scss +0 -1
  215. package/src/styles/01-settings/_settings.colors.scss +8 -8
  216. package/src/styles/01-settings/_settings.design-tokens.scss +61 -50
  217. package/src/styles/01-settings/_settings.navbar.scss +1 -1
  218. package/src/styles/01-settings/_settings.spacing.scss +3 -4
  219. package/src/styles/01-settings/_settings.tooltip.scss +1 -1
  220. package/src/styles/01-settings/_settings.typography.scss +1 -1
  221. package/src/styles/02-tools/_tools.button.scss +51 -21
  222. package/src/styles/02-tools/_tools.utility-api.scss +30 -18
  223. package/src/styles/03-generic/_generic.root.scss +4 -3
  224. package/src/styles/06-components/_components.atomix-glass.scss +13 -9
  225. package/src/styles/06-components/_components.button.scss +16 -4
  226. package/src/styles/06-components/_components.callout.scss +27 -21
  227. package/src/styles/06-components/_components.card.scss +5 -14
  228. package/src/styles/06-components/_components.chart.scss +22 -19
  229. package/src/styles/06-components/_components.checkbox.scss +3 -1
  230. package/src/styles/06-components/_components.color-mode-toggle.scss +3 -1
  231. package/src/styles/06-components/_components.edge-panel.scss +9 -2
  232. package/src/styles/06-components/_components.footer.scss +1 -1
  233. package/src/styles/06-components/_components.side-menu.scss +5 -5
  234. package/src/styles/06-components/_components.toggle.scss +18 -0
  235. package/src/styles/06-components/_index.scss +1 -1
  236. package/src/styles/06-components/old.chart.styles.scss +0 -2
  237. package/src/styles/99-utilities/_utilities.border.scss +69 -27
  238. package/src/styles/99-utilities/_utilities.display.scss +1 -1
  239. package/src/styles/99-utilities/_utilities.opacity.scss +10 -0
  240. package/src/styles/99-utilities/_utilities.position.scss +16 -9
  241. package/src/styles/99-utilities/_utilities.scss +1 -1
  242. package/src/styles/99-utilities/_utilities.sizes.scss +47 -18
  243. package/src/styles/99-utilities/_utilities.spacing.scss +118 -66
  244. package/src/styles/99-utilities/_utilities.text-gradient.scss +30 -30
  245. package/src/styles/99-utilities/_utilities.text.scss +67 -46
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Theme Provider
3
- *
3
+ *
4
4
  * React context provider for theme management with separated concerns
5
5
  * Updated to use the new simplified theme system
6
6
  */
@@ -89,8 +89,6 @@ export const ThemeProvider: React.FC<ThemeProviderProps> = ({
89
89
  return defaultTheme;
90
90
  }
91
91
 
92
-
93
-
94
92
  // Default fallback
95
93
  return 'default';
96
94
  }, [defaultTheme, enablePersistence, storageKey]);
@@ -176,235 +174,230 @@ export const ThemeProvider: React.FC<ThemeProviderProps> = ({
176
174
  }, []);
177
175
 
178
176
  // Function to set theme with proper type handling
179
- const setTheme = useCallback(async (
180
- theme: string | DesignTokens | Partial<DesignTokens>,
181
- options?: ThemeLoadOptions
182
- ) => {
183
- // Cancel previous theme load if in progress
184
- if (abortControllerRef.current) {
185
- abortControllerRef.current.abort();
186
- }
177
+ const setTheme = useCallback(
178
+ async (theme: string | DesignTokens | Partial<DesignTokens>, options?: ThemeLoadOptions) => {
179
+ // Cancel previous theme load if in progress
180
+ if (abortControllerRef.current) {
181
+ abortControllerRef.current.abort();
182
+ }
187
183
 
188
- // Create new AbortController for this theme load
189
- const abortController = new AbortController();
190
- abortControllerRef.current = abortController;
184
+ // Create new AbortController for this theme load
185
+ const abortController = new AbortController();
186
+ abortControllerRef.current = abortController;
191
187
 
192
- setIsLoading(true);
193
- setError(null);
188
+ setIsLoading(true);
189
+ setError(null);
194
190
 
195
- try {
196
- let themeName: string;
191
+ try {
192
+ let themeName: string;
197
193
 
198
- if (typeof theme === 'string') {
199
- themeName = theme;
200
- } else {
201
- // Check if aborted before processing
202
- if (abortController.signal.aborted) {
203
- return;
204
- }
194
+ if (typeof theme === 'string') {
195
+ themeName = theme;
196
+ } else {
197
+ // Check if aborted before processing
198
+ if (abortController.signal.aborted) {
199
+ return;
200
+ }
205
201
 
206
- // Validate and merge DesignTokens
207
- const { tokens: validatedTokens, validation } = validateAndMergeTokens(theme);
202
+ // Validate and merge DesignTokens
203
+ const { tokens: validatedTokens, validation } = validateAndMergeTokens(theme);
204
+
205
+ if (!validation.valid) {
206
+ const errorMsg = `Invalid DesignTokens provided: ${validation.errors.join(', ')}`;
207
+ const validationError = new Error(errorMsg);
208
+ logger.error('Theme validation failed', validationError, {
209
+ errors: validation.errors,
210
+ warnings: validation.warnings,
211
+ });
212
+
213
+ // Check if we should fallback to default theme
214
+ const shouldFallback = options?.fallbackOnError !== false; // Default to true
215
+ if (shouldFallback) {
216
+ logger.warn('Falling back to default theme due to validation errors');
217
+ // Use default tokens instead
218
+ const { tokens: defaultTokens } = validateAndMergeTokens({});
219
+ const css = createTheme(defaultTokens);
220
+ const themeId = 'tokens-theme-fallback';
221
+
222
+ // Check if aborted before state update
223
+ if (abortController.signal.aborted) {
224
+ return;
225
+ }
208
226
 
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
- });
227
+ // Remove any previously loaded theme CSS
228
+ removeCSS(`theme-${currentTheme}`);
216
229
 
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) {
230
+ // Inject new theme CSS
231
+ injectCSS(css, `theme-${themeId}`);
232
+
233
+ // Store default tokens
234
+ setActiveTokens(defaultTokens);
235
+ setCurrentTheme(themeId);
236
+ handleThemeChange(defaultTokens);
237
+ handleError(validationError, themeId);
238
+ setIsLoading(false);
228
239
  return;
240
+ } else {
241
+ // No fallback, throw the error
242
+ throw validationError;
229
243
  }
244
+ }
230
245
 
231
- // Remove any previously loaded theme CSS
232
- removeCSS(`theme-${currentTheme}`);
233
-
234
- // Inject new theme CSS
235
- injectCSS(css, `theme-${themeId}`);
246
+ // For valid DesignTokens, create CSS and inject it
247
+ const css = createTheme(validatedTokens);
248
+ const themeId = 'tokens-theme';
236
249
 
237
- // Store default tokens
238
- setActiveTokens(defaultTokens);
239
- setCurrentTheme(themeId);
240
- handleThemeChange(defaultTokens);
241
- handleError(validationError, themeId);
242
- setIsLoading(false);
250
+ // Check if aborted after async operation
251
+ if (abortController.signal.aborted) {
243
252
  return;
244
- } else {
245
- // No fallback, throw the error
246
- throw validationError;
247
253
  }
248
- }
249
-
250
- // For valid DesignTokens, create CSS and inject it
251
- const css = createTheme(validatedTokens);
252
- const themeId = 'tokens-theme';
253
254
 
254
- // Check if aborted after async operation
255
- if (abortController.signal.aborted) {
256
- return;
257
- }
255
+ // Remove any previously loaded theme CSS
256
+ removeCSS(`theme-${currentTheme}`);
258
257
 
259
- // Remove any previously loaded theme CSS
260
- removeCSS(`theme-${currentTheme}`);
258
+ // Inject new theme CSS
259
+ injectCSS(css, `theme-${themeId}`);
261
260
 
262
- // Inject new theme CSS
263
- injectCSS(css, `theme-${themeId}`);
264
-
265
- // Store validated tokens for reference
266
-
267
- // Check if aborted before state update
268
- if (abortController.signal.aborted) {
269
- return;
270
- }
271
-
272
- setActiveTokens(validatedTokens);
273
- setCurrentTheme(themeId);
274
- handleThemeChange(validatedTokens);
275
- setIsLoading(false);
276
- return;
277
- }
261
+ // Store validated tokens for reference
278
262
 
279
- // If it's a string theme name, load the associated CSS
280
- if (typeof theme === 'string' && themes[theme]) {
281
- // Check if theme is already loading
282
- if (themePromisesRef.current[theme]) {
283
- try {
284
- await themePromisesRef.current[theme];
285
- // Check if aborted
286
- if (abortController.signal.aborted) {
287
- return;
288
- }
289
- setCurrentTheme(theme);
290
- setActiveTokens(null);
291
- handleThemeChange(theme);
292
- setIsLoading(false);
263
+ // Check if aborted before state update
264
+ if (abortController.signal.aborted) {
293
265
  return;
294
- } catch {
295
- // If previous load failed, continue with new load
296
266
  }
267
+
268
+ setActiveTokens(validatedTokens);
269
+ setCurrentTheme(themeId);
270
+ handleThemeChange(validatedTokens);
271
+ setIsLoading(false);
272
+ return;
297
273
  }
298
274
 
299
- // Load CSS theme
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 () => {
275
+ // If it's a string theme name, load the associated CSS
276
+ if (typeof theme === 'string' && themes[theme]) {
277
+ // Check if theme is already loading
278
+ if (themePromisesRef.current[theme]) {
303
279
  try {
280
+ await themePromisesRef.current[theme];
304
281
  // Check if aborted
305
282
  if (abortController.signal.aborted) {
306
- resolve();
307
283
  return;
308
284
  }
285
+ setCurrentTheme(theme);
286
+ setActiveTokens(null);
287
+ handleThemeChange(theme);
288
+ setIsLoading(false);
289
+ return;
290
+ } catch {
291
+ // If previous load failed, continue with new load
292
+ }
293
+ }
309
294
 
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
-
295
+ // Load CSS theme
296
+ const themeLoadPromise = new Promise<void>((resolve, reject) => {
297
+ // Handle the async operations inside the promise without making the executor async
298
+ const loadTheme = async () => {
299
+ try {
321
300
  // Check if aborted
322
301
  if (abortController.signal.aborted) {
323
302
  resolve();
324
303
  return;
325
304
  }
326
305
 
327
- // Load CSS file (using loadThemeCSS from domUtils)
328
- const { loadThemeCSS } = await import('../utils/domUtils');
329
- await loadThemeCSS(cssPath, `theme-${theme}`);
306
+ const themeMetadata = themes[theme];
307
+
308
+ if (themeMetadata) {
309
+ // Build CSS path using utility function
310
+ const cssPath = buildThemePath(theme, basePath, useMinified, cdnPath);
311
+
312
+ // Check if aborted
313
+ if (abortController.signal.aborted) {
314
+ resolve();
315
+ return;
316
+ }
317
+
318
+ // Load CSS file (using loadThemeCSS from domUtils)
319
+ const { loadThemeCSS } = await import('../utils/domUtils');
320
+ await loadThemeCSS(cssPath, `theme-${theme}`);
321
+
322
+ // Check if aborted after async operation
323
+ if (abortController.signal.aborted) {
324
+ resolve();
325
+ return;
326
+ }
327
+
328
+ // Remove any previously loaded theme CSS
329
+ removeCSS(`theme-${String(currentTheme)}`);
330
330
 
331
- // Check if aborted after async operation
331
+ loadedThemesRef.current.add(theme);
332
+
333
+ setCurrentTheme(theme);
334
+ setActiveTokens(null);
335
+ handleThemeChange(theme);
336
+ resolve();
337
+ } else {
338
+ throw new Error(`Theme metadata not found for theme: ${theme}`);
339
+ }
340
+ } catch (err) {
341
+ // Don't reject if aborted
332
342
  if (abortController.signal.aborted) {
333
343
  resolve();
334
344
  return;
335
345
  }
336
346
 
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
351
- if (abortController.signal.aborted) {
352
- resolve();
353
- return;
347
+ const error = err instanceof Error ? err : new Error(String(err));
348
+ setError(error);
349
+ handleError(error, String(theme));
350
+ reject(error);
354
351
  }
352
+ };
355
353
 
356
- const error = err instanceof Error ? err : new Error(String(err));
357
- setError(error);
358
- handleError(error, String(theme));
359
- reject(error);
360
- }
361
- };
362
-
363
- // Start the async operation
364
- loadTheme();
365
- });
354
+ // Start the async operation
355
+ loadTheme();
356
+ });
366
357
 
367
- themePromisesRef.current[theme] = themeLoadPromise;
358
+ themePromisesRef.current[theme] = themeLoadPromise;
368
359
 
369
- try {
370
- await themeLoadPromise;
371
- } catch {
372
- // Error already handled in promise
373
- }
360
+ try {
361
+ await themeLoadPromise;
362
+ } catch {
363
+ // Error already handled in promise
364
+ }
374
365
 
375
- // Clean up completed promise after a delay to prevent memory leak
376
- setTimeout(() => {
377
- if (themePromisesRef.current[theme] === themeLoadPromise) {
378
- delete themePromisesRef.current[theme];
366
+ // Clean up completed promise after a delay to prevent memory leak
367
+ setTimeout(() => {
368
+ if (themePromisesRef.current[theme] === themeLoadPromise) {
369
+ delete themePromisesRef.current[theme];
370
+ }
371
+ }, 1000);
372
+ } else {
373
+ // Check if aborted
374
+ if (abortController.signal.aborted) {
375
+ return;
379
376
  }
380
- }, 1000);
381
- } else {
382
- // Check if aborted
377
+
378
+ // For string theme that isn't in our themes record, just set the name
379
+ setCurrentTheme(themeName);
380
+ setActiveTokens(null);
381
+ handleThemeChange(themeName);
382
+ }
383
+ } catch (err) {
384
+ // Don't set error if aborted
383
385
  if (abortController.signal.aborted) {
384
386
  return;
385
387
  }
386
388
 
387
- // For string theme that isn't in our themes record, just set the name
388
- setCurrentTheme(themeName);
389
- setActiveTokens(null);
390
- handleThemeChange(themeName);
391
- }
392
- } catch (err) {
393
- // Don't set error if aborted
394
- if (abortController.signal.aborted) {
395
- return;
396
- }
397
-
398
- const error = err instanceof Error ? err : new Error(String(err));
399
- setError(error);
400
- handleError(error, String(theme));
401
- } finally {
402
- // Only update loading state if not aborted
403
- if (!abortController.signal.aborted) {
404
- setIsLoading(false);
389
+ const error = err instanceof Error ? err : new Error(String(err));
390
+ setError(error);
391
+ handleError(error, String(theme));
392
+ } finally {
393
+ // Only update loading state if not aborted
394
+ if (!abortController.signal.aborted) {
395
+ setIsLoading(false);
396
+ }
405
397
  }
406
- }
407
- }, [themes, currentTheme, handleThemeChange, handleError, basePath, useMinified, cdnPath]);
398
+ },
399
+ [themes, currentTheme, handleThemeChange, handleError, basePath, useMinified, cdnPath]
400
+ );
408
401
 
409
402
  // Check if theme is loaded
410
403
  const isThemeLoaded = useCallback((themeName: string) => {
@@ -412,32 +405,30 @@ export const ThemeProvider: React.FC<ThemeProviderProps> = ({
412
405
  }, []);
413
406
 
414
407
  // Preload theme function
415
- const preloadTheme = useCallback(async (themeName: string) => {
416
- if (!themes[themeName] || isThemeLoaded(themeName)) {
417
- return;
418
- }
408
+ const preloadTheme = useCallback(
409
+ async (themeName: string) => {
410
+ if (!themes[themeName] || isThemeLoaded(themeName)) {
411
+ return;
412
+ }
419
413
 
420
- setIsLoading(true);
421
- try {
422
- // Build CSS path using utility function
423
- const cssPath = buildThemePath(
424
- themeName,
425
- basePath,
426
- useMinified,
427
- cdnPath
428
- );
429
-
430
- // Preload CSS by fetching it
431
- await fetch(cssPath);
432
- loadedThemesRef.current.add(themeName);
433
- } catch (err) {
434
- const error = err instanceof Error ? err : new Error(String(err));
435
- setError(error);
436
- handleError(error, themeName);
437
- } finally {
438
- setIsLoading(false);
439
- }
440
- }, [themes, isThemeLoaded, handleError, basePath, useMinified, cdnPath]);
414
+ setIsLoading(true);
415
+ try {
416
+ // Build CSS path using utility function
417
+ const cssPath = buildThemePath(themeName, basePath, useMinified, cdnPath);
418
+
419
+ // Preload CSS by fetching it
420
+ await fetch(cssPath);
421
+ loadedThemesRef.current.add(themeName);
422
+ } catch (err) {
423
+ const error = err instanceof Error ? err : new Error(String(err));
424
+ setError(error);
425
+ handleError(error, themeName);
426
+ } finally {
427
+ setIsLoading(false);
428
+ }
429
+ },
430
+ [themes, isThemeLoaded, handleError, basePath, useMinified, cdnPath]
431
+ );
441
432
 
442
433
  // Create a mock theme manager instance for the context
443
434
  const themeManager = useMemo(() => {
@@ -449,40 +440,40 @@ export const ThemeProvider: React.FC<ThemeProviderProps> = ({
449
440
  }, []);
450
441
 
451
442
  // Memoize available themes to prevent unnecessary recalculations
452
- const availableThemes = useMemo(() =>
453
- Object.entries(themes).map(([name, metadata]) => ({
454
- ...metadata,
455
- name: name, // Ensure name is set from the key
456
- })),
443
+ const availableThemes = useMemo(
444
+ () =>
445
+ Object.entries(themes).map(([name, metadata]) => ({
446
+ ...metadata,
447
+ name: name, // Ensure name is set from the key
448
+ })),
457
449
  [themes]
458
450
  );
459
451
 
460
452
  // Theme context value
461
- const contextValue = useMemo(() => ({
462
- theme: currentTheme,
463
- activeTokens,
464
- setTheme,
465
- availableThemes,
466
- isLoading,
467
- error,
468
- isThemeLoaded,
469
- preloadTheme,
470
- themeManager,
471
- }), [
472
- currentTheme,
473
- activeTokens,
474
- setTheme,
475
- availableThemes, // Use memoized value
476
- isLoading,
477
- error,
478
- isThemeLoaded,
479
- preloadTheme,
480
- themeManager
481
- ]);
482
-
483
- return (
484
- <ThemeContext.Provider value={contextValue}>
485
- {children}
486
- </ThemeContext.Provider>
453
+ const contextValue = useMemo(
454
+ () => ({
455
+ theme: currentTheme,
456
+ activeTokens,
457
+ setTheme,
458
+ availableThemes,
459
+ isLoading,
460
+ error,
461
+ isThemeLoaded,
462
+ preloadTheme,
463
+ themeManager,
464
+ }),
465
+ [
466
+ currentTheme,
467
+ activeTokens,
468
+ setTheme,
469
+ availableThemes, // Use memoized value
470
+ isLoading,
471
+ error,
472
+ isThemeLoaded,
473
+ preloadTheme,
474
+ themeManager,
475
+ ]
487
476
  );
488
- };
477
+
478
+ return <ThemeContext.Provider value={contextValue}>{children}</ThemeContext.Provider>;
479
+ };
@@ -302,4 +302,4 @@ describe('ThemeProvider Integration', () => {
302
302
  expect(applyThemeAttributes).toHaveBeenCalledWith('high-contrast', 'data-bs-theme');
303
303
  });
304
304
  });
305
- });
305
+ });