@shohojdhara/atomix 0.3.1 → 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 (90) hide show
  1. package/CHANGELOG.md +0 -1
  2. package/README.md +3 -5
  3. package/dist/atomix.css +458 -552
  4. package/dist/atomix.min.css +3 -3
  5. package/dist/index.d.ts +2435 -358
  6. package/dist/index.esm.js +5758 -1901
  7. package/dist/index.esm.js.map +1 -1
  8. package/dist/index.js +5768 -1933
  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/lib/composables/useAtomixGlass.ts +46 -46
  14. package/src/lib/index.ts +1 -4
  15. package/src/lib/theme/config/index.ts +21 -0
  16. package/src/lib/theme/config/loader.ts +276 -0
  17. package/src/lib/theme/config/types.ts +98 -0
  18. package/src/lib/theme/config/validator.ts +326 -0
  19. package/src/lib/theme/constants.ts +183 -0
  20. package/src/lib/theme/core/ThemeCache.ts +283 -0
  21. package/src/lib/theme/core/ThemeEngine.test.ts +146 -0
  22. package/src/lib/theme/core/ThemeEngine.ts +657 -0
  23. package/src/lib/theme/core/ThemeRegistry.ts +284 -0
  24. package/src/lib/theme/core/ThemeValidator.ts +530 -0
  25. package/src/lib/theme/core/index.ts +24 -0
  26. package/src/lib/theme/createTheme.ts +81 -70
  27. package/src/lib/theme/devtools/CLI.ts +279 -0
  28. package/src/lib/theme/devtools/Inspector.tsx +594 -0
  29. package/src/lib/theme/devtools/Preview.tsx +392 -0
  30. package/src/lib/theme/devtools/index.ts +21 -0
  31. package/src/lib/theme/errors.test.ts +207 -0
  32. package/src/lib/theme/errors.ts +233 -0
  33. package/src/lib/theme/generateCSSVariables.ts +93 -9
  34. package/src/lib/theme/generators/CSSGenerator.ts +311 -0
  35. package/src/lib/theme/generators/ConfigGenerator.ts +287 -0
  36. package/src/lib/theme/generators/TypeGenerator.ts +228 -0
  37. package/src/lib/theme/generators/index.ts +21 -0
  38. package/src/lib/theme/i18n/index.ts +9 -0
  39. package/src/lib/theme/i18n/rtl.ts +325 -0
  40. package/src/lib/theme/index.ts +155 -11
  41. package/src/lib/theme/monitoring/ThemeAnalytics.ts +409 -0
  42. package/src/lib/theme/monitoring/index.ts +17 -0
  43. package/src/lib/theme/overrides/ComponentOverrides.ts +243 -0
  44. package/src/lib/theme/overrides/index.ts +15 -0
  45. package/src/lib/theme/runtime/ThemeErrorBoundary.tsx +233 -0
  46. package/src/lib/theme/runtime/ThemeManager.test.ts +176 -0
  47. package/src/lib/theme/runtime/ThemeManager.ts +442 -0
  48. package/src/lib/theme/runtime/ThemeProvider.tsx +318 -0
  49. package/src/lib/theme/runtime/index.ts +17 -0
  50. package/src/lib/theme/runtime/useTheme.ts +52 -0
  51. package/src/lib/theme/studio/ThemeStudio.tsx +312 -0
  52. package/src/lib/theme/studio/index.ts +8 -0
  53. package/src/lib/theme/types.ts +3 -1
  54. package/src/lib/theme/utils.ts +23 -22
  55. package/src/lib/theme/whitelabel/WhiteLabelManager.ts +364 -0
  56. package/src/lib/theme/whitelabel/index.ts +13 -0
  57. package/src/styles/01-settings/_settings.badge.scss +1 -1
  58. package/src/styles/01-settings/_settings.callout.scss +1 -1
  59. package/src/styles/01-settings/_settings.card.scss +1 -1
  60. package/src/styles/01-settings/_settings.input.scss +1 -1
  61. package/src/styles/01-settings/_settings.navbar.scss +1 -1
  62. package/src/styles/01-settings/_settings.upload.scss +1 -1
  63. package/src/styles/06-components/_components.chart.scss +2 -2
  64. package/src/styles/99-utilities/_utilities.border.scss +27 -58
  65. package/src/styles/99-utilities/_utilities.gradient.scss +12 -0
  66. package/src/styles/99-utilities/_utilities.position.scss +8 -15
  67. package/src/styles/99-utilities/_utilities.scss +2 -0
  68. package/src/styles/99-utilities/_utilities.spacing.scss +76 -121
  69. package/src/styles/99-utilities/_utilities.text.scss +30 -49
  70. package/dist/themes/applemix.css +0 -15615
  71. package/dist/themes/applemix.min.css +0 -70
  72. package/dist/themes/boomdevs.css +0 -15193
  73. package/dist/themes/boomdevs.min.css +0 -403
  74. package/dist/themes/esrar.css +0 -17399
  75. package/dist/themes/esrar.min.css +0 -187
  76. package/dist/themes/flashtrade.css +0 -16613
  77. package/dist/themes/flashtrade.min.css +0 -190
  78. package/dist/themes/mashroom.css +0 -30104
  79. package/dist/themes/mashroom.min.css +0 -401
  80. package/dist/themes/shaj-default.css +0 -16228
  81. package/dist/themes/shaj-default.min.css +0 -498
  82. package/src/lib/theme/ThemeManager.integration.test.ts +0 -124
  83. package/src/lib/theme/ThemeManager.stories.tsx +0 -472
  84. package/src/lib/theme/ThemeManager.test.ts +0 -190
  85. package/src/lib/theme/ThemeManager.ts +0 -645
  86. package/src/lib/theme/ThemeProvider.tsx +0 -377
  87. package/src/lib/theme/createTheme.test.ts +0 -475
  88. package/src/lib/theme/useTheme.test.tsx +0 -67
  89. package/src/lib/theme/useTheme.ts +0 -64
  90. package/src/lib/theme/utils.test.ts +0 -140
