@shohojdhara/atomix 0.3.5 → 0.3.7

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 (182) hide show
  1. package/README.md +101 -199
  2. package/atomix.config.ts +241 -0
  3. package/dist/atomix.css +260 -179
  4. package/dist/atomix.css.map +1 -1
  5. package/dist/atomix.min.css +250 -179
  6. package/dist/atomix.min.css.map +1 -1
  7. package/dist/charts.js +69 -166
  8. package/dist/charts.js.map +1 -1
  9. package/dist/core.js +184 -263
  10. package/dist/core.js.map +1 -1
  11. package/dist/forms.js +55 -131
  12. package/dist/forms.js.map +1 -1
  13. package/dist/heavy.js +184 -263
  14. package/dist/heavy.js.map +1 -1
  15. package/dist/index.d.ts +1831 -1657
  16. package/dist/index.esm.js +4497 -4318
  17. package/dist/index.esm.js.map +1 -1
  18. package/dist/index.js +4510 -4328
  19. package/dist/index.js.map +1 -1
  20. package/dist/index.min.js +1 -1
  21. package/dist/index.min.js.map +1 -1
  22. package/dist/theme.d.ts +1431 -1472
  23. package/dist/theme.js +4175 -4138
  24. package/dist/theme.js.map +1 -1
  25. package/package.json +6 -20
  26. package/src/components/Accordion/Accordion.stories.tsx +50 -17
  27. package/src/components/AtomixGlass/AtomixGlass.tsx +128 -322
  28. package/src/components/AtomixGlass/AtomixGlassContainer.tsx +12 -5
  29. package/src/components/AtomixGlass/stories/AtomixGlass.stories.tsx +1 -32
  30. package/src/components/AtomixGlass/stories/Examples.stories.tsx +2 -2
  31. package/src/components/AtomixGlass/stories/shared-components.tsx +0 -31
  32. package/src/components/Avatar/Avatar.stories.tsx +7 -0
  33. package/src/components/Badge/Badge.stories.tsx +91 -13
  34. package/src/components/Block/Block.stories.tsx +7 -23
  35. package/src/components/Breadcrumb/Breadcrumb.stories.tsx +7 -0
  36. package/src/components/Button/Button.stories.tsx +141 -22
  37. package/src/components/Button/Button.tsx +85 -167
  38. package/src/components/Button/ButtonGroup.stories.tsx +315 -0
  39. package/src/components/Button/ButtonGroup.tsx +67 -0
  40. package/src/components/Button/index.ts +2 -0
  41. package/src/components/Callout/Callout.stories.tsx +8 -6
  42. package/src/components/Card/Card.stories.tsx +82 -28
  43. package/src/components/Chart/AnimatedChart.tsx +0 -1
  44. package/src/components/Chart/AreaChart.tsx +0 -1
  45. package/src/components/Chart/BarChart.tsx +0 -1
  46. package/src/components/Chart/BubbleChart.tsx +0 -1
  47. package/src/components/Chart/CandlestickChart.tsx +0 -1
  48. package/src/components/Chart/Chart.stories.tsx +5 -7
  49. package/src/components/Chart/Chart.tsx +0 -16
  50. package/src/components/Chart/ChartRenderer.tsx +1 -1
  51. package/src/components/Chart/DonutChart.tsx +0 -1
  52. package/src/components/Chart/FunnelChart.tsx +0 -1
  53. package/src/components/Chart/GaugeChart.tsx +0 -1
  54. package/src/components/Chart/HeatmapChart.tsx +0 -1
  55. package/src/components/Chart/LineChart.tsx +0 -1
  56. package/src/components/Chart/MultiAxisChart.tsx +0 -1
  57. package/src/components/Chart/PieChart.tsx +0 -1
  58. package/src/components/Chart/RadarChart.tsx +0 -1
  59. package/src/components/Chart/ScatterChart.tsx +0 -1
  60. package/src/components/Chart/WaterfallChart.tsx +0 -1
  61. package/src/components/ColorModeToggle/ColorModeToggle.stories.tsx +7 -0
  62. package/src/components/DataTable/DataTable.stories.tsx +23 -16
  63. package/src/components/DatePicker/DatePicker.stories.tsx +27 -19
  64. package/src/components/Dropdown/Dropdown.stories.tsx +11 -19
  65. package/src/components/EdgePanel/EdgePanel.stories.tsx +1 -0
  66. package/src/components/Footer/Footer.stories.tsx +8 -6
  67. package/src/components/Footer/FooterLink.tsx +9 -2
  68. package/src/components/Form/Checkbox.stories.tsx +7 -0
  69. package/src/components/Form/Form.stories.tsx +7 -0
  70. package/src/components/Form/FormGroup.stories.tsx +9 -1
  71. package/src/components/Form/Input.stories.tsx +69 -16
  72. package/src/components/Form/Radio.stories.tsx +9 -1
  73. package/src/components/Form/Select.stories.tsx +9 -1
  74. package/src/components/Form/Textarea.stories.tsx +10 -2
  75. package/src/components/Hero/Hero.stories.tsx +7 -0
  76. package/src/components/List/List.stories.tsx +7 -0
  77. package/src/components/Messages/Messages.stories.tsx +8 -7
  78. package/src/components/Modal/Modal.stories.tsx +17 -6
  79. package/src/components/Navigation/Menu/Menu.stories.tsx +7 -0
  80. package/src/components/Navigation/Nav/Nav.stories.tsx +7 -0
  81. package/src/components/Navigation/Navbar/Navbar.stories.tsx +1 -0
  82. package/src/components/Navigation/SideMenu/SideMenu.stories.tsx +1 -1
  83. package/src/components/Pagination/Pagination.stories.tsx +188 -111
  84. package/src/components/Pagination/Pagination.tsx +83 -3
  85. package/src/components/PhotoViewer/PhotoViewer.stories.tsx +10 -5
  86. package/src/components/Popover/Popover.stories.tsx +191 -115
  87. package/src/components/ProductReview/ProductReview.stories.tsx +80 -58
  88. package/src/components/Progress/Progress.stories.tsx +79 -49
  89. package/src/components/Rating/Rating.stories.tsx +109 -84
  90. package/src/components/River/River.stories.tsx +194 -114
  91. package/src/components/SectionIntro/SectionIntro.stories.tsx +19 -9
  92. package/src/components/Slider/Slider.stories.tsx +7 -0
  93. package/src/components/Spinner/Spinner.stories.tsx +15 -11
  94. package/src/components/Steps/Steps.stories.tsx +132 -98
  95. package/src/components/Tabs/Tabs.stories.tsx +163 -112
  96. package/src/components/Testimonial/Testimonial.stories.tsx +114 -68
  97. package/src/components/Todo/Todo.stories.tsx +38 -12
  98. package/src/components/Toggle/Toggle.stories.tsx +61 -28
  99. package/src/components/Tooltip/Tooltip.stories.tsx +318 -200
  100. package/src/components/Upload/Upload.stories.tsx +122 -84
  101. package/src/components/VideoPlayer/VideoPlayer.stories.tsx +7 -24
  102. package/src/components/index.ts +1 -0
  103. package/src/lib/composables/useAtomixGlass.ts +9 -10
  104. package/src/lib/composables/useNavbar.ts +0 -10
  105. package/src/lib/config/loader.ts +4 -4
  106. package/src/lib/constants/components.ts +17 -0
  107. package/src/lib/hooks/useComponentCustomization.ts +1 -1
  108. package/src/lib/hooks/usePerformanceMonitor.ts +1 -1
  109. package/src/lib/hooks/useThemeTokens.ts +105 -0
  110. package/src/lib/theme/README.md +174 -0
  111. package/src/lib/theme/adapters/index.ts +31 -0
  112. package/src/lib/theme/adapters/themeAdapter.ts +287 -0
  113. package/src/lib/theme/config/__tests__/configLoader.test.ts +207 -0
  114. package/src/lib/theme/config/configLoader.ts +95 -0
  115. package/src/lib/theme/config/loader.ts +37 -54
  116. package/src/lib/theme/config/types.ts +2 -2
  117. package/src/lib/theme/config/validator.ts +15 -91
  118. package/src/lib/theme/{constants.ts → constants/constants.ts} +1 -19
  119. package/src/lib/theme/constants/index.ts +8 -0
  120. package/src/lib/theme/core/ThemeRegistry.ts +75 -266
  121. package/src/lib/theme/core/__tests__/createTheme.test.ts +132 -0
  122. package/src/lib/theme/core/composeTheme.ts +105 -0
  123. package/src/lib/theme/core/createTheme.ts +108 -0
  124. package/src/lib/theme/{createTheme.ts → core/createThemeObject.ts} +12 -8
  125. package/src/lib/theme/core/index.ts +19 -19
  126. package/src/lib/theme/devtools/Comparator.tsx +346 -22
  127. package/src/lib/theme/devtools/IMPROVEMENTS.md +139 -38
  128. package/src/lib/theme/devtools/Inspector.tsx +335 -51
  129. package/src/lib/theme/devtools/LiveEditor.tsx +478 -107
  130. package/src/lib/theme/devtools/Preview.tsx +471 -221
  131. package/src/lib/theme/{core → devtools}/ThemeValidator.ts +1 -1
  132. package/src/lib/theme/devtools/index.ts +14 -4
  133. package/src/lib/theme/devtools/useHistory.ts +130 -0
  134. package/src/lib/theme/{errors.ts → errors/errors.ts} +1 -1
  135. package/src/lib/theme/errors/index.ts +12 -0
  136. package/src/lib/theme/generators/cssFile.ts +79 -0
  137. package/src/lib/theme/generators/generateCSS.ts +89 -0
  138. package/src/lib/theme/generators/generateCSSNested.ts +130 -0
  139. package/src/lib/theme/{generateCSSVariables.ts → generators/generateCSSVariables.ts} +3 -13
  140. package/src/lib/theme/generators/index.ts +25 -0
  141. package/src/lib/theme/i18n/rtl.ts +5 -6
  142. package/src/lib/theme/index.ts +149 -19
  143. package/src/lib/theme/runtime/ThemeApplicator.ts +53 -112
  144. package/src/lib/theme/{ThemeContext.tsx → runtime/ThemeContext.tsx} +1 -1
  145. package/src/lib/theme/runtime/ThemeErrorBoundary.tsx +5 -5
  146. package/src/lib/theme/runtime/ThemeProvider.tsx +266 -282
  147. package/src/lib/theme/runtime/index.ts +2 -2
  148. package/src/lib/theme/runtime/useTheme.ts +1 -2
  149. package/src/lib/theme/runtime/useThemeTokens.ts +131 -0
  150. package/src/lib/theme/test/testTheme.ts +385 -0
  151. package/src/lib/theme/tokens/index.ts +12 -0
  152. package/src/lib/theme/tokens/tokens.ts +721 -0
  153. package/src/lib/theme/types.ts +6 -42
  154. package/src/lib/theme/utils/componentTheming.ts +132 -0
  155. package/src/lib/theme/{utils.ts → utils/domUtils.ts} +2 -2
  156. package/src/lib/theme/utils/index.ts +11 -0
  157. package/src/lib/theme/utils/injectCSS.ts +90 -0
  158. package/src/lib/theme/utils/naming.ts +100 -0
  159. package/src/lib/theme/utils/themeHelpers.ts +78 -0
  160. package/src/lib/theme/{themeUtils.ts → utils/themeUtils.ts} +7 -7
  161. package/src/lib/theme-tools.ts +7 -8
  162. package/src/lib/types/components.ts +40 -130
  163. package/src/lib/utils/componentUtils.ts +2 -2
  164. package/src/lib/utils/memoryMonitor.ts +3 -3
  165. package/src/lib/utils/themeNaming.ts +135 -0
  166. package/src/styles/01-settings/_settings.design-tokens.scss +4 -1
  167. package/src/styles/02-tools/_tools.button.scss +66 -79
  168. package/src/styles/06-components/_components.atomix-glass.scss +13 -3
  169. package/src/styles/06-components/_components.pagination.scss +88 -0
  170. package/scripts/sync-theme-config.js +0 -309
  171. package/src/lib/theme/composeTheme.ts +0 -370
  172. package/src/lib/theme/core/ThemeCache.ts +0 -283
  173. package/src/lib/theme/core/ThemeEngine.test.ts +0 -146
  174. package/src/lib/theme/core/ThemeEngine.ts +0 -665
  175. package/src/lib/theme/createThemeFromConfig.ts +0 -132
  176. package/src/lib/theme/devtools/CLI.ts +0 -364
  177. package/src/lib/theme/runtime/ThemeManager.test.ts +0 -192
  178. package/src/lib/theme/runtime/ThemeManager.ts +0 -446
  179. package/src/styles/03-generic/_generated-root.css +0 -26
  180. package/src/themes/README.md +0 -442
  181. package/src/themes/themes.config.js +0 -68
  182. /package/src/lib/theme/{cssVariableMapper.ts → adapters/cssVariableMapper.ts} +0 -0
