@shohojdhara/atomix 0.3.13 → 0.3.15
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 +39 -0
- package/README.md +2 -0
- package/build-tools/EXAMPLES.md +372 -0
- package/build-tools/README.md +242 -0
- package/build-tools/__tests__/error-handler.test.js +230 -0
- package/build-tools/__tests__/index.test.js +141 -0
- package/build-tools/__tests__/rollup-plugin.test.js +194 -0
- package/build-tools/__tests__/utils.test.js +161 -0
- package/build-tools/__tests__/vite-plugin.test.js +129 -0
- package/build-tools/__tests__/webpack-loader.test.js +190 -0
- package/build-tools/error-handler.js +308 -0
- package/build-tools/index.d.ts +43 -0
- package/build-tools/index.js +88 -0
- package/build-tools/package.json +67 -0
- package/build-tools/rollup-plugin.js +236 -0
- package/build-tools/types.d.ts +163 -0
- package/build-tools/utils.js +203 -0
- package/build-tools/vite-plugin.js +161 -0
- package/build-tools/webpack-loader.js +123 -0
- package/dist/atomix.css +298 -167
- package/dist/atomix.css.map +1 -1
- package/dist/atomix.min.css +3 -3
- package/dist/atomix.min.css.map +1 -1
- package/dist/build-tools/EXAMPLES.md +372 -0
- package/dist/build-tools/README.md +242 -0
- package/dist/build-tools/__tests__/error-handler.test.js +230 -0
- package/dist/build-tools/__tests__/index.test.js +141 -0
- package/dist/build-tools/__tests__/rollup-plugin.test.js +194 -0
- package/dist/build-tools/__tests__/utils.test.js +161 -0
- package/dist/build-tools/__tests__/vite-plugin.test.js +129 -0
- package/dist/build-tools/__tests__/webpack-loader.test.js +190 -0
- package/dist/build-tools/error-handler.js +308 -0
- package/dist/build-tools/index.d.ts +43 -0
- package/dist/build-tools/index.js +88 -0
- package/dist/build-tools/package.json +67 -0
- package/dist/build-tools/rollup-plugin.js +236 -0
- package/dist/build-tools/types.d.ts +163 -0
- package/dist/build-tools/utils.js +203 -0
- package/dist/build-tools/vite-plugin.js +161 -0
- package/dist/build-tools/webpack-loader.js +123 -0
- package/dist/charts.d.ts +2 -2
- package/dist/charts.js +87 -58
- package/dist/charts.js.map +1 -1
- package/dist/core.d.ts +42 -12
- package/dist/core.js +175 -135
- package/dist/core.js.map +1 -1
- package/dist/forms.d.ts +30 -16
- package/dist/forms.js +146 -131
- package/dist/forms.js.map +1 -1
- package/dist/heavy.d.ts +2 -2
- package/dist/heavy.js +151 -118
- package/dist/heavy.js.map +1 -1
- package/dist/index.d.ts +130 -106
- package/dist/index.esm.js +1083 -465
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +1102 -483
- 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 +27 -2
- package/dist/theme.js +721 -108
- package/dist/theme.js.map +1 -1
- package/package.json +23 -8
- package/scripts/atomix-cli.js +749 -1153
- package/scripts/cli/__tests__/README.md +81 -0
- package/scripts/cli/__tests__/basic.test.js +115 -0
- package/scripts/cli/__tests__/component-generator.test.js +332 -0
- package/scripts/cli/__tests__/integration.test.js +327 -0
- package/scripts/cli/__tests__/test-setup.js +133 -0
- package/scripts/cli/__tests__/token-manager.test.js +251 -0
- package/scripts/cli/__tests__/utils.test.js +78 -118
- package/scripts/cli/component-generator.js +564 -0
- package/scripts/cli/dependency-checker.js +355 -0
- package/scripts/cli/documentation-sync.js +542 -0
- package/scripts/cli/interactive-init.js +129 -292
- package/scripts/cli/mappings.js +211 -0
- package/scripts/cli/migration-tools.js +95 -288
- package/scripts/cli/template-manager.js +105 -0
- package/scripts/cli/templates/README.md +123 -0
- package/scripts/cli/templates/common-templates.js +636 -0
- package/scripts/cli/templates/composable-templates.js +171 -0
- package/scripts/cli/templates/config-templates.js +126 -0
- package/scripts/cli/templates/index.js +102 -0
- package/scripts/cli/templates/project-templates.js +342 -0
- package/scripts/cli/templates/react-templates.js +331 -0
- package/scripts/cli/templates/scss-templates.js +155 -0
- package/scripts/cli/templates/storybook-templates.js +236 -0
- package/scripts/cli/templates/testing-templates.js +224 -0
- package/scripts/cli/templates/testing-utils.js +278 -0
- package/scripts/cli/templates/token-templates.js +447 -0
- package/scripts/cli/templates/types-templates.js +147 -0
- package/scripts/cli/templates.js +35 -0
- package/scripts/cli/theme-bridge.js +28 -16
- package/scripts/cli/token-manager.js +432 -247
- package/scripts/cli/utils.js +37 -26
- package/src/components/Accordion/Accordion.stories.tsx +369 -870
- package/src/components/Accordion/Accordion.test.tsx +57 -0
- package/src/components/Accordion/Accordion.tsx +4 -0
- package/src/components/AtomixGlass/AtomixGlass.tsx +80 -39
- package/src/components/AtomixGlass/AtomixGlassContainer.tsx +103 -81
- package/src/components/AtomixGlass/__snapshots__/AtomixGlass.test.tsx.snap +8 -7
- package/src/components/AtomixGlass/glass-utils.ts +2 -2
- package/src/components/AtomixGlass/shader-utils.ts +5 -0
- package/src/components/AtomixGlass/stories/Customization.stories.tsx +131 -0
- package/src/components/AtomixGlass/stories/Examples.stories.tsx +2965 -2861
- package/src/components/AtomixGlass/stories/Modes.stories.tsx +1 -1
- package/src/components/AtomixGlass/stories/Overview.stories.tsx +348 -0
- package/src/components/AtomixGlass/stories/Performance.stories.tsx +103 -0
- package/src/components/AtomixGlass/stories/Playground.stories.tsx +73 -59
- package/src/components/AtomixGlass/stories/{ShaderVariants.stories.tsx → Shaders.stories.tsx} +1 -1
- package/src/components/AtomixGlass/stories/shared-components.tsx +90 -190
- package/src/components/Avatar/Avatar.stories.tsx +239 -27
- package/src/components/Badge/Badge.stories.tsx +132 -373
- package/src/components/Badge/Badge.test.tsx +51 -0
- package/src/components/Badge/Badge.tsx +20 -1
- package/src/components/Block/Block.stories.tsx +26 -17
- package/src/components/Breadcrumb/Breadcrumb.stories.tsx +141 -23
- package/src/components/Breadcrumb/Breadcrumb.tsx +2 -2
- package/src/components/Button/Button.stories.tsx +463 -1126
- package/src/components/Button/Button.test.tsx +107 -0
- package/src/components/Button/Button.tsx +50 -54
- package/src/components/Button/ButtonGroup.stories.tsx +373 -217
- package/src/components/Button/README.md +5 -0
- package/src/components/Callout/Callout.stories.tsx +299 -644
- package/src/components/Callout/Callout.test.tsx +10 -10
- package/src/components/Callout/Callout.tsx +7 -7
- package/src/components/Callout/README.md +9 -8
- package/src/components/Card/Card.stories.tsx +248 -68
- package/src/components/Card/Card.tsx +2 -2
- package/src/components/Chart/Chart.stories.tsx +156 -14
- package/src/components/Chart/Chart.tsx +1 -1
- package/src/components/ColorModeToggle/ColorModeToggle.stories.tsx +151 -69
- package/src/components/Countdown/Countdown.stories.tsx +115 -8
- package/src/components/DataTable/DataTable.stories.tsx +346 -146
- package/src/components/DataTable/DataTable.tsx +14 -12
- package/src/components/DatePicker/DatePicker.stories.tsx +325 -1066
- package/src/components/Dropdown/Dropdown.stories.tsx +157 -37
- package/src/components/EdgePanel/EdgePanel.stories.tsx +230 -21
- package/src/components/Footer/Footer.stories.tsx +392 -328
- package/src/components/Form/Checkbox.stories.tsx +143 -9
- package/src/components/Form/Checkbox.test.tsx +63 -0
- package/src/components/Form/Checkbox.tsx +90 -52
- package/src/components/Form/Form.stories.tsx +121 -22
- package/src/components/Form/FormGroup.stories.tsx +128 -5
- package/src/components/Form/Input.stories.tsx +28 -16
- package/src/components/Form/Input.test.tsx +59 -0
- package/src/components/Form/Input.tsx +97 -95
- package/src/components/Form/Radio.stories.tsx +232 -97
- package/src/components/Form/Radio.tsx +2 -2
- package/src/components/Form/Select.stories.tsx +144 -12
- package/src/components/Form/Select.tsx +2 -2
- package/src/components/Form/Textarea.stories.tsx +171 -13
- package/src/components/Form/Textarea.test.tsx +45 -0
- package/src/components/Form/Textarea.tsx +88 -86
- package/src/components/Hero/Hero.stories.tsx +333 -32
- package/src/components/List/List.stories.tsx +143 -5
- package/src/components/Modal/Modal.stories.tsx +185 -46
- package/src/components/Navigation/Navbar/Navbar.stories.tsx +5 -5
- package/src/components/Navigation/Navbar/Navbar.tsx +1 -1
- package/src/components/Navigation/README.md +1 -1
- package/src/components/Pagination/Pagination.stories.tsx +5 -2
- package/src/components/Pagination/Pagination.tsx +1 -1
- package/src/components/PhotoViewer/PhotoViewer.stories.tsx +10 -10
- package/src/components/Popover/Popover.stories.tsx +449 -99
- package/src/components/ProductReview/ProductReview.tsx +1 -1
- package/src/components/Progress/Progress.stories.tsx +167 -5
- package/src/components/Progress/Progress.tsx +46 -46
- package/src/components/Rating/Rating.stories.tsx +4 -4
- package/src/components/Rating/Rating.tsx +8 -8
- package/src/components/River/River.stories.tsx +1 -1
- package/src/components/SectionIntro/SectionIntro.stories.tsx +240 -48
- package/src/components/Slider/Slider.stories.tsx +63 -63
- package/src/components/Spinner/Spinner.stories.tsx +104 -10
- package/src/components/Spinner/Spinner.test.tsx +35 -0
- package/src/components/Spinner/Spinner.tsx +9 -2
- package/src/components/Steps/Steps.stories.tsx +172 -43
- package/src/components/Tabs/Tabs.stories.tsx +136 -10
- package/src/components/Testimonial/Testimonial.stories.tsx +121 -4
- package/src/components/Todo/Todo.stories.tsx +198 -9
- package/src/components/Toggle/Toggle.stories.tsx +153 -43
- package/src/components/Toggle/Toggle.test.tsx +91 -0
- package/src/components/Toggle/Toggle.tsx +44 -27
- package/src/components/Tooltip/Tooltip.stories.tsx +194 -104
- package/src/components/Tooltip/Tooltip.tsx +1 -1
- package/src/components/Upload/Upload.stories.tsx +113 -24
- package/src/layouts/Grid/Grid.stories.tsx +49 -49
- package/src/layouts/MasonryGrid/MasonryGrid.stories.tsx +2 -2
- package/src/lib/README.md +2 -2
- package/src/lib/__tests__/theme-tools.test.ts +193 -0
- package/src/lib/composables/index.ts +2 -2
- package/src/lib/composables/useAccordion.ts +12 -3
- package/src/lib/composables/useAtomixGlass.ts +28 -56
- package/src/lib/composables/useBreadcrumb.ts +2 -2
- package/src/lib/composables/useCallout.ts +7 -7
- package/src/lib/composables/useChartExport.ts +2 -7
- package/src/lib/composables/useDataTable.ts +46 -29
- package/src/lib/composables/useNavbar.ts +1 -1
- package/src/lib/constants/components.ts +10 -33
- package/src/lib/storybook/InteractiveDemo.tsx +113 -0
- package/src/lib/storybook/PreviewContainer.tsx +36 -0
- package/src/lib/storybook/VariantsGrid.tsx +21 -0
- package/src/lib/storybook/index.ts +3 -0
- package/src/lib/theme/core/createThemeObject.ts +9 -5
- package/src/lib/theme/devtools/CLI.ts +155 -0
- package/src/lib/theme/devtools/DesignTokensCustomizer.stories.tsx +213 -0
- package/src/lib/theme/devtools/DesignTokensCustomizer.tsx +566 -0
- package/src/lib/theme/devtools/LiveEditor.tsx +2 -1
- package/src/lib/theme/devtools/index.ts +3 -0
- package/src/lib/theme/errors/errors.ts +8 -0
- package/src/lib/theme/runtime/ThemeProvider.tsx +117 -57
- package/src/lib/theme/runtime/__tests__/ThemeProvider.integration.test.tsx +305 -0
- package/src/lib/theme/runtime/__tests__/ThemeProvider.test.tsx +588 -0
- package/src/lib/theme/utils/__tests__/themeValidation.test.ts +264 -0
- package/src/lib/theme/utils/index.ts +1 -0
- package/src/lib/theme/utils/themeValidation.ts +501 -0
- package/src/lib/theme-tools.ts +32 -3
- package/src/lib/types/components.ts +82 -27
- package/src/lib/utils/__tests__/csv.test.ts +45 -0
- package/src/lib/utils/csv.ts +17 -0
- package/src/lib/utils/dataTableExport.ts +1 -10
- package/src/lib/utils/themeNaming.ts +1 -1
- package/src/styles/01-settings/_index.scss +2 -1
- package/src/styles/01-settings/_settings.accordion.scss +28 -7
- package/src/styles/01-settings/_settings.colors.scss +11 -11
- package/src/styles/01-settings/_settings.typography.scss +5 -5
- package/src/styles/02-tools/_tools.utility-api.scss +14 -0
- package/src/styles/06-components/_components.accordion.scss +56 -14
- package/src/styles/06-components/_components.callout.scss +29 -33
- package/src/styles/06-components/_components.checkbox.scss +23 -17
- package/src/styles/06-components/_index.scss +1 -1
- package/src/styles/99-utilities/_index.scss +2 -0
- package/src/styles/99-utilities/_utilities.display.scss +14 -3
- package/src/styles/99-utilities/_utilities.flex.scss +10 -10
- package/src/styles/99-utilities/_utilities.scss +3 -1
- package/src/styles/99-utilities/_utilities.text-gradient.scss +45 -0
- package/src/styles/99-utilities/_utilities.text.scss +28 -8
- package/themes/dark-complementary/README.md +98 -0
- package/themes/dark-complementary/index.scss +158 -0
- package/themes/default-light/README.md +81 -0
- package/themes/default-light/index.scss +154 -0
- package/themes/high-contrast/README.md +105 -0
- package/themes/high-contrast/index.scss +172 -0
- package/themes/test-theme/README.md +38 -0
- package/themes/test-theme/index.scss +47 -0
- package/scripts/cli/__tests__/cli-commands.test.js +0 -204
- package/scripts/cli/__tests__/vitest.config.js +0 -26
- package/src/components/AtomixGlass/stories/AtomixGlass.stories.tsx +0 -1438
- package/src/lib/composables/useButton.ts +0 -93
- package/src/lib/composables/useCheckbox.ts +0 -70
|
@@ -13,6 +13,9 @@ import { createTokens } from '../tokens/tokens';
|
|
|
13
13
|
import { getLogger } from '../errors';
|
|
14
14
|
import { createTheme } from '../core';
|
|
15
15
|
import { injectCSS, removeCSS } from '../utils/injectCSS';
|
|
16
|
+
import { validateAndMergeTokens } from '../utils/themeValidation';
|
|
17
|
+
|
|
18
|
+
const logger = getLogger();
|
|
16
19
|
import {
|
|
17
20
|
isServer,
|
|
18
21
|
createLocalStorageAdapter,
|
|
@@ -102,9 +105,18 @@ export const ThemeProvider: React.FC<ThemeProviderProps> = ({
|
|
|
102
105
|
});
|
|
103
106
|
|
|
104
107
|
const [activeTokens, setActiveTokens] = useState<DesignTokens | null>(() => {
|
|
105
|
-
// If defaultTheme is DesignTokens, store them
|
|
108
|
+
// If defaultTheme is DesignTokens, validate and store them
|
|
106
109
|
if (defaultTheme && typeof defaultTheme !== 'string') {
|
|
107
|
-
|
|
110
|
+
const { tokens, validation } = validateAndMergeTokens(defaultTheme);
|
|
111
|
+
if (validation.valid) {
|
|
112
|
+
return tokens;
|
|
113
|
+
} else {
|
|
114
|
+
logger.warn('Invalid default theme tokens, using defaults', {
|
|
115
|
+
errors: validation.errors,
|
|
116
|
+
warnings: validation.warnings,
|
|
117
|
+
});
|
|
118
|
+
return createTokens({}); // Use defaults if validation fails
|
|
119
|
+
}
|
|
108
120
|
}
|
|
109
121
|
return null;
|
|
110
122
|
});
|
|
@@ -120,8 +132,8 @@ export const ThemeProvider: React.FC<ThemeProviderProps> = ({
|
|
|
120
132
|
// Handle initial DesignTokens defaultTheme
|
|
121
133
|
useEffect(() => {
|
|
122
134
|
if (defaultTheme && typeof defaultTheme !== 'string' && activeTokens && !isServer()) {
|
|
123
|
-
// If defaultTheme is DesignTokens, inject CSS on mount
|
|
124
|
-
const css = createTheme(
|
|
135
|
+
// If defaultTheme is DesignTokens, inject CSS on mount (tokens are already validated)
|
|
136
|
+
const css = createTheme(activeTokens);
|
|
125
137
|
injectCSS(css, 'theme-tokens-theme');
|
|
126
138
|
}
|
|
127
139
|
}, [defaultTheme, activeTokens]); // Run when defaultTheme or activeTokens change
|
|
@@ -191,9 +203,52 @@ export const ThemeProvider: React.FC<ThemeProviderProps> = ({
|
|
|
191
203
|
return;
|
|
192
204
|
}
|
|
193
205
|
|
|
194
|
-
//
|
|
195
|
-
const {
|
|
196
|
-
|
|
206
|
+
// Validate and merge DesignTokens
|
|
207
|
+
const { tokens: validatedTokens, validation } = validateAndMergeTokens(theme);
|
|
208
|
+
|
|
209
|
+
if (!validation.valid) {
|
|
210
|
+
const errorMsg = `Invalid DesignTokens provided: ${validation.errors.join(', ')}`;
|
|
211
|
+
const validationError = new Error(errorMsg);
|
|
212
|
+
logger.error('Theme validation failed', validationError, {
|
|
213
|
+
errors: validation.errors,
|
|
214
|
+
warnings: validation.warnings,
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
// Check if we should fallback to default theme
|
|
218
|
+
const shouldFallback = options?.fallbackOnError !== false; // Default to true
|
|
219
|
+
if (shouldFallback) {
|
|
220
|
+
logger.warn('Falling back to default theme due to validation errors');
|
|
221
|
+
// Use default tokens instead
|
|
222
|
+
const { tokens: defaultTokens } = validateAndMergeTokens({});
|
|
223
|
+
const css = createTheme(defaultTokens);
|
|
224
|
+
const themeId = 'tokens-theme-fallback';
|
|
225
|
+
|
|
226
|
+
// Check if aborted before state update
|
|
227
|
+
if (abortController.signal.aborted) {
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// Remove any previously loaded theme CSS
|
|
232
|
+
removeCSS(`theme-${currentTheme}`);
|
|
233
|
+
|
|
234
|
+
// Inject new theme CSS
|
|
235
|
+
injectCSS(css, `theme-${themeId}`);
|
|
236
|
+
|
|
237
|
+
// Store default tokens
|
|
238
|
+
setActiveTokens(defaultTokens);
|
|
239
|
+
setCurrentTheme(themeId);
|
|
240
|
+
handleThemeChange(defaultTokens);
|
|
241
|
+
handleError(validationError, themeId);
|
|
242
|
+
setIsLoading(false);
|
|
243
|
+
return;
|
|
244
|
+
} else {
|
|
245
|
+
// No fallback, throw the error
|
|
246
|
+
throw validationError;
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// For valid DesignTokens, create CSS and inject it
|
|
251
|
+
const css = createTheme(validatedTokens);
|
|
197
252
|
const themeId = 'tokens-theme';
|
|
198
253
|
|
|
199
254
|
// Check if aborted after async operation
|
|
@@ -207,17 +262,16 @@ export const ThemeProvider: React.FC<ThemeProviderProps> = ({
|
|
|
207
262
|
// Inject new theme CSS
|
|
208
263
|
injectCSS(css, `theme-${themeId}`);
|
|
209
264
|
|
|
210
|
-
// Store tokens for reference
|
|
211
|
-
const fullTokens = createTokens(theme);
|
|
265
|
+
// Store validated tokens for reference
|
|
212
266
|
|
|
213
267
|
// Check if aborted before state update
|
|
214
268
|
if (abortController.signal.aborted) {
|
|
215
269
|
return;
|
|
216
270
|
}
|
|
217
271
|
|
|
218
|
-
setActiveTokens(
|
|
272
|
+
setActiveTokens(validatedTokens);
|
|
219
273
|
setCurrentTheme(themeId);
|
|
220
|
-
handleThemeChange(
|
|
274
|
+
handleThemeChange(validatedTokens);
|
|
221
275
|
setIsLoading(false);
|
|
222
276
|
return;
|
|
223
277
|
}
|
|
@@ -243,65 +297,71 @@ export const ThemeProvider: React.FC<ThemeProviderProps> = ({
|
|
|
243
297
|
}
|
|
244
298
|
|
|
245
299
|
// Load CSS theme
|
|
246
|
-
const themeLoadPromise = new Promise<void>(
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
resolve();
|
|
251
|
-
return;
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
const themeMetadata = themes[theme];
|
|
255
|
-
|
|
256
|
-
if (themeMetadata) {
|
|
257
|
-
// Build CSS path using utility function
|
|
258
|
-
const cssPath = buildThemePath(
|
|
259
|
-
theme,
|
|
260
|
-
basePath,
|
|
261
|
-
useMinified,
|
|
262
|
-
cdnPath
|
|
263
|
-
);
|
|
264
|
-
|
|
300
|
+
const themeLoadPromise = new Promise<void>((resolve, reject) => {
|
|
301
|
+
// Handle the async operations inside the promise without making the executor async
|
|
302
|
+
const loadTheme = async () => {
|
|
303
|
+
try {
|
|
265
304
|
// Check if aborted
|
|
266
305
|
if (abortController.signal.aborted) {
|
|
267
306
|
resolve();
|
|
268
307
|
return;
|
|
269
308
|
}
|
|
270
309
|
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
310
|
+
const themeMetadata = themes[theme];
|
|
311
|
+
|
|
312
|
+
if (themeMetadata) {
|
|
313
|
+
// Build CSS path using utility function
|
|
314
|
+
const cssPath = buildThemePath(
|
|
315
|
+
theme,
|
|
316
|
+
basePath,
|
|
317
|
+
useMinified,
|
|
318
|
+
cdnPath
|
|
319
|
+
);
|
|
320
|
+
|
|
321
|
+
// Check if aborted
|
|
322
|
+
if (abortController.signal.aborted) {
|
|
323
|
+
resolve();
|
|
324
|
+
return;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
// Load CSS file (using loadThemeCSS from domUtils)
|
|
328
|
+
const { loadThemeCSS } = await import('../utils/domUtils');
|
|
329
|
+
await loadThemeCSS(cssPath, `theme-${theme}`);
|
|
330
|
+
|
|
331
|
+
// Check if aborted after async operation
|
|
332
|
+
if (abortController.signal.aborted) {
|
|
333
|
+
resolve();
|
|
334
|
+
return;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
// Remove any previously loaded theme CSS
|
|
338
|
+
removeCSS(`theme-${String(currentTheme)}`);
|
|
339
|
+
|
|
340
|
+
loadedThemesRef.current.add(theme);
|
|
341
|
+
|
|
342
|
+
setCurrentTheme(theme);
|
|
343
|
+
setActiveTokens(null);
|
|
344
|
+
handleThemeChange(theme);
|
|
345
|
+
resolve();
|
|
346
|
+
} else {
|
|
347
|
+
throw new Error(`Theme metadata not found for theme: ${theme}`);
|
|
348
|
+
}
|
|
349
|
+
} catch (err) {
|
|
350
|
+
// Don't reject if aborted
|
|
276
351
|
if (abortController.signal.aborted) {
|
|
277
352
|
resolve();
|
|
278
353
|
return;
|
|
279
354
|
}
|
|
280
355
|
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
setCurrentTheme(theme);
|
|
287
|
-
setActiveTokens(null);
|
|
288
|
-
handleThemeChange(theme);
|
|
289
|
-
resolve();
|
|
290
|
-
} else {
|
|
291
|
-
throw new Error(`Theme metadata not found for theme: ${theme}`);
|
|
292
|
-
}
|
|
293
|
-
} catch (err) {
|
|
294
|
-
// Don't reject if aborted
|
|
295
|
-
if (abortController.signal.aborted) {
|
|
296
|
-
resolve();
|
|
297
|
-
return;
|
|
356
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
357
|
+
setError(error);
|
|
358
|
+
handleError(error, String(theme));
|
|
359
|
+
reject(error);
|
|
298
360
|
}
|
|
361
|
+
};
|
|
299
362
|
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
handleError(error, String(theme));
|
|
303
|
-
reject(error);
|
|
304
|
-
}
|
|
363
|
+
// Start the async operation
|
|
364
|
+
loadTheme();
|
|
305
365
|
});
|
|
306
366
|
|
|
307
367
|
themePromisesRef.current[theme] = themeLoadPromise;
|
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Theme Provider Integration Tests
|
|
3
|
+
*
|
|
4
|
+
* Integration tests that render components with themes and verify styling application.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import React from 'react';
|
|
8
|
+
import { describe, it, expect, beforeEach, vi, afterEach } from 'vitest';
|
|
9
|
+
import { render, screen, waitFor } from '@testing-library/react';
|
|
10
|
+
import { ThemeProvider } from '../ThemeProvider';
|
|
11
|
+
import { useTheme } from '../useTheme';
|
|
12
|
+
import { createTokens, defaultTokens } from '../../tokens/tokens';
|
|
13
|
+
import type { ThemeMetadata, DesignTokens } from '../../types';
|
|
14
|
+
|
|
15
|
+
// Mock DOM utilities
|
|
16
|
+
vi.mock('../../utils/domUtils', () => ({
|
|
17
|
+
isServer: vi.fn(() => false),
|
|
18
|
+
createLocalStorageAdapter: vi.fn(() => ({
|
|
19
|
+
getItem: vi.fn(() => null),
|
|
20
|
+
setItem: vi.fn(),
|
|
21
|
+
removeItem: vi.fn(),
|
|
22
|
+
isAvailable: vi.fn(() => true),
|
|
23
|
+
})),
|
|
24
|
+
applyThemeAttributes: vi.fn(),
|
|
25
|
+
buildThemePath: vi.fn((theme, basePath) => `${basePath}${theme}.css`),
|
|
26
|
+
loadThemeCSS: vi.fn(() => Promise.resolve()),
|
|
27
|
+
}));
|
|
28
|
+
|
|
29
|
+
// Mock CSS injection utilities
|
|
30
|
+
vi.mock('../../utils/injectCSS', () => ({
|
|
31
|
+
injectCSS: vi.fn(),
|
|
32
|
+
removeCSS: vi.fn(),
|
|
33
|
+
}));
|
|
34
|
+
|
|
35
|
+
// Mock theme creation
|
|
36
|
+
vi.mock('../../core', () => ({
|
|
37
|
+
createTheme: vi.fn((tokens: DesignTokens) => {
|
|
38
|
+
const css = Object.entries(tokens)
|
|
39
|
+
.map(([key, value]) => `--atomix-${key}: ${value};`)
|
|
40
|
+
.join('\n');
|
|
41
|
+
return `:root {\n${css}\n}`;
|
|
42
|
+
}),
|
|
43
|
+
}));
|
|
44
|
+
|
|
45
|
+
// Mock validation
|
|
46
|
+
vi.mock('../../utils/themeValidation', () => ({
|
|
47
|
+
validateAndMergeTokens: vi.fn((tokens?: Partial<DesignTokens>) => ({
|
|
48
|
+
tokens: tokens ? createTokens(tokens) : defaultTokens,
|
|
49
|
+
validation: { valid: true, errors: [], warnings: [] },
|
|
50
|
+
})),
|
|
51
|
+
}));
|
|
52
|
+
|
|
53
|
+
// Mock logger
|
|
54
|
+
vi.mock('../../errors', () => ({
|
|
55
|
+
getLogger: vi.fn(() => ({
|
|
56
|
+
warn: vi.fn(),
|
|
57
|
+
error: vi.fn(),
|
|
58
|
+
})),
|
|
59
|
+
}));
|
|
60
|
+
|
|
61
|
+
// Sample component that uses CSS variables
|
|
62
|
+
const StyledComponent: React.FC = () => {
|
|
63
|
+
const style = {
|
|
64
|
+
color: 'var(--atomix-primary-text-emphasis)',
|
|
65
|
+
backgroundColor: 'var(--atomix-primary-bg-subtle)',
|
|
66
|
+
border: '1px solid var(--atomix-primary-border-subtle)',
|
|
67
|
+
padding: 'var(--atomix-spacing-4)',
|
|
68
|
+
borderRadius: 'var(--atomix-border-radius)',
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
return (
|
|
72
|
+
<div data-testid="styled-component" style={style}>
|
|
73
|
+
Styled Component
|
|
74
|
+
</div>
|
|
75
|
+
);
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
// Component that uses theme context
|
|
79
|
+
const ThemeAwareComponent: React.FC = () => {
|
|
80
|
+
const { theme, activeTokens } = useTheme();
|
|
81
|
+
|
|
82
|
+
return (
|
|
83
|
+
<div>
|
|
84
|
+
<div data-testid="theme-name">{theme}</div>
|
|
85
|
+
<div data-testid="has-tokens">{activeTokens ? 'has-tokens' : 'no-tokens'}</div>
|
|
86
|
+
<StyledComponent />
|
|
87
|
+
</div>
|
|
88
|
+
);
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
// Mock theme metadata
|
|
92
|
+
const mockThemes: Record<string, ThemeMetadata> = {
|
|
93
|
+
'default-light': {
|
|
94
|
+
name: 'Default Light',
|
|
95
|
+
class: 'theme-default-light',
|
|
96
|
+
description: 'Default light theme',
|
|
97
|
+
supportsDarkMode: false,
|
|
98
|
+
status: 'stable',
|
|
99
|
+
},
|
|
100
|
+
'dark-complementary': {
|
|
101
|
+
name: 'Dark Complementary',
|
|
102
|
+
class: 'theme-dark-complementary',
|
|
103
|
+
description: 'Dark theme with complementary colors',
|
|
104
|
+
supportsDarkMode: true,
|
|
105
|
+
status: 'stable',
|
|
106
|
+
},
|
|
107
|
+
'high-contrast': {
|
|
108
|
+
name: 'High Contrast',
|
|
109
|
+
class: 'theme-high-contrast',
|
|
110
|
+
description: 'High contrast theme for accessibility',
|
|
111
|
+
supportsDarkMode: true,
|
|
112
|
+
status: 'stable',
|
|
113
|
+
a11y: {
|
|
114
|
+
contrastTarget: 7,
|
|
115
|
+
modes: ['dark', 'light'],
|
|
116
|
+
},
|
|
117
|
+
},
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
describe('ThemeProvider Integration', () => {
|
|
121
|
+
beforeEach(() => {
|
|
122
|
+
vi.clearAllMocks();
|
|
123
|
+
// Clear any injected styles
|
|
124
|
+
document.head.innerHTML = '';
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
afterEach(() => {
|
|
128
|
+
vi.restoreAllMocks();
|
|
129
|
+
document.head.innerHTML = '';
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
describe('CSS Variable Application to Components', () => {
|
|
133
|
+
it('should apply theme attributes to document element for string themes', async () => {
|
|
134
|
+
await render(
|
|
135
|
+
<ThemeProvider themes={mockThemes} defaultTheme="dark-complementary">
|
|
136
|
+
<ThemeAwareComponent />
|
|
137
|
+
</ThemeProvider>
|
|
138
|
+
);
|
|
139
|
+
|
|
140
|
+
expect(screen.getByTestId('theme-name')).toHaveTextContent('dark-complementary');
|
|
141
|
+
expect(screen.getByTestId('has-tokens')).toHaveTextContent('no-tokens');
|
|
142
|
+
|
|
143
|
+
const { applyThemeAttributes } = await import('../../utils/domUtils');
|
|
144
|
+
expect(applyThemeAttributes).toHaveBeenCalledWith('dark-complementary', 'data-theme');
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
it('should apply DesignTokens and inject CSS on mount', async () => {
|
|
148
|
+
const customTokens: DesignTokens = {
|
|
149
|
+
...defaultTokens,
|
|
150
|
+
'primary-text-emphasis': '#ff0000',
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
await render(
|
|
154
|
+
<ThemeProvider defaultTheme={customTokens}>
|
|
155
|
+
<ThemeAwareComponent />
|
|
156
|
+
</ThemeProvider>
|
|
157
|
+
);
|
|
158
|
+
|
|
159
|
+
// Check that theme is set
|
|
160
|
+
expect(screen.getByTestId('theme-name')).toHaveTextContent('tokens-theme');
|
|
161
|
+
expect(screen.getByTestId('has-tokens')).toHaveTextContent('has-tokens');
|
|
162
|
+
|
|
163
|
+
// Check that CSS was injected
|
|
164
|
+
const { injectCSS } = await import('../../utils/injectCSS');
|
|
165
|
+
expect(injectCSS).toHaveBeenCalledWith(
|
|
166
|
+
expect.stringContaining('--atomix-primary-text-emphasis: #ff0000'),
|
|
167
|
+
'theme-tokens-theme'
|
|
168
|
+
);
|
|
169
|
+
});
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
describe('Component Styling Verification', () => {
|
|
173
|
+
it('should render components with theme context', () => {
|
|
174
|
+
render(
|
|
175
|
+
<ThemeProvider>
|
|
176
|
+
<ThemeAwareComponent />
|
|
177
|
+
</ThemeProvider>
|
|
178
|
+
);
|
|
179
|
+
|
|
180
|
+
const component = screen.getByTestId('styled-component');
|
|
181
|
+
expect(component).toBeInTheDocument();
|
|
182
|
+
expect(component).toHaveTextContent('Styled Component');
|
|
183
|
+
expect(screen.getByTestId('theme-name')).toHaveTextContent('default');
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
it('should apply different DesignTokens themes to components', async () => {
|
|
187
|
+
const customTokens = createTokens({
|
|
188
|
+
'primary-text-emphasis': '#ff0000',
|
|
189
|
+
'primary-bg-subtle': '#f0f0f0',
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
render(
|
|
193
|
+
<ThemeProvider defaultTheme={customTokens}>
|
|
194
|
+
<ThemeAwareComponent />
|
|
195
|
+
</ThemeProvider>
|
|
196
|
+
);
|
|
197
|
+
|
|
198
|
+
expect(screen.getByTestId('theme-name')).toHaveTextContent('tokens-theme');
|
|
199
|
+
expect(screen.getByTestId('has-tokens')).toHaveTextContent('has-tokens');
|
|
200
|
+
|
|
201
|
+
// CSS injection should have occurred
|
|
202
|
+
const { injectCSS } = await import('../../utils/injectCSS');
|
|
203
|
+
expect(injectCSS).toHaveBeenCalledWith(
|
|
204
|
+
expect.stringContaining('--atomix-primary-text-emphasis: #ff0000'),
|
|
205
|
+
'theme-tokens-theme'
|
|
206
|
+
);
|
|
207
|
+
});
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
describe('Theme Persistence Integration', () => {
|
|
211
|
+
it('should restore theme from localStorage on mount', async () => {
|
|
212
|
+
const { createLocalStorageAdapter } = await import('../../utils/domUtils');
|
|
213
|
+
const mockStorage = {
|
|
214
|
+
getItem: vi.fn(() => 'dark-complementary'),
|
|
215
|
+
setItem: vi.fn(),
|
|
216
|
+
removeItem: vi.fn(),
|
|
217
|
+
isAvailable: vi.fn(() => true),
|
|
218
|
+
};
|
|
219
|
+
createLocalStorageAdapter.mockReturnValue(mockStorage);
|
|
220
|
+
|
|
221
|
+
render(
|
|
222
|
+
<ThemeProvider themes={mockThemes} enablePersistence={true}>
|
|
223
|
+
<ThemeAwareComponent />
|
|
224
|
+
</ThemeProvider>
|
|
225
|
+
);
|
|
226
|
+
|
|
227
|
+
expect(screen.getByTestId('theme-name')).toHaveTextContent('dark-complementary');
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
it('should persist theme changes to localStorage', async () => {
|
|
231
|
+
const { createLocalStorageAdapter } = await import('../../utils/domUtils');
|
|
232
|
+
const mockStorage = {
|
|
233
|
+
getItem: vi.fn(() => null),
|
|
234
|
+
setItem: vi.fn(),
|
|
235
|
+
removeItem: vi.fn(),
|
|
236
|
+
isAvailable: vi.fn(() => true),
|
|
237
|
+
};
|
|
238
|
+
createLocalStorageAdapter.mockReturnValue(mockStorage);
|
|
239
|
+
|
|
240
|
+
render(
|
|
241
|
+
<ThemeProvider themes={mockThemes} enablePersistence={true}>
|
|
242
|
+
<ThemeAwareComponent />
|
|
243
|
+
</ThemeProvider>
|
|
244
|
+
);
|
|
245
|
+
|
|
246
|
+
expect(mockStorage.setItem).toHaveBeenCalledWith('atomix-theme', 'default');
|
|
247
|
+
});
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
describe('Error Handling Integration', () => {
|
|
251
|
+
it('should continue rendering components even when theme loading fails', async () => {
|
|
252
|
+
const { loadThemeCSS } = await import('../../utils/domUtils');
|
|
253
|
+
loadThemeCSS.mockRejectedValueOnce(new Error('CSS load failed'));
|
|
254
|
+
|
|
255
|
+
render(
|
|
256
|
+
<ThemeProvider themes={mockThemes}>
|
|
257
|
+
<ThemeAwareComponent />
|
|
258
|
+
</ThemeProvider>
|
|
259
|
+
);
|
|
260
|
+
|
|
261
|
+
// Component should still render
|
|
262
|
+
expect(screen.getByTestId('styled-component')).toBeInTheDocument();
|
|
263
|
+
expect(screen.getByTestId('theme-name')).toHaveTextContent('default');
|
|
264
|
+
});
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
describe('Accessibility Integration', () => {
|
|
268
|
+
it('should apply high contrast theme correctly', async () => {
|
|
269
|
+
// High contrast tokens would have better contrast ratios
|
|
270
|
+
const highContrastTokens = createTokens({
|
|
271
|
+
'primary-text-emphasis': '#000000',
|
|
272
|
+
'primary-bg-subtle': '#ffffff',
|
|
273
|
+
'secondary-text-emphasis': '#000000',
|
|
274
|
+
'secondary-bg-subtle': '#ffffff',
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
render(
|
|
278
|
+
<ThemeProvider defaultTheme={highContrastTokens}>
|
|
279
|
+
<ThemeAwareComponent />
|
|
280
|
+
</ThemeProvider>
|
|
281
|
+
);
|
|
282
|
+
|
|
283
|
+
const { injectCSS } = await import('../../utils/injectCSS');
|
|
284
|
+
expect(injectCSS).toHaveBeenCalledWith(
|
|
285
|
+
expect.stringContaining('--atomix-primary-text-emphasis: #000000'),
|
|
286
|
+
'theme-tokens-theme'
|
|
287
|
+
);
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
it('should support data attributes for theme switching', async () => {
|
|
291
|
+
render(
|
|
292
|
+
<ThemeProvider
|
|
293
|
+
themes={mockThemes}
|
|
294
|
+
dataAttribute="data-bs-theme"
|
|
295
|
+
defaultTheme="high-contrast"
|
|
296
|
+
>
|
|
297
|
+
<ThemeAwareComponent />
|
|
298
|
+
</ThemeProvider>
|
|
299
|
+
);
|
|
300
|
+
|
|
301
|
+
const { applyThemeAttributes } = await import('../../utils/domUtils');
|
|
302
|
+
expect(applyThemeAttributes).toHaveBeenCalledWith('high-contrast', 'data-bs-theme');
|
|
303
|
+
});
|
|
304
|
+
});
|
|
305
|
+
});
|