@shohojdhara/atomix 0.3.6 → 0.3.7
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.
- package/README.md +3 -3
- package/dist/charts.js +50 -142
- package/dist/charts.js.map +1 -1
- package/dist/core.js +179 -274
- package/dist/core.js.map +1 -1
- package/dist/forms.js +50 -142
- package/dist/forms.js.map +1 -1
- package/dist/heavy.js +179 -274
- package/dist/heavy.js.map +1 -1
- package/dist/index.d.ts +669 -703
- package/dist/index.esm.js +966 -1649
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +1211 -1890
- package/dist/index.js.map +1 -1
- package/dist/index.min.js +1 -1
- package/dist/index.min.js.map +1 -1
- package/dist/theme.d.ts +163 -334
- package/dist/theme.js +774 -1473
- package/dist/theme.js.map +1 -1
- package/package.json +1 -1
- package/src/components/AtomixGlass/AtomixGlass.tsx +128 -356
- package/src/components/AtomixGlass/AtomixGlassContainer.tsx +1 -1
- package/src/components/Button/Button.tsx +85 -167
- package/src/lib/composables/useAtomixGlass.ts +7 -7
- package/src/lib/config/loader.ts +2 -3
- package/src/lib/constants/components.ts +7 -0
- package/src/lib/hooks/usePerformanceMonitor.ts +1 -1
- package/src/lib/hooks/useThemeTokens.ts +105 -0
- package/src/lib/theme/config/configLoader.ts +60 -219
- package/src/lib/theme/config/loader.ts +15 -21
- package/src/lib/theme/constants/constants.ts +1 -1
- package/src/lib/theme/core/ThemeRegistry.ts +75 -279
- package/src/lib/theme/core/composeTheme.ts +14 -64
- package/src/lib/theme/core/createTheme.ts +54 -40
- package/src/lib/theme/core/createThemeObject.ts +2 -2
- package/src/lib/theme/core/index.ts +15 -1
- package/src/lib/theme/errors/errors.ts +1 -1
- package/src/lib/theme/generators/generateCSSNested.ts +130 -0
- package/src/lib/theme/generators/index.ts +6 -0
- package/src/lib/theme/index.ts +35 -10
- package/src/lib/theme/runtime/ThemeApplicator.ts +1 -1
- package/src/lib/theme/runtime/ThemeErrorBoundary.tsx +4 -4
- package/src/lib/theme/runtime/ThemeProvider.tsx +261 -554
- package/src/lib/theme/runtime/index.ts +1 -0
- package/src/lib/theme/runtime/useThemeTokens.ts +131 -0
- package/src/lib/theme/utils/componentTheming.ts +132 -0
- package/src/lib/theme/utils/naming.ts +100 -0
- package/src/lib/theme/utils/themeUtils.ts +6 -6
- package/src/lib/utils/componentUtils.ts +1 -1
- package/src/lib/utils/memoryMonitor.ts +3 -3
- package/src/lib/utils/themeNaming.ts +135 -0
|
@@ -1,29 +1,22 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Theme Provider
|
|
3
3
|
*
|
|
4
|
-
* React context provider for theme management
|
|
4
|
+
* React context provider for theme management with separated concerns
|
|
5
|
+
* Updated to use the new simplified theme system
|
|
5
6
|
*/
|
|
6
7
|
|
|
7
8
|
import React, { useState, useEffect, useCallback, useMemo, useRef } from 'react';
|
|
8
9
|
import { ThemeContext } from './ThemeContext';
|
|
9
|
-
import type { ThemeProviderProps,
|
|
10
|
-
import type { DesignTokens } from '../tokens/tokens';
|
|
10
|
+
import type { ThemeProviderProps, Theme, ThemeLoadOptions } from '../types';
|
|
11
11
|
import { isJSTheme } from '../utils/themeUtils';
|
|
12
|
-
import { getLogger } from '../errors
|
|
13
|
-
import {
|
|
14
|
-
import { ThemeApplicator } from './ThemeApplicator';
|
|
15
|
-
import { createTheme } from '../core/createTheme';
|
|
12
|
+
import { getLogger } from '../errors';
|
|
13
|
+
import { createTheme } from '../core';
|
|
16
14
|
import { injectCSS, removeCSS } from '../utils/injectCSS';
|
|
17
|
-
import { loadThemeFromConfigSync } from '../config/configLoader';
|
|
18
15
|
import {
|
|
19
16
|
isServer,
|
|
20
17
|
createLocalStorageAdapter,
|
|
21
|
-
loadThemeCSS,
|
|
22
|
-
removeThemeCSS,
|
|
23
18
|
applyThemeAttributes,
|
|
24
|
-
getThemeLinkId,
|
|
25
19
|
buildThemePath,
|
|
26
|
-
isThemeLoaded as checkThemeLoaded,
|
|
27
20
|
} from '../utils/domUtils';
|
|
28
21
|
import {
|
|
29
22
|
DEFAULT_STORAGE_KEY,
|
|
@@ -32,564 +25,278 @@ import {
|
|
|
32
25
|
} from '../constants/constants';
|
|
33
26
|
|
|
34
27
|
/**
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
* import { ThemeProvider } from '@shohojdhara/atomix/theme';
|
|
45
|
-
*
|
|
46
|
-
* // Loads from atomix.config.ts (config file required)
|
|
47
|
-
* function App() {
|
|
48
|
-
* return (
|
|
49
|
-
* <ThemeProvider>
|
|
50
|
-
* <YourApp />
|
|
51
|
-
* </ThemeProvider>
|
|
52
|
-
* );
|
|
53
|
-
* }
|
|
54
|
-
*
|
|
55
|
-
* // Provide explicit theme (bypasses config)
|
|
56
|
-
* function App() {
|
|
57
|
-
* return (
|
|
58
|
-
* <ThemeProvider defaultTheme="dark">
|
|
59
|
-
* <YourApp />
|
|
60
|
-
* </ThemeProvider>
|
|
61
|
-
* );
|
|
62
|
-
* }
|
|
63
|
-
* ```
|
|
28
|
+
* Theme Provider
|
|
29
|
+
*
|
|
30
|
+
* React context provider for theme management with separated concerns.
|
|
31
|
+
* Simplified version focusing on core functionality:
|
|
32
|
+
* - String-based themes (CSS files)
|
|
33
|
+
* - JS Theme objects
|
|
34
|
+
* - Persistence via localStorage
|
|
35
|
+
*
|
|
36
|
+
* Falls back to 'default' theme if no configuration is found.
|
|
64
37
|
*/
|
|
65
38
|
export const ThemeProvider: React.FC<ThemeProviderProps> = ({
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
onThemeChange,
|
|
78
|
-
onError,
|
|
39
|
+
children,
|
|
40
|
+
defaultTheme,
|
|
41
|
+
themes = {},
|
|
42
|
+
basePath = DEFAULT_BASE_PATH,
|
|
43
|
+
cdnPath = null,
|
|
44
|
+
useMinified = false,
|
|
45
|
+
storageKey = DEFAULT_STORAGE_KEY,
|
|
46
|
+
dataAttribute = DEFAULT_DATA_ATTRIBUTE,
|
|
47
|
+
enablePersistence = true,
|
|
48
|
+
onThemeChange,
|
|
49
|
+
onError,
|
|
79
50
|
}) => {
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
51
|
+
// Store callbacks in refs to avoid recreating when they change
|
|
52
|
+
const onThemeChangeRef = useRef(onThemeChange);
|
|
53
|
+
const onErrorRef = useRef(onError);
|
|
54
|
+
|
|
55
|
+
// Update ref when callback changes
|
|
56
|
+
useEffect(() => {
|
|
57
|
+
onThemeChangeRef.current = onThemeChange;
|
|
58
|
+
onErrorRef.current = onError;
|
|
59
|
+
}, [onThemeChange, onError]);
|
|
60
|
+
|
|
61
|
+
// Create stable wrapper functions that read from ref
|
|
62
|
+
const handleThemeChange = useCallback((theme: string | Theme) => {
|
|
63
|
+
onThemeChangeRef.current?.(theme);
|
|
64
|
+
}, []);
|
|
65
|
+
|
|
66
|
+
const handleError = useCallback((error: Error, themeName: string) => {
|
|
67
|
+
onErrorRef.current?.(error, themeName);
|
|
68
|
+
}, []);
|
|
69
|
+
|
|
70
|
+
// Initialize storage adapter
|
|
71
|
+
const storageAdapter = useMemo(() => createLocalStorageAdapter(), []);
|
|
72
|
+
|
|
73
|
+
// Get initial default theme
|
|
74
|
+
const initialDefaultTheme = useMemo(() => {
|
|
75
|
+
// Check storage first
|
|
76
|
+
if (enablePersistence && storageAdapter.isAvailable()) {
|
|
77
|
+
const stored = storageAdapter.getItem(storageKey);
|
|
78
|
+
if (stored) {
|
|
79
|
+
return stored;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// If defaultTheme is provided, use it
|
|
84
|
+
if (defaultTheme !== undefined && defaultTheme !== null) {
|
|
85
|
+
return defaultTheme;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Try to load from atomix.config.ts as fallback, but only in Node.js/SSR environments
|
|
89
|
+
if (typeof window === 'undefined') {
|
|
90
|
+
try {
|
|
91
|
+
// Dynamically import the config loader to avoid bundling issues in browser
|
|
92
|
+
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
93
|
+
const { loadThemeFromConfigSync } = require('../config/configLoader');
|
|
119
94
|
|
|
120
|
-
const logger = useMemo(() => getLogger(), []);
|
|
121
|
-
|
|
122
|
-
// Initialize registry
|
|
123
|
-
const registry = useMemo(() => {
|
|
124
|
-
const reg = new ThemeRegistry();
|
|
125
|
-
// Register themes from props
|
|
126
|
-
if (themesStable && Object.keys(themesStable).length > 0) {
|
|
127
|
-
for (const [themeId, metadata] of Object.entries(themesStable)) {
|
|
128
|
-
if (!reg.has(themeId)) {
|
|
129
|
-
reg.register(themeId, {
|
|
130
|
-
type: 'css',
|
|
131
|
-
name: metadata.name,
|
|
132
|
-
class: metadata.class || themeId,
|
|
133
|
-
description: metadata.description,
|
|
134
|
-
author: metadata.author,
|
|
135
|
-
version: metadata.version,
|
|
136
|
-
tags: metadata.tags,
|
|
137
|
-
supportsDarkMode: metadata.supportsDarkMode,
|
|
138
|
-
status: metadata.status,
|
|
139
|
-
a11y: metadata.a11y,
|
|
140
|
-
color: metadata.color,
|
|
141
|
-
features: metadata.features,
|
|
142
|
-
dependencies: metadata.dependencies,
|
|
143
|
-
});
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
return reg;
|
|
148
|
-
}, [themesStable]);
|
|
149
|
-
|
|
150
|
-
// Initialize storage adapter
|
|
151
|
-
const storageAdapter = useMemo(() => createLocalStorageAdapter(), []);
|
|
152
|
-
|
|
153
|
-
// Initialize theme applicator for JS themes
|
|
154
|
-
const themeApplicator = useMemo(() => {
|
|
155
|
-
if (isServer()) return null;
|
|
156
|
-
return new ThemeApplicator();
|
|
157
|
-
}, []);
|
|
158
|
-
|
|
159
|
-
// Get initial default theme (with config loading)
|
|
160
|
-
const initialDefaultTheme = useMemo(() => {
|
|
161
|
-
// Check storage first
|
|
162
|
-
if (enablePersistence && storageAdapter.isAvailable()) {
|
|
163
|
-
const stored = storageAdapter.getItem(storageKey);
|
|
164
|
-
if (stored) {
|
|
165
|
-
return stored;
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
// If defaultTheme is provided, use it
|
|
170
|
-
if (defaultTheme !== undefined && defaultTheme !== null) {
|
|
171
|
-
return defaultTheme;
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
// Load from atomix.config.ts (required)
|
|
175
95
|
const configTokens = loadThemeFromConfigSync();
|
|
176
96
|
if (configTokens && Object.keys(configTokens).length > 0) {
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
// Config is required - this will be caught in useEffect
|
|
181
|
-
return null;
|
|
182
|
-
}, [enablePersistence, storageAdapter, storageKey, defaultTheme]);
|
|
183
|
-
|
|
184
|
-
// State for React re-renders
|
|
185
|
-
const [currentTheme, setCurrentTheme] = useState<string>(() => {
|
|
186
|
-
if (typeof initialDefaultTheme === 'string') {
|
|
187
|
-
return initialDefaultTheme;
|
|
188
|
-
}
|
|
189
|
-
if (isJSTheme(initialDefaultTheme)) {
|
|
190
|
-
return initialDefaultTheme.name || 'js-theme';
|
|
97
|
+
// For simplicity, we'll treat config tokens as a special theme name
|
|
98
|
+
return 'config-theme';
|
|
191
99
|
}
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
if (configTokens && Object.keys(configTokens).length > 0) {
|
|
251
|
-
// Return config tokens as Partial<DesignTokens>
|
|
252
|
-
return configTokens;
|
|
100
|
+
} catch (error) {
|
|
101
|
+
console.warn('Failed to load theme from config, using default');
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Default fallback
|
|
106
|
+
return 'default';
|
|
107
|
+
}, [defaultTheme, enablePersistence, storageKey]);
|
|
108
|
+
|
|
109
|
+
// State for current theme
|
|
110
|
+
const [currentTheme, setCurrentTheme] = useState<string | Theme>(() => initialDefaultTheme);
|
|
111
|
+
const [activeTheme, setActiveTheme] = useState<Theme | null>(null);
|
|
112
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
113
|
+
const [error, setError] = useState<Error | null>(null);
|
|
114
|
+
|
|
115
|
+
// Track loaded themes
|
|
116
|
+
const loadedThemesRef = useRef<Set<string>>(new Set());
|
|
117
|
+
const themePromisesRef = useRef<Record<string, Promise<void>>>({});
|
|
118
|
+
|
|
119
|
+
// Apply initial theme attributes to document element
|
|
120
|
+
useEffect(() => {
|
|
121
|
+
if (!isServer()) {
|
|
122
|
+
applyThemeAttributes(String(currentTheme), dataAttribute);
|
|
123
|
+
}
|
|
124
|
+
}, [currentTheme, dataAttribute]);
|
|
125
|
+
|
|
126
|
+
// Handle theme persistence
|
|
127
|
+
useEffect(() => {
|
|
128
|
+
if (enablePersistence && storageAdapter.isAvailable()) {
|
|
129
|
+
storageAdapter.setItem(storageKey, String(currentTheme));
|
|
130
|
+
}
|
|
131
|
+
}, [currentTheme, storageKey, enablePersistence]);
|
|
132
|
+
|
|
133
|
+
// Function to set theme with proper type handling
|
|
134
|
+
const setTheme = useCallback(async (
|
|
135
|
+
theme: string | Theme | import('../tokens').DesignTokens | Partial<import('../tokens').DesignTokens>,
|
|
136
|
+
options?: ThemeLoadOptions
|
|
137
|
+
) => {
|
|
138
|
+
setIsLoading(true);
|
|
139
|
+
setError(null);
|
|
140
|
+
|
|
141
|
+
try {
|
|
142
|
+
let themeName: string;
|
|
143
|
+
let themeObj: Theme | null = null;
|
|
144
|
+
|
|
145
|
+
if (typeof theme === 'string') {
|
|
146
|
+
themeName = theme;
|
|
147
|
+
} else {
|
|
148
|
+
// If it's a Theme object or DesignTokens, we need to process it
|
|
149
|
+
if (isJSTheme(theme)) {
|
|
150
|
+
themeObj = theme as Theme;
|
|
151
|
+
// For JS themes, we use a generic name
|
|
152
|
+
themeName = 'js-theme';
|
|
153
|
+
setActiveTheme(themeObj);
|
|
154
|
+
} else {
|
|
155
|
+
// For DesignTokens, we might create a theme from tokens
|
|
156
|
+
themeName = 'tokens-theme';
|
|
157
|
+
// Create theme from tokens if needed
|
|
253
158
|
}
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// If it's a string theme name, load the associated CSS
|
|
162
|
+
if (typeof theme === 'string' && themes[theme]) {
|
|
163
|
+
// Check if theme is already loading
|
|
164
|
+
if (themePromisesRef.current[theme]) {
|
|
165
|
+
await themePromisesRef.current[theme];
|
|
166
|
+
setCurrentTheme(theme);
|
|
167
|
+
setActiveTheme(null);
|
|
168
|
+
handleThemeChange(theme);
|
|
169
|
+
return;
|
|
262
170
|
}
|
|
263
171
|
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
172
|
+
// Load CSS theme
|
|
173
|
+
const themeLoadPromise = new Promise<void>(async (resolve, reject) => {
|
|
174
|
+
try {
|
|
175
|
+
const themeMetadata = themes[theme];
|
|
267
176
|
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
document.documentElement.style.removeProperty(key);
|
|
272
|
-
});
|
|
273
|
-
}
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
// Check if it's DesignTokens
|
|
277
|
-
const isDesignTokens = theme !== null &&
|
|
278
|
-
typeof theme === 'object' &&
|
|
279
|
-
!('palette' in theme) &&
|
|
280
|
-
!('typography' in theme) &&
|
|
281
|
-
!('__isJSTheme' in theme);
|
|
282
|
-
|
|
283
|
-
if (isDesignTokens) {
|
|
284
|
-
// Use unified theme system for DesignTokens
|
|
285
|
-
const css = createTheme(theme as Partial<DesignTokens>);
|
|
286
|
-
injectCSS(css, 'atomix-theme');
|
|
287
|
-
} else {
|
|
288
|
-
// Use ThemeApplicator for Theme objects
|
|
289
|
-
themeApplicator?.applyTheme(theme as Theme);
|
|
290
|
-
}
|
|
291
|
-
}, [activeTheme, themeApplicator]);
|
|
292
|
-
|
|
293
|
-
// Set theme function (supports string, Theme, or DesignTokens)
|
|
294
|
-
const setTheme = useCallback(async (theme: string | Theme | DesignTokens | Partial<DesignTokens>, options?: ThemeLoadOptions): Promise<void> => {
|
|
295
|
-
const { removePrevious = true, fallbackOnError = true, customPath } = options || {};
|
|
296
|
-
|
|
297
|
-
setIsLoading(true);
|
|
298
|
-
setError(null);
|
|
299
|
-
|
|
300
|
-
try {
|
|
301
|
-
// Handle Theme or DesignTokens object directly
|
|
302
|
-
if (typeof theme !== 'string') {
|
|
303
|
-
// Check if it's DesignTokens
|
|
304
|
-
const isDesignTokens = theme !== null &&
|
|
305
|
-
typeof theme === 'object' &&
|
|
306
|
-
!('palette' in theme) &&
|
|
307
|
-
!('typography' in theme) &&
|
|
308
|
-
!('__isJSTheme' in theme);
|
|
309
|
-
|
|
310
|
-
if (isDesignTokens) {
|
|
311
|
-
// Handle DesignTokens using unified theme system
|
|
312
|
-
await applyJSTheme(theme as DesignTokens, removePrevious);
|
|
313
|
-
const themeName = 'design-tokens-theme';
|
|
314
|
-
previousThemeRef.current = currentTheme;
|
|
315
|
-
setCurrentTheme(themeName);
|
|
316
|
-
setActiveTheme(null); // DesignTokens don't have Theme object
|
|
317
|
-
|
|
318
|
-
// Emit change event
|
|
319
|
-
const event: ThemeChangeEvent = {
|
|
320
|
-
previousTheme: previousThemeRef.current,
|
|
321
|
-
currentTheme: themeName,
|
|
322
|
-
themeObject: null,
|
|
323
|
-
timestamp: Date.now(),
|
|
324
|
-
source: 'user',
|
|
325
|
-
};
|
|
326
|
-
handleThemeChange(themeName);
|
|
327
|
-
|
|
328
|
-
// Persist to storage
|
|
329
|
-
if (enablePersistence && storageAdapter.isAvailable()) {
|
|
330
|
-
storageAdapter.setItem(storageKey, themeName);
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
setIsLoading(false);
|
|
334
|
-
return;
|
|
335
|
-
} else if (isJSTheme(theme)) {
|
|
336
|
-
// Handle Theme object
|
|
337
|
-
await applyJSTheme(theme, removePrevious);
|
|
338
|
-
const themeName = theme.name || 'js-theme';
|
|
339
|
-
previousThemeRef.current = currentTheme;
|
|
340
|
-
setCurrentTheme(themeName);
|
|
341
|
-
setActiveTheme(theme);
|
|
342
|
-
|
|
343
|
-
// Emit change event
|
|
344
|
-
const event: ThemeChangeEvent = {
|
|
345
|
-
previousTheme: previousThemeRef.current,
|
|
346
|
-
currentTheme: themeName,
|
|
347
|
-
themeObject: theme,
|
|
348
|
-
timestamp: Date.now(),
|
|
349
|
-
source: 'user',
|
|
350
|
-
};
|
|
351
|
-
handleThemeChange(theme);
|
|
352
|
-
|
|
353
|
-
// Persist to storage
|
|
354
|
-
if (enablePersistence && storageAdapter.isAvailable()) {
|
|
355
|
-
storageAdapter.setItem(storageKey, themeName);
|
|
356
|
-
}
|
|
357
|
-
|
|
358
|
-
setIsLoading(false);
|
|
359
|
-
return;
|
|
360
|
-
} else {
|
|
361
|
-
const error = new Error('Invalid theme object provided');
|
|
362
|
-
handleError(error, 'js-theme');
|
|
363
|
-
setError(error);
|
|
364
|
-
setIsLoading(false);
|
|
365
|
-
throw error;
|
|
366
|
-
}
|
|
367
|
-
}
|
|
368
|
-
|
|
369
|
-
// Check if theme exists
|
|
370
|
-
if (!registry.has(theme)) {
|
|
371
|
-
const error = new Error(`Theme "${theme}" not found in registry`);
|
|
372
|
-
handleError(error, theme);
|
|
373
|
-
setError(error);
|
|
374
|
-
if (fallbackOnError && currentTheme) {
|
|
375
|
-
setIsLoading(false);
|
|
376
|
-
return;
|
|
377
|
-
}
|
|
378
|
-
setIsLoading(false);
|
|
379
|
-
throw error;
|
|
380
|
-
}
|
|
381
|
-
|
|
382
|
-
// Load theme CSS if needed
|
|
383
|
-
const themePath = customPath || buildThemePath(
|
|
177
|
+
if (themeMetadata) {
|
|
178
|
+
// Build CSS path using utility function
|
|
179
|
+
const cssPath = buildThemePath(
|
|
384
180
|
theme,
|
|
385
181
|
basePath,
|
|
386
182
|
useMinified,
|
|
387
|
-
cdnPath
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
}
|
|
402
|
-
|
|
403
|
-
// Apply theme attributes
|
|
404
|
-
applyThemeAttributes(dataAttribute, theme);
|
|
405
|
-
|
|
406
|
-
// Update state
|
|
407
|
-
previousThemeRef.current = currentTheme;
|
|
408
|
-
setCurrentTheme(theme);
|
|
409
|
-
setActiveTheme(null); // CSS themes don't have active theme object
|
|
410
|
-
|
|
411
|
-
// Emit change event
|
|
412
|
-
const event: ThemeChangeEvent = {
|
|
413
|
-
previousTheme: previousThemeRef.current,
|
|
414
|
-
currentTheme: theme,
|
|
415
|
-
timestamp: Date.now(),
|
|
416
|
-
source: 'user',
|
|
417
|
-
};
|
|
418
|
-
handleThemeChange(theme);
|
|
419
|
-
|
|
420
|
-
// Persist to storage
|
|
421
|
-
if (enablePersistence && storageAdapter.isAvailable()) {
|
|
422
|
-
storageAdapter.setItem(storageKey, theme);
|
|
183
|
+
cdnPath
|
|
184
|
+
);
|
|
185
|
+
|
|
186
|
+
// Remove any previously loaded theme CSS
|
|
187
|
+
removeCSS(`theme-${String(currentTheme)}`);
|
|
188
|
+
|
|
189
|
+
// Inject new theme CSS
|
|
190
|
+
await injectCSS(cssPath, `theme-${theme}`);
|
|
191
|
+
loadedThemesRef.current.add(theme);
|
|
192
|
+
|
|
193
|
+
setCurrentTheme(theme);
|
|
194
|
+
setActiveTheme(null);
|
|
195
|
+
handleThemeChange(theme);
|
|
196
|
+
resolve();
|
|
197
|
+
} else {
|
|
198
|
+
throw new Error(`Theme metadata not found for theme: ${theme}`);
|
|
423
199
|
}
|
|
424
|
-
|
|
425
|
-
setIsLoading(false);
|
|
426
|
-
} catch (err) {
|
|
200
|
+
} catch (err) {
|
|
427
201
|
const error = err instanceof Error ? err : new Error(String(err));
|
|
428
|
-
handleError(error, typeof theme === 'string' ? theme : 'js-theme');
|
|
429
202
|
setError(error);
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
203
|
+
handleError(error, String(theme));
|
|
204
|
+
reject(error);
|
|
205
|
+
}
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
themePromisesRef.current[theme] = themeLoadPromise;
|
|
209
|
+
await themeLoadPromise;
|
|
210
|
+
} else if (themeObj) {
|
|
211
|
+
// For JS themes, set them directly
|
|
212
|
+
setCurrentTheme(themeName);
|
|
213
|
+
setActiveTheme(themeObj);
|
|
214
|
+
handleThemeChange(themeObj);
|
|
215
|
+
} else {
|
|
216
|
+
// For string theme that isn't in our themes record, just set the name
|
|
217
|
+
setCurrentTheme(themeName);
|
|
218
|
+
setActiveTheme(null);
|
|
219
|
+
handleThemeChange(themeName);
|
|
220
|
+
}
|
|
221
|
+
} catch (err) {
|
|
222
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
223
|
+
setError(error);
|
|
224
|
+
handleError(error, String(theme));
|
|
225
|
+
} finally {
|
|
226
|
+
setIsLoading(false);
|
|
227
|
+
}
|
|
228
|
+
}, [themes, currentTheme, handleThemeChange, handleError, basePath, useMinified, cdnPath]);
|
|
229
|
+
|
|
230
|
+
// Check if theme is loaded
|
|
231
|
+
const isThemeLoaded = useCallback((themeName: string) => {
|
|
232
|
+
return loadedThemesRef.current.has(themeName);
|
|
233
|
+
}, []);
|
|
234
|
+
|
|
235
|
+
// Preload theme function
|
|
236
|
+
const preloadTheme = useCallback(async (themeName: string) => {
|
|
237
|
+
if (!themes[themeName] || isThemeLoaded(themeName)) {
|
|
238
|
+
return;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
setIsLoading(true);
|
|
242
|
+
try {
|
|
243
|
+
// Build CSS path using utility function
|
|
244
|
+
const cssPath = buildThemePath(
|
|
245
|
+
themeName,
|
|
435
246
|
basePath,
|
|
436
|
-
cdnPath,
|
|
437
247
|
useMinified,
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
if (defaultThemeValue) {
|
|
494
|
-
try {
|
|
495
|
-
// Check if it's DesignTokens from config
|
|
496
|
-
const isDesignTokens = defaultThemeValue !== null &&
|
|
497
|
-
typeof defaultThemeValue === 'object' &&
|
|
498
|
-
!('palette' in defaultThemeValue) &&
|
|
499
|
-
!('typography' in defaultThemeValue) &&
|
|
500
|
-
!('__isJSTheme' in defaultThemeValue) &&
|
|
501
|
-
typeof defaultThemeValue !== 'string';
|
|
502
|
-
|
|
503
|
-
if (isDesignTokens) {
|
|
504
|
-
// Apply config tokens directly
|
|
505
|
-
await applyJSTheme(defaultThemeValue as DesignTokens, false);
|
|
506
|
-
|
|
507
|
-
// Update state and emit events
|
|
508
|
-
setCurrentTheme('config-theme');
|
|
509
|
-
setActiveTheme(null);
|
|
510
|
-
|
|
511
|
-
// Emit change event
|
|
512
|
-
const event: ThemeChangeEvent = {
|
|
513
|
-
previousTheme: null,
|
|
514
|
-
currentTheme: 'config-theme',
|
|
515
|
-
themeObject: null,
|
|
516
|
-
timestamp: Date.now(),
|
|
517
|
-
source: 'system',
|
|
518
|
-
};
|
|
519
|
-
handleThemeChange('config-theme');
|
|
520
|
-
|
|
521
|
-
// Persist to storage
|
|
522
|
-
if (enablePersistence && storageAdapter.isAvailable()) {
|
|
523
|
-
storageAdapter.setItem(storageKey, 'config-theme');
|
|
524
|
-
}
|
|
525
|
-
} else {
|
|
526
|
-
// Handle string or Theme object
|
|
527
|
-
await setTheme(defaultThemeValue, { removePrevious: false, fallbackOnError: true });
|
|
528
|
-
}
|
|
529
|
-
} catch (err) {
|
|
530
|
-
const error = err instanceof Error ? err : new Error(String(err));
|
|
531
|
-
logger.error(`Failed to load theme from config`, error, {
|
|
532
|
-
theme: defaultThemeValue,
|
|
533
|
-
});
|
|
534
|
-
handleError(error, 'config-theme');
|
|
535
|
-
setError(error);
|
|
536
|
-
throw error;
|
|
537
|
-
}
|
|
538
|
-
}
|
|
539
|
-
};
|
|
540
|
-
|
|
541
|
-
initDefaultTheme();
|
|
542
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
543
|
-
}, []); // Only run once on mount - initialDefaultTheme is stable
|
|
544
|
-
|
|
545
|
-
// Preload themes
|
|
546
|
-
useEffect(() => {
|
|
547
|
-
if (isServer() || !preload || preload.length === 0) return;
|
|
548
|
-
|
|
549
|
-
const preloadThemes = async () => {
|
|
550
|
-
for (const themeName of preload) {
|
|
551
|
-
if (!checkThemeLoaded(themeName)) {
|
|
552
|
-
try {
|
|
553
|
-
await preloadTheme(themeName);
|
|
554
|
-
} catch (err) {
|
|
555
|
-
// Silently fail for preload
|
|
556
|
-
logger.warn(`Failed to preload theme "${themeName}"`, {
|
|
557
|
-
error: err instanceof Error ? err.message : String(err),
|
|
558
|
-
});
|
|
559
|
-
}
|
|
560
|
-
}
|
|
561
|
-
}
|
|
562
|
-
};
|
|
563
|
-
|
|
564
|
-
preloadThemes();
|
|
565
|
-
}, [preload, preloadTheme, logger]);
|
|
566
|
-
|
|
567
|
-
// Context value
|
|
568
|
-
const contextValue = useMemo(() => ({
|
|
569
|
-
theme: currentTheme,
|
|
570
|
-
activeTheme,
|
|
571
|
-
setTheme,
|
|
572
|
-
availableThemes,
|
|
573
|
-
isLoading,
|
|
574
|
-
error,
|
|
575
|
-
isThemeLoaded,
|
|
576
|
-
preloadTheme,
|
|
577
|
-
}), [
|
|
578
|
-
currentTheme,
|
|
579
|
-
activeTheme,
|
|
580
|
-
setTheme,
|
|
581
|
-
availableThemes,
|
|
582
|
-
isLoading,
|
|
583
|
-
error,
|
|
584
|
-
isThemeLoaded,
|
|
585
|
-
preloadTheme,
|
|
586
|
-
]);
|
|
587
|
-
|
|
588
|
-
return (
|
|
589
|
-
<ThemeContext.Provider value={contextValue}>
|
|
590
|
-
{children}
|
|
591
|
-
</ThemeContext.Provider>
|
|
592
|
-
);
|
|
593
|
-
};
|
|
594
|
-
|
|
595
|
-
export default ThemeProvider;
|
|
248
|
+
cdnPath
|
|
249
|
+
);
|
|
250
|
+
|
|
251
|
+
// Preload CSS by fetching it
|
|
252
|
+
await fetch(cssPath);
|
|
253
|
+
loadedThemesRef.current.add(themeName);
|
|
254
|
+
} catch (err) {
|
|
255
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
256
|
+
setError(error);
|
|
257
|
+
handleError(error, themeName);
|
|
258
|
+
} finally {
|
|
259
|
+
setIsLoading(false);
|
|
260
|
+
}
|
|
261
|
+
}, [themes, isThemeLoaded, handleError, basePath, useMinified, cdnPath]);
|
|
262
|
+
|
|
263
|
+
// Create a mock theme manager instance for the context
|
|
264
|
+
const themeManager = useMemo(() => {
|
|
265
|
+
// This would normally be a real ThemeManager instance
|
|
266
|
+
// For now, we'll create a mock implementation that satisfies the type
|
|
267
|
+
return {
|
|
268
|
+
// Mock implementation - in a real app this would be a full ThemeManager
|
|
269
|
+
} ;
|
|
270
|
+
}, []);
|
|
271
|
+
|
|
272
|
+
// Theme context value
|
|
273
|
+
const contextValue = useMemo(() => ({
|
|
274
|
+
theme: typeof currentTheme === 'string' ? currentTheme : 'js-theme',
|
|
275
|
+
activeTheme,
|
|
276
|
+
setTheme,
|
|
277
|
+
availableThemes: Object.entries(themes).map(([name, metadata]) => ({
|
|
278
|
+
...metadata
|
|
279
|
+
})),
|
|
280
|
+
isLoading,
|
|
281
|
+
error,
|
|
282
|
+
isThemeLoaded,
|
|
283
|
+
preloadTheme,
|
|
284
|
+
themeManager,
|
|
285
|
+
}), [
|
|
286
|
+
currentTheme,
|
|
287
|
+
activeTheme,
|
|
288
|
+
setTheme,
|
|
289
|
+
themes,
|
|
290
|
+
isLoading,
|
|
291
|
+
error,
|
|
292
|
+
isThemeLoaded,
|
|
293
|
+
preloadTheme,
|
|
294
|
+
themeManager
|
|
295
|
+
]);
|
|
296
|
+
|
|
297
|
+
return (
|
|
298
|
+
<ThemeContext.Provider value={contextValue}>
|
|
299
|
+
{children}
|
|
300
|
+
</ThemeContext.Provider>
|
|
301
|
+
);
|
|
302
|
+
};
|