@shohojdhara/atomix 0.3.0 → 0.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (144) hide show
  1. package/CHANGELOG.md +0 -1
  2. package/README.md +3 -5
  3. package/dist/atomix.css +753 -643
  4. package/dist/atomix.min.css +3 -5
  5. package/dist/index.d.ts +3075 -247
  6. package/dist/index.esm.js +20412 -16601
  7. package/dist/index.esm.js.map +1 -1
  8. package/dist/index.js +20379 -16605
  9. package/dist/index.js.map +1 -1
  10. package/dist/index.min.js +1 -1
  11. package/dist/index.min.js.map +1 -1
  12. package/package.json +1 -11
  13. package/src/components/AtomixGlass/AtomixGlass.test.tsx +21 -32
  14. package/src/components/AtomixGlass/AtomixGlass.tsx +55 -42
  15. package/src/components/AtomixGlass/AtomixGlassContainer.tsx +205 -57
  16. package/src/components/AtomixGlass/GlassFilter.tsx +22 -8
  17. package/src/components/AtomixGlass/__snapshots__/AtomixGlass.test.tsx.snap +221 -0
  18. package/src/components/AtomixGlass/atomixGLass.old.tsx +0 -3
  19. package/src/components/AtomixGlass/shader-utils.ts +8 -0
  20. package/src/components/AtomixGlass/stories/AtomixGlass.stories.tsx +319 -100
  21. package/src/components/AtomixGlass/stories/Examples.stories.tsx +601 -105
  22. package/src/components/AtomixGlass/stories/Modes.stories.tsx +30 -12
  23. package/src/components/AtomixGlass/stories/Playground.stories.tsx +173 -38
  24. package/src/components/AtomixGlass/stories/ShaderVariants.stories.tsx +18 -18
  25. package/src/components/AtomixGlass/stories/shared-components.tsx +27 -5
  26. package/src/components/Button/Button.tsx +62 -17
  27. package/src/components/Callout/Callout.test.tsx +8 -14
  28. package/src/components/Card/Card.tsx +103 -1
  29. package/src/components/Card/index.ts +3 -2
  30. package/src/components/Icon/index.ts +1 -1
  31. package/src/components/Modal/Modal.stories.tsx +29 -38
  32. package/src/components/Modal/Modal.tsx +4 -4
  33. package/src/components/Navigation/SideMenu/SideMenu.tsx +49 -41
  34. package/src/components/Navigation/SideMenu/SideMenuItem.tsx +63 -24
  35. package/src/components/Popover/Popover.tsx +1 -1
  36. package/src/components/VideoPlayer/VideoPlayer.stories.tsx +977 -400
  37. package/src/components/VideoPlayer/VideoPlayer.tsx +1 -6
  38. package/src/lib/composables/shared-mouse-tracker.ts +133 -0
  39. package/src/lib/composables/useAtomixGlass.ts +333 -145
  40. package/src/lib/index.ts +1 -4
  41. package/src/lib/theme/composeTheme.ts +375 -0
  42. package/src/lib/theme/config/index.ts +21 -0
  43. package/src/lib/theme/config/loader.ts +276 -0
  44. package/src/lib/theme/config/types.ts +98 -0
  45. package/src/lib/theme/config/validator.ts +326 -0
  46. package/src/lib/theme/constants.ts +183 -0
  47. package/src/lib/theme/core/ThemeCache.ts +283 -0
  48. package/src/lib/theme/core/ThemeEngine.test.ts +146 -0
  49. package/src/lib/theme/core/ThemeEngine.ts +657 -0
  50. package/src/lib/theme/core/ThemeRegistry.ts +284 -0
  51. package/src/lib/theme/core/ThemeValidator.ts +530 -0
  52. package/src/lib/theme/core/index.ts +24 -0
  53. package/src/lib/theme/createTheme.ts +521 -0
  54. package/src/lib/theme/devtools/CLI.ts +279 -0
  55. package/src/lib/theme/devtools/Inspector.tsx +594 -0
  56. package/src/lib/theme/devtools/Preview.tsx +392 -0
  57. package/src/lib/theme/devtools/index.ts +21 -0
  58. package/src/lib/theme/errors.test.ts +207 -0
  59. package/src/lib/theme/errors.ts +233 -0
  60. package/src/lib/theme/generateCSSVariables.ts +797 -0
  61. package/src/lib/theme/generators/CSSGenerator.ts +311 -0
  62. package/src/lib/theme/generators/ConfigGenerator.ts +287 -0
  63. package/src/lib/theme/generators/TypeGenerator.ts +228 -0
  64. package/src/lib/theme/generators/index.ts +21 -0
  65. package/src/lib/theme/i18n/index.ts +9 -0
  66. package/src/lib/theme/i18n/rtl.ts +325 -0
  67. package/src/lib/theme/index.ts +221 -10
  68. package/src/lib/theme/monitoring/ThemeAnalytics.ts +409 -0
  69. package/src/lib/theme/monitoring/index.ts +17 -0
  70. package/src/lib/theme/overrides/ComponentOverrides.ts +243 -0
  71. package/src/lib/theme/overrides/index.ts +15 -0
  72. package/src/lib/theme/runtime/ThemeErrorBoundary.tsx +233 -0
  73. package/src/lib/theme/runtime/ThemeManager.test.ts +176 -0
  74. package/src/lib/theme/runtime/ThemeManager.ts +442 -0
  75. package/src/lib/theme/runtime/ThemeProvider.tsx +318 -0
  76. package/src/lib/theme/runtime/index.ts +17 -0
  77. package/src/lib/theme/runtime/useTheme.ts +52 -0
  78. package/src/lib/theme/studio/ThemeStudio.tsx +312 -0
  79. package/src/lib/theme/studio/index.ts +8 -0
  80. package/src/lib/theme/themeUtils.ts +333 -0
  81. package/src/lib/theme/types.ts +340 -9
  82. package/src/lib/theme/utils.ts +23 -22
  83. package/src/lib/theme/whitelabel/WhiteLabelManager.ts +364 -0
  84. package/src/lib/theme/whitelabel/index.ts +13 -0
  85. package/src/lib/types/components.ts +148 -59
  86. package/src/styles/01-settings/_index.scss +2 -2
  87. package/src/styles/01-settings/_settings.badge.scss +3 -3
  88. package/src/styles/01-settings/_settings.border-radius.scss +1 -1
  89. package/src/styles/01-settings/_settings.callout.scss +1 -1
  90. package/src/styles/01-settings/_settings.card.scss +1 -1
  91. package/src/styles/01-settings/{_settings.maps.scss → _settings.design-tokens.scss} +163 -49
  92. package/src/styles/01-settings/_settings.input.scss +1 -1
  93. package/src/styles/01-settings/_settings.modal.scss +1 -1
  94. package/src/styles/01-settings/_settings.navbar.scss +1 -1
  95. package/src/styles/01-settings/_settings.spacing.scss +14 -13
  96. package/src/styles/01-settings/_settings.upload.scss +1 -1
  97. package/src/styles/03-generic/_generic.root.scss +131 -50
  98. package/src/styles/05-objects/_objects.block.scss +1 -1
  99. package/src/styles/06-components/_components.atomix-glass.scss +20 -22
  100. package/src/styles/06-components/_components.badge.scss +2 -2
  101. package/src/styles/06-components/_components.button.scss +1 -1
  102. package/src/styles/06-components/_components.callout.scss +1 -1
  103. package/src/styles/06-components/_components.card.scss +74 -2
  104. package/src/styles/06-components/_components.chart.scss +3 -3
  105. package/src/styles/06-components/_components.dropdown.scss +6 -0
  106. package/src/styles/06-components/_components.footer.scss +1 -1
  107. package/src/styles/06-components/_components.list-group.scss +1 -1
  108. package/src/styles/06-components/_components.list.scss +1 -1
  109. package/src/styles/06-components/_components.menu.scss +1 -1
  110. package/src/styles/06-components/_components.messages.scss +1 -1
  111. package/src/styles/06-components/_components.modal.scss +7 -2
  112. package/src/styles/06-components/_components.navbar.scss +1 -1
  113. package/src/styles/06-components/_components.popover.scss +10 -0
  114. package/src/styles/06-components/_components.product-review.scss +1 -1
  115. package/src/styles/06-components/_components.progress.scss +1 -1
  116. package/src/styles/06-components/_components.rating.scss +1 -1
  117. package/src/styles/06-components/_components.spinner.scss +1 -1
  118. package/src/styles/99-utilities/_utilities.background.scss +1 -1
  119. package/src/styles/99-utilities/_utilities.border.scss +28 -59
  120. package/src/styles/99-utilities/_utilities.gradient.scss +12 -0
  121. package/src/styles/99-utilities/_utilities.link.scss +1 -1
  122. package/src/styles/99-utilities/_utilities.position.scss +8 -15
  123. package/src/styles/99-utilities/_utilities.scss +2 -0
  124. package/src/styles/99-utilities/_utilities.spacing.scss +76 -121
  125. package/src/styles/99-utilities/_utilities.text.scss +31 -50
  126. package/dist/themes/applemix.css +0 -15411
  127. package/dist/themes/applemix.min.css +0 -72
  128. package/dist/themes/boomdevs.css +0 -15001
  129. package/dist/themes/boomdevs.min.css +0 -405
  130. package/dist/themes/esrar.css +0 -17195
  131. package/dist/themes/esrar.min.css +0 -189
  132. package/dist/themes/flashtrade.css +0 -16408
  133. package/dist/themes/flashtrade.min.css +0 -192
  134. package/dist/themes/mashroom.css +0 -29900
  135. package/dist/themes/mashroom.min.css +0 -403
  136. package/dist/themes/shaj-default.css +0 -16024
  137. package/dist/themes/shaj-default.min.css +0 -500
  138. package/src/lib/theme/ThemeManager.stories.tsx +0 -472
  139. package/src/lib/theme/ThemeManager.test.ts +0 -186
  140. package/src/lib/theme/ThemeManager.ts +0 -501
  141. package/src/lib/theme/ThemeProvider.tsx +0 -227
  142. package/src/lib/theme/useTheme.test.tsx +0 -66
  143. package/src/lib/theme/useTheme.ts +0 -80
  144. package/src/lib/theme/utils.test.ts +0 -140