@@ -1,284 +1,93 @@
1
1
  /**
2
- * Theme Registry
3
- *
4
- * Central registry for all themes with discovery and dependency management
2
+ * Theme Metadata interface
5
3
  */
4
+ export interface ThemeMetadata {
5
+ name: string;
6
+ class: string;
7
+ description?: string;
8
+ version?: string;
9
+ [key: string]: any;
10
+ }
6
11
 
7
- import type { Theme, ThemeMetadata } from '../types';
8
- import type { ThemeDefinition, LoadedThemeConfig } from '../config/types';
9
- import { loadThemeConfig } from '../config/loader';
12
+ /**
13
+ * Theme Registry type - a record of theme IDs to metadata
14
+ */
15
+ export type ThemeRegistry = Record<string, ThemeMetadata>;
10
16
 
11
17
  /**
12
- * Registry entry
18
+ * Create a new theme registry
13
19
  */
14
- interface RegistryEntry {
15
- /** Theme ID */
16
- id: string;
17
- /** Theme definition from config */
18
- definition: ThemeDefinition;
19
- /** Resolved theme object (for JS themes) */
20
- theme?: Theme;
21
- /** Whether theme is loaded */
22
- loaded: boolean;
23
- /** Loading promise (if currently loading) */
24
- loading?: Promise<Theme | void>;
25
- /** Dependencies */
26
- dependencies: string[];
27
- /** Dependent themes (themes that depend on this one) */
28
- dependents: string[];
20
+ export function createThemeRegistry(): ThemeRegistry {
21
+ return {};
29
22
  }
30
23
 
31
24
  /**
32
- * Theme Registry
33
- *
34
- * Manages theme registration, discovery, and dependency resolution
25
+ * Register a theme
26
+ * @param registry - Theme registry object
27
+ * @param id - Theme identifier
28
+ * @param metadata - Theme metadata
35
29
  */
36
- export class ThemeRegistry {
37
- private entries: Map<string, RegistryEntry> = new Map();
38
- private config: LoadedThemeConfig | null = null;
39
- private initialized: boolean = false;
40
-
41
- /**
42
- * Initialize registry from config
43
- */
44
- async initialize(config?: LoadedThemeConfig): Promise<void> {
45
- if (this.initialized) {
46
- return;
47
- }
48
-
49
- // Load config if not provided
50
- if (!config) {
51
- try {
52
- this.config = loadThemeConfig();
53
- } catch (error) {
54
- // In browser environments, config loading will fail
55
- // Use empty config as fallback
56
- this.config = {
57
- themes: {},
58
- build: {
59
- output: { directory: 'themes', formats: { expanded: '.css', compressed: '.min.css' } },
60
- sass: { style: 'expanded', sourceMap: true, loadPaths: ['src'] },
61
- },
62
- runtime: {
63
- basePath: '',
64
- defaultTheme: undefined, // No default - use built-in styles
65
- },
66
- integration: {
67
- cssVariables: { colorMode: '--color-mode' },
68
- classNames: { theme: 'data-theme', colorMode: 'data-color-mode' },
69
- },
70
- dependencies: {},
71
- validated: false,
72
- errors: [],
73
- warnings: [],
74
- };
75
- }
76
- } else {
77
- this.config = config;
78
- }
79
-
80
- // Register all themes from config
81
- for (const [themeId, definition] of Object.entries(this.config.themes)) {
82
- this.register(themeId, definition);
83
- }
84
-
85
- // Resolve dependencies
86
- this.resolveDependencies();
87
-
88
- this.initialized = true;
89
- }
90
-
91
- /**
92
- * Register a theme
93
- */
94
- register(themeId: string, definition: ThemeDefinition): void {
95
- // Get dependencies from config or definition
96
- const dependencies =
97
- this.config?.dependencies?.[themeId] ||
98
- definition.dependencies ||
99
- [];
100
-
101
- const entry: RegistryEntry = {
102
- id: themeId,
103
- definition,
104
- loaded: false,
105
- dependencies: [...dependencies],
106
- dependents: [],
107
- };
108
-
109
- this.entries.set(themeId, entry);
110
- }
111
-
112
- /**
113
- * Get theme entry
114
- */
115
- get(themeId: string): RegistryEntry | undefined {
116
- return this.entries.get(themeId);
117
- }
118
-
119
- /**
120
- * Check if theme exists
121
- */
122
- has(themeId: string): boolean {
123
- return this.entries.has(themeId);
124
- }
125
-
126
- /**
127
- * Get all theme IDs
128
- */
129
- getAllIds(): string[] {
130
- return Array.from(this.entries.keys());
131
- }
132
-
133
- /**
134
- * Get all theme metadata
135
- */
136
- getAllMetadata(): ThemeMetadata[] {
137
- return Array.from(this.entries.values()).map(entry => ({
138
- id: entry.id,
139
- name: entry.definition.name,
140
- type: entry.definition.type,
141
- class: entry.definition.class,
142
- description: entry.definition.description,
143
- author: entry.definition.author,
144
- version: entry.definition.version,
145
- tags: entry.definition.tags,
146
- supportsDarkMode: entry.definition.supportsDarkMode,
147
- status: entry.definition.status,
148
- a11y: entry.definition.a11y,
149
- color: entry.definition.color,
150
- features: entry.definition.features,
151
- dependencies: entry.dependencies,
152
- }));
153
- }
154
-
155
- /**
156
- * Get theme definition
157
- */
158
- getDefinition(themeId: string): ThemeDefinition | undefined {
159
- return this.entries.get(themeId)?.definition;
160
- }
161
-
162
- /**
163
- * Get theme object (for JS themes)
164
- */
165
- getTheme(themeId: string): Theme | undefined {
166
- return this.entries.get(themeId)?.theme;
167
- }
168
-
169
- /**
170
- * Set theme object (for JS themes)
171
- */
172
- setTheme(themeId: string, theme: Theme): void {
173
- const entry = this.entries.get(themeId);
174
- if (entry) {
175
- entry.theme = theme;
176
- entry.loaded = true;
177
- }
178
- }
179
-
180
- /**
181
- * Get dependencies for a theme
182
- */
183
- getDependencies(themeId: string): string[] {
184
- return this.entries.get(themeId)?.dependencies || [];
185
- }
186
-
187
- /**
188
- * Get dependents for a theme (themes that depend on this one)
189
- */
190
- getDependents(themeId: string): string[] {
191
- return this.entries.get(themeId)?.dependents || [];
192
- }
193
-
194
- /**
195
- * Resolve all dependencies in correct order
196
- */
197
- resolveDependencyOrder(themeId: string): string[] {
198
- const resolved: string[] = [];
199
- const visited = new Set<string>();
200
- const visiting = new Set<string>();
201
-
202
- const visit = (id: string): void => {
203
- if (visiting.has(id)) {
204
- throw new Error(`Circular dependency detected involving theme: ${id}`);
205
- }
206
- if (visited.has(id)) {
207
- return;
208
- }
209
-
210
- visiting.add(id);
211
- const entry = this.entries.get(id);
212
- if (entry) {
213
- for (const dep of entry.dependencies) {
214
- if (!this.has(dep)) {
215
- throw new Error(`Theme "${id}" depends on non-existent theme: ${dep}`);
216
- }
217
- visit(dep);
218
- }
219
- }
220
- visiting.delete(id);
221
- visited.add(id);
222
- resolved.push(id);
223
- };
30
+ export function registerTheme(registry: ThemeRegistry, id: string, metadata: ThemeMetadata): void {
31
+ registry[id] = metadata;
32
+ }
224
33
 
225
- visit(themeId);
226
- return resolved;
227
- }
34
+ /**
35
+ * Unregister a theme
36
+ * @param registry - Theme registry object
37
+ * @param id - Theme identifier
38
+ */
39
+ export function unregisterTheme(registry: ThemeRegistry, id: string): boolean {
40
+ const exists = id in registry;
41
+ delete registry[id];
42
+ return exists;
43
+ }
228
44
 
229
- /**
230
- * Resolve dependencies and build dependency graph
231
- */
232
- private resolveDependencies(): void {
233
- // Build dependents map
234
- for (const entry of this.entries.values()) {
235
- for (const dep of entry.dependencies) {
236
- const depEntry = this.entries.get(dep);
237
- if (depEntry) {
238
- if (!depEntry.dependents.includes(entry.id)) {
239
- depEntry.dependents.push(entry.id);
240
- }
241
- }
242
- }
243
- }
244
- }
45
+ /**
46
+ * Check if a theme is registered
47
+ * @param registry - Theme registry object
48
+ * @param id - Theme identifier
49
+ */
50
+ export function hasTheme(registry: ThemeRegistry, id: string): boolean {
51
+ return id in registry;
52
+ }
245
53
 
246
- /**
247
- * Validate all themes
248
- */
249
- validate(): { valid: boolean; errors: string[] } {
250
- const errors: string[] = [];
54
+ /**
55
+ * Get theme metadata
56
+ * @param registry - Theme registry object
57
+ * @param id - Theme identifier
58
+ */
59
+ export function getTheme(registry: ThemeRegistry, id: string): ThemeMetadata | undefined {
60
+ return registry[id];
61
+ }
251
62
 
252
- // Check for circular dependencies
253
- for (const themeId of this.entries.keys()) {
254
- try {
255
- this.resolveDependencyOrder(themeId);
256
- } catch (error) {
257
- errors.push(error instanceof Error ? error.message : String(error));
258
- }
259
- }
63
+ /**
64
+ * Get all registered theme metadata
65
+ * @param registry - Theme registry object
66
+ */
67
+ export function getAllThemes(registry: ThemeRegistry): ThemeMetadata[] {
68
+ return Object.values(registry);
69
+ }
260
70
 
261
- // Check for missing dependencies
262
- for (const [themeId, entry] of this.entries.entries()) {
263
- for (const dep of entry.dependencies) {
264
- if (!this.has(dep)) {
265
- errors.push(`Theme "${themeId}" depends on non-existent theme: ${dep}`);
266
- }
267
- }
268
- }
71
+ /**
72
+ * Get all registered theme IDs
73
+ * @param registry - Theme registry object
74
+ */
75
+ export function getThemeIds(registry: ThemeRegistry): string[] {
76
+ return Object.keys(registry);
77
+ }
269
78
 
270
- return {
271
- valid: errors.length === 0,
272
- errors,
273
- };
274
- }
79
+ /**
80
+ * Clear all registered themes
81
+ * @param registry - Theme registry object
82
+ */
83
+ export function clearThemes(registry: ThemeRegistry): void {
84
+ Object.keys(registry).forEach(key => delete registry[key]);
85
+ }
275
86
 
276
- /**
277
- * Clear registry
278
- */
279
- clear(): void {
280
- this.entries.clear();
281
- this.config = null;
282
- this.initialized = false;
283
- }
87
+ /**
88
+ * Get the number of registered themes
89
+ * @param registry - Theme registry object
90
+ */
91
+ export function getThemeCount(registry: ThemeRegistry): number {
92
+ return Object.keys(registry).length;
284
93
  }
@@ -0,0 +1,132 @@
1
+ /**
2
+ * createTheme Tests
3
+ *
4
+ * Tests for createTheme function including automatic config loading
5
+ */
6
+
7
+ import { describe, it, expect, vi, beforeEach } from 'vitest';
8
+ import { createTheme } from '../createTheme';
9
+ import type { DesignTokens } from '../../tokens/tokens';
10
+ import type { Theme } from '../../types';
11
+
12
+ describe('createTheme', () => {
13
+ beforeEach(() => {
14
+ vi.clearAllMocks();
15
+ });
16
+
17
+ describe('Automatic Config Loading', () => {
18
+ it('should work without input (uses defaults or config)', () => {
19
+ // createTheme() should work even if config is not available
20
+ // It will fall back to default tokens
21
+ const css = createTheme();
22
+
23
+ expect(typeof css).toBe('string');
24
+ expect(css.length).toBeGreaterThan(0);
25
+ expect(css).toContain(':root');
26
+ });
27
+
28
+ it('should generate valid CSS with default tokens', () => {
29
+ const css = createTheme();
30
+
31
+ // Should contain CSS variable declarations
32
+ expect(css).toMatch(/--atomix-[a-z-]+:\s*[^;]+;/);
33
+ });
34
+ });
35
+
36
+ describe('DesignTokens Input', () => {
37
+ it('should accept DesignTokens and generate CSS', () => {
38
+ const tokens: Partial<DesignTokens> = {
39
+ 'primary': '#7AFFD7',
40
+ 'secondary': '#FF5733',
41
+ 'spacing-4': '1rem',
42
+ };
43
+
44
+ const css = createTheme(tokens);
45
+
46
+ expect(css).toContain('--atomix-primary');
47
+ expect(css).toContain('#7AFFD7');
48
+ expect(css).toContain('--atomix-secondary');
49
+ expect(css).toContain('#FF5733');
50
+ expect(css).toContain('--atomix-spacing-4');
51
+ expect(css).toContain('1rem');
52
+ });
53
+
54
+ it('should merge with default tokens', () => {
55
+ const tokens: Partial<DesignTokens> = {
56
+ 'primary': '#CUSTOM',
57
+ };
58
+
59
+ const css = createTheme(tokens);
60
+
61
+ // Should contain custom primary
62
+ expect(css).toContain('--atomix-primary');
63
+ expect(css).toContain('#CUSTOM');
64
+
65
+ // Should also contain default tokens
66
+ expect(css).toContain('--atomix-secondary');
67
+ });
68
+ });
69
+
70
+ describe('Options', () => {
71
+ it('should respect prefix option', () => {
72
+ const tokens: Partial<DesignTokens> = {
73
+ 'primary': '#7AFFD7',
74
+ };
75
+
76
+ const css = createTheme(tokens, { prefix: 'myapp' });
77
+
78
+ expect(css).toContain('--myapp-primary');
79
+ expect(css).not.toContain('--atomix-primary');
80
+ });
81
+
82
+ it('should respect selector option', () => {
83
+ const tokens: Partial<DesignTokens> = {
84
+ 'primary': '#7AFFD7',
85
+ };
86
+
87
+ const css = createTheme(tokens, { selector: '[data-theme="dark"]' });
88
+
89
+ expect(css).toContain('[data-theme="dark"]');
90
+ expect(css).not.toContain(':root');
91
+ });
92
+
93
+ it('should use default prefix when not provided', () => {
94
+ const tokens: Partial<DesignTokens> = {
95
+ 'primary': '#7AFFD7',
96
+ };
97
+
98
+ const css = createTheme(tokens);
99
+
100
+ expect(css).toContain('--atomix-primary');
101
+ });
102
+ });
103
+
104
+ describe('CSS Output Format', () => {
105
+ it('should generate valid CSS syntax', () => {
106
+ const tokens: Partial<DesignTokens> = {
107
+ 'primary': '#7AFFD7',
108
+ };
109
+
110
+ const css = createTheme(tokens);
111
+
112
+ // Should be valid CSS
113
+ expect(css).toMatch(/^[^{]*\{[^}]*\}/s);
114
+ expect(css).toContain(':');
115
+ expect(css).toContain(';');
116
+ });
117
+
118
+ it('should format CSS with proper indentation', () => {
119
+ const tokens: Partial<DesignTokens> = {
120
+ 'primary': '#7AFFD7',
121
+ 'secondary': '#FF5733',
122
+ };
123
+
124
+ const css = createTheme(tokens);
125
+
126
+ // Should have proper formatting
127
+ expect(css).toContain('\n');
128
+ });
129
+ });
130
+ });
131
+
132
+
@@ -0,0 +1,105 @@
1
+ /**
2
+ * Theme Composition Utilities
3
+ *
4
+ * Simplified utilities for composing, merging, and extending themes.
5
+ */
6
+
7
+ import type { Theme, ThemeOptions } from '../types';
8
+ import { createThemeObject } from './createThemeObject';
9
+
10
+ // ============================================================================
11
+ // Deep Merge Utility
12
+ // ============================================================================
13
+
14
+ /**
15
+ * Check if value is an object
16
+ */
17
+ function isObject(item: any): item is Record<string, any> {
18
+ return item && typeof item === 'object' && !Array.isArray(item) && typeof item !== 'function';
19
+ }
20
+
21
+ /**
22
+ * Deep merge multiple objects
23
+ * Later objects override earlier ones
24
+ */
25
+ export function deepMerge<T extends Record<string, unknown>>(...objects: Partial<T>[]): T {
26
+ if (objects.length === 0) return {} as T;
27
+ if (objects.length === 1) return objects[0] as T;
28
+
29
+ const [target, ...sources] = objects;
30
+ const result = { ...target } as T;
31
+
32
+ for (const source of sources) {
33
+ if (!source) continue;
34
+
35
+ for (const key in source) {
36
+ if (!Object.prototype.hasOwnProperty.call(source, key)) continue;
37
+
38
+ const targetValue = result[key];
39
+ const sourceValue = source[key];
40
+
41
+ if (isObject(targetValue) && isObject(sourceValue)) {
42
+ // Recursively merge objects
43
+ result[key] = deepMerge(targetValue as Record<string, unknown>, sourceValue as Record<string, unknown>) as T[Extract<keyof T, string>];
44
+ } else {
45
+ // Override with source value
46
+ result[key] = sourceValue as T[Extract<keyof T, string>];
47
+ }
48
+ }
49
+ }
50
+
51
+ return result;
52
+ }
53
+
54
+ // ============================================================================
55
+ // Theme Merging
56
+ // ============================================================================
57
+
58
+ /**
59
+ * Merge multiple theme options into a single theme options object
60
+ *
61
+ * @param themes - Theme options to merge
62
+ * @returns Merged theme options
63
+ *
64
+ * @example
65
+ * ```typescript
66
+ * const baseTheme = { palette: { primary: { main: '#000' } } };
67
+ * const customTheme = { palette: { secondary: { main: '#fff' } } };
68
+ * const merged = mergeTheme(baseTheme, customTheme);
69
+ * ```
70
+ */
71
+ export function mergeTheme(...themes: ThemeOptions[]): ThemeOptions {
72
+ return deepMerge({}, ...themes);
73
+ }
74
+
75
+ /**
76
+ * Extend an existing theme with new options
77
+ *
78
+ * @param baseTheme - Base theme to extend (can be Theme or ThemeOptions)
79
+ * @param extension - Theme options to extend with
80
+ * @returns New theme with extended options
81
+ *
82
+ * @example
83
+ * ```typescript
84
+ * const base = createTheme({ palette: { primary: { main: '#000' } } });
85
+ * const extended = extendTheme(base, {
86
+ * palette: { secondary: { main: '#fff' } }
87
+ * });
88
+ * ```
89
+ */
90
+ export function extendTheme(baseTheme: Theme | ThemeOptions, extension: ThemeOptions): Theme {
91
+ // Convert baseTheme to ThemeOptions if it's a complete Theme
92
+ const baseOptions: ThemeOptions = (baseTheme as Theme & { __isJSTheme?: boolean }).__isJSTheme
93
+ ? { ...baseTheme } as ThemeOptions
94
+ : baseTheme;
95
+
96
+ // Merge and create new theme
97
+ const merged = mergeTheme(baseOptions, extension);
98
+ return createThemeObject(merged);
99
+ }
100
+
101
+ export default {
102
+ deepMerge,
103
+ mergeTheme,
104
+ extendTheme,
105
+ };