@shohojdhara/atomix 0.3.7 → 0.3.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +19 -1
- package/dist/atomix.css +77 -0
- package/dist/atomix.css.map +1 -1
- package/dist/atomix.min.css +77 -0
- package/dist/atomix.min.css.map +1 -1
- package/dist/charts.js.map +1 -1
- package/dist/core.js.map +1 -1
- package/dist/forms.js.map +1 -1
- package/dist/heavy.js.map +1 -1
- package/dist/index.d.ts +578 -515
- package/dist/index.esm.js +3150 -2632
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +10485 -9973
- 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 +237 -420
- package/dist/theme.js +1616 -1701
- package/dist/theme.js.map +1 -1
- package/package.json +1 -1
- package/src/components/DataTable/DataTable.stories.tsx +238 -0
- package/src/components/DataTable/DataTable.test.tsx +450 -0
- package/src/components/DataTable/DataTable.tsx +384 -61
- package/src/components/DatePicker/DatePicker.tsx +29 -38
- package/src/components/Upload/Upload.tsx +539 -40
- package/src/lib/composables/useDataTable.ts +355 -15
- package/src/lib/composables/useDatePicker.ts +19 -0
- package/src/lib/constants/components.ts +10 -0
- package/src/lib/theme/adapters/cssVariableMapper.ts +29 -14
- package/src/lib/theme/adapters/index.ts +1 -4
- package/src/lib/theme/config/configLoader.ts +53 -35
- package/src/lib/theme/core/composeTheme.ts +22 -30
- package/src/lib/theme/core/createTheme.ts +49 -26
- package/src/lib/theme/core/index.ts +0 -1
- package/src/lib/theme/generators/generateCSSNested.ts +4 -3
- package/src/lib/theme/generators/generateCSSVariables.ts +24 -16
- package/src/lib/theme/index.ts +10 -17
- package/src/lib/theme/runtime/ThemeApplicator.ts +6 -109
- package/src/lib/theme/runtime/ThemeErrorBoundary.tsx +3 -3
- package/src/lib/theme/runtime/ThemeProvider.tsx +205 -64
- package/src/lib/theme/runtime/useTheme.ts +1 -1
- package/src/lib/theme/runtime/useThemeTokens.ts +7 -16
- package/src/lib/theme/test/testTheme.ts +2 -1
- package/src/lib/theme/types.ts +14 -14
- package/src/lib/theme/utils/componentTheming.ts +35 -27
- package/src/lib/theme/utils/domUtils.ts +57 -15
- package/src/lib/theme/utils/injectCSS.ts +0 -1
- package/src/lib/theme/utils/themeHelpers.ts +1 -39
- package/src/lib/theme/utils/themeUtils.ts +1 -170
- package/src/lib/types/components.ts +145 -0
- package/src/lib/utils/dataTableExport.ts +143 -0
- package/src/styles/06-components/_components.data-table.scss +95 -0
- package/src/lib/hooks/useThemeTokens.ts +0 -105
|
@@ -148,7 +148,7 @@ export class ThemeErrorBoundary extends Component<
|
|
|
148
148
|
};
|
|
149
149
|
}
|
|
150
150
|
|
|
151
|
-
componentDidCatch(error: Error, errorInfo: ErrorInfo): void {
|
|
151
|
+
override componentDidCatch(error: Error, errorInfo: ErrorInfo): void {
|
|
152
152
|
// Log error
|
|
153
153
|
const themeError = error instanceof ThemeError
|
|
154
154
|
? error
|
|
@@ -187,7 +187,7 @@ export class ThemeErrorBoundary extends Component<
|
|
|
187
187
|
}
|
|
188
188
|
}
|
|
189
189
|
|
|
190
|
-
componentDidUpdate(prevProps: ThemeErrorBoundaryProps): void {
|
|
190
|
+
override componentDidUpdate(prevProps: ThemeErrorBoundaryProps): void {
|
|
191
191
|
// Reset error if resetOnPropsChange is true and children changed
|
|
192
192
|
if (
|
|
193
193
|
this.props.resetOnPropsChange &&
|
|
@@ -202,7 +202,7 @@ export class ThemeErrorBoundary extends Component<
|
|
|
202
202
|
}
|
|
203
203
|
}
|
|
204
204
|
|
|
205
|
-
render(): ReactNode {
|
|
205
|
+
override render(): ReactNode {
|
|
206
206
|
if (this.state.hasError && this.state.error && this.state.errorInfo) {
|
|
207
207
|
// Use custom fallback if provided
|
|
208
208
|
if (this.props.fallback) {
|
|
@@ -7,21 +7,22 @@
|
|
|
7
7
|
|
|
8
8
|
import React, { useState, useEffect, useCallback, useMemo, useRef } from 'react';
|
|
9
9
|
import { ThemeContext } from './ThemeContext';
|
|
10
|
-
import type { ThemeProviderProps,
|
|
11
|
-
import {
|
|
10
|
+
import type { ThemeProviderProps, ThemeLoadOptions } from '../types';
|
|
11
|
+
import type { DesignTokens } from '../tokens/tokens';
|
|
12
|
+
import { createTokens } from '../tokens/tokens';
|
|
12
13
|
import { getLogger } from '../errors';
|
|
13
14
|
import { createTheme } from '../core';
|
|
14
15
|
import { injectCSS, removeCSS } from '../utils/injectCSS';
|
|
15
16
|
import {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
17
|
+
isServer,
|
|
18
|
+
createLocalStorageAdapter,
|
|
19
|
+
applyThemeAttributes,
|
|
20
|
+
buildThemePath,
|
|
20
21
|
} from '../utils/domUtils';
|
|
21
22
|
import {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
23
|
+
DEFAULT_STORAGE_KEY,
|
|
24
|
+
DEFAULT_DATA_ATTRIBUTE,
|
|
25
|
+
DEFAULT_BASE_PATH,
|
|
25
26
|
} from '../constants/constants';
|
|
26
27
|
|
|
27
28
|
/**
|
|
@@ -30,7 +31,7 @@ import {
|
|
|
30
31
|
* React context provider for theme management with separated concerns.
|
|
31
32
|
* Simplified version focusing on core functionality:
|
|
32
33
|
* - String-based themes (CSS files)
|
|
33
|
-
* -
|
|
34
|
+
* - DesignTokens (dynamic themes)
|
|
34
35
|
* - Persistence via localStorage
|
|
35
36
|
*
|
|
36
37
|
* Falls back to 'default' theme if no configuration is found.
|
|
@@ -59,7 +60,7 @@ export const ThemeProvider: React.FC<ThemeProviderProps> = ({
|
|
|
59
60
|
}, [onThemeChange, onError]);
|
|
60
61
|
|
|
61
62
|
// Create stable wrapper functions that read from ref
|
|
62
|
-
const handleThemeChange = useCallback((theme: string |
|
|
63
|
+
const handleThemeChange = useCallback((theme: string | DesignTokens) => {
|
|
63
64
|
onThemeChangeRef.current?.(theme);
|
|
64
65
|
}, []);
|
|
65
66
|
|
|
@@ -98,7 +99,7 @@ export const ThemeProvider: React.FC<ThemeProviderProps> = ({
|
|
|
98
99
|
return 'config-theme';
|
|
99
100
|
}
|
|
100
101
|
} catch (error) {
|
|
101
|
-
|
|
102
|
+
// Failed to load theme from config, using default
|
|
102
103
|
}
|
|
103
104
|
}
|
|
104
105
|
|
|
@@ -106,15 +107,39 @@ export const ThemeProvider: React.FC<ThemeProviderProps> = ({
|
|
|
106
107
|
return 'default';
|
|
107
108
|
}, [defaultTheme, enablePersistence, storageKey]);
|
|
108
109
|
|
|
109
|
-
//
|
|
110
|
-
const [currentTheme, setCurrentTheme] = useState<string
|
|
111
|
-
|
|
110
|
+
// Initialize state - handle both string and DesignTokens for defaultTheme
|
|
111
|
+
const [currentTheme, setCurrentTheme] = useState<string>(() => {
|
|
112
|
+
if (typeof initialDefaultTheme === 'string') {
|
|
113
|
+
return initialDefaultTheme;
|
|
114
|
+
}
|
|
115
|
+
// If it's DesignTokens, we'll handle it in useEffect
|
|
116
|
+
return 'tokens-theme';
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
const [activeTokens, setActiveTokens] = useState<DesignTokens | null>(() => {
|
|
120
|
+
// If defaultTheme is DesignTokens, store them
|
|
121
|
+
if (defaultTheme && typeof defaultTheme !== 'string') {
|
|
122
|
+
return createTokens(defaultTheme);
|
|
123
|
+
}
|
|
124
|
+
return null;
|
|
125
|
+
});
|
|
112
126
|
const [isLoading, setIsLoading] = useState(false);
|
|
113
127
|
const [error, setError] = useState<Error | null>(null);
|
|
114
128
|
|
|
115
129
|
// Track loaded themes
|
|
116
130
|
const loadedThemesRef = useRef<Set<string>>(new Set());
|
|
117
131
|
const themePromisesRef = useRef<Record<string, Promise<void>>>({});
|
|
132
|
+
// AbortController for cancelling in-flight theme loads
|
|
133
|
+
const abortControllerRef = useRef<AbortController | null>(null);
|
|
134
|
+
|
|
135
|
+
// Handle initial DesignTokens defaultTheme
|
|
136
|
+
useEffect(() => {
|
|
137
|
+
if (defaultTheme && typeof defaultTheme !== 'string' && activeTokens && !isServer()) {
|
|
138
|
+
// If defaultTheme is DesignTokens, inject CSS on mount
|
|
139
|
+
const css = createTheme(defaultTheme);
|
|
140
|
+
injectCSS(css, 'theme-tokens-theme');
|
|
141
|
+
}
|
|
142
|
+
}, [defaultTheme, activeTokens]); // Run when defaultTheme or activeTokens change
|
|
118
143
|
|
|
119
144
|
// Apply initial theme attributes to document element
|
|
120
145
|
useEffect(() => {
|
|
@@ -128,52 +153,121 @@ export const ThemeProvider: React.FC<ThemeProviderProps> = ({
|
|
|
128
153
|
if (enablePersistence && storageAdapter.isAvailable()) {
|
|
129
154
|
storageAdapter.setItem(storageKey, String(currentTheme));
|
|
130
155
|
}
|
|
131
|
-
}, [currentTheme, storageKey, enablePersistence]);
|
|
156
|
+
}, [currentTheme, storageKey, enablePersistence, storageAdapter]);
|
|
157
|
+
|
|
158
|
+
// Cleanup: Remove completed promises and abort controllers on unmount
|
|
159
|
+
useEffect(() => {
|
|
160
|
+
return () => {
|
|
161
|
+
// Cancel any in-flight theme loads
|
|
162
|
+
if (abortControllerRef.current) {
|
|
163
|
+
abortControllerRef.current.abort();
|
|
164
|
+
abortControllerRef.current = null;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// Clean up completed promises (keep only pending ones)
|
|
168
|
+
// In practice, completed promises are automatically garbage collected,
|
|
169
|
+
// but we can clear the ref to be explicit
|
|
170
|
+
const pendingPromises: Record<string, Promise<void>> = {};
|
|
171
|
+
Object.entries(themePromisesRef.current).forEach(([key, promise]) => {
|
|
172
|
+
// Check if promise is still pending (this is a best-effort cleanup)
|
|
173
|
+
// In practice, we rely on garbage collection for completed promises
|
|
174
|
+
pendingPromises[key] = promise;
|
|
175
|
+
});
|
|
176
|
+
// Clear all on unmount
|
|
177
|
+
themePromisesRef.current = {};
|
|
178
|
+
};
|
|
179
|
+
}, []);
|
|
132
180
|
|
|
133
181
|
// Function to set theme with proper type handling
|
|
134
182
|
const setTheme = useCallback(async (
|
|
135
|
-
theme: string |
|
|
183
|
+
theme: string | DesignTokens | Partial<DesignTokens>,
|
|
136
184
|
options?: ThemeLoadOptions
|
|
137
185
|
) => {
|
|
186
|
+
// Cancel previous theme load if in progress
|
|
187
|
+
if (abortControllerRef.current) {
|
|
188
|
+
abortControllerRef.current.abort();
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// Create new AbortController for this theme load
|
|
192
|
+
const abortController = new AbortController();
|
|
193
|
+
abortControllerRef.current = abortController;
|
|
194
|
+
|
|
138
195
|
setIsLoading(true);
|
|
139
196
|
setError(null);
|
|
140
|
-
|
|
197
|
+
|
|
141
198
|
try {
|
|
142
199
|
let themeName: string;
|
|
143
|
-
|
|
144
|
-
|
|
200
|
+
|
|
145
201
|
if (typeof theme === 'string') {
|
|
146
202
|
themeName = theme;
|
|
147
203
|
} else {
|
|
148
|
-
//
|
|
149
|
-
if (
|
|
150
|
-
|
|
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
|
|
204
|
+
// Check if aborted before processing
|
|
205
|
+
if (abortController.signal.aborted) {
|
|
206
|
+
return;
|
|
158
207
|
}
|
|
208
|
+
|
|
209
|
+
// For DesignTokens, create CSS and inject it
|
|
210
|
+
const { createTheme } = await import('../core');
|
|
211
|
+
const css = createTheme(theme);
|
|
212
|
+
const themeId = 'tokens-theme';
|
|
213
|
+
|
|
214
|
+
// Check if aborted after async operation
|
|
215
|
+
if (abortController.signal.aborted) {
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// Remove any previously loaded theme CSS
|
|
220
|
+
removeCSS(`theme-${currentTheme}`);
|
|
221
|
+
|
|
222
|
+
// Inject new theme CSS
|
|
223
|
+
injectCSS(css, `theme-${themeId}`);
|
|
224
|
+
|
|
225
|
+
// Store tokens for reference
|
|
226
|
+
const fullTokens = createTokens(theme);
|
|
227
|
+
|
|
228
|
+
// Check if aborted before state update
|
|
229
|
+
if (abortController.signal.aborted) {
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
setActiveTokens(fullTokens);
|
|
234
|
+
setCurrentTheme(themeId);
|
|
235
|
+
handleThemeChange(fullTokens);
|
|
236
|
+
setIsLoading(false);
|
|
237
|
+
return;
|
|
159
238
|
}
|
|
160
239
|
|
|
161
240
|
// If it's a string theme name, load the associated CSS
|
|
162
241
|
if (typeof theme === 'string' && themes[theme]) {
|
|
163
242
|
// Check if theme is already loading
|
|
164
243
|
if (themePromisesRef.current[theme]) {
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
244
|
+
try {
|
|
245
|
+
await themePromisesRef.current[theme];
|
|
246
|
+
// Check if aborted
|
|
247
|
+
if (abortController.signal.aborted) {
|
|
248
|
+
return;
|
|
249
|
+
}
|
|
250
|
+
setCurrentTheme(theme);
|
|
251
|
+
setActiveTokens(null);
|
|
252
|
+
handleThemeChange(theme);
|
|
253
|
+
setIsLoading(false);
|
|
254
|
+
return;
|
|
255
|
+
} catch {
|
|
256
|
+
// If previous load failed, continue with new load
|
|
257
|
+
}
|
|
170
258
|
}
|
|
171
259
|
|
|
172
260
|
// Load CSS theme
|
|
173
261
|
const themeLoadPromise = new Promise<void>(async (resolve, reject) => {
|
|
174
262
|
try {
|
|
263
|
+
// Check if aborted
|
|
264
|
+
if (abortController.signal.aborted) {
|
|
265
|
+
resolve();
|
|
266
|
+
return;
|
|
267
|
+
}
|
|
268
|
+
|
|
175
269
|
const themeMetadata = themes[theme];
|
|
176
|
-
|
|
270
|
+
|
|
177
271
|
if (themeMetadata) {
|
|
178
272
|
// Build CSS path using utility function
|
|
179
273
|
const cssPath = buildThemePath(
|
|
@@ -182,22 +276,42 @@ export const ThemeProvider: React.FC<ThemeProviderProps> = ({
|
|
|
182
276
|
useMinified,
|
|
183
277
|
cdnPath
|
|
184
278
|
);
|
|
185
|
-
|
|
279
|
+
|
|
280
|
+
// Check if aborted
|
|
281
|
+
if (abortController.signal.aborted) {
|
|
282
|
+
resolve();
|
|
283
|
+
return;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// Load CSS file (using loadThemeCSS from domUtils)
|
|
287
|
+
const { loadThemeCSS } = await import('../utils/domUtils');
|
|
288
|
+
await loadThemeCSS(cssPath, `theme-${theme}`);
|
|
289
|
+
|
|
290
|
+
// Check if aborted after async operation
|
|
291
|
+
if (abortController.signal.aborted) {
|
|
292
|
+
resolve();
|
|
293
|
+
return;
|
|
294
|
+
}
|
|
295
|
+
|
|
186
296
|
// Remove any previously loaded theme CSS
|
|
187
297
|
removeCSS(`theme-${String(currentTheme)}`);
|
|
188
|
-
|
|
189
|
-
// Inject new theme CSS
|
|
190
|
-
await injectCSS(cssPath, `theme-${theme}`);
|
|
298
|
+
|
|
191
299
|
loadedThemesRef.current.add(theme);
|
|
192
|
-
|
|
300
|
+
|
|
193
301
|
setCurrentTheme(theme);
|
|
194
|
-
|
|
302
|
+
setActiveTokens(null);
|
|
195
303
|
handleThemeChange(theme);
|
|
196
304
|
resolve();
|
|
197
305
|
} else {
|
|
198
306
|
throw new Error(`Theme metadata not found for theme: ${theme}`);
|
|
199
307
|
}
|
|
200
308
|
} catch (err) {
|
|
309
|
+
// Don't reject if aborted
|
|
310
|
+
if (abortController.signal.aborted) {
|
|
311
|
+
resolve();
|
|
312
|
+
return;
|
|
313
|
+
}
|
|
314
|
+
|
|
201
315
|
const error = err instanceof Error ? err : new Error(String(err));
|
|
202
316
|
setError(error);
|
|
203
317
|
handleError(error, String(theme));
|
|
@@ -206,24 +320,44 @@ export const ThemeProvider: React.FC<ThemeProviderProps> = ({
|
|
|
206
320
|
});
|
|
207
321
|
|
|
208
322
|
themePromisesRef.current[theme] = themeLoadPromise;
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
323
|
+
|
|
324
|
+
try {
|
|
325
|
+
await themeLoadPromise;
|
|
326
|
+
} catch {
|
|
327
|
+
// Error already handled in promise
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
// Clean up completed promise after a delay to prevent memory leak
|
|
331
|
+
setTimeout(() => {
|
|
332
|
+
if (themePromisesRef.current[theme] === themeLoadPromise) {
|
|
333
|
+
delete themePromisesRef.current[theme];
|
|
334
|
+
}
|
|
335
|
+
}, 1000);
|
|
215
336
|
} else {
|
|
337
|
+
// Check if aborted
|
|
338
|
+
if (abortController.signal.aborted) {
|
|
339
|
+
return;
|
|
340
|
+
}
|
|
341
|
+
|
|
216
342
|
// For string theme that isn't in our themes record, just set the name
|
|
217
343
|
setCurrentTheme(themeName);
|
|
218
|
-
|
|
344
|
+
setActiveTokens(null);
|
|
219
345
|
handleThemeChange(themeName);
|
|
220
346
|
}
|
|
221
347
|
} catch (err) {
|
|
348
|
+
// Don't set error if aborted
|
|
349
|
+
if (abortController.signal.aborted) {
|
|
350
|
+
return;
|
|
351
|
+
}
|
|
352
|
+
|
|
222
353
|
const error = err instanceof Error ? err : new Error(String(err));
|
|
223
354
|
setError(error);
|
|
224
355
|
handleError(error, String(theme));
|
|
225
356
|
} finally {
|
|
226
|
-
|
|
357
|
+
// Only update loading state if not aborted
|
|
358
|
+
if (!abortController.signal.aborted) {
|
|
359
|
+
setIsLoading(false);
|
|
360
|
+
}
|
|
227
361
|
}
|
|
228
362
|
}, [themes, currentTheme, handleThemeChange, handleError, basePath, useMinified, cdnPath]);
|
|
229
363
|
|
|
@@ -247,7 +381,7 @@ export const ThemeProvider: React.FC<ThemeProviderProps> = ({
|
|
|
247
381
|
useMinified,
|
|
248
382
|
cdnPath
|
|
249
383
|
);
|
|
250
|
-
|
|
384
|
+
|
|
251
385
|
// Preload CSS by fetching it
|
|
252
386
|
await fetch(cssPath);
|
|
253
387
|
loadedThemesRef.current.add(themeName);
|
|
@@ -266,30 +400,37 @@ export const ThemeProvider: React.FC<ThemeProviderProps> = ({
|
|
|
266
400
|
// For now, we'll create a mock implementation that satisfies the type
|
|
267
401
|
return {
|
|
268
402
|
// Mock implementation - in a real app this would be a full ThemeManager
|
|
269
|
-
}
|
|
403
|
+
};
|
|
270
404
|
}, []);
|
|
271
405
|
|
|
406
|
+
// Memoize available themes to prevent unnecessary recalculations
|
|
407
|
+
const availableThemes = useMemo(() =>
|
|
408
|
+
Object.entries(themes).map(([name, metadata]) => ({
|
|
409
|
+
...metadata,
|
|
410
|
+
name: name, // Ensure name is set from the key
|
|
411
|
+
})),
|
|
412
|
+
[themes]
|
|
413
|
+
);
|
|
414
|
+
|
|
272
415
|
// Theme context value
|
|
273
416
|
const contextValue = useMemo(() => ({
|
|
274
|
-
theme:
|
|
275
|
-
|
|
417
|
+
theme: currentTheme,
|
|
418
|
+
activeTokens,
|
|
276
419
|
setTheme,
|
|
277
|
-
availableThemes
|
|
278
|
-
...metadata
|
|
279
|
-
})),
|
|
420
|
+
availableThemes,
|
|
280
421
|
isLoading,
|
|
281
422
|
error,
|
|
282
423
|
isThemeLoaded,
|
|
283
424
|
preloadTheme,
|
|
284
425
|
themeManager,
|
|
285
426
|
}), [
|
|
286
|
-
currentTheme,
|
|
287
|
-
|
|
288
|
-
setTheme,
|
|
289
|
-
|
|
290
|
-
isLoading,
|
|
291
|
-
error,
|
|
292
|
-
isThemeLoaded,
|
|
427
|
+
currentTheme,
|
|
428
|
+
activeTokens,
|
|
429
|
+
setTheme,
|
|
430
|
+
availableThemes, // Use memoized value
|
|
431
|
+
isLoading,
|
|
432
|
+
error,
|
|
433
|
+
isThemeLoaded,
|
|
293
434
|
preloadTheme,
|
|
294
435
|
themeManager
|
|
295
436
|
]);
|
|
@@ -38,7 +38,7 @@ export function useTheme(): UseThemeReturn {
|
|
|
38
38
|
|
|
39
39
|
return {
|
|
40
40
|
theme: context.theme,
|
|
41
|
-
|
|
41
|
+
activeTokens: context.activeTokens,
|
|
42
42
|
setTheme: context.setTheme,
|
|
43
43
|
availableThemes: context.availableThemes,
|
|
44
44
|
isLoading: context.isLoading,
|
|
@@ -1,18 +1,17 @@
|
|
|
1
1
|
import { useCallback } from 'react';
|
|
2
2
|
import { useTheme } from './useTheme';
|
|
3
|
-
import type {
|
|
3
|
+
import type { DesignTokens } from '../tokens/tokens';
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* Standardized hook for accessing theme tokens in components
|
|
7
7
|
*
|
|
8
|
-
* Provides consistent access to theme values
|
|
9
|
-
*
|
|
8
|
+
* Provides consistent access to theme values using CSS custom properties
|
|
9
|
+
* and DesignTokens.
|
|
10
10
|
*/
|
|
11
11
|
type ThemeTokens = {
|
|
12
12
|
theme: string;
|
|
13
|
-
|
|
13
|
+
activeTokens: DesignTokens | null;
|
|
14
14
|
getToken: (tokenName: string, fallback?: string) => string;
|
|
15
|
-
getThemeValue: (path: string, fallback?: any) => any;
|
|
16
15
|
colors: {
|
|
17
16
|
primary: string;
|
|
18
17
|
secondary: string;
|
|
@@ -35,7 +34,7 @@ type ThemeTokens = {
|
|
|
35
34
|
};
|
|
36
35
|
|
|
37
36
|
export function useThemeTokens(): ThemeTokens {
|
|
38
|
-
const { theme,
|
|
37
|
+
const { theme, activeTokens } = useTheme();
|
|
39
38
|
|
|
40
39
|
// Helper function to get CSS variable value
|
|
41
40
|
const getToken = useCallback((tokenName: string, fallback?: string) => {
|
|
@@ -46,20 +45,12 @@ export function useThemeTokens(): ThemeTokens {
|
|
|
46
45
|
return computedStyle.getPropertyValue(cssVarName).trim() || fallback || '';
|
|
47
46
|
}, []);
|
|
48
47
|
|
|
49
|
-
// Helper function to get theme object value
|
|
50
|
-
const getThemeValue = useCallback((path: string, fallback?: any) => {
|
|
51
|
-
if (!activeTheme) return fallback;
|
|
52
|
-
|
|
53
|
-
// Navigate through nested theme object using dot notation
|
|
54
|
-
return path.split('.').reduce((obj, key) => obj?.[key], activeTheme) || fallback;
|
|
55
|
-
}, [activeTheme]);
|
|
56
|
-
|
|
57
48
|
// Return unified API for accessing theme values
|
|
49
|
+
// Note: For SSR or direct token access, use activeTokens directly
|
|
58
50
|
return <ThemeTokens>{
|
|
59
51
|
theme,
|
|
60
|
-
|
|
52
|
+
activeTokens,
|
|
61
53
|
getToken,
|
|
62
|
-
getThemeValue,
|
|
63
54
|
// Commonly used tokens with fallbacks
|
|
64
55
|
colors: {
|
|
65
56
|
primary: getToken('primary', '#3b82f6'),
|
|
@@ -270,7 +270,8 @@ export function createTestThemeObject(): Theme {
|
|
|
270
270
|
*/
|
|
271
271
|
export function generateTestThemeCSSFromObject(): string {
|
|
272
272
|
const theme = createTestThemeObject();
|
|
273
|
-
|
|
273
|
+
const tokens = themeToDesignTokens(theme);
|
|
274
|
+
return createTheme(tokens);
|
|
274
275
|
}
|
|
275
276
|
|
|
276
277
|
// ============================================================================
|
package/src/lib/theme/types.ts
CHANGED
|
@@ -50,8 +50,8 @@ export interface ThemeChangeEvent {
|
|
|
50
50
|
previousTheme: string | null;
|
|
51
51
|
/** New theme name */
|
|
52
52
|
currentTheme: string;
|
|
53
|
-
/**
|
|
54
|
-
|
|
53
|
+
/** DesignTokens if using tokens-based theme */
|
|
54
|
+
tokens?: import('./tokens').DesignTokens | null;
|
|
55
55
|
/** Timestamp of the change */
|
|
56
56
|
timestamp: number;
|
|
57
57
|
/** Whether the change was from user action or system */
|
|
@@ -126,7 +126,7 @@ export interface UseThemeOptions {
|
|
|
126
126
|
/** Custom storage key */
|
|
127
127
|
storageKey?: string;
|
|
128
128
|
/** Callback when theme changes */
|
|
129
|
-
onChange?: (theme: string |
|
|
129
|
+
onChange?: (theme: string | import('./tokens').DesignTokens) => void;
|
|
130
130
|
}
|
|
131
131
|
|
|
132
132
|
/**
|
|
@@ -135,10 +135,10 @@ export interface UseThemeOptions {
|
|
|
135
135
|
export interface UseThemeReturn {
|
|
136
136
|
/** Current theme name */
|
|
137
137
|
theme: string;
|
|
138
|
-
/** Current active
|
|
139
|
-
|
|
140
|
-
/** Function to change theme (supports string
|
|
141
|
-
setTheme: (theme: string |
|
|
138
|
+
/** Current active DesignTokens (if using tokens-based theme) */
|
|
139
|
+
activeTokens: import('./tokens').DesignTokens | null;
|
|
140
|
+
/** Function to change theme (supports string or DesignTokens) */
|
|
141
|
+
setTheme: (theme: string | import('./tokens').DesignTokens | Partial<import('./tokens').DesignTokens>, options?: ThemeLoadOptions) => Promise<void>;
|
|
142
142
|
/** Available themes */
|
|
143
143
|
availableThemes: ThemeMetadata[];
|
|
144
144
|
/** Whether a theme is currently loading */
|
|
@@ -213,8 +213,8 @@ export interface ThemeComponentOverrides {
|
|
|
213
213
|
export interface ThemeProviderProps {
|
|
214
214
|
/** Child components */
|
|
215
215
|
children: React.ReactNode;
|
|
216
|
-
/** Default theme */
|
|
217
|
-
defaultTheme?: string |
|
|
216
|
+
/** Default theme (string name or DesignTokens) */
|
|
217
|
+
defaultTheme?: string | import('./tokens').DesignTokens | Partial<import('./tokens').DesignTokens>;
|
|
218
218
|
/** Available themes */
|
|
219
219
|
themes?: Record<string, ThemeMetadata>;
|
|
220
220
|
/** Base path for theme CSS */
|
|
@@ -234,7 +234,7 @@ export interface ThemeProviderProps {
|
|
|
234
234
|
/** Use minified CSS */
|
|
235
235
|
useMinified?: boolean;
|
|
236
236
|
/** Callback when theme changes */
|
|
237
|
-
onThemeChange?: (theme: string |
|
|
237
|
+
onThemeChange?: (theme: string | import('./tokens').DesignTokens) => void;
|
|
238
238
|
/** Callback on error */
|
|
239
239
|
onError?: (error: Error, themeName: string) => void;
|
|
240
240
|
}
|
|
@@ -245,10 +245,10 @@ export interface ThemeProviderProps {
|
|
|
245
245
|
export interface ThemeContextValue {
|
|
246
246
|
/** Current theme name */
|
|
247
247
|
theme: string;
|
|
248
|
-
/** Current active
|
|
249
|
-
|
|
250
|
-
/** Set theme function (supports string
|
|
251
|
-
setTheme: (theme: string |
|
|
248
|
+
/** Current active DesignTokens (if using tokens-based theme) */
|
|
249
|
+
activeTokens: import('./tokens').DesignTokens | null;
|
|
250
|
+
/** Set theme function (supports string or DesignTokens) */
|
|
251
|
+
setTheme: (theme: string | import('./tokens').DesignTokens | Partial<import('./tokens').DesignTokens>, options?: ThemeLoadOptions) => Promise<void>;
|
|
252
252
|
/** Available themes */
|
|
253
253
|
availableThemes: ThemeMetadata[];
|
|
254
254
|
/** Loading state */
|