tangled
alpha
login
or
join now
mary.my.id
/
danaus
4
fork
atom
work-in-progress atproto PDS
typescript
atproto
pds
atcute
4
fork
atom
overview
issues
pulls
pipelines
feat: color token thingy
mary.my.id
1 month ago
24353fff
ebb4668a
verified
This commit was signed with the committer's
known signature
.
mary.my.id
SSH Key Fingerprint:
SHA256:ZlTP/auFSGpGnaoDg4mCTG1g9OZvXp62jWR4c6H4O3c=
+1008
12 changed files
expand all
collapse all
unified
split
packages
chrysalis
package.json
src
css.ts
grey.ts
index.test.ts
index.ts
ramp.ts
tokens
brand.ts
index.ts
neutral.ts
status.ts
types.ts
tsconfig.json
+20
packages/chrysalis/package.json
···
1
1
+
{
2
2
+
"name": "@kelinci/chrysalis",
3
3
+
"version": "0.0.0",
4
4
+
"private": true,
5
5
+
"description": "color token generation library",
6
6
+
"type": "module",
7
7
+
"exports": {
8
8
+
".": "./src/index.ts"
9
9
+
},
10
10
+
"scripts": {
11
11
+
"tsc": "tsc -b"
12
12
+
},
13
13
+
"dependencies": {
14
14
+
"culori": "^4.0.2"
15
15
+
},
16
16
+
"devDependencies": {
17
17
+
"@types/bun": "^1.3.6",
18
18
+
"@types/culori": "^4.0.1"
19
19
+
}
20
20
+
}
+40
packages/chrysalis/src/css.ts
···
1
1
+
import type { Theme } from './types.js';
2
2
+
3
3
+
/**
4
4
+
* converts a camelCase token name to kebab-case CSS variable name.
5
5
+
* e.g., colorNeutralBackground1 -> --color-neutral-background-1
6
6
+
*/
7
7
+
function tokenToCssVar(token: string): string {
8
8
+
return (
9
9
+
'--' +
10
10
+
token
11
11
+
.replace(/([A-Z])/g, '-$1')
12
12
+
.replace(/(\d+)/g, '-$1')
13
13
+
.toLowerCase()
14
14
+
);
15
15
+
}
16
16
+
17
17
+
/**
18
18
+
* converts a theme to CSS with light/dark media query support.
19
19
+
* @param theme generated theme
20
20
+
* @returns CSS string with :root light theme and @media dark theme
21
21
+
*/
22
22
+
export function themeToCss(theme: Theme): string {
23
23
+
const lines: string[] = [':root {', '\t& {'];
24
24
+
25
25
+
// light theme tokens
26
26
+
for (const [token, value] of Object.entries(theme.light)) {
27
27
+
lines.push(`\t\t${tokenToCssVar(token)}: ${value};`);
28
28
+
}
29
29
+
30
30
+
lines.push('\t}', '', '\t@media (prefers-color-scheme: dark) {');
31
31
+
32
32
+
// dark theme tokens
33
33
+
for (const [token, value] of Object.entries(theme.dark)) {
34
34
+
lines.push(`\t\t${tokenToCssVar(token)}: ${value};`);
35
35
+
}
36
36
+
37
37
+
lines.push('\t}', '}', '');
38
38
+
39
39
+
return lines.join('\n');
40
40
+
}
+23
packages/chrysalis/src/grey.ts
···
1
1
+
import { formatHex, type Hsl } from 'culori';
2
2
+
3
3
+
import type { GreyRamp } from './types.js';
4
4
+
5
5
+
/**
6
6
+
* generates a grey ramp with 49 stops (lightness 2-98% in steps of 2).
7
7
+
* @returns grey ramp keyed by lightness percentage
8
8
+
*/
9
9
+
export function generateGreyRamp(): GreyRamp {
10
10
+
const ramp: GreyRamp = {};
11
11
+
12
12
+
for (let i = 2; i <= 98; i += 2) {
13
13
+
const color: Hsl = {
14
14
+
mode: 'hsl',
15
15
+
h: 0,
16
16
+
s: 0,
17
17
+
l: i / 100,
18
18
+
};
19
19
+
ramp[i] = formatHex(color)!;
20
20
+
}
21
21
+
22
22
+
return ramp;
23
23
+
}
+185
packages/chrysalis/src/index.test.ts
···
1
1
+
import { expect, test, describe } from 'bun:test';
2
2
+
3
3
+
import {
4
4
+
generateTheme,
5
5
+
generateGreyRamp,
6
6
+
generateBrandRamp,
7
7
+
generateColorRamp,
8
8
+
themeToCss,
9
9
+
} from './index.js';
10
10
+
11
11
+
describe('grey ramp', () => {
12
12
+
const grey = generateGreyRamp();
13
13
+
14
14
+
test('generates 49 stops from 2 to 98', () => {
15
15
+
expect(Object.keys(grey).length).toBe(49);
16
16
+
expect(grey[2]).toBeDefined();
17
17
+
expect(grey[98]).toBeDefined();
18
18
+
});
19
19
+
20
20
+
test('generates greys at correct lightness', () => {
21
21
+
expect(grey[14]).toBe('#242424');
22
22
+
expect(grey[26]).toBe('#424242');
23
23
+
expect(grey[38]).toBe('#616161');
24
24
+
expect(grey[74]).toBe('#bdbdbd');
25
25
+
expect(grey[84]).toBe('#d6d6d6');
26
26
+
expect(grey[68]).toBe('#adadad');
27
27
+
});
28
28
+
});
29
29
+
30
30
+
describe('color ramp', () => {
31
31
+
const ramp = generateColorRamp('#107c10');
32
32
+
33
33
+
test('primary matches input color', () => {
34
34
+
expect(ramp.primary).toBe('#107c10');
35
35
+
});
36
36
+
37
37
+
test('shades are darker than primary', () => {
38
38
+
expect(ramp.shade10).not.toBe(ramp.primary);
39
39
+
expect(ramp.shade50).not.toBe(ramp.primary);
40
40
+
});
41
41
+
42
42
+
test('tints are lighter than primary', () => {
43
43
+
expect(ramp.tint10).not.toBe(ramp.primary);
44
44
+
expect(ramp.tint60).not.toBe(ramp.primary);
45
45
+
});
46
46
+
});
47
47
+
48
48
+
describe('brand ramp', () => {
49
49
+
const brand = generateBrandRamp('#0f6cbd');
50
50
+
51
51
+
test('generates 16 positions from 10 to 160', () => {
52
52
+
expect(Object.keys(brand).length).toBe(16);
53
53
+
for (let i = 10; i <= 160; i += 10) {
54
54
+
expect(brand[i]).toBeDefined();
55
55
+
}
56
56
+
});
57
57
+
58
58
+
test('position 80 is the base color', () => {
59
59
+
expect(brand[80]).toBe('#0f6cbd');
60
60
+
});
61
61
+
62
62
+
test('lower positions are darker', () => {
63
63
+
expect(brand[10]).not.toBe(brand[80]);
64
64
+
expect(brand[40]).not.toBe(brand[80]);
65
65
+
});
66
66
+
67
67
+
test('higher positions are lighter', () => {
68
68
+
expect(brand[100]).not.toBe(brand[80]);
69
69
+
expect(brand[160]).not.toBe(brand[80]);
70
70
+
});
71
71
+
});
72
72
+
73
73
+
describe('theme generation', () => {
74
74
+
const theme = generateTheme({ brand: '#0f6cbd' });
75
75
+
76
76
+
describe('light theme neutral tokens', () => {
77
77
+
test('foreground colors use grey ramp', () => {
78
78
+
expect(theme.light.colorNeutralForeground1).toBe('#242424');
79
79
+
expect(theme.light.colorNeutralForeground2).toBe('#424242');
80
80
+
expect(theme.light.colorNeutralForeground3).toBe('#616161');
81
81
+
expect(theme.light.colorNeutralForegroundDisabled).toBe('#bdbdbd');
82
82
+
});
83
83
+
84
84
+
test('background colors', () => {
85
85
+
expect(theme.light.colorNeutralBackground1).toBe('#ffffff');
86
86
+
expect(theme.light.colorNeutralBackground2).toBe('#fafafa');
87
87
+
expect(theme.light.colorNeutralBackground3).toBe('#f5f5f5');
88
88
+
});
89
89
+
90
90
+
test('stroke colors', () => {
91
91
+
expect(theme.light.colorNeutralStroke1).toBe('#d1d1d1');
92
92
+
expect(theme.light.colorNeutralStroke2).toBe('#e0e0e0');
93
93
+
expect(theme.light.colorNeutralStrokeAccessible).toBe('#616161');
94
94
+
});
95
95
+
96
96
+
test('special values', () => {
97
97
+
expect(theme.light.colorNeutralForegroundInverted).toBe('#ffffff');
98
98
+
expect(theme.light.colorNeutralForegroundOnBrand).toBe('#ffffff');
99
99
+
expect(theme.light.colorBackgroundOverlay).toBe('rgba(0, 0, 0, 0.4)');
100
100
+
expect(theme.light.colorStrokeFocus2).toBe('#000000');
101
101
+
});
102
102
+
});
103
103
+
104
104
+
describe('dark theme neutral tokens', () => {
105
105
+
test('foreground colors', () => {
106
106
+
expect(theme.dark.colorNeutralForeground1).toBe('#ffffff');
107
107
+
expect(theme.dark.colorNeutralForeground2).toBe('#d6d6d6');
108
108
+
expect(theme.dark.colorNeutralForeground3).toBe('#adadad');
109
109
+
expect(theme.dark.colorNeutralForegroundDisabled).toBe('#5c5c5c');
110
110
+
});
111
111
+
112
112
+
test('background colors', () => {
113
113
+
expect(theme.dark.colorNeutralBackground1).toBe('#292929');
114
114
+
expect(theme.dark.colorNeutralBackground2).toBe('#1f1f1f');
115
115
+
expect(theme.dark.colorNeutralBackground3).toBe('#141414');
116
116
+
});
117
117
+
118
118
+
test('stroke colors', () => {
119
119
+
expect(theme.dark.colorNeutralStroke1).toBe('#666666');
120
120
+
expect(theme.dark.colorNeutralStroke2).toBe('#525252');
121
121
+
expect(theme.dark.colorNeutralStrokeAccessible).toBe('#adadad');
122
122
+
expect(theme.dark.colorNeutralStrokeDisabled).toBe('#424242');
123
123
+
});
124
124
+
});
125
125
+
126
126
+
describe('brand tokens', () => {
127
127
+
test('light theme brand background uses brand[80]', () => {
128
128
+
expect(theme.light.colorBrandBackground).toBe('#0f6cbd');
129
129
+
});
130
130
+
131
131
+
test('light theme compound brand uses brand[80]', () => {
132
132
+
expect(theme.light.colorCompoundBrandBackground).toBe('#0f6cbd');
133
133
+
expect(theme.light.colorCompoundBrandForeground1).toBe('#0f6cbd');
134
134
+
expect(theme.light.colorCompoundBrandStroke).toBe('#0f6cbd');
135
135
+
});
136
136
+
137
137
+
test('dark theme brand uses different positions', () => {
138
138
+
expect(theme.dark.colorBrandBackground).not.toBe(theme.light.colorBrandBackground);
139
139
+
});
140
140
+
});
141
141
+
142
142
+
describe('status tokens', () => {
143
143
+
test('success tokens exist', () => {
144
144
+
expect(theme.light.colorStatusSuccessBackground1).toBeDefined();
145
145
+
expect(theme.light.colorStatusSuccessForeground1).toBeDefined();
146
146
+
expect(theme.light.colorStatusSuccessBorder1).toBeDefined();
147
147
+
});
148
148
+
149
149
+
test('warning tokens exist', () => {
150
150
+
expect(theme.light.colorStatusWarningBackground1).toBeDefined();
151
151
+
expect(theme.light.colorStatusWarningForeground3).toBeDefined();
152
152
+
expect(theme.light.colorStatusWarningBorder1).toBeDefined();
153
153
+
});
154
154
+
155
155
+
test('danger tokens exist', () => {
156
156
+
expect(theme.light.colorStatusDangerBackground1).toBeDefined();
157
157
+
expect(theme.light.colorStatusDangerForeground1).toBeDefined();
158
158
+
expect(theme.light.colorStatusDangerBorder1).toBeDefined();
159
159
+
});
160
160
+
});
161
161
+
});
162
162
+
163
163
+
describe('CSS output', () => {
164
164
+
const theme = generateTheme({ brand: '#0f6cbd' });
165
165
+
const css = themeToCss(theme);
166
166
+
167
167
+
test('outputs :root selector', () => {
168
168
+
expect(css).toContain(':root {');
169
169
+
});
170
170
+
171
171
+
test('outputs light theme tokens', () => {
172
172
+
expect(css).toContain('--color-neutral-foreground-1: #242424');
173
173
+
expect(css).toContain('--color-brand-background: #0f6cbd');
174
174
+
});
175
175
+
176
176
+
test('outputs dark media query', () => {
177
177
+
expect(css).toContain('@media (prefers-color-scheme: dark)');
178
178
+
});
179
179
+
180
180
+
test('converts camelCase to kebab-case', () => {
181
181
+
expect(css).toContain('--color-neutral-foreground-1');
182
182
+
expect(css).toContain('--color-brand-background');
183
183
+
expect(css).toContain('--color-compound-brand-foreground-1');
184
184
+
});
185
185
+
});
+5
packages/chrysalis/src/index.ts
···
1
1
+
export type { BrandRamp, ColorRamp, GreyRamp, Theme, ThemeConfig } from './types.js';
2
2
+
export { generateColorRamp, generateBrandRamp } from './ramp.js';
3
3
+
export { generateGreyRamp } from './grey.js';
4
4
+
export { generateTheme } from './tokens/index.js';
5
5
+
export { themeToCss } from './css.js';
+122
packages/chrysalis/src/ramp.ts
···
1
1
+
import { formatHex, hsv as toHsv, type Hsv } from 'culori';
2
2
+
import { convertHsvToRgb } from 'culori/fn';
3
3
+
4
4
+
import type { BrandRamp, ColorRamp } from './types.js';
5
5
+
6
6
+
/**
7
7
+
* generates a color ramp with 12 stops from a base color.
8
8
+
* shades are generated by reducing value, tints by reducing saturation and expanding value.
9
9
+
* @param baseColor base color in hex format
10
10
+
* @returns color ramp with shade50-shade10, primary, tint10-tint60
11
11
+
*/
12
12
+
export function generateColorRamp(baseColor: string): ColorRamp {
13
13
+
const hsv = toHsv(baseColor);
14
14
+
if (!hsv) {
15
15
+
throw new Error(`invalid color: ${baseColor}`);
16
16
+
}
17
17
+
18
18
+
// shade factors: [0.1, 0.24, 0.44, 0.7, 0.84] for shade10-shade50
19
19
+
const shadeFactors = [0.1, 0.24, 0.44, 0.7, 0.84];
20
20
+
// tint factors: [0.12, 0.24, 0.4, 0.7, 0.84, 0.96] for tint10-tint60
21
21
+
const tintFactors = [0.12, 0.24, 0.4, 0.7, 0.84, 0.96];
22
22
+
23
23
+
const shades = shadeFactors.map((factor) => generateShade(hsv, factor));
24
24
+
const tints = tintFactors.map((factor) => generateTint(hsv, factor));
25
25
+
26
26
+
return {
27
27
+
shade50: shades[4],
28
28
+
shade40: shades[3],
29
29
+
shade30: shades[2],
30
30
+
shade20: shades[1],
31
31
+
shade10: shades[0],
32
32
+
primary: formatHex(hsv)!,
33
33
+
tint10: tints[0],
34
34
+
tint20: tints[1],
35
35
+
tint30: tints[2],
36
36
+
tint40: tints[3],
37
37
+
tint50: tints[4],
38
38
+
tint60: tints[5],
39
39
+
};
40
40
+
}
41
41
+
42
42
+
/**
43
43
+
* generates a brand ramp with 16 stops from a base color.
44
44
+
* positions 10-160 in steps of 10.
45
45
+
* @param baseColor base color in hex format
46
46
+
* @returns brand ramp keyed by position (10-160)
47
47
+
*/
48
48
+
export function generateBrandRamp(baseColor: string): BrandRamp {
49
49
+
const hsv = toHsv(baseColor);
50
50
+
if (!hsv) {
51
51
+
throw new Error(`invalid color: ${baseColor}`);
52
52
+
}
53
53
+
54
54
+
const ramp: BrandRamp = {};
55
55
+
56
56
+
// positions 10-80 are shades (darker), 80 is primary, 90-160 are tints (lighter)
57
57
+
// shade factors mapped to positions 10-70 (7 shades)
58
58
+
// position 80 = primary
59
59
+
// tint factors mapped to positions 90-160 (8 tints)
60
60
+
61
61
+
// shade factors for positions 10-70 (darkest to lightest shade)
62
62
+
const shadeFactors: Record<number, number> = {
63
63
+
10: 0.96,
64
64
+
20: 0.84,
65
65
+
30: 0.7,
66
66
+
40: 0.52,
67
67
+
50: 0.36,
68
68
+
60: 0.24,
69
69
+
70: 0.12,
70
70
+
};
71
71
+
72
72
+
// tint factors for positions 90-160 (lightest to most faded)
73
73
+
const tintFactors: Record<number, number> = {
74
74
+
90: 0.08,
75
75
+
100: 0.16,
76
76
+
110: 0.28,
77
77
+
120: 0.4,
78
78
+
130: 0.52,
79
79
+
140: 0.68,
80
80
+
150: 0.84,
81
81
+
160: 0.96,
82
82
+
};
83
83
+
84
84
+
for (let i = 10; i <= 70; i += 10) {
85
85
+
ramp[i] = generateShade(hsv, shadeFactors[i]);
86
86
+
}
87
87
+
88
88
+
ramp[80] = formatHex(hsv)!;
89
89
+
90
90
+
for (let i = 90; i <= 160; i += 10) {
91
91
+
ramp[i] = generateTint(hsv, tintFactors[i]);
92
92
+
}
93
93
+
94
94
+
return ramp;
95
95
+
}
96
96
+
97
97
+
/**
98
98
+
* generates a shade (darker) by reducing value.
99
99
+
*/
100
100
+
function generateShade(hsv: Hsv, factor: number): string {
101
101
+
const shaded: Hsv = {
102
102
+
mode: 'hsv',
103
103
+
h: hsv.h,
104
104
+
s: hsv.s,
105
105
+
v: (hsv.v ?? 1) * (1 - factor),
106
106
+
};
107
107
+
return formatHex(convertHsvToRgb(shaded))!;
108
108
+
}
109
109
+
110
110
+
/**
111
111
+
* generates a tint (lighter) by reducing saturation and expanding value.
112
112
+
*/
113
113
+
function generateTint(hsv: Hsv, factor: number): string {
114
114
+
const v = hsv.v ?? 1;
115
115
+
const tinted: Hsv = {
116
116
+
mode: 'hsv',
117
117
+
h: hsv.h,
118
118
+
s: (hsv.s ?? 0) * (1 - factor),
119
119
+
v: v + (1 - v) * factor,
120
120
+
};
121
121
+
return formatHex(convertHsvToRgb(tinted))!;
122
122
+
}
+127
packages/chrysalis/src/tokens/brand.ts
···
1
1
+
import type { BrandRamp } from '../types.js';
2
2
+
3
3
+
/**
4
4
+
* generates brand semantic tokens from a brand ramp.
5
5
+
* @param brand brand ramp
6
6
+
* @returns light and dark brand token mappings
7
7
+
*/
8
8
+
export function generateBrandTokens(brand: BrandRamp): {
9
9
+
light: Record<string, string>;
10
10
+
dark: Record<string, string>;
11
11
+
} {
12
12
+
const light: Record<string, string> = {
13
13
+
// #region foreground
14
14
+
colorBrandForegroundLink: brand[70],
15
15
+
colorBrandForegroundLinkHover: brand[60],
16
16
+
colorBrandForegroundLinkPressed: brand[40],
17
17
+
colorBrandForegroundLinkSelected: brand[70],
18
18
+
colorBrandForeground1: brand[80],
19
19
+
colorBrandForeground2: brand[70],
20
20
+
colorBrandForeground2Hover: brand[60],
21
21
+
colorBrandForeground2Pressed: brand[30],
22
22
+
colorBrandForegroundOnLight: brand[80],
23
23
+
colorBrandForegroundOnLightHover: brand[70],
24
24
+
colorBrandForegroundOnLightPressed: brand[50],
25
25
+
colorBrandForegroundOnLightSelected: brand[60],
26
26
+
colorBrandForegroundInverted: brand[100],
27
27
+
colorBrandForegroundInvertedHover: brand[110],
28
28
+
colorBrandForegroundInvertedPressed: brand[100],
29
29
+
// #endregion
30
30
+
31
31
+
// #region background
32
32
+
colorBrandBackground: brand[80],
33
33
+
colorBrandBackgroundHover: brand[70],
34
34
+
colorBrandBackgroundPressed: brand[40],
35
35
+
colorBrandBackgroundSelected: brand[60],
36
36
+
colorBrandBackgroundStatic: brand[80],
37
37
+
colorBrandBackground2: brand[160],
38
38
+
colorBrandBackground2Hover: brand[150],
39
39
+
colorBrandBackground2Pressed: brand[130],
40
40
+
colorBrandBackground3Static: brand[60],
41
41
+
colorBrandBackground4Static: brand[40],
42
42
+
colorBrandBackgroundInverted: '#ffffff',
43
43
+
colorBrandBackgroundInvertedHover: brand[160],
44
44
+
colorBrandBackgroundInvertedPressed: brand[140],
45
45
+
colorBrandBackgroundInvertedSelected: brand[150],
46
46
+
// #endregion
47
47
+
48
48
+
// #region stroke
49
49
+
colorBrandStroke1: brand[80],
50
50
+
colorBrandStroke2: brand[140],
51
51
+
colorBrandStroke2Hover: brand[120],
52
52
+
colorBrandStroke2Pressed: brand[80],
53
53
+
colorBrandStroke2Contrast: brand[140],
54
54
+
// #endregion
55
55
+
56
56
+
// #region compound brand
57
57
+
colorCompoundBrandForeground1: brand[80],
58
58
+
colorCompoundBrandForeground1Hover: brand[70],
59
59
+
colorCompoundBrandForeground1Pressed: brand[60],
60
60
+
colorCompoundBrandBackground: brand[80],
61
61
+
colorCompoundBrandBackgroundHover: brand[70],
62
62
+
colorCompoundBrandBackgroundPressed: brand[60],
63
63
+
colorCompoundBrandStroke: brand[80],
64
64
+
colorCompoundBrandStrokeHover: brand[70],
65
65
+
colorCompoundBrandStrokePressed: brand[60],
66
66
+
// #endregion
67
67
+
};
68
68
+
69
69
+
const dark: Record<string, string> = {
70
70
+
// #region foreground
71
71
+
colorBrandForegroundLink: brand[100],
72
72
+
colorBrandForegroundLinkHover: brand[110],
73
73
+
colorBrandForegroundLinkPressed: brand[90],
74
74
+
colorBrandForegroundLinkSelected: brand[100],
75
75
+
colorBrandForeground1: brand[100],
76
76
+
colorBrandForeground2: brand[110],
77
77
+
colorBrandForeground2Hover: brand[130],
78
78
+
colorBrandForeground2Pressed: brand[160],
79
79
+
colorBrandForegroundOnLight: brand[80],
80
80
+
colorBrandForegroundOnLightHover: brand[70],
81
81
+
colorBrandForegroundOnLightPressed: brand[50],
82
82
+
colorBrandForegroundOnLightSelected: brand[60],
83
83
+
colorBrandForegroundInverted: brand[80],
84
84
+
colorBrandForegroundInvertedHover: brand[70],
85
85
+
colorBrandForegroundInvertedPressed: brand[60],
86
86
+
// #endregion
87
87
+
88
88
+
// #region background
89
89
+
colorBrandBackground: brand[70],
90
90
+
colorBrandBackgroundHover: brand[80],
91
91
+
colorBrandBackgroundPressed: brand[40],
92
92
+
colorBrandBackgroundSelected: brand[60],
93
93
+
colorBrandBackgroundStatic: brand[80],
94
94
+
colorBrandBackground2: brand[20],
95
95
+
colorBrandBackground2Hover: brand[40],
96
96
+
colorBrandBackground2Pressed: brand[10],
97
97
+
colorBrandBackground3Static: brand[60],
98
98
+
colorBrandBackground4Static: brand[40],
99
99
+
colorBrandBackgroundInverted: '#ffffff',
100
100
+
colorBrandBackgroundInvertedHover: brand[160],
101
101
+
colorBrandBackgroundInvertedPressed: brand[140],
102
102
+
colorBrandBackgroundInvertedSelected: brand[150],
103
103
+
// #endregion
104
104
+
105
105
+
// #region stroke
106
106
+
colorBrandStroke1: brand[100],
107
107
+
colorBrandStroke2: brand[50],
108
108
+
colorBrandStroke2Hover: brand[50],
109
109
+
colorBrandStroke2Pressed: brand[30],
110
110
+
colorBrandStroke2Contrast: brand[50],
111
111
+
// #endregion
112
112
+
113
113
+
// #region compound brand
114
114
+
colorCompoundBrandForeground1: brand[100],
115
115
+
colorCompoundBrandForeground1Hover: brand[110],
116
116
+
colorCompoundBrandForeground1Pressed: brand[90],
117
117
+
colorCompoundBrandBackground: brand[100],
118
118
+
colorCompoundBrandBackgroundHover: brand[110],
119
119
+
colorCompoundBrandBackgroundPressed: brand[90],
120
120
+
colorCompoundBrandStroke: brand[100],
121
121
+
colorCompoundBrandStrokeHover: brand[110],
122
122
+
colorCompoundBrandStrokePressed: brand[90],
123
123
+
// #endregion
124
124
+
};
125
125
+
126
126
+
return { light, dark };
127
127
+
}
+45
packages/chrysalis/src/tokens/index.ts
···
1
1
+
import { generateGreyRamp } from '../grey.js';
2
2
+
import { generateBrandRamp, generateColorRamp } from '../ramp.js';
3
3
+
import type { Theme, ThemeConfig } from '../types.js';
4
4
+
5
5
+
import { generateBrandTokens } from './brand.js';
6
6
+
import { generateNeutralTokens } from './neutral.js';
7
7
+
import { generateStatusTokens } from './status.js';
8
8
+
9
9
+
/** default cranberry color for danger status */
10
10
+
const DEFAULT_DANGER = '#c50f1f';
11
11
+
/** default green color for success status */
12
12
+
const DEFAULT_SUCCESS = '#107c10';
13
13
+
/** default orange color for warning status */
14
14
+
const DEFAULT_WARNING = '#f7630c';
15
15
+
16
16
+
/**
17
17
+
* generates a complete theme from configuration.
18
18
+
* @param config theme configuration with brand and optional status colors
19
19
+
* @returns theme with light and dark token mappings
20
20
+
*/
21
21
+
export function generateTheme(config: ThemeConfig): Theme {
22
22
+
const grey = generateGreyRamp();
23
23
+
const brand = generateBrandRamp(config.brand);
24
24
+
25
25
+
const dangerRamp = generateColorRamp(config.danger ?? DEFAULT_DANGER);
26
26
+
const successRamp = generateColorRamp(config.success ?? DEFAULT_SUCCESS);
27
27
+
const warningRamp = generateColorRamp(config.warning ?? DEFAULT_WARNING);
28
28
+
29
29
+
const neutral = generateNeutralTokens(grey, brand);
30
30
+
const brandTokens = generateBrandTokens(brand);
31
31
+
const status = generateStatusTokens(dangerRamp, successRamp, warningRamp);
32
32
+
33
33
+
return {
34
34
+
light: {
35
35
+
...neutral.light,
36
36
+
...brandTokens.light,
37
37
+
...status.light,
38
38
+
},
39
39
+
dark: {
40
40
+
...neutral.dark,
41
41
+
...brandTokens.dark,
42
42
+
...status.dark,
43
43
+
},
44
44
+
};
45
45
+
}
+260
packages/chrysalis/src/tokens/neutral.ts
···
1
1
+
import type { BrandRamp, GreyRamp } from '../types.js';
2
2
+
3
3
+
/**
4
4
+
* generates neutral semantic tokens from grey and brand ramps.
5
5
+
* @param grey grey ramp
6
6
+
* @param brand brand ramp
7
7
+
* @returns light and dark neutral token mappings
8
8
+
*/
9
9
+
export function generateNeutralTokens(
10
10
+
grey: GreyRamp,
11
11
+
brand: BrandRamp,
12
12
+
): { light: Record<string, string>; dark: Record<string, string> } {
13
13
+
const light: Record<string, string> = {
14
14
+
// #region foreground
15
15
+
colorNeutralForeground1: grey[14],
16
16
+
colorNeutralForeground1Hover: grey[14],
17
17
+
colorNeutralForeground1Pressed: grey[14],
18
18
+
colorNeutralForeground1Selected: grey[14],
19
19
+
colorNeutralForeground2: grey[26],
20
20
+
colorNeutralForeground2Hover: grey[14],
21
21
+
colorNeutralForeground2Pressed: grey[14],
22
22
+
colorNeutralForeground2Selected: grey[14],
23
23
+
colorNeutralForeground2BrandHover: brand[80],
24
24
+
colorNeutralForeground2BrandPressed: brand[70],
25
25
+
colorNeutralForeground2BrandSelected: brand[80],
26
26
+
colorNeutralForeground3: grey[38],
27
27
+
colorNeutralForeground3Hover: grey[26],
28
28
+
colorNeutralForeground3Pressed: grey[26],
29
29
+
colorNeutralForeground3Selected: grey[26],
30
30
+
colorNeutralForeground4: grey[44],
31
31
+
colorNeutralForeground5: grey[38],
32
32
+
colorNeutralForeground5Hover: grey[14],
33
33
+
colorNeutralForeground5Pressed: grey[14],
34
34
+
colorNeutralForeground5Selected: grey[14],
35
35
+
colorNeutralForegroundDisabled: grey[74],
36
36
+
colorNeutralForeground1Static: grey[14],
37
37
+
colorNeutralForegroundStaticInverted: '#ffffff',
38
38
+
colorNeutralForegroundInverted: '#ffffff',
39
39
+
colorNeutralForegroundInvertedHover: '#ffffff',
40
40
+
colorNeutralForegroundInvertedPressed: '#ffffff',
41
41
+
colorNeutralForegroundInvertedSelected: '#ffffff',
42
42
+
colorNeutralForegroundInverted2: '#ffffff',
43
43
+
colorNeutralForegroundOnBrand: '#ffffff',
44
44
+
// #endregion
45
45
+
46
46
+
// #region background
47
47
+
colorNeutralBackground1: '#ffffff',
48
48
+
colorNeutralBackground1Hover: grey[96],
49
49
+
colorNeutralBackground1Pressed: grey[88],
50
50
+
colorNeutralBackground1Selected: grey[92],
51
51
+
colorNeutralBackground2: grey[98],
52
52
+
colorNeutralBackground2Hover: grey[94],
53
53
+
colorNeutralBackground2Pressed: grey[86],
54
54
+
colorNeutralBackground2Selected: grey[90],
55
55
+
colorNeutralBackground3: grey[96],
56
56
+
colorNeutralBackground3Hover: grey[92],
57
57
+
colorNeutralBackground3Pressed: grey[84],
58
58
+
colorNeutralBackground3Selected: grey[88],
59
59
+
colorNeutralBackground4: grey[94],
60
60
+
colorNeutralBackground4Hover: grey[98],
61
61
+
colorNeutralBackground4Pressed: grey[96],
62
62
+
colorNeutralBackground4Selected: '#ffffff',
63
63
+
colorNeutralBackground5: grey[92],
64
64
+
colorNeutralBackground5Hover: grey[96],
65
65
+
colorNeutralBackground5Pressed: grey[94],
66
66
+
colorNeutralBackground5Selected: grey[98],
67
67
+
colorNeutralBackground6: grey[90],
68
68
+
colorNeutralBackgroundAlpha: 'rgba(255, 255, 255, 0.5)',
69
69
+
colorNeutralBackgroundAlpha2: 'rgba(255, 255, 255, 0.8)',
70
70
+
colorNeutralBackgroundDisabled: grey[94],
71
71
+
colorNeutralBackgroundInverted: grey[16],
72
72
+
colorNeutralBackgroundInvertedHover: grey[24],
73
73
+
colorNeutralBackgroundInvertedPressed: grey[12],
74
74
+
colorNeutralBackgroundInvertedSelected: grey[22],
75
75
+
colorNeutralBackgroundStatic: grey[20],
76
76
+
colorSubtleBackground: 'transparent',
77
77
+
colorSubtleBackgroundHover: grey[96],
78
78
+
colorSubtleBackgroundPressed: grey[88],
79
79
+
colorSubtleBackgroundSelected: grey[92],
80
80
+
colorSubtleBackgroundLightAlphaHover: 'rgba(255, 255, 255, 0.7)',
81
81
+
colorSubtleBackgroundLightAlphaPressed: 'rgba(255, 255, 255, 0.5)',
82
82
+
colorSubtleBackgroundLightAlphaSelected: 'transparent',
83
83
+
colorSubtleBackgroundInverted: 'transparent',
84
84
+
colorSubtleBackgroundInvertedHover: 'rgba(0, 0, 0, 0.1)',
85
85
+
colorSubtleBackgroundInvertedPressed: 'rgba(0, 0, 0, 0.3)',
86
86
+
colorSubtleBackgroundInvertedSelected: 'rgba(0, 0, 0, 0.2)',
87
87
+
colorTransparentBackground: 'transparent',
88
88
+
colorTransparentBackgroundHover: 'transparent',
89
89
+
colorTransparentBackgroundPressed: 'transparent',
90
90
+
colorTransparentBackgroundSelected: 'transparent',
91
91
+
// #endregion
92
92
+
93
93
+
// #region stroke
94
94
+
colorNeutralStroke1: grey[82],
95
95
+
colorNeutralStroke1Hover: grey[78],
96
96
+
colorNeutralStroke1Pressed: grey[70],
97
97
+
colorNeutralStroke1Selected: grey[74],
98
98
+
colorNeutralStroke2: grey[88],
99
99
+
colorNeutralStroke3: grey[94],
100
100
+
colorNeutralStrokeSubtle: grey[88],
101
101
+
colorNeutralStrokeOnBrand: '#ffffff',
102
102
+
colorNeutralStrokeOnBrand2: '#ffffff',
103
103
+
colorNeutralStrokeOnBrand2Hover: '#ffffff',
104
104
+
colorNeutralStrokeOnBrand2Pressed: '#ffffff',
105
105
+
colorNeutralStrokeOnBrand2Selected: '#ffffff',
106
106
+
colorNeutralStrokeDisabled: grey[88],
107
107
+
colorNeutralStrokeInvertedDisabled: 'rgba(255, 255, 255, 0.4)',
108
108
+
colorNeutralStrokeAccessible: grey[38],
109
109
+
colorNeutralStrokeAccessibleHover: grey[34],
110
110
+
colorNeutralStrokeAccessiblePressed: grey[30],
111
111
+
colorNeutralStrokeAccessibleSelected: brand[80],
112
112
+
colorNeutralStrokeAlpha: 'rgba(0, 0, 0, 0.05)',
113
113
+
colorNeutralStrokeAlpha2: 'rgba(255, 255, 255, 0.2)',
114
114
+
colorTransparentStroke: 'transparent',
115
115
+
colorTransparentStrokeInteractive: 'transparent',
116
116
+
colorTransparentStrokeDisabled: 'transparent',
117
117
+
// #endregion
118
118
+
119
119
+
// #region overlay & focus
120
120
+
colorBackgroundOverlay: 'rgba(0, 0, 0, 0.4)',
121
121
+
colorScrollbarOverlay: 'rgba(0, 0, 0, 0.5)',
122
122
+
colorStrokeFocus1: '#ffffff',
123
123
+
colorStrokeFocus2: '#000000',
124
124
+
// #endregion
125
125
+
126
126
+
// #region shadow
127
127
+
colorNeutralShadowAmbient: 'rgba(0, 0, 0, 0.12)',
128
128
+
colorNeutralShadowKey: 'rgba(0, 0, 0, 0.14)',
129
129
+
colorNeutralShadowAmbientLighter: 'rgba(0, 0, 0, 0.06)',
130
130
+
colorNeutralShadowKeyLighter: 'rgba(0, 0, 0, 0.07)',
131
131
+
colorNeutralShadowAmbientDarker: 'rgba(0, 0, 0, 0.2)',
132
132
+
colorNeutralShadowKeyDarker: 'rgba(0, 0, 0, 0.24)',
133
133
+
// #endregion
134
134
+
};
135
135
+
136
136
+
const dark: Record<string, string> = {
137
137
+
// #region foreground
138
138
+
colorNeutralForeground1: '#ffffff',
139
139
+
colorNeutralForeground1Hover: '#ffffff',
140
140
+
colorNeutralForeground1Pressed: '#ffffff',
141
141
+
colorNeutralForeground1Selected: '#ffffff',
142
142
+
colorNeutralForeground2: grey[84],
143
143
+
colorNeutralForeground2Hover: '#ffffff',
144
144
+
colorNeutralForeground2Pressed: '#ffffff',
145
145
+
colorNeutralForeground2Selected: '#ffffff',
146
146
+
colorNeutralForeground2BrandHover: brand[100],
147
147
+
colorNeutralForeground2BrandPressed: brand[90],
148
148
+
colorNeutralForeground2BrandSelected: brand[100],
149
149
+
colorNeutralForeground3: grey[68],
150
150
+
colorNeutralForeground3Hover: grey[84],
151
151
+
colorNeutralForeground3Pressed: grey[84],
152
152
+
colorNeutralForeground3Selected: grey[84],
153
153
+
colorNeutralForeground4: grey[60],
154
154
+
colorNeutralForeground5: grey[68],
155
155
+
colorNeutralForeground5Hover: '#ffffff',
156
156
+
colorNeutralForeground5Pressed: '#ffffff',
157
157
+
colorNeutralForeground5Selected: '#ffffff',
158
158
+
colorNeutralForegroundDisabled: grey[36],
159
159
+
colorNeutralForeground1Static: grey[14],
160
160
+
colorNeutralForegroundStaticInverted: '#ffffff',
161
161
+
colorNeutralForegroundInverted: grey[14],
162
162
+
colorNeutralForegroundInvertedHover: grey[14],
163
163
+
colorNeutralForegroundInvertedPressed: grey[14],
164
164
+
colorNeutralForegroundInvertedSelected: grey[14],
165
165
+
colorNeutralForegroundInverted2: grey[14],
166
166
+
colorNeutralForegroundOnBrand: '#ffffff',
167
167
+
// #endregion
168
168
+
169
169
+
// #region background
170
170
+
colorNeutralBackground1: grey[16],
171
171
+
colorNeutralBackground1Hover: grey[24],
172
172
+
colorNeutralBackground1Pressed: grey[12],
173
173
+
colorNeutralBackground1Selected: grey[22],
174
174
+
colorNeutralBackground2: grey[12],
175
175
+
colorNeutralBackground2Hover: grey[20],
176
176
+
colorNeutralBackground2Pressed: grey[8],
177
177
+
colorNeutralBackground2Selected: grey[18],
178
178
+
colorNeutralBackground3: grey[8],
179
179
+
colorNeutralBackground3Hover: grey[16],
180
180
+
colorNeutralBackground3Pressed: grey[4],
181
181
+
colorNeutralBackground3Selected: grey[14],
182
182
+
colorNeutralBackground4: grey[4],
183
183
+
colorNeutralBackground4Hover: grey[12],
184
184
+
colorNeutralBackground4Pressed: '#000000',
185
185
+
colorNeutralBackground4Selected: grey[10],
186
186
+
colorNeutralBackground5: '#000000',
187
187
+
colorNeutralBackground5Hover: grey[8],
188
188
+
colorNeutralBackground5Pressed: grey[2],
189
189
+
colorNeutralBackground5Selected: grey[6],
190
190
+
colorNeutralBackground6: grey[20],
191
191
+
colorNeutralBackgroundAlpha: 'rgba(26, 26, 26, 0.5)',
192
192
+
colorNeutralBackgroundAlpha2: 'rgba(26, 26, 26, 0.7)',
193
193
+
colorNeutralBackgroundDisabled: grey[8],
194
194
+
colorNeutralBackgroundInverted: '#ffffff',
195
195
+
colorNeutralBackgroundInvertedHover: grey[96],
196
196
+
colorNeutralBackgroundInvertedPressed: grey[88],
197
197
+
colorNeutralBackgroundInvertedSelected: grey[92],
198
198
+
colorNeutralBackgroundStatic: grey[24],
199
199
+
colorSubtleBackground: 'transparent',
200
200
+
colorSubtleBackgroundHover: grey[22],
201
201
+
colorSubtleBackgroundPressed: grey[18],
202
202
+
colorSubtleBackgroundSelected: grey[20],
203
203
+
colorSubtleBackgroundLightAlphaHover: 'rgba(255, 255, 255, 0.1)',
204
204
+
colorSubtleBackgroundLightAlphaPressed: 'rgba(255, 255, 255, 0.05)',
205
205
+
colorSubtleBackgroundLightAlphaSelected: 'transparent',
206
206
+
colorSubtleBackgroundInverted: 'transparent',
207
207
+
colorSubtleBackgroundInvertedHover: 'rgba(0, 0, 0, 0.1)',
208
208
+
colorSubtleBackgroundInvertedPressed: 'rgba(0, 0, 0, 0.3)',
209
209
+
colorSubtleBackgroundInvertedSelected: 'rgba(0, 0, 0, 0.2)',
210
210
+
colorTransparentBackground: 'transparent',
211
211
+
colorTransparentBackgroundHover: 'transparent',
212
212
+
colorTransparentBackgroundPressed: 'transparent',
213
213
+
colorTransparentBackgroundSelected: 'transparent',
214
214
+
// #endregion
215
215
+
216
216
+
// #region stroke
217
217
+
colorNeutralStroke1: grey[40],
218
218
+
colorNeutralStroke1Hover: grey[46],
219
219
+
colorNeutralStroke1Pressed: grey[42],
220
220
+
colorNeutralStroke1Selected: grey[44],
221
221
+
colorNeutralStroke2: grey[32],
222
222
+
colorNeutralStroke3: grey[24],
223
223
+
colorNeutralStrokeSubtle: grey[4],
224
224
+
colorNeutralStrokeOnBrand: grey[16],
225
225
+
colorNeutralStrokeOnBrand2: '#ffffff',
226
226
+
colorNeutralStrokeOnBrand2Hover: '#ffffff',
227
227
+
colorNeutralStrokeOnBrand2Pressed: '#ffffff',
228
228
+
colorNeutralStrokeOnBrand2Selected: '#ffffff',
229
229
+
colorNeutralStrokeDisabled: grey[26],
230
230
+
colorNeutralStrokeInvertedDisabled: 'rgba(255, 255, 255, 0.4)',
231
231
+
colorNeutralStrokeAccessible: grey[68],
232
232
+
colorNeutralStrokeAccessibleHover: grey[74],
233
233
+
colorNeutralStrokeAccessiblePressed: grey[70],
234
234
+
colorNeutralStrokeAccessibleSelected: brand[100],
235
235
+
colorNeutralStrokeAlpha: 'rgba(255, 255, 255, 0.1)',
236
236
+
colorNeutralStrokeAlpha2: 'rgba(255, 255, 255, 0.2)',
237
237
+
colorTransparentStroke: 'transparent',
238
238
+
colorTransparentStrokeInteractive: 'transparent',
239
239
+
colorTransparentStrokeDisabled: 'transparent',
240
240
+
// #endregion
241
241
+
242
242
+
// #region overlay & focus
243
243
+
colorBackgroundOverlay: 'rgba(0, 0, 0, 0.5)',
244
244
+
colorScrollbarOverlay: 'rgba(255, 255, 255, 0.6)',
245
245
+
colorStrokeFocus1: '#000000',
246
246
+
colorStrokeFocus2: '#ffffff',
247
247
+
// #endregion
248
248
+
249
249
+
// #region shadow
250
250
+
colorNeutralShadowAmbient: 'rgba(0, 0, 0, 0.24)',
251
251
+
colorNeutralShadowKey: 'rgba(0, 0, 0, 0.28)',
252
252
+
colorNeutralShadowAmbientLighter: 'rgba(0, 0, 0, 0.12)',
253
253
+
colorNeutralShadowKeyLighter: 'rgba(0, 0, 0, 0.14)',
254
254
+
colorNeutralShadowAmbientDarker: 'rgba(0, 0, 0, 0.4)',
255
255
+
colorNeutralShadowKeyDarker: 'rgba(0, 0, 0, 0.48)',
256
256
+
// #endregion
257
257
+
};
258
258
+
259
259
+
return { light, dark };
260
260
+
}
+102
packages/chrysalis/src/tokens/status.ts
···
1
1
+
import type { ColorRamp } from '../types.js';
2
2
+
3
3
+
/**
4
4
+
* generates status semantic tokens from color ramps.
5
5
+
* @param danger danger color ramp (cranberry)
6
6
+
* @param success success color ramp (green)
7
7
+
* @param warning warning color ramp (orange)
8
8
+
* @returns light and dark status token mappings
9
9
+
*/
10
10
+
export function generateStatusTokens(
11
11
+
danger: ColorRamp,
12
12
+
success: ColorRamp,
13
13
+
warning: ColorRamp,
14
14
+
): { light: Record<string, string>; dark: Record<string, string> } {
15
15
+
const light: Record<string, string> = {
16
16
+
// #region success (green)
17
17
+
colorStatusSuccessBackground1: success.tint60,
18
18
+
colorStatusSuccessBackground2: success.tint40,
19
19
+
colorStatusSuccessBackground3: success.primary,
20
20
+
colorStatusSuccessForeground1: success.shade30,
21
21
+
colorStatusSuccessForeground2: success.shade10,
22
22
+
colorStatusSuccessForeground3: success.tint20,
23
23
+
colorStatusSuccessForegroundInverted: success.tint30,
24
24
+
colorStatusSuccessBorderActive: success.primary,
25
25
+
colorStatusSuccessBorder1: success.tint40,
26
26
+
colorStatusSuccessBorder2: success.tint20,
27
27
+
// #endregion
28
28
+
29
29
+
// #region warning (orange)
30
30
+
colorStatusWarningBackground1: warning.tint60,
31
31
+
colorStatusWarningBackground2: warning.tint40,
32
32
+
colorStatusWarningBackground3: warning.primary,
33
33
+
colorStatusWarningForeground1: warning.shade30,
34
34
+
colorStatusWarningForeground2: warning.shade10,
35
35
+
colorStatusWarningForeground3: warning.shade30,
36
36
+
colorStatusWarningForegroundInverted: warning.tint30,
37
37
+
colorStatusWarningBorderActive: warning.primary,
38
38
+
colorStatusWarningBorder1: warning.tint40,
39
39
+
colorStatusWarningBorder2: warning.shade30,
40
40
+
// #endregion
41
41
+
42
42
+
// #region danger (cranberry)
43
43
+
colorStatusDangerBackground1: danger.tint60,
44
44
+
colorStatusDangerBackground2: danger.tint40,
45
45
+
colorStatusDangerBackground3: danger.primary,
46
46
+
colorStatusDangerBackground3Hover: danger.shade10,
47
47
+
colorStatusDangerBackground3Pressed: danger.shade20,
48
48
+
colorStatusDangerForeground1: danger.shade30,
49
49
+
colorStatusDangerForeground2: danger.shade10,
50
50
+
colorStatusDangerForeground3: danger.primary,
51
51
+
colorStatusDangerForegroundInverted: danger.tint30,
52
52
+
colorStatusDangerBorderActive: danger.primary,
53
53
+
colorStatusDangerBorder1: danger.tint40,
54
54
+
colorStatusDangerBorder2: danger.primary,
55
55
+
// #endregion
56
56
+
};
57
57
+
58
58
+
const dark: Record<string, string> = {
59
59
+
// #region success (green)
60
60
+
colorStatusSuccessBackground1: success.shade40,
61
61
+
colorStatusSuccessBackground2: success.shade30,
62
62
+
colorStatusSuccessBackground3: success.primary,
63
63
+
colorStatusSuccessForeground1: success.tint30,
64
64
+
colorStatusSuccessForeground2: success.tint40,
65
65
+
colorStatusSuccessForeground3: success.tint20,
66
66
+
colorStatusSuccessForegroundInverted: success.shade10,
67
67
+
colorStatusSuccessBorderActive: success.tint30,
68
68
+
colorStatusSuccessBorder1: success.primary,
69
69
+
colorStatusSuccessBorder2: success.tint20,
70
70
+
// #endregion
71
71
+
72
72
+
// #region warning (orange)
73
73
+
colorStatusWarningBackground1: warning.shade40,
74
74
+
colorStatusWarningBackground2: warning.shade30,
75
75
+
colorStatusWarningBackground3: warning.primary,
76
76
+
colorStatusWarningForeground1: warning.tint30,
77
77
+
colorStatusWarningForeground2: warning.tint40,
78
78
+
colorStatusWarningForeground3: warning.tint40,
79
79
+
colorStatusWarningForegroundInverted: warning.shade30,
80
80
+
colorStatusWarningBorderActive: warning.tint30,
81
81
+
colorStatusWarningBorder1: warning.primary,
82
82
+
colorStatusWarningBorder2: warning.tint20,
83
83
+
// #endregion
84
84
+
85
85
+
// #region danger (cranberry)
86
86
+
colorStatusDangerBackground1: danger.shade40,
87
87
+
colorStatusDangerBackground2: danger.shade30,
88
88
+
colorStatusDangerBackground3: danger.primary,
89
89
+
colorStatusDangerBackground3Hover: danger.shade10,
90
90
+
colorStatusDangerBackground3Pressed: danger.shade20,
91
91
+
colorStatusDangerForeground1: danger.tint30,
92
92
+
colorStatusDangerForeground2: danger.tint40,
93
93
+
colorStatusDangerForeground3: danger.tint40,
94
94
+
colorStatusDangerForegroundInverted: danger.tint10,
95
95
+
colorStatusDangerBorderActive: danger.tint30,
96
96
+
colorStatusDangerBorder1: danger.primary,
97
97
+
colorStatusDangerBorder2: danger.tint30,
98
98
+
// #endregion
99
99
+
};
100
100
+
101
101
+
return { light, dark };
102
102
+
}
+55
packages/chrysalis/src/types.ts
···
1
1
+
/**
2
2
+
* color ramp with 12 stops for brand and status colors.
3
3
+
* shades (darker) and tints (lighter) are generated from the primary color.
4
4
+
*/
5
5
+
export interface ColorRamp {
6
6
+
/** darkest shade */
7
7
+
shade50: string;
8
8
+
shade40: string;
9
9
+
shade30: string;
10
10
+
shade20: string;
11
11
+
shade10: string;
12
12
+
/** base color */
13
13
+
primary: string;
14
14
+
tint10: string;
15
15
+
tint20: string;
16
16
+
tint30: string;
17
17
+
tint40: string;
18
18
+
tint50: string;
19
19
+
/** lightest tint */
20
20
+
tint60: string;
21
21
+
}
22
22
+
23
23
+
/**
24
24
+
* grey ramp with 49 stops (lightness 2-98% in steps of 2).
25
25
+
* keys are lightness percentages.
26
26
+
*/
27
27
+
export type GreyRamp = Record<number, string>;
28
28
+
29
29
+
/**
30
30
+
* brand ramp with 16 stops (10-160 in steps of 10).
31
31
+
* keys are position values.
32
32
+
*/
33
33
+
export type BrandRamp = Record<number, string>;
34
34
+
35
35
+
/**
36
36
+
* theme configuration for generating color tokens.
37
37
+
*/
38
38
+
export interface ThemeConfig {
39
39
+
/** brand base color (hex) */
40
40
+
brand: string;
41
41
+
/** danger color, defaults to cranberry (#c50f1f) */
42
42
+
danger?: string;
43
43
+
/** success color, defaults to green (#107c10) */
44
44
+
success?: string;
45
45
+
/** warning color, defaults to orange (#f7630c) */
46
46
+
warning?: string;
47
47
+
}
48
48
+
49
49
+
/**
50
50
+
* generated theme containing light and dark token mappings.
51
51
+
*/
52
52
+
export interface Theme {
53
53
+
light: Record<string, string>;
54
54
+
dark: Record<string, string>;
55
55
+
}
+24
packages/chrysalis/tsconfig.json
···
1
1
+
{
2
2
+
"compilerOptions": {
3
3
+
"outDir": "dist/",
4
4
+
"types": ["bun"],
5
5
+
"esModuleInterop": true,
6
6
+
"skipLibCheck": true,
7
7
+
"target": "ESNext",
8
8
+
"allowJs": true,
9
9
+
"resolveJsonModule": true,
10
10
+
"moduleDetection": "force",
11
11
+
"isolatedModules": true,
12
12
+
"verbatimModuleSyntax": true,
13
13
+
"strict": true,
14
14
+
"noImplicitOverride": true,
15
15
+
"noUnusedLocals": true,
16
16
+
"noUnusedParameters": true,
17
17
+
"noFallthroughCasesInSwitch": true,
18
18
+
"module": "NodeNext",
19
19
+
"sourceMap": true,
20
20
+
"declaration": true,
21
21
+
"declarationMap": true
22
22
+
},
23
23
+
"include": ["src"]
24
24
+
}