@@ -1,472 +0,0 @@
1
- import type { Meta, StoryObj } from '@storybook/react';
2
- import React, { useState } from 'react';
3
- import { ThemeProvider, useTheme } from './index';
4
- import { themesConfig } from '@/themes/themes.config';
5
- import { Button } from '@/components/Button/Button';
6
- import { Card } from '@/components/Card/Card';
7
- import { ColorModeToggle } from '@/components/ColorModeToggle/ColorModeToggle';
8
-
9
- /**
10
- * Theme Manager
11
- *
12
- * The Atomix Theme Manager provides dynamic theme switching capabilities
13
- * for both React and vanilla JavaScript applications.
14
- *
15
- * ## Features
16
- * - Dynamic theme loading
17
- * - Theme persistence
18
- * - Preloading support
19
- * - SSR compatible
20
- * - Event system
21
- */
22
- const meta = {
23
- title: 'Utilities/Theme Manager',
24
- parameters: {
25
- docs: {
26
- description: {
27
- component: 'Dynamic theme management system for Atomix Design System. Supports theme switching, persistence, and preloading.',
28
- },
29
- },
30
- },
31
- } satisfies Meta;
32
-
33
- export default meta;
34
- type Story = StoryObj<typeof meta>;
35
-
36
- /**
37
- * Theme Switcher Component
38
- */
39
- function ThemeSwitcher() {
40
- const { theme, setTheme, availableThemes, isLoading, error } = useTheme();
41
-
42
- if (error) {
43
- return (
44
- <div style={{ padding: '1rem', background: 'var(--atomix-red-1)', border: '1px solid var(--atomix-red-4)', borderRadius: '8px' }}>
45
- <strong>Error:</strong> {error.message}
46
- </div>
47
- );
48
- }
49
-
50
- return (
51
- <div style={{ display: 'flex', flexDirection: 'column', gap: '1rem' }}>
52
- <div>
53
- <strong>Current Theme:</strong> {theme}
54
- {isLoading && <span style={{ marginLeft: '0.5rem', color: 'var(--atomix-primary-6)' }}>Loading...</span>}
55
- </div>
56
-
57
- <div style={{ display: 'flex', flexWrap: 'wrap', gap: '0.5rem' }}>
58
- {availableThemes.map((t) => (
59
- <Button
60
- key={t.class}
61
- variant={theme === t.class ? 'primary' : 'secondary'}
62
- size="sm"
63
- onClick={() => setTheme(t.class!)}
64
- disabled={isLoading}
65
- aria-label={`Switch to ${t.name} theme`}
66
- aria-pressed={theme === t.class}
67
- >
68
- {t.name}
69
- </Button>
70
- ))}
71
- </div>
72
- </div>
73
- );
74
- }
75
-
76
- /**
77
- * Theme Info Display
78
- */
79
- function ThemeInfo() {
80
- const { theme, availableThemes } = useTheme();
81
- const currentThemeMetadata = availableThemes.find(t => t.class === theme);
82
-
83
- if (!currentThemeMetadata) {
84
- return null;
85
- }
86
-
87
- return (
88
- <div style={{ display: 'grid', gap: '0.5rem', fontSize: '0.875rem' }}>
89
- <div><strong>Name:</strong> {currentThemeMetadata.name}</div>
90
- {currentThemeMetadata.description && (
91
- <div><strong>Description:</strong> {currentThemeMetadata.description}</div>
92
- )}
93
- {currentThemeMetadata.author && (
94
- <div><strong>Author:</strong> {currentThemeMetadata.author}</div>
95
- )}
96
- {currentThemeMetadata.version && (
97
- <div><strong>Version:</strong> {currentThemeMetadata.version}</div>
98
- )}
99
- {currentThemeMetadata.status && (
100
- <div><strong>Status:</strong> {currentThemeMetadata.status}</div>
101
- )}
102
- </div>
103
- );
104
- }
105
-
106
- /**
107
- * Basic Theme Switching
108
- *
109
- * Demonstrates basic theme switching functionality with the ThemeProvider and useTheme hook.
110
- */
111
- export const BasicThemeSwitching: Story = {
112
- render: () => (
113
- <ThemeProvider
114
- themes={themesConfig.metadata}
115
- defaultTheme="shaj-default"
116
- enablePersistence={false}
117
- >
118
- <div style={{ padding: '2rem' }}>
119
- <h2>Theme Switcher</h2>
120
- <p>Click a button to switch themes</p>
121
- <ThemeSwitcher />
122
- </div>
123
- </ThemeProvider>
124
- ),
125
- };
126
-
127
- /**
128
- * With Persistence
129
- *
130
- * Theme manager with localStorage persistence enabled.
131
- * The selected theme will be remembered across page reloads.
132
- */
133
- export const WithPersistence: Story = {
134
- render: () => (
135
- <ThemeProvider
136
- themes={themesConfig.metadata}
137
- defaultTheme="shaj-default"
138
- enablePersistence={true}
139
- storageKey="storybook-theme-demo"
140
- >
141
- <div style={{ padding: '2rem' }}>
142
- <h2>Theme Switcher with Persistence</h2>
143
- <p>Your theme selection will be saved to localStorage</p>
144
- <ThemeSwitcher />
145
- <div style={{ marginTop: '1rem', padding: '1rem', background: 'var(--atomix-gray-1)', borderRadius: '8px' }}>
146
- <small>
147
- <strong>Note:</strong> Reload the page to see persistence in action.
148
- The theme will be restored from localStorage.
149
- </small>
150
- </div>
151
- </div>
152
- </ThemeProvider>
153
- ),
154
- };
155
-
156
- /**
157
- * Theme Info Display
158
- *
159
- * Shows detailed information about the current theme.
160
- */
161
- export const ThemeInfoDisplay: Story = {
162
- render: () => (
163
- <ThemeProvider
164
- themes={themesConfig.metadata}
165
- defaultTheme="shaj-default"
166
- >
167
- <div style={{ padding: '2rem', display: 'grid', gap: '1.5rem' }}>
168
- <Card>
169
- <h2>Theme Switcher</h2>
170
- <ThemeSwitcher />
171
- </Card>
172
-
173
- <Card>
174
- <h2>Current Theme Details</h2>
175
- <ThemeInfo />
176
- </Card>
177
- </div>
178
- </ThemeProvider>
179
- ),
180
- };
181
-
182
- /**
183
- * With Preloading
184
- *
185
- * Demonstrates theme preloading for faster switching.
186
- */
187
- export const WithPreloading: Story = {
188
- render: () => {
189
- function PreloadDemo() {
190
- const { preloadTheme, availableThemes } = useTheme();
191
- const [preloading, setPreloading] = useState<string | null>(null);
192
- const [preloaded, setPreloaded] = useState<Set<string>>(new Set());
193
-
194
- const handlePreload = async (themeName: string) => {
195
- setPreloading(themeName);
196
- try {
197
- await preloadTheme(themeName);
198
- setPreloaded(prev => new Set([...prev, themeName]));
199
- } catch (error) {
200
- console.error('Failed to preload theme:', error);
201
- } finally {
202
- setPreloading(null);
203
- }
204
- };
205
-
206
- return (
207
- <div style={{ display: 'grid', gap: '1.5rem' }}>
208
- <Card>
209
- <h2>Theme Switcher</h2>
210
- <ThemeSwitcher />
211
- </Card>
212
-
213
- <Card>
214
- <h2>Preload Themes</h2>
215
- <p style={{ fontSize: '0.875rem', color: 'var(--atomix-gray-7)' }}>
216
- Preload themes for instant switching
217
- </p>
218
- <div style={{ display: 'flex', flexWrap: 'wrap', gap: '0.5rem', marginTop: '1rem' }}>
219
- {availableThemes.map((t) => (
220
- <Button
221
- key={t.class}
222
- variant={preloaded.has(t.class!) ? 'success' : 'outline'}
223
- size="sm"
224
- onClick={() => handlePreload(t.class!)}
225
- disabled={preloading === t.class}
226
- >
227
- {preloading === t.class ? 'Preloading...' :
228
- preloaded.has(t.class!) ? `✓ ${t.name}` :
229
- `Preload ${t.name}`}
230
- </Button>
231
- ))}
232
- </div>
233
- </Card>
234
- </div>
235
- );
236
- }
237
-
238
- return (
239
- <ThemeProvider
240
- themes={themesConfig.metadata}
241
- defaultTheme="shaj-default"
242
- preload={['shaj-default']}
243
- >
244
- <div style={{ padding: '2rem' }}>
245
- <PreloadDemo />
246
- </div>
247
- </ThemeProvider>
248
- );
249
- },
250
- };
251
-
252
- /**
253
- * With Color Mode Toggle
254
- *
255
- * Demonstrates integration with the ColorModeToggle component.
256
- */
257
- export const WithColorModeToggle: Story = {
258
- render: () => (
259
- <ThemeProvider
260
- themes={themesConfig.metadata}
261
- defaultTheme="shaj-default"
262
- >
263
- <div style={{ padding: '2rem' }}>
264
- <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '2rem' }}>
265
- <h2>Theme & Color Mode Controls</h2>
266
- <ColorModeToggle />
267
- </div>
268
-
269
- <Card>
270
- <ThemeSwitcher />
271
- </Card>
272
-
273
- <div style={{ marginTop: '1.5rem', padding: '1rem', background: 'var(--atomix-gray-1)', borderRadius: '8px' }}>
274
- <p style={{ fontSize: '0.875rem', margin: 0 }}>
275
- <strong>Tip:</strong> Use the moon/sun icon to toggle between light and dark modes.
276
- Each theme supports both color modes.
277
- </p>
278
- </div>
279
- </div>
280
- </ThemeProvider>
281
- ),
282
- };
283
-
284
- /**
285
- * Component Showcase
286
- *
287
- * Shows how different components look in the current theme.
288
- */
289
- export const ComponentShowcase: Story = {
290
- render: () => (
291
- <ThemeProvider
292
- themes={themesConfig.metadata}
293
- defaultTheme="shaj-default"
294
- >
295
- <div style={{ padding: '2rem', display: 'grid', gap: '1.5rem' }}>
296
- <Card>
297
- <h2>Theme Switcher</h2>
298
- <ThemeSwitcher />
299
- </Card>
300
-
301
- <Card>
302
- <h2>Component Showcase</h2>
303
- <p>See how components look in the current theme</p>
304
-
305
- <div style={{ display: 'grid', gap: '1.5rem', marginTop: '1rem' }}>
306
- <div>
307
- <h3 style={{ marginBottom: '0.5rem' }}>Buttons</h3>
308
- <div style={{ display: 'flex', gap: '0.5rem', flexWrap: 'wrap' }}>
309
- <Button variant="primary">Primary</Button>
310
- <Button variant="secondary">Secondary</Button>
311
- <Button variant="success">Success</Button>
312
- <Button variant="outline">Outline</Button>
313
- </div>
314
- </div>
315
-
316
- <div>
317
- <h3 style={{ marginBottom: '0.5rem' }}>Cards</h3>
318
- <div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(150px, 1fr))', gap: '1rem' }}>
319
- <Card style={{ padding: '1rem' }}>
320
- <h4 style={{ margin: '0 0 0.5rem 0' }}>Card 1</h4>
321
- <p style={{ margin: 0, fontSize: '0.875rem' }}>Content here</p>
322
- </Card>
323
- <Card style={{ padding: '1rem' }}>
324
- <h4 style={{ margin: '0 0 0.5rem 0' }}>Card 2</h4>
325
- <p style={{ margin: 0, fontSize: '0.875rem' }}>Content here</p>
326
- </Card>
327
- <Card style={{ padding: '1rem' }}>
328
- <h4 style={{ margin: '0 0 0.5rem 0' }}>Card 3</h4>
329
- <p style={{ margin: 0, fontSize: '0.875rem' }}>Content here</p>
330
- </Card>
331
- </div>
332
- </div>
333
- </div>
334
- </Card>
335
- </div>
336
- </ThemeProvider>
337
- ),
338
- };
339
-
340
- /**
341
- * Error Handling
342
- *
343
- * Demonstrates error handling when theme loading fails.
344
- */
345
- export const ErrorHandling: Story = {
346
- render: () => {
347
- function ErrorDemo() {
348
- const { setTheme, error } = useTheme();
349
- const [customError, setCustomError] = useState<string | null>(null);
350
-
351
- const handleInvalidTheme = async () => {
352
- try {
353
- await setTheme('non-existent-theme');
354
- } catch (err) {
355
- setCustomError(err instanceof Error ? err.message : 'Unknown error');
356
- }
357
- };
358
-
359
- return (
360
- <div style={{ display: 'grid', gap: '1.5rem' }}>
361
- <Card>
362
- <h2>Theme Switcher</h2>
363
- <ThemeSwitcher />
364
- </Card>
365
-
366
- <Card>
367
- <h2>Error Handling Demo</h2>
368
- <p style={{ fontSize: '0.875rem', color: 'var(--atomix-gray-7)' }}>
369
- Click the button below to trigger an error by trying to load a non-existent theme
370
- </p>
371
- <Button variant="outline" onClick={handleInvalidTheme}>
372
- Try Invalid Theme
373
- </Button>
374
-
375
- {(error || customError) && (
376
- <div style={{
377
- marginTop: '1rem',
378
- padding: '1rem',
379
- background: 'var(--atomix-red-1)',
380
- border: '1px solid var(--atomix-red-4)',
381
- borderRadius: '8px',
382
- color: 'var(--atomix-red-9)'
383
- }}>
384
- <strong>Error:</strong> {error?.message || customError}
385
- </div>
386
- )}
387
- </Card>
388
- </div>
389
- );
390
- }
391
-
392
- return (
393
- <ThemeProvider
394
- themes={themesConfig.metadata}
395
- defaultTheme="shaj-default"
396
- onError={(error, themeName) => {
397
- console.error(`Failed to load theme "${themeName}":`, error);
398
- }}
399
- >
400
- <div style={{ padding: '2rem' }}>
401
- <ErrorDemo />
402
- </div>
403
- </ThemeProvider>
404
- );
405
- },
406
- };
407
-
408
- /**
409
- * Custom Callbacks
410
- *
411
- * Demonstrates using custom callbacks for theme changes.
412
- */
413
- export const CustomCallbacks: Story = {
414
- render: () => {
415
- function CallbackDemo() {
416
- const [log, setLog] = useState<string[]>([]);
417
-
418
- const addLog = (message: string) => {
419
- setLog(prev => [...prev, `${new Date().toLocaleTimeString()}: ${message}`]);
420
- };
421
-
422
- return (
423
- <ThemeProvider
424
- themes={themesConfig.metadata}
425
- defaultTheme="shaj-default"
426
- onThemeChange={(theme) => {
427
- addLog(`Theme changed to: ${theme}`);
428
- }}
429
- onError={(error, themeName) => {
430
- addLog(`Error loading theme "${themeName}": ${error.message}`);
431
- }}
432
- >
433
- <div style={{ display: 'grid', gap: '1.5rem' }}>
434
- <Card>
435
- <h2>Theme Switcher</h2>
436
- <ThemeSwitcher />
437
- </Card>
438
-
439
- <Card>
440
- <h2>Event Log</h2>
441
- <div style={{
442
- maxHeight: '200px',
443
- overflowY: 'auto',
444
- padding: '1rem',
445
- background: 'var(--atomix-gray-1)',
446
- borderRadius: '8px',
447
- fontFamily: 'monospace',
448
- fontSize: '0.75rem'
449
- }}>
450
- {log.length === 0 ? (
451
- <div style={{ color: 'var(--atomix-gray-6)' }}>No events yet. Switch themes to see logs.</div>
452
- ) : (
453
- log.map((entry, index) => (
454
- <div key={index} style={{ marginBottom: '0.25rem' }}>
455
- {entry}
456
- </div>
457
- ))
458
- )}
459
- </div>
460
- </Card>
461
- </div>
462
- </ThemeProvider>
463
- );
464
- }
465
-
466
- return (
467
- <div style={{ padding: '2rem' }}>
468
- <CallbackDemo />
469
- </div>
470
- );
471
- },
472
- };
@@ -1,186 +0,0 @@
1
- import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
2
- import { ThemeManager } from './ThemeManager';
3
- import * as utils from './utils';
4
- import type { ThemeManagerConfig } from './types';
5
-
6
- // Mock utils
7
- vi.mock('./utils', () => ({
8
- isBrowser: vi.fn(() => true),
9
- isServer: vi.fn(() => false),
10
- loadThemeCSS: vi.fn(() => Promise.resolve()),
11
- removeThemeCSS: vi.fn(),
12
- removeAllThemeCSS: vi.fn(),
13
- applyThemeAttributes: vi.fn(),
14
- removeThemeAttributes: vi.fn(),
15
- getCurrentThemeFromDOM: vi.fn(() => null),
16
- getSystemTheme: vi.fn(() => 'light'),
17
- isThemeLoaded: vi.fn(() => false),
18
- validateThemeMetadata: vi.fn(() => ({ valid: true, errors: [], warnings: [] })),
19
- isValidThemeName: vi.fn(() => true),
20
- createLocalStorageAdapter: vi.fn(() => ({
21
- getItem: vi.fn(),
22
- setItem: vi.fn(),
23
- removeItem: vi.fn(),
24
- isAvailable: vi.fn(() => true),
25
- })),
26
- }));
27
-
28
- describe('ThemeManager', () => {
29
- const mockThemes = {
30
- 'theme-1': { name: 'Theme 1', class: 'theme-1' },
31
- 'theme-2': { name: 'Theme 2', class: 'theme-2' },
32
- };
33
-
34
- const defaultConfig: ThemeManagerConfig = {
35
- themes: mockThemes,
36
- defaultTheme: 'theme-1',
37
- };
38
-
39
- let themeManager: ThemeManager;
40
-
41
- beforeEach(() => {
42
- vi.clearAllMocks();
43
- themeManager = new ThemeManager(defaultConfig);
44
- });
45
-
46
- afterEach(() => {
47
- themeManager.destroy();
48
- });
49
-
50
- describe('Initialization', () => {
51
- it('should initialize with default theme', () => {
52
- expect(themeManager.getTheme()).toBe('theme-1');
53
- });
54
-
55
- it('should throw error if themes config is missing', () => {
56
- expect(() => new ThemeManager({} as any)).toThrow('ThemeManager: themes configuration is required');
57
- });
58
-
59
- it('should throw error if default theme is not found', () => {
60
- expect(() => new ThemeManager({
61
- themes: mockThemes,
62
- defaultTheme: 'non-existent',
63
- })).toThrow('ThemeManager: default theme "non-existent" not found');
64
- });
65
-
66
- it('should load theme from storage if persistence is enabled', () => {
67
- const mockGetItem = vi.fn(() => 'theme-2');
68
- vi.mocked(utils.createLocalStorageAdapter).mockReturnValue({
69
- getItem: mockGetItem,
70
- setItem: vi.fn(),
71
- removeItem: vi.fn(),
72
- isAvailable: vi.fn(() => true),
73
- });
74
-
75
- const tm = new ThemeManager({
76
- ...defaultConfig,
77
- enablePersistence: true,
78
- });
79
-
80
- expect(tm.getTheme()).toBe('theme-2');
81
- });
82
- });
83
-
84
- describe('Theme Switching', () => {
85
- it('should set theme successfully', async () => {
86
- await themeManager.setTheme('theme-2');
87
- expect(themeManager.getTheme()).toBe('theme-2');
88
- expect(utils.loadThemeCSS).toHaveBeenCalledWith('theme-2', '/themes', false, null);
89
- expect(utils.applyThemeAttributes).toHaveBeenCalledWith('theme-2', 'data-theme');
90
- });
91
-
92
- it('should not reload if theme is already active', async () => {
93
- await themeManager.setTheme('theme-1');
94
- expect(utils.loadThemeCSS).not.toHaveBeenCalled();
95
- });
96
-
97
- it('should force reload if force option is true', async () => {
98
- await themeManager.setTheme('theme-1', { force: true });
99
- expect(utils.loadThemeCSS).toHaveBeenCalledWith('theme-1', '/themes', false, null);
100
- });
101
-
102
- it('should throw error for invalid theme', async () => {
103
- await expect(themeManager.setTheme('invalid-theme')).rejects.toThrow('Theme "invalid-theme" not found');
104
- });
105
-
106
- it('should emit themeChange event', async () => {
107
- const spy = vi.fn();
108
- themeManager.on('themeChange', spy);
109
- await themeManager.setTheme('theme-2');
110
- expect(spy).toHaveBeenCalledWith(expect.objectContaining({
111
- previousTheme: 'theme-1',
112
- currentTheme: 'theme-2',
113
- source: 'user',
114
- }));
115
- });
116
-
117
- it('should fallback to default theme on error if fallbackOnError is true', async () => {
118
- vi.mocked(utils.loadThemeCSS).mockRejectedValueOnce(new Error('Load failed'));
119
-
120
- await themeManager.setTheme('theme-2', { fallbackOnError: true });
121
-
122
- expect(themeManager.getTheme()).toBe('theme-1');
123
- // Should have tried to load theme-2 first
124
- expect(utils.loadThemeCSS).toHaveBeenCalledWith('theme-2', '/themes', false, null);
125
- // Then should have tried to load theme-1 (default) - actually default might be loaded or not
126
- // If default is already loaded (it is initialized), it might skip loading CSS unless forced
127
- // But setTheme calls preloadTheme which checks isThemeLoaded.
128
- });
129
- });
130
-
131
- describe('Preloading', () => {
132
- it('should preload theme', async () => {
133
- await themeManager.preloadTheme('theme-2');
134
- expect(utils.loadThemeCSS).toHaveBeenCalledWith('theme-2', '/themes', false, null);
135
- expect(themeManager.isThemeLoaded('theme-2')).toBe(true);
136
- });
137
-
138
- it('should not preload if already loaded', async () => {
139
- vi.mocked(utils.isThemeLoaded).mockReturnValue(true);
140
- await themeManager.preloadTheme('theme-2');
141
- // ThemeManager checks its own loadedThemes set OR utils.isThemeLoaded
142
- // Since we mocked utils.isThemeLoaded to return true, it might skip loading
143
- // But ThemeManager.isThemeLoaded calls utils.isThemeLoaded
144
- });
145
- });
146
-
147
- describe('Persistence', () => {
148
- it('should save theme to storage on change', async () => {
149
- const mockSetItem = vi.fn();
150
- vi.mocked(utils.createLocalStorageAdapter).mockReturnValue({
151
- getItem: vi.fn(),
152
- setItem: mockSetItem,
153
- removeItem: vi.fn(),
154
- isAvailable: vi.fn(() => true),
155
- });
156
-
157
- const tm = new ThemeManager({
158
- ...defaultConfig,
159
- enablePersistence: true,
160
- storageKey: 'test-key',
161
- });
162
-
163
- await tm.setTheme('theme-2');
164
- expect(mockSetItem).toHaveBeenCalledWith('test-key', 'theme-2');
165
- });
166
-
167
- it('should enable/disable persistence', () => {
168
- const mockSetItem = vi.fn();
169
- const mockRemoveItem = vi.fn();
170
- vi.mocked(utils.createLocalStorageAdapter).mockReturnValue({
171
- getItem: vi.fn(),
172
- setItem: mockSetItem,
173
- removeItem: mockRemoveItem,
174
- isAvailable: vi.fn(() => true),
175
- });
176
-
177
- const tm = new ThemeManager(defaultConfig);
178
-
179
- tm.enablePersistence('new-key');
180
- expect(mockSetItem).toHaveBeenCalledWith('new-key', 'theme-1');
181
-
182
- tm.disablePersistence();
183
- expect(mockRemoveItem).toHaveBeenCalledWith('new-key');
184
- });
185
- });
186
- });