@shohojdhara/atomix 0.3.0 → 0.3.1

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 (95) hide show
  1. package/dist/atomix.css +309 -105
  2. package/dist/atomix.min.css +3 -5
  3. package/dist/index.d.ts +804 -53
  4. package/dist/index.esm.js +16367 -16413
  5. package/dist/index.esm.js.map +1 -1
  6. package/dist/index.js +16275 -16336
  7. package/dist/index.js.map +1 -1
  8. package/dist/index.min.js +1 -1
  9. package/dist/index.min.js.map +1 -1
  10. package/dist/themes/applemix.css +309 -105
  11. package/dist/themes/applemix.min.css +5 -7
  12. package/dist/themes/boomdevs.css +202 -10
  13. package/dist/themes/boomdevs.min.css +3 -5
  14. package/dist/themes/esrar.css +309 -105
  15. package/dist/themes/esrar.min.css +4 -6
  16. package/dist/themes/flashtrade.css +310 -105
  17. package/dist/themes/flashtrade.min.css +5 -7
  18. package/dist/themes/mashroom.css +300 -96
  19. package/dist/themes/mashroom.min.css +4 -6
  20. package/dist/themes/shaj-default.css +300 -96
  21. package/dist/themes/shaj-default.min.css +4 -6
  22. package/package.json +1 -1
  23. package/src/components/AtomixGlass/AtomixGlass.test.tsx +21 -32
  24. package/src/components/AtomixGlass/AtomixGlass.tsx +55 -42
  25. package/src/components/AtomixGlass/AtomixGlassContainer.tsx +205 -57
  26. package/src/components/AtomixGlass/GlassFilter.tsx +22 -8
  27. package/src/components/AtomixGlass/__snapshots__/AtomixGlass.test.tsx.snap +221 -0
  28. package/src/components/AtomixGlass/atomixGLass.old.tsx +0 -3
  29. package/src/components/AtomixGlass/shader-utils.ts +8 -0
  30. package/src/components/AtomixGlass/stories/AtomixGlass.stories.tsx +319 -100
  31. package/src/components/AtomixGlass/stories/Examples.stories.tsx +601 -105
  32. package/src/components/AtomixGlass/stories/Modes.stories.tsx +30 -12
  33. package/src/components/AtomixGlass/stories/Playground.stories.tsx +173 -38
  34. package/src/components/AtomixGlass/stories/ShaderVariants.stories.tsx +18 -18
  35. package/src/components/AtomixGlass/stories/shared-components.tsx +27 -5
  36. package/src/components/Button/Button.tsx +62 -17
  37. package/src/components/Callout/Callout.test.tsx +8 -14
  38. package/src/components/Card/Card.tsx +103 -1
  39. package/src/components/Card/index.ts +3 -2
  40. package/src/components/Icon/index.ts +1 -1
  41. package/src/components/Modal/Modal.stories.tsx +29 -38
  42. package/src/components/Modal/Modal.tsx +4 -4
  43. package/src/components/Navigation/SideMenu/SideMenu.tsx +49 -41
  44. package/src/components/Navigation/SideMenu/SideMenuItem.tsx +63 -24
  45. package/src/components/Popover/Popover.tsx +1 -1
  46. package/src/components/VideoPlayer/VideoPlayer.stories.tsx +977 -400
  47. package/src/components/VideoPlayer/VideoPlayer.tsx +1 -6
  48. package/src/lib/composables/shared-mouse-tracker.ts +133 -0
  49. package/src/lib/composables/useAtomixGlass.ts +303 -115
  50. package/src/lib/theme/ThemeManager.integration.test.ts +124 -0
  51. package/src/lib/theme/ThemeManager.stories.tsx +13 -13
  52. package/src/lib/theme/ThemeManager.test.ts +4 -0
  53. package/src/lib/theme/ThemeManager.ts +203 -59
  54. package/src/lib/theme/ThemeProvider.tsx +183 -33
  55. package/src/lib/theme/composeTheme.ts +375 -0
  56. package/src/lib/theme/createTheme.test.ts +475 -0
  57. package/src/lib/theme/createTheme.ts +510 -0
  58. package/src/lib/theme/generateCSSVariables.ts +713 -0
  59. package/src/lib/theme/index.ts +67 -0
  60. package/src/lib/theme/themeUtils.ts +333 -0
  61. package/src/lib/theme/types.ts +337 -8
  62. package/src/lib/theme/useTheme.test.tsx +2 -1
  63. package/src/lib/theme/useTheme.ts +6 -22
  64. package/src/lib/types/components.ts +148 -59
  65. package/src/styles/01-settings/_index.scss +2 -2
  66. package/src/styles/01-settings/_settings.badge.scss +2 -2
  67. package/src/styles/01-settings/_settings.border-radius.scss +1 -1
  68. package/src/styles/01-settings/{_settings.maps.scss → _settings.design-tokens.scss} +163 -49
  69. package/src/styles/01-settings/_settings.modal.scss +1 -1
  70. package/src/styles/01-settings/_settings.spacing.scss +14 -13
  71. package/src/styles/03-generic/_generic.root.scss +131 -50
  72. package/src/styles/05-objects/_objects.block.scss +1 -1
  73. package/src/styles/06-components/_components.atomix-glass.scss +20 -22
  74. package/src/styles/06-components/_components.badge.scss +2 -2
  75. package/src/styles/06-components/_components.button.scss +1 -1
  76. package/src/styles/06-components/_components.callout.scss +1 -1
  77. package/src/styles/06-components/_components.card.scss +74 -2
  78. package/src/styles/06-components/_components.chart.scss +1 -1
  79. package/src/styles/06-components/_components.dropdown.scss +6 -0
  80. package/src/styles/06-components/_components.footer.scss +1 -1
  81. package/src/styles/06-components/_components.list-group.scss +1 -1
  82. package/src/styles/06-components/_components.list.scss +1 -1
  83. package/src/styles/06-components/_components.menu.scss +1 -1
  84. package/src/styles/06-components/_components.messages.scss +1 -1
  85. package/src/styles/06-components/_components.modal.scss +7 -2
  86. package/src/styles/06-components/_components.navbar.scss +1 -1
  87. package/src/styles/06-components/_components.popover.scss +10 -0
  88. package/src/styles/06-components/_components.product-review.scss +1 -1
  89. package/src/styles/06-components/_components.progress.scss +1 -1
  90. package/src/styles/06-components/_components.rating.scss +1 -1
  91. package/src/styles/06-components/_components.spinner.scss +1 -1
  92. package/src/styles/99-utilities/_utilities.background.scss +1 -1
  93. package/src/styles/99-utilities/_utilities.border.scss +1 -1
  94. package/src/styles/99-utilities/_utilities.link.scss +1 -1
  95. package/src/styles/99-utilities/_utilities.text.scss +1 -1
