@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,166 @@
1
+ import {
2
+ colorCritical400,
3
+ colorCritical50,
4
+ colorCritical500,
5
+ colorInfo400,
6
+ colorInfo50,
7
+ colorInfo600,
8
+ colorInfo700,
9
+ colorPrimary500,
10
+ colorPrimary700,
11
+ colorPrimary900,
12
+ colorSecondary50,
13
+ colorSuccess300,
14
+ colorSuccess50,
15
+ colorSuccess700,
16
+ colorSuccess900,
17
+ colorSurface100,
18
+ colorSurface200,
19
+ colorSurface300,
20
+ colorSurface50,
21
+ colorSurface500,
22
+ colorSurface900,
23
+ colorTertiary50,
24
+ colorWarning300,
25
+ colorWarning50,
26
+ colorWarning500,
27
+ colorWarning600,
28
+ colorWarning800,
29
+ } from '../tailwind/cssVars';
30
+ import { colorStepArrayToObject, generateNormalColorSteps, generateSurfaceColorSteps } from '../utils/color';
31
+ import { wrapOpacity, wrapVar } from '../utils/wrapVar';
32
+ import { ColorAliasesObject, NonColorCSSVariables } from './generateCSSVars';
33
+
34
+ // "Boardroom Blue" palette (DS-83, approved 2026-07-12): deep corporate-blue primary that is no
35
+ // longer identical to the info status — info moves to its own cyan family (additive key). Key
36
+ // names are stable API (buildTheme exposes them); comments give the actual hue.
37
+ export const coreColors = {
38
+ amber: colorStepArrayToObject(generateNormalColorSteps('#B45309')), // burnt amber
39
+ blue: colorStepArrayToObject(generateNormalColorSteps('#1D4ED8')), // corporate blue
40
+ cyan: colorStepArrayToObject(generateNormalColorSteps('#0C6478')), // info cyan
41
+ green: colorStepArrayToObject(generateNormalColorSteps('#047857')), // deep green
42
+ indigo: colorStepArrayToObject(generateNormalColorSteps('#4F46E5')), // refined indigo
43
+ neutral: colorStepArrayToObject(generateSurfaceColorSteps('#525B6E', true)), // cool slate-gray
44
+ red: colorStepArrayToObject(generateNormalColorSteps('#D42424')), // signal red
45
+ slate: colorStepArrayToObject(generateNormalColorSteps('#475569')), // slate
46
+ };
47
+
48
+ export const colorAliases = {
49
+ critical: coreColors.red,
50
+ info: coreColors.cyan,
51
+ primary: coreColors.blue,
52
+ secondary: coreColors.slate,
53
+ success: coreColors.green,
54
+ surface: coreColors.neutral,
55
+ tertiary: coreColors.indigo,
56
+ warning: coreColors.amber,
57
+ } satisfies ColorAliasesObject;
58
+
59
+ export const nonColorVariables = {
60
+ '--box-shadow-01': `0px 4px 4px ${wrapOpacity(wrapVar(colorSurface200), 50)}`,
61
+ '--box-shadow-02': `0px 1px 4px ${wrapOpacity(wrapVar(colorSurface300), 50)}, 0px 1px 8px ${wrapOpacity(wrapVar(colorSurface200), 50)}`,
62
+ '--box-shadow-03': `0px 2px 8px ${wrapOpacity(wrapVar(colorSurface300), 50)}, 0px 4px 16px ${wrapOpacity(wrapVar(colorSurface200), 50)}`,
63
+ '--box-shadow-04': `0px 3px 12px ${wrapOpacity(wrapVar(colorSurface500), 50)}, 0px 6px 24px ${wrapOpacity(wrapVar(colorSurface300), 50)}`,
64
+ '--box-shadow-05': `0px -4px 4px ${wrapOpacity(wrapVar(colorSurface200), 50)}`,
65
+ '--button-border-radius': '8px',
66
+ '--checkbox-border-radius': '4px',
67
+ '--color-background-critical-dark': wrapVar(colorCritical500),
68
+ '--color-background-critical-light': wrapVar(colorCritical50),
69
+ '--color-background-disabled': wrapVar(colorSurface200),
70
+ '--color-background-hover': wrapVar(colorSurface900),
71
+ '--color-background-hover-inverse': wrapVar(colorSurface50),
72
+ '--color-background-info-dark': wrapVar(colorInfo600),
73
+ '--color-background-info-light': wrapVar(colorInfo50),
74
+ '--color-background-inverse': wrapVar(colorPrimary900),
75
+ '--color-background-overlay': wrapVar(colorSurface900),
76
+ '--color-background-pressed': wrapVar(colorSurface900),
77
+ '--color-background-pressed-inverse': wrapVar(colorSurface50),
78
+ '--color-background-primary': wrapVar(colorSurface50),
79
+ '--color-background-secondary': wrapVar(colorSecondary50),
80
+ '--color-background-subdued': wrapVar(colorSurface100),
81
+ '--color-background-success-dark': wrapVar(colorSuccess900),
82
+ '--color-background-success-light': wrapVar(colorSuccess50),
83
+ '--color-background-tertiary': wrapVar(colorTertiary50),
84
+ '--color-background-warning-dark': wrapVar(colorWarning500),
85
+ '--color-background-warning-light': wrapVar(colorWarning50),
86
+ '--color-border-black': wrapVar(colorSurface900),
87
+ '--color-border-critical': wrapVar(colorCritical400),
88
+ '--color-border-disabled': wrapVar(colorSurface300),
89
+ '--color-border-hover': wrapVar(colorPrimary700),
90
+ '--color-border-info': wrapVar(colorInfo400),
91
+ '--color-border-inverse': wrapVar(colorSurface50),
92
+ '--color-border-primary': wrapVar(colorSurface300),
93
+ '--color-border-secondary': wrapVar(colorSurface200),
94
+ '--color-border-selected': wrapVar(colorPrimary500),
95
+ '--color-border-success': wrapVar(colorSuccess300),
96
+ '--color-border-tertiary': wrapVar(colorSurface100),
97
+ '--color-border-warning': wrapVar(colorWarning300),
98
+ '--color-focus-inverse': wrapVar(colorSurface50),
99
+ '--color-focus-primary': wrapVar(colorPrimary500),
100
+ '--color-icon-critical': wrapVar(colorCritical400),
101
+ '--color-icon-disabled': wrapVar(colorSurface300),
102
+ '--color-icon-info': wrapVar(colorInfo400),
103
+ '--color-icon-inverse': wrapVar(colorSurface50),
104
+ '--color-icon-primary': wrapVar(colorSurface500),
105
+ '--color-icon-secondary': wrapVar(colorPrimary500),
106
+ '--color-icon-success': wrapVar(colorSuccess700),
107
+ '--color-icon-warning': wrapVar(colorWarning600),
108
+ '--color-link-disabled': wrapVar(colorSurface300),
109
+ '--color-link-hover': wrapVar(colorPrimary700),
110
+ '--color-link-inverse': wrapVar(colorSurface50),
111
+ '--color-link-inverse-disabled': wrapVar(colorSurface300),
112
+ '--color-link-inverse-hover': wrapVar(colorSurface100),
113
+ '--color-link-inverse-pressed': wrapVar(colorSurface200),
114
+ '--color-link-monochrome': wrapVar(colorSurface900),
115
+ '--color-link-pressed': wrapVar(colorPrimary900),
116
+ '--color-link-primary': wrapVar(colorPrimary500),
117
+ '--color-link-secondary': wrapVar(colorSurface500),
118
+ '--color-text-critical': wrapVar(colorCritical500),
119
+ '--color-text-disabled': wrapVar(colorSurface300),
120
+ '--color-text-inactive': wrapVar(colorSurface300),
121
+ '--color-text-info': wrapVar(colorInfo700),
122
+ '--color-text-inverse': wrapVar(colorSurface50),
123
+ '--color-text-primary': wrapVar(colorSurface900),
124
+ '--color-text-secondary': wrapVar(colorSurface500),
125
+ '--color-text-success': wrapVar(colorSuccess900),
126
+ '--color-text-warning': wrapVar(colorWarning800),
127
+ '--container-border-radius': '12px',
128
+ '--font-family-primary': "'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif",
129
+ '--font-family-secondary': "'Georgia', serif",
130
+ '--font-size-button-100': '14px',
131
+ '--font-size-button-200': '12px',
132
+ '--font-weight-body-primary': '400',
133
+ '--font-weight-body-secondary': '400',
134
+ '--font-weight-button': '600',
135
+ '--font-weight-caption': '400',
136
+ '--font-weight-h1': '400',
137
+ '--font-weight-h2': '400',
138
+ '--font-weight-h3': '400',
139
+ '--font-weight-h4': '400',
140
+ '--font-weight-h5': '600',
141
+ '--font-weight-h6': '600',
142
+ '--font-weight-label-mini': '400',
143
+ '--font-weight-label-strong': '600',
144
+ '--input-border-radius': '6px',
145
+ '--letter-spacing-body-primary': '0.2px',
146
+ '--letter-spacing-body-secondary': '0.2px',
147
+ '--letter-spacing-button': '1px',
148
+ '--letter-spacing-caption': '0.2px',
149
+ '--letter-spacing-h1': '0.2px',
150
+ '--letter-spacing-h1-mobile': '0.2px',
151
+ '--letter-spacing-h2': '0.2px',
152
+ '--letter-spacing-h2-mobile': '0.2px',
153
+ '--letter-spacing-h3': '0.2px',
154
+ '--letter-spacing-h3-mobile': '0.2px',
155
+ '--letter-spacing-h4': '0.2px',
156
+ '--letter-spacing-h5': '0.2px',
157
+ '--letter-spacing-h6': '1px',
158
+ '--letter-spacing-label-mini': '1px',
159
+ '--letter-spacing-label-strong': '0.2px',
160
+ '--search-input-border-radius': '24px',
161
+ '--text-transform-button': 'uppercase',
162
+ '--text-transform-h6': 'uppercase',
163
+ '--text-transform-label-mini': 'uppercase',
164
+ '--width-dense': '1200px',
165
+ '--width-page': '1644px',
166
+ } satisfies Record<NonColorCSSVariables, string>;
@@ -0,0 +1,12 @@
1
+ import type { Brands } from './brands';
2
+ import type { CleanTheme } from './themeType';
3
+
4
+ import { basketballTrainingTheme } from './runtime/basketballTraining';
5
+ import { defaultTheme } from './runtime/default';
6
+ import { professionalBrandTheme } from './runtime/professionalBrand';
7
+
8
+ export const allThemes = {
9
+ BasketballTraining: basketballTrainingTheme,
10
+ Default: defaultTheme,
11
+ ProfessionalBrand: professionalBrandTheme,
12
+ } as const satisfies Record<Brands, CleanTheme>;
@@ -0,0 +1,37 @@
1
+ export type Brands = 'BasketballTraining' | 'ProfessionalBrand' | 'Default';
2
+
3
+ export const brands: Brands[] = ['BasketballTraining', 'ProfessionalBrand', 'Default'];
4
+
5
+ export const brandsToCSS: Record<Brands, string> = {
6
+ BasketballTraining: 'brand-basketball',
7
+ Default: 'brand-default',
8
+ ProfessionalBrand: 'brand-professional',
9
+ };
10
+
11
+ export type BrandRegistryEntry = {
12
+ cssClass: string;
13
+ label: string;
14
+ storybookKey: Brands;
15
+ themeKey: Brands;
16
+ };
17
+
18
+ export const brandRegistry = {
19
+ BasketballTraining: {
20
+ cssClass: brandsToCSS.BasketballTraining,
21
+ label: 'Basketball Training',
22
+ storybookKey: 'BasketballTraining',
23
+ themeKey: 'BasketballTraining',
24
+ },
25
+ Default: {
26
+ cssClass: brandsToCSS.Default,
27
+ label: 'Default (Neutral)',
28
+ storybookKey: 'Default',
29
+ themeKey: 'Default',
30
+ },
31
+ ProfessionalBrand: {
32
+ cssClass: brandsToCSS.ProfessionalBrand,
33
+ label: 'Professional Brand',
34
+ storybookKey: 'ProfessionalBrand',
35
+ themeKey: 'ProfessionalBrand',
36
+ },
37
+ } as const satisfies Record<Brands, BrandRegistryEntry>;
@@ -0,0 +1,65 @@
1
+ import type { Theme } from './themeType';
2
+
3
+ import { borderRadius } from '../tailwind/borderRadius';
4
+ import { screens } from '../tailwind/screens';
5
+ import { spacing as tailwindSpacing } from '../tailwind/spacing';
6
+
7
+ const parsePx = (value: string): number => Number(value.replace('px', ''));
8
+
9
+ export const spacing = {
10
+ lg: tailwindSpacing.lg,
11
+ lgValue: parsePx(tailwindSpacing.lg),
12
+ md: tailwindSpacing.md,
13
+ mdValue: parsePx(tailwindSpacing.md),
14
+ sm: tailwindSpacing.sm,
15
+ smValue: parsePx(tailwindSpacing.sm),
16
+ xl: tailwindSpacing.xl,
17
+ xlValue: parsePx(tailwindSpacing.xl),
18
+ xs: tailwindSpacing.xs,
19
+ xsValue: parsePx(tailwindSpacing.xs),
20
+ xxl: tailwindSpacing['2xl'],
21
+ xxlValue: parsePx(tailwindSpacing['2xl']),
22
+ xxs: tailwindSpacing['2xs'],
23
+ xxsValue: parsePx(tailwindSpacing['2xs']),
24
+ xxxl: tailwindSpacing['3xl'],
25
+ xxxlValue: parsePx(tailwindSpacing['3xl']),
26
+ xxxs: tailwindSpacing['3xs'],
27
+ xxxsValue: parsePx(tailwindSpacing['3xs']),
28
+ } as const satisfies Theme['spacing'];
29
+
30
+ export const breakpoints = {
31
+ lgMax: screens.lgMax.max,
32
+ lgMaxValue: parsePx(screens.lgMax.max),
33
+ lgMin: screens.lgMin.min,
34
+ lgMinValue: parsePx(screens.lgMin.min),
35
+ mdMax: screens.mdMax.max,
36
+ mdMaxValue: parsePx(screens.mdMax.max),
37
+ mdMin: screens.mdMin.min,
38
+ mdMinValue: parsePx(screens.mdMin.min),
39
+ smMax: screens.smMax.max,
40
+ smMaxValue: parsePx(screens.smMax.max),
41
+ smMin: screens.smMin.min,
42
+ smMinValue: parsePx(screens.smMin.min),
43
+ xlMin: screens.xlMin.min,
44
+ xlMinValue: parsePx(screens.xlMin.min),
45
+ xsMax: screens.xsMax.max,
46
+ xsMaxValue: parsePx(screens.xsMax.max),
47
+ } as const satisfies Theme['breakpoints'];
48
+
49
+ export const radius = {
50
+ circle: '50%',
51
+ lg: borderRadius.lg,
52
+ md: borderRadius.md,
53
+ sm: borderRadius.sm,
54
+ xl: borderRadius.xl,
55
+ xs: borderRadius.xs,
56
+ zero: borderRadius.none,
57
+ } as const satisfies Theme['border']['radius'];
58
+
59
+ export const width = {
60
+ lg: '8px',
61
+ md: '4px',
62
+ sm: '2px',
63
+ xs: '1px',
64
+ xxs: '0.5px',
65
+ } as const satisfies Theme['border']['width'];
@@ -0,0 +1,46 @@
1
+ /**
2
+ * CSS Generation Script
3
+ *
4
+ * Generates brand theme CSS files from TypeScript definitions.
5
+ * Run after tsup build: `node dist/themes/generate.js`
6
+ *
7
+ * Output: dist/css/<BrandName>.css for each brand
8
+ */
9
+
10
+ import { mkdirSync } from 'fs';
11
+
12
+ import {
13
+ nonColorVariables as BasketballTraining,
14
+ colorAliases as BasketballTrainingAliases,
15
+ } from './BasketballTraining';
16
+ import { nonColorVariables as Default, colorAliases as DefaultAliases } from './Default';
17
+ import { generateCSSVars, makeColorCSSVarsFromObject } from './generateCSSVars';
18
+ import { nonColorVariables as ProfessionalBrand, colorAliases as ProfessionalBrandAliases } from './ProfessionalBrand';
19
+ import { writeToCSSFile } from './writeToCSSFile';
20
+
21
+ const distDir = `${__dirname}/../css`;
22
+ mkdirSync(distDir, { recursive: true });
23
+
24
+ writeToCSSFile(
25
+ generateCSSVars(
26
+ { ...BasketballTraining, ...makeColorCSSVarsFromObject(BasketballTrainingAliases) },
27
+ 'brand-basketball'
28
+ ),
29
+ `${distDir}/BasketballTraining.css`
30
+ );
31
+
32
+ writeToCSSFile(
33
+ generateCSSVars(
34
+ { ...ProfessionalBrand, ...makeColorCSSVarsFromObject(ProfessionalBrandAliases) },
35
+ 'brand-professional'
36
+ ),
37
+ `${distDir}/ProfessionalBrand.css`
38
+ );
39
+
40
+ writeToCSSFile(
41
+ generateCSSVars({ ...Default, ...makeColorCSSVarsFromObject(DefaultAliases) }, 'brand-default'),
42
+ `${distDir}/Default.css`
43
+ );
44
+
45
+ // eslint-disable-next-line no-console
46
+ console.log('✅ Generated theme CSS files in dist/css/');
@@ -0,0 +1,123 @@
1
+ import type * as CSSVars from '../tailwind/cssVars';
2
+
3
+ import { StepObject, steps } from '../utils/color';
4
+
5
+ export type CSSVariables = (typeof CSSVars)[keyof typeof CSSVars];
6
+ export type NonColorCSSVariables = Exclude<
7
+ CSSVariables,
8
+ | '--color-primary-50'
9
+ | '--color-primary-100'
10
+ | '--color-primary-200'
11
+ | '--color-primary-300'
12
+ | '--color-primary-400'
13
+ | '--color-primary-500'
14
+ | '--color-primary-600'
15
+ | '--color-primary-700'
16
+ | '--color-primary-800'
17
+ | '--color-primary-900'
18
+ | '--color-secondary-50'
19
+ | '--color-secondary-100'
20
+ | '--color-secondary-200'
21
+ | '--color-secondary-300'
22
+ | '--color-secondary-400'
23
+ | '--color-secondary-500'
24
+ | '--color-secondary-600'
25
+ | '--color-secondary-700'
26
+ | '--color-secondary-800'
27
+ | '--color-secondary-900'
28
+ | '--color-tertiary-50'
29
+ | '--color-tertiary-100'
30
+ | '--color-tertiary-200'
31
+ | '--color-tertiary-300'
32
+ | '--color-tertiary-400'
33
+ | '--color-tertiary-500'
34
+ | '--color-tertiary-600'
35
+ | '--color-tertiary-700'
36
+ | '--color-tertiary-800'
37
+ | '--color-tertiary-900'
38
+ | '--color-info-50'
39
+ | '--color-info-100'
40
+ | '--color-info-200'
41
+ | '--color-info-300'
42
+ | '--color-info-400'
43
+ | '--color-info-500'
44
+ | '--color-info-600'
45
+ | '--color-info-700'
46
+ | '--color-info-800'
47
+ | '--color-info-900'
48
+ | '--color-success-50'
49
+ | '--color-success-100'
50
+ | '--color-success-200'
51
+ | '--color-success-300'
52
+ | '--color-success-400'
53
+ | '--color-success-500'
54
+ | '--color-success-600'
55
+ | '--color-success-700'
56
+ | '--color-success-800'
57
+ | '--color-success-900'
58
+ | '--color-critical-50'
59
+ | '--color-critical-100'
60
+ | '--color-critical-200'
61
+ | '--color-critical-300'
62
+ | '--color-critical-400'
63
+ | '--color-critical-500'
64
+ | '--color-critical-600'
65
+ | '--color-critical-700'
66
+ | '--color-critical-800'
67
+ | '--color-critical-900'
68
+ | '--color-warning-50'
69
+ | '--color-warning-100'
70
+ | '--color-warning-200'
71
+ | '--color-warning-300'
72
+ | '--color-warning-400'
73
+ | '--color-warning-500'
74
+ | '--color-warning-600'
75
+ | '--color-warning-700'
76
+ | '--color-warning-800'
77
+ | '--color-warning-900'
78
+ | '--color-surface-50'
79
+ | '--color-surface-100'
80
+ | '--color-surface-200'
81
+ | '--color-surface-300'
82
+ | '--color-surface-400'
83
+ | '--color-surface-500'
84
+ | '--color-surface-600'
85
+ | '--color-surface-700'
86
+ | '--color-surface-800'
87
+ | '--color-surface-900'
88
+ >;
89
+
90
+ export type ColorAliases =
91
+ | 'primary'
92
+ | 'secondary'
93
+ | 'tertiary'
94
+ | 'info'
95
+ | 'success'
96
+ | 'warning'
97
+ | 'critical'
98
+ | 'surface';
99
+ export type ColorAliasesObject = Record<ColorAliases, StepObject>;
100
+
101
+ export const makeColorCSSVarsFromObject = (colors: ColorAliasesObject): Record<string, string> => {
102
+ const colorVars: Record<string, string> = {};
103
+ for (const [key, value] of Object.entries(colors)) {
104
+ for (const shade of steps) {
105
+ const varName = `--color-${key}-${shade}`;
106
+ colorVars[varName] = `${value[shade]}`;
107
+ }
108
+ }
109
+
110
+ return colorVars;
111
+ };
112
+
113
+ export const generateCSSVars = (vars: Record<string, string>, className: string): string => {
114
+ const classBlock = `.${className} {\n${Object.entries(vars)
115
+ .map(([key, value]) => ` ${key}: ${value};`)
116
+ .join('\n')}\n}`;
117
+
118
+ const rootBlock = `:root {\n${Object.entries(vars)
119
+ .map(([key, value]) => ` ${key}: ${value};`)
120
+ .join('\n')}\n}`;
121
+
122
+ return `${classBlock}\n\n${rootBlock}`;
123
+ };
@@ -0,0 +1,3 @@
1
+ import { buildRuntimeTheme } from './buildTheme';
2
+
3
+ export const basketballTrainingTheme = buildRuntimeTheme('BasketballTraining');