@@ -1,475 +0,0 @@
1
- /**
2
- * createTheme Tests
3
- *
4
- * Tests for the createTheme function and related utilities
5
- */
6
-
7
- import { describe, it, expect } from 'vitest';
8
- import { createTheme } from './createTheme';
9
- import { generateCSSVariables } from './generateCSSVariables';
10
- import { mergeTheme, extendTheme, createThemeVariants } from './composeTheme';
11
- import { lighten, darken, alpha, getContrastText } from './themeUtils';
12
-
13
- describe('createTheme', () => {
14
- it('should create a theme with default values', () => {
15
- const theme = createTheme();
16
-
17
- expect(theme).toBeDefined();
18
- expect(theme.__isJSTheme).toBe(true);
19
- expect(theme.palette).toBeDefined();
20
- expect(theme.typography).toBeDefined();
21
- expect(theme.spacing).toBeDefined();
22
- expect(theme.breakpoints).toBeDefined();
23
- });
24
-
25
- it('should create a theme with custom palette', () => {
26
- const theme = createTheme({
27
- palette: {
28
- primary: { main: '#FF0000' },
29
- secondary: { main: '#00FF00' },
30
- },
31
- });
32
-
33
- expect(theme.palette.primary.main).toBe('#FF0000');
34
- expect(theme.palette.secondary.main).toBe('#00FF00');
35
- expect(theme.palette.primary.light).toBeDefined();
36
- expect(theme.palette.primary.dark).toBeDefined();
37
- expect(theme.palette.primary.contrastText).toBeDefined();
38
- });
39
-
40
- it('should create a theme with custom typography', () => {
41
- const theme = createTheme({
42
- typography: {
43
- fontFamily: 'Arial, sans-serif',
44
- fontSize: 16,
45
- },
46
- });
47
-
48
- expect(theme.typography.fontFamily).toBe('Arial, sans-serif');
49
- expect(theme.typography.fontSize).toBe(16);
50
- });
51
-
52
- it('should create spacing function', () => {
53
- const theme = createTheme({ spacing: 8 });
54
-
55
- expect(theme.spacing(1)).toBe('8px');
56
- expect(theme.spacing(2)).toBe('16px');
57
- expect(theme.spacing(1, 2)).toBe('8px 16px');
58
- });
59
-
60
- it('should create breakpoints with helpers', () => {
61
- const theme = createTheme();
62
-
63
- expect(theme.breakpoints.up('sm')).toContain('min-width');
64
- expect(theme.breakpoints.down('md')).toContain('max-width');
65
- expect(theme.breakpoints.between('sm', 'lg')).toContain('min-width');
66
- });
67
-
68
- it('should merge multiple theme options', () => {
69
- const theme = createTheme(
70
- { palette: { primary: { main: '#000' } } },
71
- { palette: { secondary: { main: '#FFF' } } }
72
- );
73
-
74
- expect(theme.palette.primary.main).toBe('#000');
75
- expect(theme.palette.secondary.main).toBe('#FFF');
76
- });
77
- });
78
-
79
- describe('CSS Variable Generation', () => {
80
- it('should generate CSS variables from theme', () => {
81
- const theme = createTheme({
82
- palette: {
83
- primary: { main: '#7AFFD7' },
84
- },
85
- });
86
-
87
- const css = generateCSSVariables(theme);
88
-
89
- expect(css).toContain('--atomix-primary');
90
- expect(css).toContain('#7AFFD7');
91
- expect(css).toContain(':root');
92
- });
93
-
94
- it('should generate CSS variables with custom selector', () => {
95
- const theme = createTheme();
96
- const css = generateCSSVariables(theme, { selector: '.my-theme' });
97
-
98
- expect(css).toContain('.my-theme');
99
- });
100
-
101
- it('should generate CSS variables with custom prefix', () => {
102
- const theme = createTheme();
103
- const css = generateCSSVariables(theme, { prefix: 'custom' });
104
-
105
- expect(css).toContain('--custom-primary');
106
- });
107
-
108
- describe('Border Radius Variables', () => {
109
- it('should generate all border radius variables', () => {
110
- const theme = createTheme();
111
- const css = generateCSSVariables(theme);
112
-
113
- expect(css).toContain('--atomix-border-radius');
114
- expect(css).toContain('--atomix-border-radius-sm');
115
- expect(css).toContain('--atomix-border-radius-lg');
116
- expect(css).toContain('--atomix-border-radius-xl');
117
- expect(css).toContain('--atomix-border-radius-xxl');
118
- expect(css).toContain('--atomix-border-radius-2xl');
119
- expect(css).toContain('--atomix-border-radius-3xl');
120
- expect(css).toContain('--atomix-border-radius-4xl');
121
- expect(css).toContain('--atomix-border-radius-pill');
122
- });
123
-
124
- it('should use custom border radius values', () => {
125
- const theme = createTheme({
126
- borderRadius: {
127
- base: '1rem',
128
- sm: '0.5rem',
129
- lg: '1.5rem',
130
- },
131
- });
132
- const css = generateCSSVariables(theme);
133
-
134
- expect(css).toContain('--atomix-border-radius: 1rem');
135
- expect(css).toContain('--atomix-border-radius-sm: 0.5rem');
136
- expect(css).toContain('--atomix-border-radius-lg: 1.5rem');
137
- });
138
-
139
- it('should accept numeric border radius values', () => {
140
- const theme = createTheme({
141
- borderRadius: {
142
- base: 8,
143
- sm: 4,
144
- },
145
- });
146
- const css = generateCSSVariables(theme);
147
-
148
- expect(css).toContain('--atomix-border-radius: 8px');
149
- expect(css).toContain('--atomix-border-radius-sm: 4px');
150
- });
151
- });
152
-
153
- describe('Color Palette Scales', () => {
154
- it('should generate primary color scale (1-10)', () => {
155
- const theme = createTheme({
156
- palette: {
157
- primary: { main: '#7AFFD7' },
158
- },
159
- });
160
- const css = generateCSSVariables(theme);
161
-
162
- for (let i = 1; i <= 10; i++) {
163
- expect(css).toContain(`--atomix-primary-${i}`);
164
- }
165
- });
166
-
167
- it('should generate secondary color scale (1-10)', () => {
168
- const theme = createTheme({
169
- palette: {
170
- secondary: { main: '#FF5733' },
171
- },
172
- });
173
- const css = generateCSSVariables(theme);
174
-
175
- for (let i = 1; i <= 10; i++) {
176
- expect(css).toContain(`--atomix-secondary-${i}`);
177
- }
178
- });
179
-
180
- it('should generate gray color scale (1-10)', () => {
181
- const theme = createTheme({
182
- palette: {
183
- text: {
184
- primary: '#000000',
185
- secondary: '#666666',
186
- disabled: '#999999',
187
- },
188
- },
189
- });
190
- const css = generateCSSVariables(theme);
191
-
192
- for (let i = 1; i <= 10; i++) {
193
- expect(css).toContain(`--atomix-gray-${i}`);
194
- }
195
- });
196
-
197
- it('should generate semantic color scales (red, green, blue, yellow)', () => {
198
- const theme = createTheme({
199
- palette: {
200
- error: { main: '#F44336' },
201
- success: { main: '#4CAF50' },
202
- info: { main: '#2196F3' },
203
- warning: { main: '#FF9800' },
204
- },
205
- });
206
- const css = generateCSSVariables(theme);
207
-
208
- // Red scale from error
209
- for (let i = 1; i <= 10; i++) {
210
- expect(css).toContain(`--atomix-red-${i}`);
211
- }
212
-
213
- // Green scale from success
214
- for (let i = 1; i <= 10; i++) {
215
- expect(css).toContain(`--atomix-green-${i}`);
216
- }
217
-
218
- // Blue scale from info
219
- for (let i = 1; i <= 10; i++) {
220
- expect(css).toContain(`--atomix-blue-${i}`);
221
- }
222
-
223
- // Yellow scale from warning
224
- for (let i = 1; i <= 10; i++) {
225
- expect(css).toContain(`--atomix-yellow-${i}`);
226
- }
227
- });
228
-
229
- it('should generate color scale step 6 as the main color', () => {
230
- const theme = createTheme({
231
- palette: {
232
- primary: { main: '#7AFFD7' },
233
- },
234
- });
235
- const css = generateCSSVariables(theme);
236
-
237
- // Step 6 should be the main color
238
- expect(css).toMatch(/--atomix-primary-6:\s*#7AFFD7/i);
239
- });
240
- });
241
-
242
- describe('Typography Variables', () => {
243
- it('should generate root font size variable', () => {
244
- const theme = createTheme({
245
- typography: {
246
- fontSize: 16,
247
- },
248
- });
249
- const css = generateCSSVariables(theme);
250
-
251
- expect(css).toContain('--atomix-root-font-size');
252
- expect(css).toContain('16px');
253
- });
254
-
255
- it('should generate extended font size scale', () => {
256
- const theme = createTheme({
257
- typography: {
258
- fontSize: 16,
259
- },
260
- });
261
- const css = generateCSSVariables(theme);
262
-
263
- expect(css).toContain('--atomix-font-size-xs');
264
- expect(css).toContain('--atomix-font-size-sm');
265
- expect(css).toContain('--atomix-font-size-md');
266
- expect(css).toContain('--atomix-font-size-lg');
267
- expect(css).toContain('--atomix-font-size-xl');
268
- expect(css).toContain('--atomix-font-size-2xl');
269
- });
270
-
271
- it('should calculate font sizes correctly from base', () => {
272
- const theme = createTheme({
273
- typography: {
274
- fontSize: 16,
275
- },
276
- });
277
- const css = generateCSSVariables(theme);
278
-
279
- // xs = 0.75 * 16 = 12px
280
- expect(css).toMatch(/--atomix-font-size-xs:\s*12px/);
281
- // sm = 0.875 * 16 = 14px
282
- expect(css).toMatch(/--atomix-font-size-sm:\s*14px/);
283
- // md = 1 * 16 = 16px
284
- expect(css).toMatch(/--atomix-font-size-md:\s*16px/);
285
- // lg = 1.125 * 16 = 18px
286
- expect(css).toMatch(/--atomix-font-size-lg:\s*18px/);
287
- // xl = 1.5 * 16 = 24px
288
- expect(css).toMatch(/--atomix-font-size-xl:\s*24px/);
289
- // 2xl = 2 * 16 = 32px
290
- expect(css).toMatch(/--atomix-font-size-2xl:\s*32px/);
291
- });
292
- });
293
-
294
- describe('Link Variables', () => {
295
- it('should generate link decoration variables', () => {
296
- const theme = createTheme({
297
- palette: {
298
- primary: { main: '#7AFFD7' },
299
- },
300
- });
301
- const css = generateCSSVariables(theme);
302
-
303
- expect(css).toContain('--atomix-link-decoration');
304
- expect(css).toContain('--atomix-link-hover-decoration');
305
- });
306
-
307
- it('should set default link decoration values', () => {
308
- const theme = createTheme({
309
- palette: {
310
- primary: { main: '#7AFFD7' },
311
- },
312
- });
313
- const css = generateCSSVariables(theme);
314
-
315
- expect(css).toContain('--atomix-link-decoration: underline');
316
- expect(css).toContain('--atomix-link-hover-decoration: none');
317
- });
318
- });
319
-
320
- describe('Code and Highlight Variables', () => {
321
- it('should generate highlight background variable', () => {
322
- const theme = createTheme();
323
- const css = generateCSSVariables(theme);
324
-
325
- expect(css).toContain('--atomix-highlight-bg');
326
- });
327
-
328
- it('should generate code color variable', () => {
329
- const theme = createTheme({
330
- palette: {
331
- text: {
332
- primary: '#000000',
333
- secondary: '#666666',
334
- disabled: '#999999',
335
- },
336
- },
337
- });
338
- const css = generateCSSVariables(theme);
339
-
340
- expect(css).toContain('--atomix-code-color');
341
- // Should use text secondary color (value may vary based on theme defaults)
342
- expect(css).toMatch(/--atomix-code-color:\s*[^;]+/);
343
- });
344
-
345
- it('should use warning color for highlight background when available', () => {
346
- const theme = createTheme({
347
- palette: {
348
- warning: { main: '#FF9800' },
349
- },
350
- });
351
- const css = generateCSSVariables(theme);
352
-
353
- expect(css).toContain('--atomix-highlight-bg');
354
- // Should contain rgba with warning color
355
- expect(css).toMatch(/--atomix-highlight-bg:\s*rgba\(/);
356
- });
357
- });
358
-
359
- describe('Variable Format and Structure', () => {
360
- it('should generate valid CSS variable syntax', () => {
361
- const theme = createTheme();
362
- const css = generateCSSVariables(theme);
363
-
364
- // Check for valid CSS variable format: --name: value;
365
- const variablePattern = /--[\w-]+:\s*[^;]+;/g;
366
- const matches = css.match(variablePattern);
367
- expect(matches).toBeTruthy();
368
- expect(matches!.length).toBeGreaterThan(0);
369
- });
370
-
371
- it('should include all variable categories', () => {
372
- const theme = createTheme({
373
- palette: {
374
- primary: { main: '#7AFFD7' },
375
- secondary: { main: '#FF5733' },
376
- error: { main: '#F44336' },
377
- success: { main: '#4CAF50' },
378
- info: { main: '#2196F3' },
379
- warning: { main: '#FF9800' },
380
- },
381
- typography: {
382
- fontSize: 16,
383
- },
384
- });
385
- const css = generateCSSVariables(theme);
386
-
387
- // Check for variables from each category
388
- expect(css).toContain('--atomix-primary'); // Palette
389
- expect(css).toContain('--atomix-body-font-family'); // Typography
390
- expect(css).toContain('--atomix-box-shadow'); // Shadows
391
- expect(css).toContain('--atomix-transition-duration'); // Transitions
392
- expect(css).toContain('--atomix-z-modal'); // Z-index
393
- expect(css).toContain('--atomix-breakpoint-sm'); // Breakpoints
394
- expect(css).toContain('--atomix-spacing-4'); // Spacing
395
- expect(css).toContain('--atomix-border-width'); // Borders
396
- expect(css).toContain('--atomix-border-radius'); // Border radius
397
- expect(css).toContain('--atomix-focus-ring-width'); // Focus ring
398
- });
399
-
400
- it('should not duplicate variables', () => {
401
- const theme = createTheme();
402
- const css = generateCSSVariables(theme);
403
-
404
- // Extract all variable names
405
- const variableNames = css.match(/--([\w-]+):/g) || [];
406
- const uniqueNames = new Set(variableNames);
407
-
408
- // Should have same count (no duplicates)
409
- expect(variableNames.length).toBe(uniqueNames.size);
410
- });
411
- });
412
- });
413
-
414
- describe('Theme Composition', () => {
415
- it('should merge themes', () => {
416
- const merged = mergeTheme(
417
- { palette: { primary: { main: '#000' } } },
418
- { palette: { secondary: { main: '#FFF' } } }
419
- );
420
-
421
- const primary = merged.palette?.primary;
422
- const secondary = merged.palette?.secondary;
423
-
424
- expect(primary && typeof primary === 'object' ? primary.main : primary).toBe('#000');
425
- expect(secondary && typeof secondary === 'object' ? secondary.main : secondary).toBe('#FFF');
426
- });
427
-
428
- it('should extend theme', () => {
429
- const base = createTheme({ palette: { primary: { main: '#000' } } });
430
- const extended = extendTheme(base, {
431
- palette: { secondary: { main: '#FFF' } },
432
- });
433
-
434
- expect(extended.palette.primary.main).toBe('#000');
435
- expect(extended.palette.secondary.main).toBe('#FFF');
436
- });
437
-
438
- it('should create theme variants', () => {
439
- const { light, dark } = createThemeVariants({
440
- palette: { primary: { main: '#7AFFD7' } },
441
- });
442
-
443
- expect(light).toBeDefined();
444
- expect(dark).toBeDefined();
445
- expect(dark.palette.background.default).toBe('#121212');
446
- });
447
- });
448
-
449
- describe('Theme Utilities', () => {
450
- it('should lighten color', () => {
451
- const lightened = lighten('#000000', 0.5);
452
- expect(lightened).not.toBe('#000000');
453
- expect(lightened).toMatch(/^#[0-9a-f]{6}$/i);
454
- });
455
-
456
- it('should darken color', () => {
457
- const darkened = darken('#FFFFFF', 0.5);
458
- expect(darkened).not.toBe('#FFFFFF');
459
- expect(darkened).toMatch(/^#[0-9a-f]{6}$/i);
460
- });
461
-
462
- it('should add alpha to color', () => {
463
- const withAlpha = alpha('#FF0000', 0.5);
464
- expect(withAlpha).toContain('rgba');
465
- expect(withAlpha).toContain('0.5');
466
- });
467
-
468
- it('should get contrast text', () => {
469
- const whiteContrast = getContrastText('#000000');
470
- const blackContrast = getContrastText('#FFFFFF');
471
-
472
- expect(whiteContrast).toBe('#FFFFFF');
473
- expect(blackContrast).toBe('#000000');
474
- });
475
- });
@@ -1,67 +0,0 @@
1
- import React from 'react';
2
- import { describe, it, expect, vi } from 'vitest';
3
- import { renderHook, act } from '@testing-library/react';
4
- import { useTheme } from './useTheme';
5
- import { ThemeContext } from './ThemeContext';
6
- import type { ThemeContextValue } from './types';
7
-
8
- describe('useTheme', () => {
9
- const mockSetTheme = vi.fn(() => Promise.resolve());
10
- const mockPreloadTheme = vi.fn(() => Promise.resolve());
11
- const mockIsThemeLoaded = vi.fn(() => true);
12
-
13
- const mockContextValue: ThemeContextValue = {
14
- theme: 'default-theme',
15
- activeTheme: null,
16
- setTheme: mockSetTheme,
17
- availableThemes: [{ name: 'Default', class: 'default-theme' }],
18
- isLoading: false,
19
- error: null,
20
- isThemeLoaded: mockIsThemeLoaded,
21
- preloadTheme: mockPreloadTheme,
22
- themeManager: {} as any, // We don't need the actual manager for this test
23
- };
24
-
25
- it('should throw error when used outside ThemeProvider', () => {
26
- const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => { });
27
-
28
- expect(() => {
29
- renderHook(() => useTheme());
30
- }).toThrow('useTheme must be used within a ThemeProvider');
31
-
32
- consoleSpy.mockRestore();
33
- });
34
-
35
- it('should return theme context values', () => {
36
- const wrapper = ({ children }: { children: React.ReactNode }) => (
37
- <ThemeContext.Provider value={mockContextValue}>
38
- {children}
39
- </ThemeContext.Provider>
40
- );
41
-
42
- const { result } = renderHook(() => useTheme(), { wrapper });
43
-
44
- expect(result.current.theme).toBe('default-theme');
45
- expect(result.current.availableThemes).toHaveLength(1);
46
- expect(result.current.isLoading).toBe(false);
47
- });
48
-
49
- it('should call onChange callback when provided', async () => {
50
- const onChangeSpy = vi.fn();
51
-
52
- const wrapper = ({ children }: { children: React.ReactNode }) => (
53
- <ThemeContext.Provider value={mockContextValue}>
54
- {children}
55
- </ThemeContext.Provider>
56
- );
57
-
58
- const { result } = renderHook(() => useTheme({ onChange: onChangeSpy }), { wrapper });
59
-
60
- await act(async () => {
61
- await result.current.setTheme('new-theme');
62
- });
63
-
64
- expect(mockSetTheme).toHaveBeenCalledWith('new-theme', undefined);
65
- expect(onChangeSpy).toHaveBeenCalledWith('new-theme');
66
- });
67
- });
@@ -1,64 +0,0 @@
1
- /**
2
- * useTheme Hook
3
- *
4
- * React hook for accessing and managing theme state
5
- */
6
-
7
- import { useContext, useCallback } from 'react';
8
- import { ThemeContext } from './ThemeContext';
9
- import type { UseThemeReturn, UseThemeOptions, ThemeLoadOptions, Theme } from './types';
10
-
11
- /**
12
- * useTheme hook
13
- *
14
- * Access theme context and manage theme state in React components.
15
- * Must be used within a ThemeProvider.
16
- */
17
- export const useTheme = (options: UseThemeOptions = {}): UseThemeReturn => {
18
- const context = useContext(ThemeContext);
19
-
20
- if (!context) {
21
- throw new Error(
22
- 'useTheme must be used within a ThemeProvider. ' +
23
- 'Wrap your component tree with <ThemeProvider> to use this hook.'
24
- );
25
- }
26
-
27
- const {
28
- theme,
29
- activeTheme,
30
- setTheme: contextSetTheme,
31
- availableThemes,
32
- isLoading,
33
- error,
34
- isThemeLoaded,
35
- preloadTheme,
36
- } = context;
37
-
38
- // Extract onChange callback to avoid dependency on entire options object
39
- const onChange = options?.onChange;
40
-
41
- // Wrap setTheme to call onChange callback if provided
42
- const setTheme = useCallback(
43
- async (themeOrName: string | Theme, themeOptions?: ThemeLoadOptions): Promise<void> => {
44
- await contextSetTheme(themeOrName, themeOptions);
45
- if (onChange) {
46
- onChange(themeOrName);
47
- }
48
- },
49
- [contextSetTheme, onChange]
50
- );
51
-
52
- return {
53
- theme,
54
- activeTheme,
55
- setTheme,
56
- availableThemes,
57
- isLoading,
58
- error,
59
- isThemeLoaded,
60
- preloadTheme,
61
- };
62
- };
63
-
64
- export default useTheme;