@@ -4,10 +4,11 @@
4
4
  * React context provider for theme management
5
5
  */
6
6
 
7
- import React, { useState, useEffect, useCallback, useMemo } from 'react';
7
+ import React, { useState, useEffect, useCallback, useMemo, useRef } from 'react';
8
8
  import { ThemeManager } from './ThemeManager';
9
9
  import { ThemeContext } from './ThemeContext';
10
- import type { ThemeProviderProps, ThemeMetadata } from './types';
10
+ import type { ThemeProviderProps, ThemeMetadata, Theme } from './types';
11
+ import { isJSTheme } from './themeUtils';
11
12
 
12
13
  /**
13
14
  * ThemeProvider component
@@ -46,11 +47,51 @@ export const ThemeProvider: React.FC<ThemeProviderProps> = ({
46
47
  onThemeChange,
47
48
  onError,
48
49
  }) => {
49
- // Initialize theme manager
50
+ // Store callbacks in refs to avoid recreating ThemeManager when they change
51
+ const onThemeChangeRef = useRef(onThemeChange);
52
+ const onErrorRef = useRef(onError);
53
+
54
+ // Update refs when callbacks change
55
+ useEffect(() => {
56
+ onThemeChangeRef.current = onThemeChange;
57
+ onErrorRef.current = onError;
58
+ }, [onThemeChange, onError]);
59
+
60
+ // Create stable wrapper functions that read from refs
61
+ const handleThemeChange = useCallback((theme: string | Theme) => {
62
+ onThemeChangeRef.current?.(theme);
63
+ }, []);
64
+
65
+ const handleError = useCallback((error: Error, themeName: string) => {
66
+ onErrorRef.current?.(error, themeName);
67
+ }, []);
68
+
69
+ // Stabilize themes object reference to prevent unnecessary recreations
70
+ const themesRef = useRef(themes);
71
+ const themesStable = useMemo(() => {
72
+ // Only update if themes object actually changed (shallow comparison)
73
+ const currentKeys = Object.keys(themes);
74
+ const prevKeys = Object.keys(themesRef.current);
75
+
76
+ if (currentKeys.length !== prevKeys.length) {
77
+ themesRef.current = themes;
78
+ return themes;
79
+ }
80
+
81
+ const hasChanged = currentKeys.some(key => themes[key] !== themesRef.current[key]);
82
+ if (hasChanged) {
83
+ themesRef.current = themes;
84
+ return themes;
85
+ }
86
+
87
+ return themesRef.current;
88
+ }, [themes]);
89
+
90
+ // Initialize theme manager (only recreate when config changes, not callbacks)
50
91
  const themeManager = useMemo(() => {
51
92
  try {
52
93
  return new ThemeManager({
53
- themes,
94
+ themes: themesStable,
54
95
  defaultTheme,
55
96
  basePath,
56
97
  cdnPath,
@@ -60,19 +101,73 @@ export const ThemeProvider: React.FC<ThemeProviderProps> = ({
60
101
  dataAttribute,
61
102
  enablePersistence,
62
103
  useMinified,
63
- onThemeChange,
64
- onError,
104
+ onThemeChange: handleThemeChange,
105
+ onError: handleError,
65
106
  });
66
107
  } catch (error) {
67
108
  console.error('Failed to initialize ThemeManager:', error);
68
109
  // Return a minimal fallback manager
69
- return new ThemeManager({
70
- themes: { [defaultTheme]: { name: defaultTheme } },
71
- defaultTheme,
72
- });
110
+ const fallbackThemes: Record<string, ThemeMetadata> = {};
111
+ let fallbackDefault: string | Theme | undefined = defaultTheme;
112
+
113
+ if (typeof defaultTheme === 'string') {
114
+ // If defaultTheme is a string, add it to fallback themes
115
+ fallbackThemes[defaultTheme] = { name: defaultTheme };
116
+ } else if (defaultTheme && typeof defaultTheme === 'object') {
117
+ // If defaultTheme is a Theme object, add it to fallback themes map
118
+ // so it can be looked up by name later (e.g., getThemeMetadata, validateTheme)
119
+ const themeName = defaultTheme.name || 'custom-theme';
120
+ // Extract ThemeMetadata properties from Theme object (Theme extends ThemeMetadata)
121
+ // Use themeName (with fallback) instead of defaultTheme.name directly to ensure name is always a string
122
+ fallbackThemes[themeName] = {
123
+ name: themeName,
124
+ class: defaultTheme.class,
125
+ description: defaultTheme.description,
126
+ author: defaultTheme.author,
127
+ version: defaultTheme.version,
128
+ tags: defaultTheme.tags,
129
+ supportsDarkMode: defaultTheme.supportsDarkMode,
130
+ status: defaultTheme.status,
131
+ a11y: defaultTheme.a11y,
132
+ color: defaultTheme.color,
133
+ features: defaultTheme.features,
134
+ dependencies: defaultTheme.dependencies,
135
+ };
136
+ // Keep the Theme object as defaultTheme for ThemeManager
137
+ fallbackDefault = defaultTheme;
138
+ } else {
139
+ // If defaultTheme is undefined, create a minimal fallback theme
140
+ // to prevent ThemeManager from throwing an error
141
+ const fallbackThemeName = 'fallback-theme';
142
+ fallbackThemes[fallbackThemeName] = { name: fallbackThemeName };
143
+ fallbackDefault = fallbackThemeName;
144
+ }
145
+
146
+ try {
147
+ return new ThemeManager({
148
+ themes: fallbackThemes,
149
+ defaultTheme: fallbackDefault,
150
+ basePath,
151
+ cdnPath,
152
+ preload,
153
+ lazy,
154
+ storageKey,
155
+ dataAttribute,
156
+ enablePersistence,
157
+ useMinified,
158
+ onThemeChange: handleThemeChange,
159
+ onError: handleError,
160
+ });
161
+ } catch (fallbackError) {
162
+ // If even the fallback fails, log and throw
163
+ console.error('Failed to create fallback ThemeManager:', fallbackError);
164
+ throw new Error(
165
+ 'ThemeManager initialization failed. Please provide a valid themes configuration or defaultTheme.'
166
+ );
167
+ }
73
168
  }
74
169
  }, [
75
- themes,
170
+ themesStable,
76
171
  defaultTheme,
77
172
  basePath,
78
173
  cdnPath,
@@ -82,12 +177,13 @@ export const ThemeProvider: React.FC<ThemeProviderProps> = ({
82
177
  dataAttribute,
83
178
  enablePersistence,
84
179
  useMinified,
85
- onThemeChange,
86
- onError,
180
+ handleThemeChange,
181
+ handleError,
87
182
  ]);
88
183
 
89
184
  // State
90
185
  const [currentTheme, setCurrentTheme] = useState<string>(themeManager.getTheme());
186
+ const [activeTheme, setActiveTheme] = useState<Theme | null>(themeManager.getActiveTheme());
91
187
  const [isLoading, setIsLoading] = useState<boolean>(false);
92
188
  const [error, setError] = useState<Error | null>(null);
93
189
 
@@ -99,30 +195,38 @@ export const ThemeProvider: React.FC<ThemeProviderProps> = ({
99
195
 
100
196
  // Set theme function
101
197
  const setTheme = useCallback(
102
- async (themeName: string, options?: { fallbackOnError?: boolean }): Promise<void> => {
198
+ async (themeOrName: string | Theme, options?: { fallbackOnError?: boolean }): Promise<void> => {
103
199
  setIsLoading(true);
104
200
  setError(null);
105
201
 
106
202
  try {
107
- await themeManager.setTheme(themeName, options);
108
- setCurrentTheme(themeName);
203
+ await themeManager.setTheme(themeOrName, options);
204
+ setCurrentTheme(themeManager.getTheme());
205
+ setActiveTheme(themeManager.getActiveTheme());
109
206
  } catch (err) {
110
207
  const error = err instanceof Error ? err : new Error(String(err));
111
208
  setError(error);
112
-
113
- // If fallback is enabled and theme is not default, try to fallback
114
- if (options?.fallbackOnError && themeName !== defaultTheme) {
115
- try {
116
- await themeManager.setTheme(defaultTheme, { fallbackOnError: false });
117
- setCurrentTheme(defaultTheme);
118
- setError(null);
119
- return;
120
- } catch (fallbackErr) {
121
- // If fallback also fails, throw original error
122
- throw error;
209
+
210
+ // If fallback is enabled and it's safe to fallback
211
+ if (options?.fallbackOnError && defaultTheme) {
212
+ // Avoid infinite loops if fallback is same as current attempt
213
+ const targetName = isJSTheme(themeOrName) ? themeOrName.name : themeOrName;
214
+ const defName = isJSTheme(defaultTheme) ? defaultTheme.name : defaultTheme;
215
+
216
+ if (targetName !== defName) {
217
+ try {
218
+ await themeManager.setTheme(defaultTheme, { fallbackOnError: false });
219
+ setCurrentTheme(themeManager.getTheme());
220
+ setActiveTheme(themeManager.getActiveTheme());
221
+ setError(null);
222
+ return;
223
+ } catch (fallbackErr) {
224
+ // If fallback also fails, throw original error
225
+ throw error;
226
+ }
123
227
  }
124
228
  }
125
-
229
+
126
230
  throw error;
127
231
  } finally {
128
232
  setIsLoading(false);
@@ -157,6 +261,7 @@ export const ThemeProvider: React.FC<ThemeProviderProps> = ({
157
261
  useEffect(() => {
158
262
  const handleThemeChange = () => {
159
263
  setCurrentTheme(themeManager.getTheme());
264
+ setActiveTheme(themeManager.getActiveTheme());
160
265
  };
161
266
 
162
267
  themeManager.on('themeChange', handleThemeChange);
@@ -166,22 +271,65 @@ export const ThemeProvider: React.FC<ThemeProviderProps> = ({
166
271
  };
167
272
  }, [themeManager]);
168
273
 
169
- // Load initial theme
274
+ // Track the last themeManager instance we initialized
275
+ const initializedManagerRef = useRef<ThemeManager | null>(null);
276
+
277
+ // Load initial theme (once per themeManager instance)
170
278
  useEffect(() => {
279
+ // Skip if we've already initialized this exact themeManager instance
280
+ if (initializedManagerRef.current === themeManager) {
281
+ return;
282
+ }
283
+
284
+ // Mark this themeManager as initialized synchronously before async work
285
+ // This prevents race conditions where the effect runs again before async completes
286
+ initializedManagerRef.current = themeManager;
287
+
288
+ let isMounted = true;
289
+
171
290
  const loadInitialTheme = async () => {
172
291
  setIsLoading(true);
173
292
  try {
174
- await themeManager.setTheme(themeManager.getTheme());
293
+ // If currentTheme is set (from config/storage), use it.
294
+ // If activeTheme is set (from config default), use it.
295
+ const current = themeManager.getTheme();
296
+ const active = themeManager.getActiveTheme();
297
+
298
+ // Only load if theme is not already loaded
299
+ const isAlreadyLoaded = themeManager.isThemeLoaded(current) || (active && themeManager.isThemeLoaded(active.name || ''));
300
+
301
+ if (!isAlreadyLoaded) {
302
+ // If we have an active object, or a name, ensure it's "set" (loaded).
303
+ if (active) {
304
+ await themeManager.setTheme(active, { removePrevious: false });
305
+ } else if (current) {
306
+ await themeManager.setTheme(current, { removePrevious: false });
307
+ }
308
+ }
309
+
310
+ // Update state even if theme was already loaded
311
+ if (isMounted) {
312
+ setCurrentTheme(themeManager.getTheme());
313
+ setActiveTheme(themeManager.getActiveTheme());
314
+ }
175
315
  } catch (err) {
176
316
  const error = err instanceof Error ? err : new Error(String(err));
177
- setError(error);
178
- console.error('Failed to load initial theme:', error);
317
+ if (isMounted) {
318
+ setError(error);
319
+ console.error('Failed to load initial theme:', error);
320
+ }
179
321
  } finally {
180
- setIsLoading(false);
322
+ if (isMounted) {
323
+ setIsLoading(false);
324
+ }
181
325
  }
182
326
  };
183
327
 
184
328
  loadInitialTheme();
329
+
330
+ return () => {
331
+ isMounted = false;
332
+ };
185
333
  }, [themeManager]);
186
334
 
187
335
  // Cleanup on unmount
@@ -195,6 +343,7 @@ export const ThemeProvider: React.FC<ThemeProviderProps> = ({
195
343
  const contextValue = useMemo(
196
344
  () => ({
197
345
  theme: currentTheme,
346
+ activeTheme,
198
347
  setTheme,
199
348
  availableThemes,
200
349
  isLoading,
@@ -205,6 +354,7 @@ export const ThemeProvider: React.FC<ThemeProviderProps> = ({
205
354
  }),
206
355
  [
207
356
  currentTheme,
357
+ activeTheme,
208
358
  setTheme,
209
359
  availableThemes,
210
360
  isLoading,
@@ -0,0 +1,375 @@
1
+ /**
2
+ * Theme Composition Utilities
3
+ *
4
+ * Utilities for composing, merging, and extending themes.
5
+ */
6
+
7
+ import type { Theme, ThemeOptions } from './types';
8
+ import { createTheme } from './createTheme';
9
+
10
+ // ============================================================================
11
+ // Deep Merge Utility
12
+ // ============================================================================
13
+
14
+ /**
15
+ * Check if value is an object
16
+ */
17
+ function isObject(item: any): item is Record<string, any> {
18
+ return item && typeof item === 'object' && !Array.isArray(item) && typeof item !== 'function';
19
+ }
20
+
21
+ /**
22
+ * Deep merge multiple objects
23
+ * Later objects override earlier ones
24
+ */
25
+ export function deepMerge<T extends Record<string, any>>(...objects: Partial<T>[]): T {
26
+ if (objects.length === 0) return {} as T;
27
+ if (objects.length === 1) return objects[0] as T;
28
+
29
+ const [target, ...sources] = objects;
30
+ const result = { ...target } as T;
31
+
32
+ for (const source of sources) {
33
+ if (!source) continue;
34
+
35
+ for (const key in source) {
36
+ if (!source.hasOwnProperty(key)) continue;
37
+
38
+ const targetValue = result[key];
39
+ const sourceValue = source[key];
40
+
41
+ if (isObject(targetValue) && isObject(sourceValue)) {
42
+ // Recursively merge objects
43
+ result[key] = deepMerge(targetValue as any, sourceValue as any) as any;
44
+ } else {
45
+ // Override with source value
46
+ result[key] = sourceValue as any;
47
+ }
48
+ }
49
+ }
50
+
51
+ return result;
52
+ }
53
+
54
+ // ============================================================================
55
+ // Theme Merging
56
+ // ============================================================================
57
+
58
+ /**
59
+ * Merge multiple theme options into a single theme options object
60
+ *
61
+ * @param themes - Theme options to merge
62
+ * @returns Merged theme options
63
+ *
64
+ * @example
65
+ * ```typescript
66
+ * const baseTheme = { palette: { primary: { main: '#000' } } };
67
+ * const customTheme = { palette: { secondary: { main: '#fff' } } };
68
+ * const merged = mergeTheme(baseTheme, customTheme);
69
+ * ```
70
+ */
71
+ export function mergeTheme(...themes: ThemeOptions[]): ThemeOptions {
72
+ return deepMerge({}, ...themes);
73
+ }
74
+
75
+ /**
76
+ * Extend an existing theme with new options
77
+ *
78
+ * @param baseTheme - Base theme to extend (can be Theme or ThemeOptions)
79
+ * @param extension - Theme options to extend with
80
+ * @returns New theme with extended options
81
+ *
82
+ * @example
83
+ * ```typescript
84
+ * const base = createTheme({ palette: { primary: { main: '#000' } } });
85
+ * const extended = extendTheme(base, {
86
+ * palette: { secondary: { main: '#fff' } }
87
+ * });
88
+ * ```
89
+ */
90
+ export function extendTheme(baseTheme: Theme | ThemeOptions, extension: ThemeOptions): Theme {
91
+ // If baseTheme is a complete Theme, extract the options
92
+ const baseOptions: ThemeOptions = (baseTheme as any).__isJSTheme
93
+ ? extractThemeOptions(baseTheme as Theme)
94
+ : (baseTheme as ThemeOptions);
95
+
96
+ const merged = mergeTheme(baseOptions, extension);
97
+ return createTheme(merged);
98
+ }
99
+
100
+ /**
101
+ * Extract theme options from a complete Theme object
102
+ */
103
+ function extractThemeOptions(theme: Theme): ThemeOptions {
104
+ return {
105
+ name: theme.name,
106
+ class: theme.class,
107
+ description: theme.description,
108
+ author: theme.author,
109
+ version: theme.version,
110
+ tags: theme.tags,
111
+ supportsDarkMode: theme.supportsDarkMode,
112
+ status: theme.status,
113
+ a11y: theme.a11y,
114
+ color: theme.color,
115
+ features: theme.features,
116
+ dependencies: theme.dependencies,
117
+ palette: {
118
+ primary: theme.palette.primary,
119
+ secondary: theme.palette.secondary,
120
+ error: theme.palette.error,
121
+ warning: theme.palette.warning,
122
+ info: theme.palette.info,
123
+ success: theme.palette.success,
124
+ background: theme.palette.background,
125
+ text: theme.palette.text,
126
+ },
127
+ typography: {
128
+ fontFamily: theme.typography.fontFamily,
129
+ fontSize: theme.typography.fontSize,
130
+ fontWeightLight: theme.typography.fontWeightLight,
131
+ fontWeightRegular: theme.typography.fontWeightRegular,
132
+ fontWeightMedium: theme.typography.fontWeightMedium,
133
+ fontWeightSemiBold: theme.typography.fontWeightSemiBold,
134
+ fontWeightBold: theme.typography.fontWeightBold,
135
+ h1: theme.typography.h1,
136
+ h2: theme.typography.h2,
137
+ h3: theme.typography.h3,
138
+ h4: theme.typography.h4,
139
+ h5: theme.typography.h5,
140
+ h6: theme.typography.h6,
141
+ body1: theme.typography.body1,
142
+ body2: theme.typography.body2,
143
+ },
144
+ shadows: theme.shadows,
145
+ transitions: theme.transitions,
146
+ zIndex: theme.zIndex,
147
+ custom: theme.custom,
148
+ };
149
+ }
150
+
151
+ // ============================================================================
152
+ // Theme Variants
153
+ // ============================================================================
154
+
155
+ /**
156
+ * Create light and dark variants from a base theme
157
+ *
158
+ * @param baseTheme - Base theme options
159
+ * @returns Object with light and dark theme variants
160
+ *
161
+ * @example
162
+ * ```typescript
163
+ * const { light, dark } = createThemeVariants({
164
+ * palette: { primary: { main: '#7AFFD7' } }
165
+ * });
166
+ * ```
167
+ */
168
+ export function createThemeVariants(baseTheme: ThemeOptions): {
169
+ light: Theme;
170
+ dark: Theme;
171
+ } {
172
+ // Light theme (use base as-is or with light adjustments)
173
+ const lightTheme = createTheme({
174
+ ...baseTheme,
175
+ name: `${baseTheme.name || 'Custom'} Light`,
176
+ supportsDarkMode: false,
177
+ });
178
+
179
+ // Dark theme (invert colors)
180
+ const darkTheme = createTheme({
181
+ ...baseTheme,
182
+ name: `${baseTheme.name || 'Custom'} Dark`,
183
+ supportsDarkMode: true,
184
+ palette: {
185
+ ...baseTheme.palette,
186
+ background: {
187
+ default: '#121212',
188
+ paper: '#1E1E1E',
189
+ subtle: '#2A2A2A',
190
+ ...baseTheme.palette?.background,
191
+ },
192
+ text: {
193
+ primary: 'rgba(255, 255, 255, 0.87)',
194
+ secondary: 'rgba(255, 255, 255, 0.6)',
195
+ disabled: 'rgba(255, 255, 255, 0.38)',
196
+ ...baseTheme.palette?.text,
197
+ },
198
+ },
199
+ });
200
+
201
+ return { light: lightTheme, dark: darkTheme };
202
+ }
203
+
204
+ // ============================================================================
205
+ // Theme Overrides
206
+ // ============================================================================
207
+
208
+ /**
209
+ * Create a theme with specific overrides
210
+ *
211
+ * @param baseTheme - Base theme
212
+ * @param overrides - Specific overrides to apply
213
+ * @returns New theme with overrides
214
+ *
215
+ * @example
216
+ * ```typescript
217
+ * const theme = overrideTheme(baseTheme, {
218
+ * 'palette.primary.main': '#FF0000',
219
+ * 'typography.fontSize': 16,
220
+ * });
221
+ * ```
222
+ */
223
+ export function overrideTheme(
224
+ baseTheme: Theme | ThemeOptions,
225
+ overrides: Record<string, any>
226
+ ): Theme {
227
+ const baseOptions: ThemeOptions = (baseTheme as any).__isJSTheme
228
+ ? extractThemeOptions(baseTheme as Theme)
229
+ : (baseTheme as ThemeOptions);
230
+
231
+ // Convert dot notation overrides to nested object
232
+ const nestedOverrides: any = {};
233
+ for (const [path, value] of Object.entries(overrides)) {
234
+ const keys = path.split('.');
235
+ let current = nestedOverrides;
236
+
237
+ for (let i = 0; i < keys.length - 1; i++) {
238
+ const key = keys[i] as string;
239
+ if (typeof key !== 'string' || key === '') {
240
+ throw new Error('Invalid override key in theme override: ' + String(key));
241
+ }
242
+ if (typeof current !== 'object' || current === null) {
243
+ throw new Error('Cannot set override for path due to non-object path segment');
244
+ }
245
+ if (!(key in current) || typeof current[key] !== 'object' || current[key] === null) {
246
+ current[key] = {};
247
+ }
248
+ current = current[key] as Record<string, any>;
249
+ }
250
+
251
+ const lastKey = keys[keys.length - 1] as string;
252
+ if (typeof lastKey === 'string') {
253
+ current[lastKey] = value;
254
+ }
255
+
256
+ }
257
+
258
+ return createTheme(deepMerge(baseOptions, nestedOverrides));
259
+ }
260
+
261
+ // ============================================================================
262
+ // Theme Composition Helpers
263
+ // ============================================================================
264
+
265
+ /**
266
+ * Compose multiple themes by merging them in order
267
+ *
268
+ * @param themes - Themes to compose (later themes override earlier ones)
269
+ * @returns Composed theme
270
+ *
271
+ * @example
272
+ * ```typescript
273
+ * const theme = composeThemes(
274
+ * baseTheme,
275
+ * brandTheme,
276
+ * customizationTheme
277
+ * );
278
+ * ```
279
+ */
280
+ export function composeThemes(...themes: (Theme | ThemeOptions)[]): Theme {
281
+ const options = themes.map((theme) =>
282
+ (theme as any).__isJSTheme ? extractThemeOptions(theme as Theme) : (theme as ThemeOptions)
283
+ );
284
+
285
+ return createTheme(mergeTheme(...options));
286
+ }
287
+
288
+ /**
289
+ * Create a theme preset with common configurations
290
+ *
291
+ * @param preset - Preset name
292
+ * @param customizations - Additional customizations
293
+ * @returns Theme with preset applied
294
+ */
295
+ export function createThemePreset(
296
+ preset: 'minimal' | 'modern' | 'classic' | 'vibrant',
297
+ customizations?: ThemeOptions
298
+ ): Theme {
299
+ const presets: Record<string, ThemeOptions> = {
300
+ minimal: {
301
+ name: 'Minimal',
302
+ palette: {
303
+ primary: { main: '#000000' },
304
+ secondary: { main: '#FFFFFF' },
305
+ background: {
306
+ default: '#FFFFFF',
307
+ paper: '#F5F5F5',
308
+ subtle: '#FAFAFA',
309
+ },
310
+ },
311
+ typography: {
312
+ fontFamily: '"Helvetica Neue", Helvetica, Arial, sans-serif',
313
+ },
314
+ },
315
+ modern: {
316
+ name: 'Modern',
317
+ palette: {
318
+ primary: { main: '#7AFFD7' },
319
+ secondary: { main: '#FF5733' },
320
+ background: {
321
+ default: '#FAFAFA',
322
+ paper: '#FFFFFF',
323
+ subtle: '#F5F5F5',
324
+ },
325
+ },
326
+ typography: {
327
+ fontFamily: '"Inter", "Roboto", sans-serif',
328
+ },
329
+ },
330
+ classic: {
331
+ name: 'Classic',
332
+ palette: {
333
+ primary: { main: '#1976D2' },
334
+ secondary: { main: '#DC004E' },
335
+ background: {
336
+ default: '#FFFFFF',
337
+ paper: '#F5F5F5',
338
+ subtle: '#EEEEEE',
339
+ },
340
+ },
341
+ typography: {
342
+ fontFamily: '"Roboto", "Helvetica", "Arial", sans-serif',
343
+ },
344
+ },
345
+ vibrant: {
346
+ name: 'Vibrant',
347
+ palette: {
348
+ primary: { main: '#FF6B6B' },
349
+ secondary: { main: '#4ECDC4' },
350
+ background: {
351
+ default: '#FFF8F0',
352
+ paper: '#FFFFFF',
353
+ subtle: '#FFF0E0',
354
+ },
355
+ },
356
+ typography: {
357
+ fontFamily: '"Poppins", "Roboto", sans-serif',
358
+ },
359
+ },
360
+ };
361
+
362
+ const basePreset: ThemeOptions = (presets[preset] ?? presets['modern']) as ThemeOptions;
363
+ const customThemeOptions: ThemeOptions = customizations ?? ({} as ThemeOptions);
364
+ return createTheme(mergeTheme(basePreset, customThemeOptions));
365
+ }
366
+
367
+ export default {
368
+ deepMerge,
369
+ mergeTheme,
370
+ extendTheme,
371
+ createThemeVariants,
372
+ overrideTheme,
373
+ composeThemes,
374
+ createThemePreset,
375
+ };