@shohojdhara/atomix 0.3.7 → 0.3.8
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/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.d.ts +2 -2
- 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 +3157 -2626
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +10496 -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 +1629 -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 +186 -44
- 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,8 +7,8 @@
|
|
|
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
12
|
import { getLogger } from '../errors';
|
|
13
13
|
import { createTheme } from '../core';
|
|
14
14
|
import { injectCSS, removeCSS } from '../utils/injectCSS';
|
|
@@ -30,7 +30,7 @@ import {
|
|
|
30
30
|
* React context provider for theme management with separated concerns.
|
|
31
31
|
* Simplified version focusing on core functionality:
|
|
32
32
|
* - String-based themes (CSS files)
|
|
33
|
-
* -
|
|
33
|
+
* - DesignTokens (dynamic themes)
|
|
34
34
|
* - Persistence via localStorage
|
|
35
35
|
*
|
|
36
36
|
* Falls back to 'default' theme if no configuration is found.
|
|
@@ -59,7 +59,7 @@ export const ThemeProvider: React.FC<ThemeProviderProps> = ({
|
|
|
59
59
|
}, [onThemeChange, onError]);
|
|
60
60
|
|
|
61
61
|
// Create stable wrapper functions that read from ref
|
|
62
|
-
const handleThemeChange = useCallback((theme: string |
|
|
62
|
+
const handleThemeChange = useCallback((theme: string | DesignTokens) => {
|
|
63
63
|
onThemeChangeRef.current?.(theme);
|
|
64
64
|
}, []);
|
|
65
65
|
|
|
@@ -98,7 +98,7 @@ export const ThemeProvider: React.FC<ThemeProviderProps> = ({
|
|
|
98
98
|
return 'config-theme';
|
|
99
99
|
}
|
|
100
100
|
} catch (error) {
|
|
101
|
-
|
|
101
|
+
// Failed to load theme from config, using default
|
|
102
102
|
}
|
|
103
103
|
}
|
|
104
104
|
|
|
@@ -106,15 +106,40 @@ export const ThemeProvider: React.FC<ThemeProviderProps> = ({
|
|
|
106
106
|
return 'default';
|
|
107
107
|
}, [defaultTheme, enablePersistence, storageKey]);
|
|
108
108
|
|
|
109
|
-
//
|
|
110
|
-
const [currentTheme, setCurrentTheme] = useState<string
|
|
111
|
-
|
|
109
|
+
// Initialize state - handle both string and DesignTokens for defaultTheme
|
|
110
|
+
const [currentTheme, setCurrentTheme] = useState<string>(() => {
|
|
111
|
+
if (typeof initialDefaultTheme === 'string') {
|
|
112
|
+
return initialDefaultTheme;
|
|
113
|
+
}
|
|
114
|
+
// If it's DesignTokens, we'll handle it in useEffect
|
|
115
|
+
return 'tokens-theme';
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
const [activeTokens, setActiveTokens] = useState<DesignTokens | null>(() => {
|
|
119
|
+
// If defaultTheme is DesignTokens, store them
|
|
120
|
+
if (defaultTheme && typeof defaultTheme !== 'string') {
|
|
121
|
+
const { createTokens } = require('../tokens/tokens');
|
|
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,50 +153,120 @@ 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
|
-
let themeObj: Theme | null = null;
|
|
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 { createTokens } = await import('../tokens/tokens');
|
|
227
|
+
const fullTokens = createTokens(theme);
|
|
228
|
+
|
|
229
|
+
// Check if aborted before state update
|
|
230
|
+
if (abortController.signal.aborted) {
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
setActiveTokens(fullTokens);
|
|
235
|
+
setCurrentTheme(themeId);
|
|
236
|
+
handleThemeChange(fullTokens);
|
|
237
|
+
setIsLoading(false);
|
|
238
|
+
return;
|
|
159
239
|
}
|
|
160
240
|
|
|
161
241
|
// If it's a string theme name, load the associated CSS
|
|
162
242
|
if (typeof theme === 'string' && themes[theme]) {
|
|
163
243
|
// Check if theme is already loading
|
|
164
244
|
if (themePromisesRef.current[theme]) {
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
245
|
+
try {
|
|
246
|
+
await themePromisesRef.current[theme];
|
|
247
|
+
// Check if aborted
|
|
248
|
+
if (abortController.signal.aborted) {
|
|
249
|
+
return;
|
|
250
|
+
}
|
|
251
|
+
setCurrentTheme(theme);
|
|
252
|
+
setActiveTokens(null);
|
|
253
|
+
handleThemeChange(theme);
|
|
254
|
+
setIsLoading(false);
|
|
255
|
+
return;
|
|
256
|
+
} catch {
|
|
257
|
+
// If previous load failed, continue with new load
|
|
258
|
+
}
|
|
170
259
|
}
|
|
171
260
|
|
|
172
261
|
// Load CSS theme
|
|
173
262
|
const themeLoadPromise = new Promise<void>(async (resolve, reject) => {
|
|
174
263
|
try {
|
|
264
|
+
// Check if aborted
|
|
265
|
+
if (abortController.signal.aborted) {
|
|
266
|
+
resolve();
|
|
267
|
+
return;
|
|
268
|
+
}
|
|
269
|
+
|
|
175
270
|
const themeMetadata = themes[theme];
|
|
176
271
|
|
|
177
272
|
if (themeMetadata) {
|
|
@@ -183,21 +278,41 @@ export const ThemeProvider: React.FC<ThemeProviderProps> = ({
|
|
|
183
278
|
cdnPath
|
|
184
279
|
);
|
|
185
280
|
|
|
281
|
+
// Check if aborted
|
|
282
|
+
if (abortController.signal.aborted) {
|
|
283
|
+
resolve();
|
|
284
|
+
return;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// Load CSS file (using loadThemeCSS from domUtils)
|
|
288
|
+
const { loadThemeCSS } = await import('../utils/domUtils');
|
|
289
|
+
await loadThemeCSS(cssPath, `theme-${theme}`);
|
|
290
|
+
|
|
291
|
+
// Check if aborted after async operation
|
|
292
|
+
if (abortController.signal.aborted) {
|
|
293
|
+
resolve();
|
|
294
|
+
return;
|
|
295
|
+
}
|
|
296
|
+
|
|
186
297
|
// Remove any previously loaded theme CSS
|
|
187
298
|
removeCSS(`theme-${String(currentTheme)}`);
|
|
188
299
|
|
|
189
|
-
// Inject new theme CSS
|
|
190
|
-
await injectCSS(cssPath, `theme-${theme}`);
|
|
191
300
|
loadedThemesRef.current.add(theme);
|
|
192
301
|
|
|
193
302
|
setCurrentTheme(theme);
|
|
194
|
-
|
|
303
|
+
setActiveTokens(null);
|
|
195
304
|
handleThemeChange(theme);
|
|
196
305
|
resolve();
|
|
197
306
|
} else {
|
|
198
307
|
throw new Error(`Theme metadata not found for theme: ${theme}`);
|
|
199
308
|
}
|
|
200
309
|
} catch (err) {
|
|
310
|
+
// Don't reject if aborted
|
|
311
|
+
if (abortController.signal.aborted) {
|
|
312
|
+
resolve();
|
|
313
|
+
return;
|
|
314
|
+
}
|
|
315
|
+
|
|
201
316
|
const error = err instanceof Error ? err : new Error(String(err));
|
|
202
317
|
setError(error);
|
|
203
318
|
handleError(error, String(theme));
|
|
@@ -206,24 +321,44 @@ export const ThemeProvider: React.FC<ThemeProviderProps> = ({
|
|
|
206
321
|
});
|
|
207
322
|
|
|
208
323
|
themePromisesRef.current[theme] = themeLoadPromise;
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
324
|
+
|
|
325
|
+
try {
|
|
326
|
+
await themeLoadPromise;
|
|
327
|
+
} catch {
|
|
328
|
+
// Error already handled in promise
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
// Clean up completed promise after a delay to prevent memory leak
|
|
332
|
+
setTimeout(() => {
|
|
333
|
+
if (themePromisesRef.current[theme] === themeLoadPromise) {
|
|
334
|
+
delete themePromisesRef.current[theme];
|
|
335
|
+
}
|
|
336
|
+
}, 1000);
|
|
215
337
|
} else {
|
|
338
|
+
// Check if aborted
|
|
339
|
+
if (abortController.signal.aborted) {
|
|
340
|
+
return;
|
|
341
|
+
}
|
|
342
|
+
|
|
216
343
|
// For string theme that isn't in our themes record, just set the name
|
|
217
344
|
setCurrentTheme(themeName);
|
|
218
|
-
|
|
345
|
+
setActiveTokens(null);
|
|
219
346
|
handleThemeChange(themeName);
|
|
220
347
|
}
|
|
221
348
|
} catch (err) {
|
|
349
|
+
// Don't set error if aborted
|
|
350
|
+
if (abortController.signal.aborted) {
|
|
351
|
+
return;
|
|
352
|
+
}
|
|
353
|
+
|
|
222
354
|
const error = err instanceof Error ? err : new Error(String(err));
|
|
223
355
|
setError(error);
|
|
224
356
|
handleError(error, String(theme));
|
|
225
357
|
} finally {
|
|
226
|
-
|
|
358
|
+
// Only update loading state if not aborted
|
|
359
|
+
if (!abortController.signal.aborted) {
|
|
360
|
+
setIsLoading(false);
|
|
361
|
+
}
|
|
227
362
|
}
|
|
228
363
|
}, [themes, currentTheme, handleThemeChange, handleError, basePath, useMinified, cdnPath]);
|
|
229
364
|
|
|
@@ -269,14 +404,21 @@ export const ThemeProvider: React.FC<ThemeProviderProps> = ({
|
|
|
269
404
|
} ;
|
|
270
405
|
}, []);
|
|
271
406
|
|
|
407
|
+
// Memoize available themes to prevent unnecessary recalculations
|
|
408
|
+
const availableThemes = useMemo(() =>
|
|
409
|
+
Object.entries(themes).map(([name, metadata]) => ({
|
|
410
|
+
...metadata,
|
|
411
|
+
name: name, // Ensure name is set from the key
|
|
412
|
+
})),
|
|
413
|
+
[themes]
|
|
414
|
+
);
|
|
415
|
+
|
|
272
416
|
// Theme context value
|
|
273
417
|
const contextValue = useMemo(() => ({
|
|
274
|
-
theme:
|
|
275
|
-
|
|
418
|
+
theme: currentTheme,
|
|
419
|
+
activeTokens,
|
|
276
420
|
setTheme,
|
|
277
|
-
availableThemes
|
|
278
|
-
...metadata
|
|
279
|
-
})),
|
|
421
|
+
availableThemes,
|
|
280
422
|
isLoading,
|
|
281
423
|
error,
|
|
282
424
|
isThemeLoaded,
|
|
@@ -284,9 +426,9 @@ export const ThemeProvider: React.FC<ThemeProviderProps> = ({
|
|
|
284
426
|
themeManager,
|
|
285
427
|
}), [
|
|
286
428
|
currentTheme,
|
|
287
|
-
|
|
429
|
+
activeTokens,
|
|
288
430
|
setTheme,
|
|
289
|
-
|
|
431
|
+
availableThemes, // Use memoized value
|
|
290
432
|
isLoading,
|
|
291
433
|
error,
|
|
292
434
|
isThemeLoaded,
|
|
@@ -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 */
|
|
@@ -2,15 +2,16 @@
|
|
|
2
2
|
* Component Theming Utilities
|
|
3
3
|
*
|
|
4
4
|
* Provides consistent patterns for applying theme values to components
|
|
5
|
+
* using DesignTokens and CSS variables
|
|
5
6
|
*/
|
|
6
7
|
|
|
7
|
-
import type {
|
|
8
|
+
import type { DesignTokens } from '../tokens/tokens';
|
|
8
9
|
|
|
9
10
|
export interface ComponentThemeOptions {
|
|
10
11
|
component: string;
|
|
11
12
|
variant?: string;
|
|
12
13
|
size?: string;
|
|
13
|
-
|
|
14
|
+
tokens?: Partial<DesignTokens>;
|
|
14
15
|
}
|
|
15
16
|
|
|
16
17
|
/**
|
|
@@ -44,18 +45,18 @@ export function getComponentThemeValue(
|
|
|
44
45
|
}
|
|
45
46
|
|
|
46
47
|
/**
|
|
47
|
-
* Generate component-specific CSS variables from
|
|
48
|
+
* Generate component-specific CSS variables from DesignTokens
|
|
48
49
|
*/
|
|
49
50
|
export function generateComponentCSSVars(
|
|
50
51
|
component: string,
|
|
51
|
-
|
|
52
|
+
tokens?: Partial<DesignTokens>,
|
|
52
53
|
variant?: string,
|
|
53
54
|
size?: string
|
|
54
55
|
): Record<string, string> {
|
|
55
56
|
const vars: Record<string, string> = {};
|
|
56
57
|
|
|
57
|
-
|
|
58
|
-
|
|
58
|
+
if (!tokens) return vars;
|
|
59
|
+
|
|
59
60
|
const prefixParts = ['atomix', component];
|
|
60
61
|
|
|
61
62
|
if (variant) {
|
|
@@ -68,47 +69,54 @@ export function generateComponentCSSVars(
|
|
|
68
69
|
|
|
69
70
|
const prefix = prefixParts.join('-');
|
|
70
71
|
|
|
71
|
-
//
|
|
72
|
-
if (
|
|
73
|
-
vars[`--${prefix}-color`] =
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
vars[`--${prefix}-color-
|
|
72
|
+
// Map common DesignTokens to component-specific CSS variables
|
|
73
|
+
if (tokens.primary) {
|
|
74
|
+
vars[`--${prefix}-color`] = tokens.primary;
|
|
75
|
+
}
|
|
76
|
+
if (tokens['primary-9']) {
|
|
77
|
+
vars[`--${prefix}-color-hover`] = tokens['primary-9'];
|
|
78
|
+
}
|
|
79
|
+
if (tokens['body-color']) {
|
|
80
|
+
vars[`--${prefix}-color-disabled`] = tokens['body-color'];
|
|
77
81
|
}
|
|
78
82
|
|
|
79
|
-
if (
|
|
80
|
-
vars[`--${prefix}-font-family`] =
|
|
81
|
-
|
|
83
|
+
if (tokens['body-font-family']) {
|
|
84
|
+
vars[`--${prefix}-font-family`] = tokens['body-font-family'];
|
|
85
|
+
}
|
|
86
|
+
if (tokens['body-font-size']) {
|
|
87
|
+
vars[`--${prefix}-font-size`] = tokens['body-font-size'];
|
|
82
88
|
}
|
|
83
89
|
|
|
84
|
-
if (
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
vars[`--${prefix}-spacing-md`] =
|
|
89
|
-
|
|
90
|
+
if (tokens['spacing-1']) {
|
|
91
|
+
vars[`--${prefix}-spacing-sm`] = tokens['spacing-1'];
|
|
92
|
+
}
|
|
93
|
+
if (tokens['spacing-2']) {
|
|
94
|
+
vars[`--${prefix}-spacing-md`] = tokens['spacing-2'];
|
|
95
|
+
}
|
|
96
|
+
if (tokens['spacing-4']) {
|
|
97
|
+
vars[`--${prefix}-spacing-lg`] = tokens['spacing-4'];
|
|
90
98
|
}
|
|
91
99
|
|
|
92
100
|
return vars;
|
|
93
101
|
}
|
|
94
102
|
|
|
95
103
|
/**
|
|
96
|
-
* Apply consistent theme to component style object
|
|
104
|
+
* Apply consistent theme to component style object using DesignTokens
|
|
97
105
|
*/
|
|
98
106
|
export function applyComponentTheme(
|
|
99
107
|
component: string,
|
|
100
108
|
style: React.CSSProperties = {},
|
|
101
109
|
variant?: string,
|
|
102
110
|
size?: string,
|
|
103
|
-
|
|
111
|
+
tokens?: Partial<DesignTokens>
|
|
104
112
|
): React.CSSProperties {
|
|
105
|
-
// If no
|
|
106
|
-
if (!
|
|
113
|
+
// If no tokens provided, return original style
|
|
114
|
+
if (!tokens) {
|
|
107
115
|
return style;
|
|
108
116
|
}
|
|
109
117
|
|
|
110
118
|
// Generate component-specific CSS variables
|
|
111
|
-
const componentVars = generateComponentCSSVars(component,
|
|
119
|
+
const componentVars = generateComponentCSSVars(component, tokens, variant, size);
|
|
112
120
|
|
|
113
121
|
// Merge with existing style
|
|
114
122
|
return {
|
|
@@ -124,7 +132,7 @@ export function useComponentTheme(
|
|
|
124
132
|
component: string,
|
|
125
133
|
variant?: string,
|
|
126
134
|
size?: string,
|
|
127
|
-
|
|
135
|
+
tokens?: Partial<DesignTokens>
|
|
128
136
|
): (property: string) => string {
|
|
129
137
|
return (property: string) => {
|
|
130
138
|
return getComponentThemeValue(component, property, variant, size);
|