@astryxdesign/theme-stone 0.7.6-canary.cebf4a9 → 0.7.6-canary.cfbc66f
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +17 -4
- package/dist/stoneColorMapping.test.d.ts +0 -2
- package/dist/stoneColorMapping.test.d.ts.map +0 -1
- package/dist/stonePalettes.test.d.ts +0 -2
- package/dist/stonePalettes.test.d.ts.map +0 -1
- package/dist/stoneTheme.test.d.ts +0 -2
- package/dist/stoneTheme.test.d.ts.map +0 -1
- package/src/stoneColorMapping.test.ts +0 -44
- package/src/stonePalettes.test.ts +0 -87
- package/src/stoneTheme.test.ts +0 -214
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@astryxdesign/theme-stone",
|
|
3
|
-
"version": "0.7.6-canary.
|
|
3
|
+
"version": "0.7.6-canary.cfbc66f",
|
|
4
4
|
"displayName": "Stone Theme",
|
|
5
5
|
"private": false,
|
|
6
6
|
"description": "Warm stone and slate, earthy and understated, with just enough character to feel handcrafted.",
|
|
@@ -52,17 +52,30 @@
|
|
|
52
52
|
"src",
|
|
53
53
|
"fonts.css",
|
|
54
54
|
"fonts.css.d.ts",
|
|
55
|
-
"palette.config.json"
|
|
55
|
+
"palette.config.json",
|
|
56
|
+
"!**/*.test.*",
|
|
57
|
+
"!**/*.spec.js",
|
|
58
|
+
"!**/*.spec.jsx",
|
|
59
|
+
"!**/*.spec.mjs",
|
|
60
|
+
"!**/*.spec.cjs",
|
|
61
|
+
"!**/*.spec.ts",
|
|
62
|
+
"!**/*.spec.tsx",
|
|
63
|
+
"!**/*.spec.mts",
|
|
64
|
+
"!**/*.spec.cts",
|
|
65
|
+
"!**/*.spec.d.ts",
|
|
66
|
+
"!**/__tests__/**",
|
|
67
|
+
"!**/__mocks__/**",
|
|
68
|
+
"!**/*.bench.*"
|
|
56
69
|
],
|
|
57
70
|
"dependencies": {
|
|
58
71
|
"lucide-react": "^1.18.0"
|
|
59
72
|
},
|
|
60
73
|
"peerDependencies": {
|
|
61
|
-
"@astryxdesign/core": "0.7.6-canary.
|
|
74
|
+
"@astryxdesign/core": "0.7.6-canary.cfbc66f",
|
|
62
75
|
"react": ">=19"
|
|
63
76
|
},
|
|
64
77
|
"devDependencies": {
|
|
65
|
-
"@astryxdesign/cli": "0.7.6-canary.
|
|
78
|
+
"@astryxdesign/cli": "0.7.6-canary.cfbc66f"
|
|
66
79
|
},
|
|
67
80
|
"module": "./dist/source.mjs",
|
|
68
81
|
"scripts": {
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"stoneColorMapping.test.d.ts","sourceRoot":"","sources":["../src/stoneColorMapping.test.ts"],"names":[],"mappings":""}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"stonePalettes.test.d.ts","sourceRoot":"","sources":["../src/stonePalettes.test.ts"],"names":[],"mappings":""}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"stoneTheme.test.d.ts","sourceRoot":"","sources":["../src/stoneTheme.test.ts"],"names":[],"mappings":""}
|
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
-
|
|
3
|
-
import {readFileSync} from 'node:fs';
|
|
4
|
-
import {describe, expect, it} from 'vitest';
|
|
5
|
-
import {stoneTheme} from './stoneTheme';
|
|
6
|
-
|
|
7
|
-
const source = readFileSync(
|
|
8
|
-
new URL('./stoneTheme.ts', import.meta.url),
|
|
9
|
-
'utf8',
|
|
10
|
-
);
|
|
11
|
-
|
|
12
|
-
const publicPaletteStart = source.indexOf('export const stonePalettes');
|
|
13
|
-
if (publicPaletteStart < 0) {
|
|
14
|
-
throw new Error('stoneTheme.ts: stonePalettes export marker not found');
|
|
15
|
-
}
|
|
16
|
-
const maintainedThemeSource = source.slice(0, publicPaletteStart);
|
|
17
|
-
|
|
18
|
-
describe('stone color token mapping completeness', () => {
|
|
19
|
-
it('maps every color token Stone owns through its reviewed palette', () => {
|
|
20
|
-
expect(
|
|
21
|
-
Object.keys(stoneTheme.tokens).filter(name =>
|
|
22
|
-
name.startsWith('--color-data-'),
|
|
23
|
-
),
|
|
24
|
-
).toEqual([]);
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
it('keeps every resolved root color concrete', () => {
|
|
28
|
-
for (const [name, value] of Object.entries(stoneTheme.tokens)) {
|
|
29
|
-
if (!name.startsWith('--color-') || name.startsWith('--color-data-')) {
|
|
30
|
-
continue;
|
|
31
|
-
}
|
|
32
|
-
expect(typeof value, name).toBe('string');
|
|
33
|
-
expect(value, name).toMatch(
|
|
34
|
-
/^(?:transparent|#[0-9a-f]{6}(?:[0-9a-f]{2})?|light-dark\(#[0-9a-f]{6}(?:[0-9a-f]{2})?, #[0-9a-f]{6}(?:[0-9a-f]{2})?\))$/i,
|
|
35
|
-
);
|
|
36
|
-
}
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
it('uses no one-off color literals in the maintained theme source', () => {
|
|
40
|
-
expect(maintainedThemeSource).not.toMatch(
|
|
41
|
-
/#[0-9a-f]{3,8}\b|\brgba?\(|\bhsl[a]?\(|\boklch\(|\bblack\b|\bwhite\b/i,
|
|
42
|
-
);
|
|
43
|
-
});
|
|
44
|
-
});
|
|
@@ -1,87 +0,0 @@
|
|
|
1
|
-
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
-
|
|
3
|
-
import {describe, expect, it} from 'vitest';
|
|
4
|
-
import {palette as stoneModePalettes} from './stonePalettes.generated';
|
|
5
|
-
import {stonePalettes} from './stoneTheme';
|
|
6
|
-
|
|
7
|
-
const stops = [
|
|
8
|
-
0, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60, 65, 70, 75, 80, 85, 90, 95,
|
|
9
|
-
100,
|
|
10
|
-
] as const;
|
|
11
|
-
|
|
12
|
-
const chromaticFamilies = [
|
|
13
|
-
'blue',
|
|
14
|
-
'cyan',
|
|
15
|
-
'green',
|
|
16
|
-
'teal',
|
|
17
|
-
'yellow',
|
|
18
|
-
'orange',
|
|
19
|
-
'red',
|
|
20
|
-
'pink',
|
|
21
|
-
'purple',
|
|
22
|
-
] as const;
|
|
23
|
-
|
|
24
|
-
const exactMidpoints = {
|
|
25
|
-
red: '#8a716d',
|
|
26
|
-
orange: '#8f7154',
|
|
27
|
-
yellow: '#927f25',
|
|
28
|
-
green: '#528b49',
|
|
29
|
-
teal: '#588174',
|
|
30
|
-
cyan: '#507b86',
|
|
31
|
-
blue: '#6c7888',
|
|
32
|
-
purple: '#7b7486',
|
|
33
|
-
pink: '#83727c',
|
|
34
|
-
} as const;
|
|
35
|
-
|
|
36
|
-
describe('stone palette artifact', () => {
|
|
37
|
-
it('keeps every reviewed family and mode structurally complete', () => {
|
|
38
|
-
expect(Object.keys(stonePalettes)).toEqual([
|
|
39
|
-
'neutral',
|
|
40
|
-
...chromaticFamilies,
|
|
41
|
-
]);
|
|
42
|
-
|
|
43
|
-
for (const familyName of ['neutral', ...chromaticFamilies] as const) {
|
|
44
|
-
const family = stonePalettes[familyName];
|
|
45
|
-
expect(family.hue).toBeTypeOf('number');
|
|
46
|
-
expect(family.chroma).toBeTypeOf('number');
|
|
47
|
-
for (const stop of stops) {
|
|
48
|
-
expect(family[stop]).toMatch(/^#[0-9a-f]{6}$/i);
|
|
49
|
-
expect(stoneModePalettes[familyName].light[stop]).toMatch(
|
|
50
|
-
/^#[0-9a-f]{6}$/i,
|
|
51
|
-
);
|
|
52
|
-
expect(stoneModePalettes[familyName].dark[stop]).toMatch(
|
|
53
|
-
/^#[0-9a-f]{6}$/i,
|
|
54
|
-
);
|
|
55
|
-
}
|
|
56
|
-
expect(family[0]).toBe('#000000');
|
|
57
|
-
expect(family[100]).toBe('#ffffff');
|
|
58
|
-
}
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
it('preserves Stone exact anchors in both modes', () => {
|
|
62
|
-
expect(stoneModePalettes.neutral.light[15]).toBe('#25252a');
|
|
63
|
-
expect(stoneModePalettes.neutral.light[50]).toBe('#77777c');
|
|
64
|
-
expect(stoneModePalettes.neutral.light[90]).toBe('#e2e2e8');
|
|
65
|
-
expect(stoneModePalettes.neutral.dark[15]).toBe('#25252a');
|
|
66
|
-
expect(stoneModePalettes.neutral.dark[50]).toBe('#77777c');
|
|
67
|
-
expect(stoneModePalettes.neutral.dark[90]).toBe('#e2e2e8');
|
|
68
|
-
|
|
69
|
-
for (const familyName of chromaticFamilies) {
|
|
70
|
-
expect(stoneModePalettes[familyName].light[50]).toBe(
|
|
71
|
-
exactMidpoints[familyName],
|
|
72
|
-
);
|
|
73
|
-
expect(stoneModePalettes[familyName].dark[50]).toBe(
|
|
74
|
-
exactMidpoints[familyName],
|
|
75
|
-
);
|
|
76
|
-
expect(stonePalettes[familyName]).not.toHaveProperty('light');
|
|
77
|
-
expect(stonePalettes[familyName]).not.toHaveProperty('dark');
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
expect(stoneModePalettes.yellow.light[90]).toBe('#f4e1b7');
|
|
81
|
-
expect(stoneModePalettes.yellow.dark[90]).toBe('#f1e1b9');
|
|
82
|
-
expect(stoneModePalettes.orange.light[90]).toBe('#ffdcbb');
|
|
83
|
-
expect(stoneModePalettes.orange.dark[90]).toBe('#ffdcbb');
|
|
84
|
-
expect(stoneModePalettes.green.light[85]).toBe('#bfdfbb');
|
|
85
|
-
expect(stoneModePalettes.green.dark[85]).toBe('#c3ddbf');
|
|
86
|
-
});
|
|
87
|
-
});
|
package/src/stoneTheme.test.ts
DELETED
|
@@ -1,214 +0,0 @@
|
|
|
1
|
-
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
-
|
|
3
|
-
import {describe, expect, it} from 'vitest';
|
|
4
|
-
import {generateThemeCSS, resolveThemeTokens} from '@astryxdesign/core/theme';
|
|
5
|
-
import {palette as stoneModePalettes} from './stonePalettes.generated';
|
|
6
|
-
import {stoneTheme} from './stoneTheme';
|
|
7
|
-
|
|
8
|
-
function channels(color: string): [number, number, number] {
|
|
9
|
-
return [1, 3, 5].map(index =>
|
|
10
|
-
Number.parseInt(color.slice(index, index + 2), 16),
|
|
11
|
-
) as [number, number, number];
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
function luminance(color: string): number {
|
|
15
|
-
const [red, green, blue] = channels(color).map(channel => {
|
|
16
|
-
const value = channel / 255;
|
|
17
|
-
return value <= 0.04045
|
|
18
|
-
? value / 12.92
|
|
19
|
-
: Math.pow((value + 0.055) / 1.055, 2.4);
|
|
20
|
-
});
|
|
21
|
-
return 0.2126 * red + 0.7152 * green + 0.0722 * blue;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
function contrast(foreground: string, background: string): number {
|
|
25
|
-
const [lighter, darker] = [luminance(foreground), luminance(background)].sort(
|
|
26
|
-
(left, right) => right - left,
|
|
27
|
-
);
|
|
28
|
-
return (lighter + 0.05) / (darker + 0.05);
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
function blend(foreground: string, background: string, alpha: number): string {
|
|
32
|
-
const front = channels(foreground);
|
|
33
|
-
const back = channels(background);
|
|
34
|
-
return `#${front
|
|
35
|
-
.map((channel, index) =>
|
|
36
|
-
Math.round(channel * alpha + back[index] * (1 - alpha))
|
|
37
|
-
.toString(16)
|
|
38
|
-
.padStart(2, '0'),
|
|
39
|
-
)
|
|
40
|
-
.join('')}`;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
describe('stone touch + narrow type pin', () => {
|
|
44
|
-
it('floors body to 16px and holds Display 1 at its desktop size', () => {
|
|
45
|
-
const rule = stoneTheme.__adaptationRules?.[0];
|
|
46
|
-
|
|
47
|
-
expect(stoneTheme.__adaptations.rules).toHaveLength(1);
|
|
48
|
-
expect(rule?.when).toEqual({pointer: 'coarse', width: {below: 'lg'}});
|
|
49
|
-
expect(rule?.query).toBe('(width < 1024px) and (pointer: coarse)');
|
|
50
|
-
// Pinned ladder: base 16 (was 14), ratio 1.25 -> 1.2225, Display 1 pinned.
|
|
51
|
-
expect(rule?.tokens['--font-size-base']).toBe('1rem');
|
|
52
|
-
expect(rule?.tokens['--font-size-lg']).toBe('1.25rem');
|
|
53
|
-
expect(rule?.tokens['--font-size-5xl']).toBe('3.3125rem');
|
|
54
|
-
// Desktop scale is untouched.
|
|
55
|
-
expect(stoneTheme.tokens['--font-size-base']).toBe('0.875rem');
|
|
56
|
-
expect(stoneTheme.tokens['--font-size-5xl']).toBe('3.3125rem');
|
|
57
|
-
expect(generateThemeCSS(stoneTheme).component).toContain(
|
|
58
|
-
'@media (width < 1024px) and (pointer: coarse)',
|
|
59
|
-
);
|
|
60
|
-
});
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
describe('stone input status roles', () => {
|
|
64
|
-
it('keeps soft per-input status rims and glyphs on the border and icon roles', () => {
|
|
65
|
-
const inputs = [
|
|
66
|
-
'text-input',
|
|
67
|
-
'text-area',
|
|
68
|
-
'number-input',
|
|
69
|
-
'date-input',
|
|
70
|
-
'time-input',
|
|
71
|
-
'selector',
|
|
72
|
-
'multi-selector',
|
|
73
|
-
'typeahead',
|
|
74
|
-
'tokenizer',
|
|
75
|
-
];
|
|
76
|
-
|
|
77
|
-
const {green, red, yellow} = stoneModePalettes;
|
|
78
|
-
for (const input of inputs) {
|
|
79
|
-
const component = stoneTheme.components![input];
|
|
80
|
-
expect(component['status:success']).toMatchObject({
|
|
81
|
-
'--color-border-success': `light-dark(${green.light[55]}, ${green.dark[70]})`,
|
|
82
|
-
'--color-icon-success': `light-dark(${green.light[55]}, ${green.dark[70]})`,
|
|
83
|
-
});
|
|
84
|
-
expect(component['status:warning']).toMatchObject({
|
|
85
|
-
'--color-border-warning': `light-dark(${yellow.light[55]}, ${yellow.dark[70]})`,
|
|
86
|
-
'--color-icon-warning': `light-dark(${yellow.light[55]}, ${yellow.dark[70]})`,
|
|
87
|
-
});
|
|
88
|
-
expect(component['status:error']).toMatchObject({
|
|
89
|
-
'--color-border-error': `light-dark(${red.light[60]}, ${red.dark[70]})`,
|
|
90
|
-
'--color-icon-error': `light-dark(${red.light[60]}, ${red.dark[70]})`,
|
|
91
|
-
});
|
|
92
|
-
}
|
|
93
|
-
});
|
|
94
|
-
});
|
|
95
|
-
|
|
96
|
-
describe('stone strong status contrast', () => {
|
|
97
|
-
it('pairs strong status fills with contrasting light and dark ink', () => {
|
|
98
|
-
for (const token of [
|
|
99
|
-
'--color-on-success',
|
|
100
|
-
'--color-on-warning',
|
|
101
|
-
'--color-on-error',
|
|
102
|
-
] as const) {
|
|
103
|
-
expect(stoneTheme.tokens[token]).toBe(
|
|
104
|
-
`light-dark(${stoneModePalettes.neutral.light[100]}, ${stoneModePalettes.neutral.dark[10]})`,
|
|
105
|
-
);
|
|
106
|
-
}
|
|
107
|
-
});
|
|
108
|
-
|
|
109
|
-
it('uses dark ink for marks on Stone’s pale progress fills', () => {
|
|
110
|
-
const mark = stoneTheme.components!['progress-bar-mark'];
|
|
111
|
-
for (const variant of ['accent', 'success', 'warning', 'error']) {
|
|
112
|
-
expect(mark[`placement:fill+variant:${variant}`]).toMatchObject({
|
|
113
|
-
backgroundColor: 'var(--color-on-light)',
|
|
114
|
-
});
|
|
115
|
-
}
|
|
116
|
-
});
|
|
117
|
-
});
|
|
118
|
-
|
|
119
|
-
describe('stone secondary ComplexSelector', () => {
|
|
120
|
-
it('keeps the hover fill off a disabled trigger in either form', () => {
|
|
121
|
-
expect(generateThemeCSS(stoneTheme).component).toMatch(
|
|
122
|
-
/:hover:not\(:has\(> ?:is\(:disabled, ?\[aria-disabled="true"\]\)\)\)/,
|
|
123
|
-
);
|
|
124
|
-
});
|
|
125
|
-
});
|
|
126
|
-
|
|
127
|
-
const categoricalFamilies = [
|
|
128
|
-
'blue',
|
|
129
|
-
'cyan',
|
|
130
|
-
'green',
|
|
131
|
-
'orange',
|
|
132
|
-
'pink',
|
|
133
|
-
'purple',
|
|
134
|
-
'red',
|
|
135
|
-
'teal',
|
|
136
|
-
'yellow',
|
|
137
|
-
] as const;
|
|
138
|
-
|
|
139
|
-
describe('stone palette mapping', () => {
|
|
140
|
-
it.each(['light', 'dark'] as const)(
|
|
141
|
-
'resolves the neutral surface ladder from reviewed %s stops',
|
|
142
|
-
mode => {
|
|
143
|
-
const tokens = resolveThemeTokens(stoneTheme, {mode});
|
|
144
|
-
const ramp = stoneModePalettes.neutral[mode];
|
|
145
|
-
|
|
146
|
-
expect(tokens['--color-background-body']).toBe(
|
|
147
|
-
ramp[mode === 'light' ? 95 : 5],
|
|
148
|
-
);
|
|
149
|
-
expect(tokens['--color-background-surface']).toBe(
|
|
150
|
-
ramp[mode === 'light' ? 100 : 10],
|
|
151
|
-
);
|
|
152
|
-
expect(tokens['--color-background-card']).toBe(
|
|
153
|
-
ramp[mode === 'light' ? 100 : 15],
|
|
154
|
-
);
|
|
155
|
-
expect(tokens['--color-background-popover']).toBe(
|
|
156
|
-
ramp[mode === 'light' ? 100 : 20],
|
|
157
|
-
);
|
|
158
|
-
expect(tokens['--color-text-primary']).toBe(
|
|
159
|
-
ramp[mode === 'light' ? 15 : 95],
|
|
160
|
-
);
|
|
161
|
-
expect(tokens['--color-text-secondary']).toBe(
|
|
162
|
-
ramp[mode === 'light' ? 40 : 70],
|
|
163
|
-
);
|
|
164
|
-
},
|
|
165
|
-
);
|
|
166
|
-
|
|
167
|
-
it.each(['light', 'dark'] as const)(
|
|
168
|
-
'keeps every categorical %s role on the same four-stop pattern',
|
|
169
|
-
mode => {
|
|
170
|
-
const tokens = resolveThemeTokens(stoneTheme, {mode});
|
|
171
|
-
for (const family of categoricalFamilies) {
|
|
172
|
-
const ramp = stoneModePalettes[family][mode];
|
|
173
|
-
expect(tokens[`--color-background-${family}`]).toBe(
|
|
174
|
-
ramp[mode === 'light' ? 90 : 35],
|
|
175
|
-
);
|
|
176
|
-
expect(tokens[`--color-border-${family}`]).toBe(
|
|
177
|
-
ramp[mode === 'light' ? 85 : 25],
|
|
178
|
-
);
|
|
179
|
-
expect(tokens[`--color-icon-${family}`]).toBe(
|
|
180
|
-
ramp[mode === 'light' ? 30 : 90],
|
|
181
|
-
);
|
|
182
|
-
expect(tokens[`--color-text-${family}`]).toBe(
|
|
183
|
-
ramp[mode === 'light' ? 30 : 90],
|
|
184
|
-
);
|
|
185
|
-
}
|
|
186
|
-
},
|
|
187
|
-
);
|
|
188
|
-
|
|
189
|
-
it('keeps light input rims and syntax attributes above their thresholds', () => {
|
|
190
|
-
const {green, neutral, red, yellow} = stoneModePalettes;
|
|
191
|
-
const surface = neutral.light[100];
|
|
192
|
-
const syntaxBackground = neutral.light[95];
|
|
193
|
-
const diffAlpha = 0x40 / 255;
|
|
194
|
-
const addedRow = blend(green.light[90], syntaxBackground, diffAlpha);
|
|
195
|
-
const removedRow = blend(red.light[90], syntaxBackground, diffAlpha);
|
|
196
|
-
|
|
197
|
-
expect(contrast(green.light[55], surface)).toBeGreaterThanOrEqual(3);
|
|
198
|
-
expect(contrast(yellow.light[55], surface)).toBeGreaterThanOrEqual(3);
|
|
199
|
-
for (const background of [syntaxBackground, addedRow, removedRow]) {
|
|
200
|
-
expect(contrast(yellow.light[40], background)).toBeGreaterThanOrEqual(
|
|
201
|
-
4.5,
|
|
202
|
-
);
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
expect(stoneTheme.components?.['text-input']?.['status:success']).toEqual({
|
|
206
|
-
'--color-border-success': `light-dark(${green.light[55]}, ${green.dark[70]})`,
|
|
207
|
-
'--color-icon-success': `light-dark(${green.light[55]}, ${green.dark[70]})`,
|
|
208
|
-
});
|
|
209
|
-
expect(stoneTheme.components?.['text-input']?.['status:warning']).toEqual({
|
|
210
|
-
'--color-border-warning': `light-dark(${yellow.light[55]}, ${yellow.dark[70]})`,
|
|
211
|
-
'--color-icon-warning': `light-dark(${yellow.light[55]}, ${yellow.dark[70]})`,
|
|
212
|
-
});
|
|
213
|
-
});
|
|
214
|
-
});
|