@evolve.ui/theme-system 1.0.0 → 1.1.0

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.
@@ -0,0 +1,424 @@
1
+ import type { Brands } from '../brands';
2
+ import type { CleanTheme, Theme, TypographySpec } from '../themeType';
3
+
4
+ import { boxShadow } from '../../tailwind/boxShadow';
5
+ import * as cssVars from '../../tailwind/cssVars';
6
+ import { fontFamily } from '../../tailwind/fontFamily';
7
+ import * as BasketballTraining from '../BasketballTraining';
8
+ import { breakpoints, radius, spacing, width } from '../common';
9
+ import * as Default from '../Default';
10
+ import * as ProfessionalBrand from '../ProfessionalBrand';
11
+
12
+ const themeSources = {
13
+ BasketballTraining,
14
+ Default,
15
+ ProfessionalBrand,
16
+ } satisfies Record<
17
+ Brands,
18
+ {
19
+ colorAliases: Record<string, unknown>;
20
+ coreColors: Record<string, unknown>;
21
+ nonColorVariables: Record<string, string>;
22
+ }
23
+ >;
24
+
25
+ const variable = (name: string): string => `var(${name})`;
26
+
27
+ type TextSpecInput = Omit<TypographySpec, 'family' | 'style'> & {
28
+ family?: TypographySpec['family'];
29
+ };
30
+
31
+ const textSpec = ({
32
+ family = fontFamily.primary,
33
+ height,
34
+ size,
35
+ spacing: letterSpacing,
36
+ transform,
37
+ weight,
38
+ }: TextSpecInput): TypographySpec => ({
39
+ family,
40
+ height,
41
+ size,
42
+ spacing: letterSpacing,
43
+ style: 'normal',
44
+ transform,
45
+ weight,
46
+ });
47
+
48
+ const buildTypography = (): Theme['typography'] => {
49
+ const bodyPrimary = textSpec({
50
+ height: '24px',
51
+ size: '16px',
52
+ spacing: variable(cssVars.letterSpacingBodyPrimary),
53
+ weight: variable(cssVars.fontWeightBodyPrimary),
54
+ });
55
+ const bodySecondary = textSpec({
56
+ height: '22px',
57
+ size: '14px',
58
+ spacing: variable(cssVars.letterSpacingBodySecondary),
59
+ weight: variable(cssVars.fontWeightBodySecondary),
60
+ });
61
+
62
+ return {
63
+ body: {
64
+ primary: bodyPrimary,
65
+ secondary: bodySecondary,
66
+ },
67
+ button: {
68
+ '100': textSpec({
69
+ height: '20px',
70
+ size: variable(cssVars.fontSizeButton100),
71
+ spacing: variable(cssVars.letterSpacingButton),
72
+ transform: variable(cssVars.textTransformButton),
73
+ weight: variable(cssVars.fontWeightButton),
74
+ }),
75
+ '200': textSpec({
76
+ height: '18px',
77
+ size: variable(cssVars.fontSizeButton200),
78
+ spacing: variable(cssVars.letterSpacingButton),
79
+ transform: variable(cssVars.textTransformButton),
80
+ weight: variable(cssVars.fontWeightButton),
81
+ }),
82
+ },
83
+ caption: {
84
+ '100': textSpec({
85
+ height: '18px',
86
+ size: '12px',
87
+ spacing: variable(cssVars.letterSpacingCaption),
88
+ weight: variable(cssVars.fontWeightCaption),
89
+ }),
90
+ },
91
+ heading: {
92
+ '000': textSpec({
93
+ family: fontFamily.secondary,
94
+ height: '56px',
95
+ size: '40px',
96
+ spacing: variable(cssVars.letterSpacingH1),
97
+ weight: variable(cssVars.fontWeightH1),
98
+ }),
99
+ '100': {
100
+ ...textSpec({
101
+ family: fontFamily.secondary,
102
+ height: '48px',
103
+ size: '32px',
104
+ spacing: variable(cssVars.letterSpacingH1),
105
+ weight: variable(cssVars.fontWeightH1),
106
+ }),
107
+ mobile: textSpec({
108
+ family: fontFamily.secondary,
109
+ height: '36px',
110
+ size: '24px',
111
+ spacing: variable(cssVars.letterSpacingH1Mobile),
112
+ weight: variable(cssVars.fontWeightH1),
113
+ }),
114
+ },
115
+ '200': {
116
+ ...textSpec({
117
+ family: fontFamily.secondary,
118
+ height: '36px',
119
+ size: '24px',
120
+ spacing: variable(cssVars.letterSpacingH2),
121
+ weight: variable(cssVars.fontWeightH2),
122
+ }),
123
+ mobile: textSpec({
124
+ family: fontFamily.secondary,
125
+ height: '30px',
126
+ size: '20px',
127
+ spacing: variable(cssVars.letterSpacingH2Mobile),
128
+ weight: variable(cssVars.fontWeightH2),
129
+ }),
130
+ },
131
+ '300': {
132
+ ...textSpec({
133
+ family: fontFamily.secondary,
134
+ height: '30px',
135
+ size: '20px',
136
+ spacing: variable(cssVars.letterSpacingH3),
137
+ weight: variable(cssVars.fontWeightH3),
138
+ }),
139
+ mobile: textSpec({
140
+ family: fontFamily.secondary,
141
+ height: '24px',
142
+ size: '16px',
143
+ spacing: variable(cssVars.letterSpacingH3Mobile),
144
+ weight: variable(cssVars.fontWeightH3),
145
+ }),
146
+ },
147
+ '400': textSpec({
148
+ family: fontFamily.secondary,
149
+ height: '24px',
150
+ size: '16px',
151
+ spacing: variable(cssVars.letterSpacingH4),
152
+ weight: variable(cssVars.fontWeightH4),
153
+ }),
154
+ '500': textSpec({
155
+ height: '24px',
156
+ size: '16px',
157
+ spacing: variable(cssVars.letterSpacingH5),
158
+ weight: variable(cssVars.fontWeightH5),
159
+ }),
160
+ '600': textSpec({
161
+ height: '22px',
162
+ size: '14px',
163
+ spacing: variable(cssVars.letterSpacingH6),
164
+ transform: variable(cssVars.textTransformH6),
165
+ weight: variable(cssVars.fontWeightH6),
166
+ }),
167
+ },
168
+ label: {
169
+ mini: textSpec({
170
+ height: '14px',
171
+ size: '12px',
172
+ spacing: variable(cssVars.letterSpacingLabelMini),
173
+ transform: variable(cssVars.textTransformLabelMini),
174
+ weight: variable(cssVars.fontWeightLabelMini),
175
+ }),
176
+ strong: textSpec({
177
+ height: '22px',
178
+ size: '14px',
179
+ spacing: variable(cssVars.letterSpacingLabelStrong),
180
+ weight: variable(cssVars.fontWeightLabelStrong),
181
+ }),
182
+ },
183
+ };
184
+ };
185
+
186
+ export const buildRuntimeTheme = (brand: Brands): CleanTheme => {
187
+ const source = themeSources[brand];
188
+ const v = variable;
189
+
190
+ return {
191
+ border: {
192
+ radius,
193
+ width,
194
+ },
195
+ brand,
196
+ breakpoints,
197
+ button: {
198
+ border: { radius: v(cssVars.buttonBorderRadius) },
199
+ favorite: {
200
+ default: {
201
+ color: { background: v(cssVars.colorBackgroundPrimary), border: v(cssVars.colorBorderPrimary) },
202
+ },
203
+ disabled: {
204
+ color: { background: v(cssVars.colorBackgroundDisabled), border: v(cssVars.colorBorderDisabled) },
205
+ },
206
+ hover: { color: { background: v(cssVars.colorBackgroundHover), border: v(cssVars.colorBorderHover) } },
207
+ pressed: {
208
+ color: { background: v(cssVars.colorBackgroundPressed), border: v(cssVars.colorBorderSelected) },
209
+ },
210
+ selected: {
211
+ color: { background: v(cssVars.colorBackgroundPrimary), border: v(cssVars.colorBorderSelected) },
212
+ },
213
+ },
214
+ inline: {
215
+ alt: {
216
+ default: { color: { text: v(cssVars.colorLinkInverse) } },
217
+ hover: { color: { text: v(cssVars.colorLinkInverseHover) } },
218
+ pressed: { color: { text: v(cssVars.colorLinkInversePressed) } },
219
+ },
220
+ default: { color: { text: v(cssVars.colorLinkPrimary) } },
221
+ hover: { color: { text: v(cssVars.colorLinkHover) } },
222
+ inverse: { color: { text: v(cssVars.colorLinkInverse) } },
223
+ pressed: { color: { text: v(cssVars.colorLinkPressed) } },
224
+ weak: { color: { text: v(cssVars.colorLinkSecondary) } },
225
+ },
226
+ primary: {
227
+ alt: {
228
+ color: {
229
+ background: v(cssVars.colorBackgroundInverse),
230
+ border: v(cssVars.colorBorderInverse),
231
+ text: v(cssVars.colorTextInverse),
232
+ },
233
+ focused: { color: { background: v(cssVars.colorBackgroundHoverInverse) } },
234
+ hover: {
235
+ color: {
236
+ background: v(cssVars.colorBackgroundHoverInverse),
237
+ border: v(cssVars.colorBorderInverse),
238
+ },
239
+ },
240
+ pressed: {
241
+ color: {
242
+ background: v(cssVars.colorBackgroundPressedInverse),
243
+ border: v(cssVars.colorBorderInverse),
244
+ },
245
+ },
246
+ },
247
+ color: { background: v(cssVars.colorLinkPrimary), text: v(cssVars.colorTextInverse) },
248
+ focused: { color: { background: v(cssVars.colorLinkHover) } },
249
+ hover: { color: { background: v(cssVars.colorLinkHover) } },
250
+ pressed: { color: { background: v(cssVars.colorLinkPressed) } },
251
+ },
252
+ secondary: {
253
+ alt: {
254
+ color: {
255
+ background: v(cssVars.colorBackgroundPrimary),
256
+ border: v(cssVars.colorBorderInverse),
257
+ text: v(cssVars.colorTextInverse),
258
+ },
259
+ focused: {
260
+ color: {
261
+ background: v(cssVars.colorBackgroundHoverInverse),
262
+ text: v(cssVars.colorTextPrimary),
263
+ },
264
+ },
265
+ hover: {
266
+ color: {
267
+ background: v(cssVars.colorBackgroundHoverInverse),
268
+ border: v(cssVars.colorBorderInverse),
269
+ text: v(cssVars.colorTextPrimary),
270
+ },
271
+ },
272
+ pressed: {
273
+ color: {
274
+ background: v(cssVars.colorBackgroundPressedInverse),
275
+ border: v(cssVars.colorBorderInverse),
276
+ },
277
+ },
278
+ },
279
+ color: {
280
+ background: v(cssVars.colorBackgroundPrimary),
281
+ border: v(cssVars.colorBorderPrimary),
282
+ text: v(cssVars.colorTextPrimary),
283
+ },
284
+ focused: { color: { background: v(cssVars.colorBackgroundHover) } },
285
+ hover: {
286
+ color: {
287
+ background: v(cssVars.colorBackgroundHover),
288
+ border: v(cssVars.colorBorderHover),
289
+ text: v(cssVars.colorTextInverse),
290
+ },
291
+ },
292
+ pressed: {
293
+ color: { background: v(cssVars.colorBackgroundPressed), border: v(cssVars.colorBorderSelected) },
294
+ },
295
+ },
296
+ tertiary: {
297
+ color: { text: v(cssVars.colorLinkPrimary) },
298
+ hover: { color: { background: v(cssVars.colorBackgroundHover) } },
299
+ pressed: { color: { background: v(cssVars.colorBackgroundPressed) } },
300
+ },
301
+ },
302
+ checkbox: { border: { radius: v(cssVars.checkboxBorderRadius) } },
303
+ color: {
304
+ background: {
305
+ critical: {
306
+ dark: v(cssVars.colorBackgroundCriticalDark),
307
+ light: v(cssVars.colorBackgroundCriticalLight),
308
+ },
309
+ decorative: v(cssVars.colorBackgroundTertiary),
310
+ disabled: v(cssVars.colorBackgroundDisabled),
311
+ field: { default: v(cssVars.colorBackgroundPrimary) },
312
+ hover: v(cssVars.colorBackgroundHover),
313
+ info: { dark: v(cssVars.colorBackgroundInfoDark), light: v(cssVars.colorBackgroundInfoLight) },
314
+ inverse: v(cssVars.colorBackgroundInverse),
315
+ neutral: { dark: v(cssVars.colorBackgroundInverse), light: v(cssVars.colorBackgroundSubdued) },
316
+ pressed: v(cssVars.colorBackgroundPressed),
317
+ primary: v(cssVars.colorBackgroundPrimary),
318
+ secondary: v(cssVars.colorBackgroundSecondary),
319
+ subdued: v(cssVars.colorBackgroundSubdued),
320
+ success: { dark: v(cssVars.colorBackgroundSuccessDark), light: v(cssVars.colorBackgroundSuccessLight) },
321
+ tertiary: v(cssVars.colorBackgroundTertiary),
322
+ warning: { dark: v(cssVars.colorBackgroundWarningDark), light: v(cssVars.colorBackgroundWarningLight) },
323
+ },
324
+ border: {
325
+ critical: v(cssVars.colorBorderCritical),
326
+ disabled: v(cssVars.colorBorderDisabled),
327
+ divider: {
328
+ black: v(cssVars.colorBorderBlack),
329
+ dark: v(cssVars.colorBorderPrimary),
330
+ default: v(cssVars.colorBorderSecondary),
331
+ inverse: v(cssVars.colorBorderInverse),
332
+ light: v(cssVars.colorBorderTertiary),
333
+ },
334
+ focused: { default: v(cssVars.colorFocusPrimary), inverse: v(cssVars.colorFocusInverse) },
335
+ hover: v(cssVars.colorBorderHover),
336
+ info: v(cssVars.colorBorderInfo),
337
+ inverse: v(cssVars.colorBorderInverse),
338
+ primary: v(cssVars.colorBorderPrimary),
339
+ secondary: v(cssVars.colorBorderSecondary),
340
+ selected: v(cssVars.colorBorderSelected),
341
+ success: v(cssVars.colorBorderSuccess),
342
+ warning: v(cssVars.colorBorderWarning),
343
+ },
344
+ icon: {
345
+ active: v(cssVars.colorIconPrimary),
346
+ alt: {
347
+ active: v(cssVars.colorIconInverse),
348
+ default: v(cssVars.colorIconInverse),
349
+ hover: v(cssVars.colorIconSecondary),
350
+ },
351
+ critical: v(cssVars.colorIconCritical),
352
+ decorative: {
353
+ alt: v(cssVars.colorIconSecondary),
354
+ default: v(cssVars.colorIconPrimary),
355
+ light: v(cssVars.colorIconDisabled),
356
+ },
357
+ default: v(cssVars.colorIconPrimary),
358
+ disabled: v(cssVars.colorIconDisabled),
359
+ hover: v(cssVars.colorIconSecondary),
360
+ info: v(cssVars.colorIconInfo),
361
+ inverse: v(cssVars.colorIconInverse),
362
+ primary: {
363
+ alt: {
364
+ active: v(cssVars.colorIconInverse),
365
+ default: v(cssVars.colorIconSecondary),
366
+ hover: v(cssVars.colorIconPrimary),
367
+ },
368
+ default: v(cssVars.colorIconPrimary),
369
+ },
370
+ success: v(cssVars.colorIconSuccess),
371
+ warning: { default: v(cssVars.colorIconWarning), light: v(cssVars.colorBackgroundWarningLight) },
372
+ },
373
+ keyboardFocus: v(cssVars.colorFocusPrimary),
374
+ keyboardFocusInverse: v(cssVars.colorFocusInverse),
375
+ shadow: {
376
+ '01': boxShadow['01'],
377
+ '02': boxShadow['02'],
378
+ '03': boxShadow['03'],
379
+ },
380
+ skeleton: { default: v(cssVars.colorBackgroundSubdued) },
381
+ text: {
382
+ alt: v(cssVars.colorTextInverse),
383
+ critical: v(cssVars.colorTextCritical),
384
+ disabled: v(cssVars.colorTextDisabled),
385
+ error: v(cssVars.colorTextCritical),
386
+ inactive: v(cssVars.colorTextInactive),
387
+ info: v(cssVars.colorTextInfo),
388
+ inverse: v(cssVars.colorTextInverse),
389
+ link: {
390
+ active: v(cssVars.colorLinkPressed),
391
+ default: v(cssVars.colorLinkPrimary),
392
+ disabled: v(cssVars.colorLinkDisabled),
393
+ focused: v(cssVars.colorLinkHover),
394
+ hover: v(cssVars.colorLinkHover),
395
+ inverse: v(cssVars.colorLinkInverse),
396
+ monochrome: v(cssVars.colorLinkMonochrome),
397
+ weak: v(cssVars.colorLinkSecondary),
398
+ },
399
+ primary: v(cssVars.colorTextPrimary),
400
+ secondary: v(cssVars.colorTextSecondary),
401
+ standard: v(cssVars.colorTextPrimary),
402
+ success: v(cssVars.colorTextSuccess),
403
+ warning: { default: v(cssVars.colorTextWarning), light: v(cssVars.colorBackgroundWarningLight) },
404
+ weak: v(cssVars.colorTextSecondary),
405
+ },
406
+ transparent00: 'rgb(0 0 0 / 0%)',
407
+ transparent05: 'rgb(0 0 0 / 5%)',
408
+ transparent20: 'rgb(0 0 0 / 20%)',
409
+ transparent40: 'rgb(0 0 0 / 40%)',
410
+ },
411
+ core: Object.fromEntries(Object.entries(source.coreColors).map(([key]) => [key, key])),
412
+ elevation: {
413
+ '01': v(cssVars.shadow01),
414
+ '02': v(cssVars.shadow02),
415
+ '03': v(cssVars.shadow03),
416
+ '04': v(cssVars.shadow04),
417
+ '05': v(cssVars.shadow05),
418
+ },
419
+ input: { border: { radius: v(cssVars.inputBorderRadius) } },
420
+ spacing,
421
+ toggle: { border: { radius: v(cssVars.buttonBorderRadius) } },
422
+ typography: buildTypography(),
423
+ };
424
+ };
@@ -0,0 +1,3 @@
1
+ import { buildRuntimeTheme } from './buildTheme';
2
+
3
+ export const defaultTheme = buildRuntimeTheme('Default');
@@ -0,0 +1,3 @@
1
+ import { buildRuntimeTheme } from './buildTheme';
2
+
3
+ export const professionalBrandTheme = buildRuntimeTheme('ProfessionalBrand');
@@ -0,0 +1,20 @@
1
+ import { nonColorVariables } from './Default';
2
+
3
+ /**
4
+ * Maps each semantic color token (e.g. `text-critical`) to the core palette step
5
+ * it resolves to (e.g. `critical-500`), parsed from the Default brand's
6
+ * `nonColorVariables`. The semantic → step mapping is identical across all brands
7
+ * (only the core hex values differ per brand), so Default is canonical.
8
+ *
9
+ * Single source of truth for docs/tooling that need to show which step a semantic
10
+ * token points at — derive from this rather than hand-maintaining a duplicate table.
11
+ */
12
+ export const semanticColorTokenSteps: Record<string, string> = Object.fromEntries(
13
+ Object.entries(nonColorVariables).flatMap(([key, value]) => {
14
+ if (!key.startsWith('--color-')) {
15
+ return [];
16
+ }
17
+ const match = /^var\(--color-(.+)\)$/.exec(value);
18
+ return match ? [[key.slice('--color-'.length), match[1]]] : [];
19
+ })
20
+ );
@@ -0,0 +1,169 @@
1
+ import type { Brands } from './brands';
2
+
3
+ type TokenValue = string;
4
+ type Px = string;
5
+
6
+ export type TypographySpec = {
7
+ family: TokenValue;
8
+ height: Px;
9
+ size: Px;
10
+ spacing: Px;
11
+ style: string;
12
+ transform?: string;
13
+ weight: number | TokenValue;
14
+ };
15
+
16
+ type Typography = {
17
+ body: Record<'primary' | 'secondary', TypographySpec>;
18
+ button: Record<'100' | '200', TypographySpec>;
19
+ caption: Record<'100', TypographySpec>;
20
+ heading: Record<'000' | '400' | '500' | '600', TypographySpec> &
21
+ Record<'100' | '200' | '300', TypographySpec & { mobile: TypographySpec }>;
22
+ label: Record<'mini' | 'strong', TypographySpec>;
23
+ };
24
+
25
+ type Color = {
26
+ background: {
27
+ critical: Record<'dark' | 'light', TokenValue>;
28
+ decorative: TokenValue;
29
+ disabled: TokenValue;
30
+ field: { default: TokenValue };
31
+ hover: TokenValue;
32
+ info: Record<'dark' | 'light', TokenValue>;
33
+ inverse: TokenValue;
34
+ neutral: Record<'dark' | 'light', TokenValue>;
35
+ pressed: TokenValue;
36
+ primary: TokenValue;
37
+ secondary: TokenValue;
38
+ subdued: TokenValue;
39
+ success: Record<'dark' | 'light', TokenValue>;
40
+ tertiary: TokenValue;
41
+ warning: Record<'dark' | 'light', TokenValue>;
42
+ };
43
+ border: {
44
+ critical: TokenValue;
45
+ disabled: TokenValue;
46
+ divider: Record<'black' | 'dark' | 'default' | 'inverse' | 'light', TokenValue>;
47
+ focused: Record<'default' | 'inverse', TokenValue>;
48
+ hover: TokenValue;
49
+ info: TokenValue;
50
+ inverse: TokenValue;
51
+ primary: TokenValue;
52
+ secondary: TokenValue;
53
+ selected: TokenValue;
54
+ success: TokenValue;
55
+ warning: TokenValue;
56
+ };
57
+ icon: {
58
+ active: TokenValue;
59
+ alt: Record<'active' | 'default' | 'hover', TokenValue>;
60
+ critical: TokenValue;
61
+ decorative: Record<'alt' | 'default' | 'light', TokenValue>;
62
+ default: TokenValue;
63
+ disabled: TokenValue;
64
+ hover: TokenValue;
65
+ info: TokenValue;
66
+ inverse: TokenValue;
67
+ primary: { alt: { active: TokenValue; default: TokenValue; hover: TokenValue }; default: TokenValue };
68
+ success: TokenValue;
69
+ warning: { default: TokenValue; light: TokenValue };
70
+ };
71
+ keyboardFocus: TokenValue;
72
+ keyboardFocusInverse: TokenValue;
73
+ shadow: Record<'01' | '02' | '03', TokenValue>;
74
+ skeleton: Record<'default', TokenValue>;
75
+ text: {
76
+ alt: TokenValue;
77
+ critical: TokenValue;
78
+ disabled: TokenValue;
79
+ error: TokenValue;
80
+ inactive: TokenValue;
81
+ info: TokenValue;
82
+ inverse: TokenValue;
83
+ link: {
84
+ active: TokenValue;
85
+ default: TokenValue;
86
+ disabled: TokenValue;
87
+ focused: TokenValue;
88
+ hover: TokenValue;
89
+ inverse: TokenValue;
90
+ monochrome: TokenValue;
91
+ weak: TokenValue;
92
+ };
93
+ primary: TokenValue;
94
+ secondary: TokenValue;
95
+ standard: TokenValue;
96
+ success: TokenValue;
97
+ warning: Record<'default' | 'light', TokenValue>;
98
+ weak: TokenValue;
99
+ };
100
+ transparent00: TokenValue;
101
+ transparent05: TokenValue;
102
+ transparent20: TokenValue;
103
+ transparent40: TokenValue;
104
+ };
105
+
106
+ type StrAndValue<Keys extends string> = Record<Keys, Px> & Record<`${Keys}Value`, number>;
107
+
108
+ type Spacing = StrAndValue<'lg' | 'md' | 'sm' | 'xl' | 'xs' | 'xxl' | 'xxs' | 'xxxl' | 'xxxs'>;
109
+ type Breakpoints = StrAndValue<'lgMax' | 'lgMin' | 'mdMax' | 'mdMin' | 'smMax' | 'smMin' | 'xlMin' | 'xsMax'>;
110
+
111
+ type ButtonTheme = {
112
+ border: { radius: Px };
113
+ favorite: Record<
114
+ 'default' | 'disabled' | 'hover' | 'pressed' | 'selected',
115
+ { color: { background: TokenValue; border: TokenValue } }
116
+ >;
117
+ inline: Record<'default' | 'hover' | 'inverse' | 'pressed' | 'weak', { color: { text: TokenValue } }> & {
118
+ alt: Record<'default' | 'hover' | 'pressed', { color: { text: TokenValue } }>;
119
+ };
120
+ primary: {
121
+ alt: {
122
+ color: { background: TokenValue; border: TokenValue; text: TokenValue };
123
+ focused: { color: { background: TokenValue } };
124
+ hover: { color: { background: TokenValue; border: TokenValue } };
125
+ pressed: { color: { background: TokenValue; border: TokenValue } };
126
+ };
127
+ color: { background: TokenValue; text: TokenValue };
128
+ focused: { color: { background: TokenValue } };
129
+ hover: { color: { background: TokenValue } };
130
+ pressed: { color: { background: TokenValue } };
131
+ };
132
+ secondary: {
133
+ alt: {
134
+ color: { background: TokenValue; border: TokenValue; text: TokenValue };
135
+ focused: { color: { background: TokenValue; text: TokenValue } };
136
+ hover: { color: { background: TokenValue; border: TokenValue; text: TokenValue } };
137
+ pressed: { color: { background: TokenValue; border: TokenValue } };
138
+ };
139
+ color: { background: TokenValue; border: TokenValue; text: TokenValue };
140
+ focused: { color: { background: TokenValue } };
141
+ hover: { color: { background: TokenValue; border: TokenValue; text: TokenValue } };
142
+ pressed: { color: { background: TokenValue; border: TokenValue } };
143
+ };
144
+ tertiary: {
145
+ color: { text: TokenValue };
146
+ hover: { color: { background: TokenValue } };
147
+ pressed: { color: { background: TokenValue } };
148
+ };
149
+ };
150
+
151
+ export type Theme = {
152
+ border: {
153
+ radius: Record<'circle' | 'lg' | 'md' | 'sm' | 'xl' | 'xs' | 'zero', string>;
154
+ width: Record<'lg' | 'md' | 'sm' | 'xs' | 'xxs', string>;
155
+ };
156
+ brand: Brands;
157
+ breakpoints: Breakpoints;
158
+ button: ButtonTheme;
159
+ checkbox: { border: { radius: Px } };
160
+ color: Color;
161
+ core: Record<string, TokenValue>;
162
+ elevation: Record<'01' | '02' | '03' | '04' | '05', string>;
163
+ input: { border: { radius: string } };
164
+ spacing: Spacing;
165
+ toggle: { border: { radius: string } };
166
+ typography: Typography;
167
+ };
168
+
169
+ export type CleanTheme = Theme;
@@ -0,0 +1,11 @@
1
+ import { writeFile } from 'fs';
2
+
3
+ export const writeToCSSFile = (css: string, path: string): void => {
4
+ writeFile(path, css, (err) => {
5
+ if (err) {
6
+ // eslint-disable-next-line no-console
7
+ console.error(err);
8
+ process.exit(1);
9
+ }
10
+ });
11
+ };
@@ -0,0 +1,35 @@
1
+ import { type ClassValue, clsx } from 'clsx';
2
+ import { twMerge } from 'tailwind-merge';
3
+
4
+ /**
5
+ * Utility function to merge Tailwind CSS classes with proper conflict resolution
6
+ *
7
+ * Combines clsx for conditional class names and tailwind-merge for
8
+ * deduplication and conflict resolution
9
+ *
10
+ * @example
11
+ * ```typescript
12
+ * import { cn } from '@evolve.ui/theme-system';
13
+ *
14
+ * // Basic usage
15
+ * cn('px-2 py-1', 'rounded-md');
16
+ * // => 'px-2 py-1 rounded-md'
17
+ *
18
+ * // With conditionals
19
+ * cn('button', isActive && 'button-active', isDisabled && 'opacity-50');
20
+ * // => 'button button-active' (if isActive is true)
21
+ *
22
+ * // Object syntax
23
+ * cn('button', {
24
+ * 'button-primary': variant === 'primary',
25
+ * 'button-secondary': variant === 'secondary',
26
+ * });
27
+ *
28
+ * // Conflict resolution (last wins)
29
+ * cn('p-4', 'p-2');
30
+ * // => 'p-2'
31
+ * ```
32
+ */
33
+ export const cn = (...inputs: ClassValue[]) => {
34
+ return twMerge(clsx(inputs));
35
+ };