@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.
- package/CHANGELOG.md +0 -1
- package/README.md +3 -5
- package/dist/atomix.css +458 -552
- package/dist/atomix.min.css +3 -3
- package/dist/index.d.ts +2435 -358
- package/dist/index.esm.js +5758 -1901
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +5768 -1933
- package/dist/index.js.map +1 -1
- package/dist/index.min.js +1 -1
- package/dist/index.min.js.map +1 -1
- package/package.json +1 -11
- package/src/lib/composables/useAtomixGlass.ts +46 -46
- package/src/lib/index.ts +1 -4
- package/src/lib/theme/config/index.ts +21 -0
- package/src/lib/theme/config/loader.ts +276 -0
- package/src/lib/theme/config/types.ts +98 -0
- package/src/lib/theme/config/validator.ts +326 -0
- package/src/lib/theme/constants.ts +183 -0
- package/src/lib/theme/core/ThemeCache.ts +283 -0
- package/src/lib/theme/core/ThemeEngine.test.ts +146 -0
- package/src/lib/theme/core/ThemeEngine.ts +657 -0
- package/src/lib/theme/core/ThemeRegistry.ts +284 -0
- package/src/lib/theme/core/ThemeValidator.ts +530 -0
- package/src/lib/theme/core/index.ts +24 -0
- package/src/lib/theme/createTheme.ts +81 -70
- package/src/lib/theme/devtools/CLI.ts +279 -0
- package/src/lib/theme/devtools/Inspector.tsx +594 -0
- package/src/lib/theme/devtools/Preview.tsx +392 -0
- package/src/lib/theme/devtools/index.ts +21 -0
- package/src/lib/theme/errors.test.ts +207 -0
- package/src/lib/theme/errors.ts +233 -0
- package/src/lib/theme/generateCSSVariables.ts +93 -9
- package/src/lib/theme/generators/CSSGenerator.ts +311 -0
- package/src/lib/theme/generators/ConfigGenerator.ts +287 -0
- package/src/lib/theme/generators/TypeGenerator.ts +228 -0
- package/src/lib/theme/generators/index.ts +21 -0
- package/src/lib/theme/i18n/index.ts +9 -0
- package/src/lib/theme/i18n/rtl.ts +325 -0
- package/src/lib/theme/index.ts +155 -11
- package/src/lib/theme/monitoring/ThemeAnalytics.ts +409 -0
- package/src/lib/theme/monitoring/index.ts +17 -0
- package/src/lib/theme/overrides/ComponentOverrides.ts +243 -0
- package/src/lib/theme/overrides/index.ts +15 -0
- package/src/lib/theme/runtime/ThemeErrorBoundary.tsx +233 -0
- package/src/lib/theme/runtime/ThemeManager.test.ts +176 -0
- package/src/lib/theme/runtime/ThemeManager.ts +442 -0
- package/src/lib/theme/runtime/ThemeProvider.tsx +318 -0
- package/src/lib/theme/runtime/index.ts +17 -0
- package/src/lib/theme/runtime/useTheme.ts +52 -0
- package/src/lib/theme/studio/ThemeStudio.tsx +312 -0
- package/src/lib/theme/studio/index.ts +8 -0
- package/src/lib/theme/types.ts +3 -1
- package/src/lib/theme/utils.ts +23 -22
- package/src/lib/theme/whitelabel/WhiteLabelManager.ts +364 -0
- package/src/lib/theme/whitelabel/index.ts +13 -0
- package/src/styles/01-settings/_settings.badge.scss +1 -1
- package/src/styles/01-settings/_settings.callout.scss +1 -1
- package/src/styles/01-settings/_settings.card.scss +1 -1
- package/src/styles/01-settings/_settings.input.scss +1 -1
- package/src/styles/01-settings/_settings.navbar.scss +1 -1
- package/src/styles/01-settings/_settings.upload.scss +1 -1
- package/src/styles/06-components/_components.chart.scss +2 -2
- package/src/styles/99-utilities/_utilities.border.scss +27 -58
- package/src/styles/99-utilities/_utilities.gradient.scss +12 -0
- package/src/styles/99-utilities/_utilities.position.scss +8 -15
- package/src/styles/99-utilities/_utilities.scss +2 -0
- package/src/styles/99-utilities/_utilities.spacing.scss +76 -121
- package/src/styles/99-utilities/_utilities.text.scss +30 -49
- package/dist/themes/applemix.css +0 -15615
- package/dist/themes/applemix.min.css +0 -70
- package/dist/themes/boomdevs.css +0 -15193
- package/dist/themes/boomdevs.min.css +0 -403
- package/dist/themes/esrar.css +0 -17399
- package/dist/themes/esrar.min.css +0 -187
- package/dist/themes/flashtrade.css +0 -16613
- package/dist/themes/flashtrade.min.css +0 -190
- package/dist/themes/mashroom.css +0 -30104
- package/dist/themes/mashroom.min.css +0 -401
- package/dist/themes/shaj-default.css +0 -16228
- package/dist/themes/shaj-default.min.css +0 -498
- package/src/lib/theme/ThemeManager.integration.test.ts +0 -124
- package/src/lib/theme/ThemeManager.stories.tsx +0 -472
- package/src/lib/theme/ThemeManager.test.ts +0 -190
- package/src/lib/theme/ThemeManager.ts +0 -645
- package/src/lib/theme/ThemeProvider.tsx +0 -377
- package/src/lib/theme/createTheme.test.ts +0 -475
- package/src/lib/theme/useTheme.test.tsx +0 -67
- package/src/lib/theme/useTheme.ts +0 -64
- 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;
|