@astryxdesign/theme-neutral 0.7.6-canary.fa3f760 → 0.7.6-canary.fdabeba

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/dist/neutral.js CHANGED
@@ -81,6 +81,15 @@ export const neutralTheme = {
81
81
  "--duration-slow-min": "525ms",
82
82
  "--duration-slow": "700ms",
83
83
  "--duration-slow-max": "935ms",
84
+ "--_astryx-pre-tempo-duration-fast-min": "95ms",
85
+ "--_astryx-pre-tempo-duration-fast": "125ms",
86
+ "--_astryx-pre-tempo-duration-fast-max": "165ms",
87
+ "--_astryx-pre-tempo-duration-medium-min": "225ms",
88
+ "--_astryx-pre-tempo-duration-medium": "300ms",
89
+ "--_astryx-pre-tempo-duration-medium-max": "400ms",
90
+ "--_astryx-pre-tempo-duration-slow-min": "525ms",
91
+ "--_astryx-pre-tempo-duration-slow": "700ms",
92
+ "--_astryx-pre-tempo-duration-slow-max": "935ms",
84
93
  "--font-family-body": "Figtree, -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, Helvetica, Arial, sans-serif",
85
94
  "--font-family-heading": "Figtree, -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, Helvetica, Arial, sans-serif",
86
95
  "--font-family-code": "ui-monospace, \"SF Mono\", Monaco, Consolas, \"Liberation Mono\", \"Courier New\", monospace",
package/dist/theme.css CHANGED
@@ -149,6 +149,15 @@
149
149
  --duration-slow-min: 525ms;
150
150
  --duration-slow: 700ms;
151
151
  --duration-slow-max: 935ms;
152
+ --_astryx-pre-tempo-duration-fast-min: 95ms;
153
+ --_astryx-pre-tempo-duration-fast: 125ms;
154
+ --_astryx-pre-tempo-duration-fast-max: 165ms;
155
+ --_astryx-pre-tempo-duration-medium-min: 225ms;
156
+ --_astryx-pre-tempo-duration-medium: 300ms;
157
+ --_astryx-pre-tempo-duration-medium-max: 400ms;
158
+ --_astryx-pre-tempo-duration-slow-min: 525ms;
159
+ --_astryx-pre-tempo-duration-slow: 700ms;
160
+ --_astryx-pre-tempo-duration-slow-max: 935ms;
152
161
  --font-family-body: Figtree, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
153
162
  --font-family-heading: Figtree, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
154
163
  --font-family-code: ui-monospace, "SF Mono", Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@astryxdesign/theme-neutral",
3
- "version": "0.7.6-canary.fa3f760",
3
+ "version": "0.7.6-canary.fdabeba",
4
4
  "displayName": "Neutral Theme",
5
5
  "private": false,
6
6
  "description": "Restrained warm grays. Minimal and quiet, so the content stays the focus.",
@@ -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.fa3f760",
74
+ "@astryxdesign/core": "0.7.6-canary.fdabeba",
62
75
  "react": ">=19"
63
76
  },
64
77
  "devDependencies": {
65
- "@astryxdesign/cli": "0.7.6-canary.fa3f760"
78
+ "@astryxdesign/cli": "0.7.6-canary.fdabeba"
66
79
  },
67
80
  "module": "./dist/source.mjs",
68
81
  "scripts": {
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=neutralPalettes.test.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"neutralPalettes.test.d.ts","sourceRoot":"","sources":["../src/neutralPalettes.test.ts"],"names":[],"mappings":""}
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=neutralTheme.test.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"neutralTheme.test.d.ts","sourceRoot":"","sources":["../src/neutralTheme.test.ts"],"names":[],"mappings":""}
@@ -1,83 +0,0 @@
1
- // Copyright (c) Meta Platforms, Inc. and affiliates.
2
-
3
- import {describe, expect, it} from 'vitest';
4
- import {neutralPalettes} from './neutralPalettes';
5
-
6
- const neutralPaletteStops = [
7
- 0, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60, 65, 70, 75, 80, 85, 90, 95,
8
- 100,
9
- ] as const;
10
-
11
- describe('neutral palette artifact', () => {
12
- it('keeps the approved palette structurally valid', () => {
13
- expect(neutralPalettes.black).toBe('#000000');
14
- expect(neutralPalettes.white).toBe('#ffffff');
15
- expect(Object.keys(neutralPalettes)).toEqual([
16
- 'black',
17
- 'white',
18
- 'neutral',
19
- 'red',
20
- 'orange',
21
- 'yellow',
22
- 'green',
23
- 'teal',
24
- 'cyan',
25
- 'blue',
26
- 'purple',
27
- 'pink',
28
- ]);
29
-
30
- for (const familyName of [
31
- 'neutral',
32
- 'red',
33
- 'orange',
34
- 'yellow',
35
- 'green',
36
- 'teal',
37
- 'cyan',
38
- 'blue',
39
- 'purple',
40
- 'pink',
41
- ] as const) {
42
- const family = neutralPalettes[familyName];
43
- for (const stop of neutralPaletteStops) {
44
- expect(family.light[stop]).toMatch(/^#[0-9a-f]{6}$/i);
45
- expect(family.dark[stop]).toMatch(/^#[0-9a-f]{6}$/i);
46
- }
47
- expect(family.light[0]).toBe('#000000');
48
- expect(family.dark[0]).toBe('#000000');
49
- expect(family.light[100]).toBe('#ffffff');
50
- expect(family.dark[100]).toBe('#ffffff');
51
- }
52
- });
53
-
54
- it('keeps the approved muted dark edge values', () => {
55
- expect(
56
- Object.fromEntries(
57
- (
58
- [
59
- 'red',
60
- 'orange',
61
- 'yellow',
62
- 'green',
63
- 'teal',
64
- 'cyan',
65
- 'blue',
66
- 'purple',
67
- 'pink',
68
- ] as const
69
- ).map(family => [family, neutralPalettes[family].dark[25]]),
70
- ),
71
- ).toEqual({
72
- red: '#5b2b28',
73
- orange: '#503424',
74
- yellow: '#453a1c',
75
- green: '#2b422b',
76
- teal: '#28413c',
77
- cyan: '#274046',
78
- blue: '#253c5a',
79
- purple: '#4a2f51',
80
- pink: '#572b3d',
81
- });
82
- });
83
- });
@@ -1,464 +0,0 @@
1
- // Copyright (c) Meta Platforms, Inc. and affiliates.
2
-
3
- import {describe, expect, it} from 'vitest';
4
- import {generateThemeCSS} from '@astryxdesign/core/theme';
5
- import {neutralPalettes} from './neutralPalettes';
6
- import {neutralTheme} from './neutralTheme';
7
-
8
- function contrastRatio(foreground: string, background: string): number {
9
- const luminance = (hex: string): number => {
10
- const channels = [0, 2, 4].map(offset =>
11
- Number.parseInt(hex.slice(offset + 1, offset + 3), 16),
12
- );
13
- return channels.reduce((sum, channel, index) => {
14
- const srgb = channel / 255;
15
- const linear =
16
- srgb <= 0.04045 ? srgb / 12.92 : Math.pow((srgb + 0.055) / 1.055, 2.4);
17
- return sum + linear * [0.2126, 0.7152, 0.0722][index];
18
- }, 0);
19
- };
20
- const foregroundLuminance = luminance(foreground);
21
- const backgroundLuminance = luminance(background);
22
- const lighter = Math.max(foregroundLuminance, backgroundLuminance);
23
- const darker = Math.min(foregroundLuminance, backgroundLuminance);
24
- return (lighter + 0.05) / (darker + 0.05);
25
- }
26
-
27
- const lightDark = (light: string, dark: string) =>
28
- `light-dark(${light}, ${dark})`;
29
-
30
- describe('neutral theme palette mappings', () => {
31
- it('keeps semantic and categorical tokens aligned with reviewed stops', () => {
32
- const mappings = {
33
- '--color-background-surface': lightDark(
34
- neutralPalettes.neutral.light[100],
35
- neutralPalettes.neutral.dark[15],
36
- ),
37
- '--color-background-body': lightDark(
38
- neutralPalettes.neutral.light[95],
39
- neutralPalettes.neutral.dark[10],
40
- ),
41
- '--color-text-primary': lightDark(
42
- neutralPalettes.neutral.light[0],
43
- neutralPalettes.neutral.dark[100],
44
- ),
45
- '--color-text-secondary': lightDark(
46
- neutralPalettes.neutral.light[30],
47
- neutralPalettes.neutral.dark[65],
48
- ),
49
- '--color-text-disabled': lightDark(
50
- neutralPalettes.neutral.light[60],
51
- neutralPalettes.neutral.dark[35],
52
- ),
53
- '--color-text-accent': lightDark(
54
- neutralPalettes.neutral.light[10],
55
- neutralPalettes.neutral.dark[95],
56
- ),
57
- '--color-icon-primary': lightDark(
58
- neutralPalettes.neutral.light[0],
59
- neutralPalettes.neutral.dark[100],
60
- ),
61
- '--color-icon-secondary': lightDark(
62
- neutralPalettes.neutral.light[45],
63
- neutralPalettes.neutral.dark[65],
64
- ),
65
- '--color-icon-disabled': lightDark(
66
- neutralPalettes.neutral.light[60],
67
- neutralPalettes.neutral.dark[35],
68
- ),
69
- '--color-icon-accent': lightDark(
70
- neutralPalettes.neutral.light[10],
71
- neutralPalettes.neutral.dark[95],
72
- ),
73
- '--color-accent': lightDark(
74
- neutralPalettes.neutral.light[10],
75
- neutralPalettes.neutral.dark[95],
76
- ),
77
- '--color-info': lightDark('#0074e2', '#6d9cfe'),
78
- '--color-info-muted': lightDark(
79
- neutralPalettes.blue.light[85],
80
- `${neutralPalettes.blue.dark[75]}3D`,
81
- ),
82
- '--color-on-info': lightDark(
83
- neutralPalettes.neutral.light[100],
84
- neutralPalettes.neutral.dark[5],
85
- ),
86
- // Strong status fills: Neutral's released Badge fill colours.
87
- '--color-success': lightDark('#198100', '#64af4c'),
88
- '--color-warning': '#ffce2f',
89
- '--color-error': lightDark('#c9303a', '#ff705d'),
90
- '--color-text-info': lightDark(
91
- neutralPalettes.blue.light[45],
92
- neutralPalettes.blue.dark[80],
93
- ),
94
- '--color-text-success': lightDark(
95
- neutralPalettes.green.light[45],
96
- neutralPalettes.green.light[75],
97
- ),
98
- '--color-text-warning': lightDark(
99
- neutralPalettes.yellow.light[45],
100
- neutralPalettes.yellow.light[80],
101
- ),
102
- '--color-text-error': lightDark(
103
- neutralPalettes.red.light[45],
104
- neutralPalettes.red.dark[80],
105
- ),
106
- '--color-on-info-muted': lightDark(
107
- neutralPalettes.blue.light[25],
108
- neutralPalettes.blue.dark[80],
109
- ),
110
- '--color-on-success-muted': lightDark(
111
- neutralPalettes.green.light[25],
112
- neutralPalettes.green.light[75],
113
- ),
114
- '--color-on-warning-muted': lightDark(
115
- neutralPalettes.yellow.light[25],
116
- neutralPalettes.yellow.light[80],
117
- ),
118
- '--color-on-error-muted': lightDark(
119
- neutralPalettes.red.light[25],
120
- neutralPalettes.red.dark[80],
121
- ),
122
- '--color-background-red': lightDark(
123
- neutralPalettes.red.light[85],
124
- neutralPalettes.red.dark[25],
125
- ),
126
- '--color-border-red': lightDark(
127
- neutralPalettes.red.light[80],
128
- neutralPalettes.red.light[65],
129
- ),
130
- '--color-icon-red': lightDark(
131
- neutralPalettes.red.light[25],
132
- neutralPalettes.red.dark[75],
133
- ),
134
- '--color-text-red': lightDark(
135
- neutralPalettes.red.light[25],
136
- neutralPalettes.red.dark[80],
137
- ),
138
- '--color-background-orange': lightDark(
139
- neutralPalettes.orange.light[85],
140
- neutralPalettes.orange.dark[25],
141
- ),
142
- '--color-border-orange': lightDark(
143
- neutralPalettes.orange.light[85],
144
- neutralPalettes.orange.dark[65],
145
- ),
146
- '--color-icon-orange': lightDark(
147
- neutralPalettes.orange.light[25],
148
- neutralPalettes.orange.light[75],
149
- ),
150
- '--color-text-orange': lightDark(
151
- neutralPalettes.orange.light[25],
152
- neutralPalettes.orange.dark[80],
153
- ),
154
- '--color-background-yellow': lightDark(
155
- neutralPalettes.yellow.dark[90],
156
- neutralPalettes.yellow.dark[25],
157
- ),
158
- '--color-border-yellow': lightDark(
159
- neutralPalettes.yellow.dark[80],
160
- neutralPalettes.yellow.light[65],
161
- ),
162
- '--color-icon-yellow': lightDark(
163
- neutralPalettes.yellow.light[25],
164
- neutralPalettes.yellow.light[75],
165
- ),
166
- '--color-text-yellow': lightDark(
167
- neutralPalettes.yellow.light[25],
168
- neutralPalettes.yellow.light[80],
169
- ),
170
- '--color-background-green': lightDark(
171
- neutralPalettes.green.dark[85],
172
- neutralPalettes.green.dark[25],
173
- ),
174
- '--color-border-green': lightDark(
175
- neutralPalettes.green.dark[80],
176
- neutralPalettes.green.light[65],
177
- ),
178
- '--color-icon-green': lightDark(
179
- neutralPalettes.green.light[25],
180
- neutralPalettes.green.light[75],
181
- ),
182
- '--color-text-green': lightDark(
183
- neutralPalettes.green.light[25],
184
- neutralPalettes.green.light[75],
185
- ),
186
- '--color-background-teal': lightDark(
187
- neutralPalettes.teal.light[85],
188
- neutralPalettes.teal.dark[25],
189
- ),
190
- '--color-border-teal': lightDark(
191
- neutralPalettes.teal.light[80],
192
- neutralPalettes.teal.dark[65],
193
- ),
194
- '--color-icon-teal': lightDark(
195
- neutralPalettes.teal.light[25],
196
- neutralPalettes.teal.dark[75],
197
- ),
198
- '--color-text-teal': lightDark(
199
- neutralPalettes.teal.light[25],
200
- neutralPalettes.teal.light[80],
201
- ),
202
- '--color-background-cyan': lightDark(
203
- neutralPalettes.cyan.dark[85],
204
- neutralPalettes.cyan.dark[25],
205
- ),
206
- '--color-border-cyan': lightDark(
207
- neutralPalettes.cyan.dark[80],
208
- neutralPalettes.cyan.dark[65],
209
- ),
210
- '--color-icon-cyan': lightDark(
211
- neutralPalettes.cyan.light[25],
212
- neutralPalettes.cyan.dark[75],
213
- ),
214
- '--color-text-cyan': lightDark(
215
- neutralPalettes.cyan.light[25],
216
- neutralPalettes.cyan.dark[80],
217
- ),
218
- '--color-background-blue': lightDark(
219
- neutralPalettes.blue.light[85],
220
- neutralPalettes.blue.dark[25],
221
- ),
222
- '--color-border-blue': lightDark(
223
- neutralPalettes.blue.light[80],
224
- neutralPalettes.blue.dark[65],
225
- ),
226
- '--color-icon-blue': lightDark(
227
- neutralPalettes.blue.light[25],
228
- neutralPalettes.blue.dark[75],
229
- ),
230
- '--color-text-blue': lightDark(
231
- neutralPalettes.blue.light[25],
232
- neutralPalettes.blue.dark[80],
233
- ),
234
- '--color-background-purple': lightDark(
235
- neutralPalettes.purple.light[90],
236
- neutralPalettes.purple.dark[25],
237
- ),
238
- '--color-border-purple': lightDark(
239
- neutralPalettes.purple.light[85],
240
- neutralPalettes.purple.light[70],
241
- ),
242
- '--color-icon-purple': lightDark(
243
- neutralPalettes.purple.light[25],
244
- neutralPalettes.purple.light[75],
245
- ),
246
- '--color-text-purple': lightDark(
247
- neutralPalettes.purple.light[25],
248
- neutralPalettes.purple.dark[80],
249
- ),
250
- '--color-background-pink': lightDark(
251
- neutralPalettes.pink.light[85],
252
- neutralPalettes.pink.dark[25],
253
- ),
254
- '--color-border-pink': lightDark(
255
- neutralPalettes.pink.light[85],
256
- neutralPalettes.pink.light[70],
257
- ),
258
- '--color-icon-pink': lightDark(
259
- neutralPalettes.pink.light[25],
260
- neutralPalettes.pink.dark[75],
261
- ),
262
- '--color-text-pink': lightDark(
263
- neutralPalettes.pink.light[25],
264
- neutralPalettes.pink.dark[80],
265
- ),
266
- '--color-background-gray': lightDark(
267
- neutralPalettes.neutral.light[90],
268
- neutralPalettes.neutral.dark[20],
269
- ),
270
- '--color-text-gray': lightDark(
271
- neutralPalettes.neutral.light[10],
272
- neutralPalettes.neutral.dark[85],
273
- ),
274
- '--color-icon-gray': lightDark(
275
- neutralPalettes.neutral.light[30],
276
- neutralPalettes.neutral.dark[65],
277
- ),
278
- };
279
-
280
- expect(neutralTheme.tokens).toMatchObject(mappings);
281
- });
282
-
283
- it('keeps dark categorical text readable on its background', () => {
284
- const pairs = [
285
- [neutralPalettes.red.dark[80], neutralPalettes.red.dark[25]],
286
- [neutralPalettes.orange.dark[80], neutralPalettes.orange.dark[25]],
287
- [neutralPalettes.yellow.light[80], neutralPalettes.yellow.dark[25]],
288
- [neutralPalettes.green.light[75], neutralPalettes.green.dark[25]],
289
- [neutralPalettes.teal.light[80], neutralPalettes.teal.dark[25]],
290
- [neutralPalettes.cyan.dark[80], neutralPalettes.cyan.dark[25]],
291
- [neutralPalettes.blue.dark[80], neutralPalettes.blue.dark[25]],
292
- [neutralPalettes.purple.dark[80], neutralPalettes.purple.dark[25]],
293
- [neutralPalettes.pink.dark[80], neutralPalettes.pink.dark[25]],
294
- [neutralPalettes.neutral.dark[85], neutralPalettes.neutral.dark[20]],
295
- ] as const;
296
-
297
- for (const [foreground, background] of pairs) {
298
- expect(contrastRatio(foreground, background)).toBeGreaterThanOrEqual(4.5);
299
- }
300
- });
301
- });
302
-
303
- describe('neutral syntax contrast', () => {
304
- it('keeps light CodeBlock comments and operators at normal-text AA', () => {
305
- const background = neutralPalettes.neutral.light[100];
306
- for (const token of ['comment', 'operator'] as const) {
307
- expect(neutralTheme.tokens[`--color-syntax-${token}`]).toBe(
308
- lightDark(
309
- neutralPalettes.neutral.light[45],
310
- neutralPalettes.neutral.dark[65],
311
- ),
312
- );
313
- expect(
314
- contrastRatio(neutralPalettes.neutral.light[45], background),
315
- ).toBeGreaterThanOrEqual(4.5);
316
- }
317
- });
318
- });
319
-
320
- describe('neutral semantic status roles', () => {
321
- it('keeps public plain-surface and on-muted roles on reviewed palette stops', () => {
322
- expect(neutralTheme.tokens).toMatchObject({
323
- '--color-text-info': lightDark(
324
- neutralPalettes.blue.light[45],
325
- neutralPalettes.blue.dark[80],
326
- ),
327
- '--color-text-success': lightDark(
328
- neutralPalettes.green.light[45],
329
- neutralPalettes.green.light[75],
330
- ),
331
- '--color-text-warning': lightDark(
332
- neutralPalettes.yellow.light[45],
333
- neutralPalettes.yellow.light[80],
334
- ),
335
- '--color-text-error': lightDark(
336
- neutralPalettes.red.light[45],
337
- neutralPalettes.red.dark[80],
338
- ),
339
- '--color-on-info-muted': lightDark(
340
- neutralPalettes.blue.light[25],
341
- neutralPalettes.blue.dark[80],
342
- ),
343
- '--color-on-success-muted': lightDark(
344
- neutralPalettes.green.light[25],
345
- neutralPalettes.green.light[75],
346
- ),
347
- '--color-on-warning-muted': lightDark(
348
- neutralPalettes.yellow.light[25],
349
- neutralPalettes.yellow.light[80],
350
- ),
351
- '--color-on-error-muted': lightDark(
352
- neutralPalettes.red.light[25],
353
- neutralPalettes.red.dark[80],
354
- ),
355
- });
356
- expect(neutralTheme.localTokens).not.toHaveProperty(
357
- '--astryx-theme-neutral-color-status-emphasized-info',
358
- );
359
- });
360
-
361
- it('keeps only the released accent local token, unused by Neutral itself', () => {
362
- expect(neutralTheme.localTokens).toEqual({
363
- '--astryx-theme-neutral-color-status-fill-accent': lightDark(
364
- '#0074e2',
365
- '#6d9cfe',
366
- ),
367
- });
368
- const css = JSON.stringify({
369
- tokens: neutralTheme.tokens,
370
- components: neutralTheme.components,
371
- });
372
- expect(css).not.toContain('--astryx-theme-neutral-');
373
- });
374
-
375
- it('uses public semantic roles on plain and pale surfaces while preserving fill roles', () => {
376
- expect(neutralTheme.tokens).toMatchObject({
377
- '--color-info': lightDark('#0074e2', '#6d9cfe'),
378
- // Strong status fills: Neutral's released Badge fill colours.
379
- '--color-success': lightDark('#198100', '#64af4c'),
380
- '--color-warning': '#ffce2f',
381
- '--color-error': lightDark('#c9303a', '#ff705d'),
382
- });
383
- expect(neutralTheme.components).not.toHaveProperty('icon');
384
- expect(neutralTheme.components).not.toHaveProperty('text-input');
385
- expect(neutralTheme.components).not.toHaveProperty('text-area-counter');
386
- expect(neutralTheme.components?.badge).not.toHaveProperty('variant:info');
387
- // Status and accent variants read the portable roles, not overrides.
388
- expect(neutralTheme.components).not.toHaveProperty('status-dot');
389
- expect(neutralTheme.components).not.toHaveProperty('step-indicator');
390
- expect(neutralTheme.components).not.toHaveProperty('avatar-status-dot');
391
- expect(neutralTheme.components?.['progress-bar']).not.toHaveProperty(
392
- 'variant:accent',
393
- );
394
- expect(
395
- neutralTheme.components?.button?.['variant:destructive'],
396
- ).toMatchObject({
397
- backgroundColor: 'var(--color-error-muted)',
398
- color: 'var(--color-on-error-muted)',
399
- });
400
- });
401
-
402
- it('keeps plain-surface status roles at their required contrast on the page background in light mode', () => {
403
- const surface = neutralPalettes.neutral.light[95];
404
- for (const foreground of [
405
- neutralPalettes.blue.light[45],
406
- neutralPalettes.green.light[45],
407
- neutralPalettes.yellow.light[45],
408
- neutralPalettes.red.light[45],
409
- ]) {
410
- expect(contrastRatio(foreground, surface)).toBeGreaterThanOrEqual(4.5);
411
- }
412
- });
413
-
414
- it('does not adopt the unapproved table row-status target', () => {
415
- expect(neutralTheme.components).not.toHaveProperty('table-row-status');
416
- });
417
- });
418
-
419
- describe('neutral Banner and destructive Button', () => {
420
- it('paint with the portable roles and no Neutral-only overrides', () => {
421
- expect(neutralTheme.components).not.toHaveProperty('banner');
422
- expect(neutralTheme.tokens['--color-background-blue']).toBe(
423
- lightDark(neutralPalettes.blue.light[85], neutralPalettes.blue.dark[25]),
424
- );
425
- expect(neutralTheme.components?.button?.['variant:destructive']).toEqual({
426
- backgroundColor: 'var(--color-error-muted)',
427
- color: 'var(--color-on-error-muted)',
428
- });
429
- });
430
- });
431
-
432
- describe('neutral theme component overrides', () => {
433
- it('keeps the roomier segmented-control inset height-neutral', () => {
434
- expect(neutralTheme.components?.['segmented-control']).toEqual({
435
- base: {padding: 'var(--spacing-1)'},
436
- });
437
- expect(neutralTheme.components?.['segmented-control-item']).toEqual({
438
- 'size:sm': {height: 'calc(var(--size-element-sm) - 8px)'},
439
- 'size:md': {height: 'calc(var(--size-element-md) - 8px)'},
440
- 'size:lg': {height: 'calc(var(--size-element-lg) - 8px)'},
441
- selected: {boxShadow: 'none'},
442
- });
443
- });
444
- });
445
-
446
- describe('neutral touch + narrow type pin', () => {
447
- it('floors body to 16px and holds Display 1 at its desktop size', () => {
448
- const rule = neutralTheme.__adaptationRules?.[0];
449
-
450
- expect(neutralTheme.__adaptations.rules).toHaveLength(1);
451
- expect(rule?.when).toEqual({pointer: 'coarse', width: {below: 'lg'}});
452
- expect(rule?.query).toBe('(width < 1024px) and (pointer: coarse)');
453
- // Pinned ladder: base 16 (was 14), ratio 1.2 -> 1.1736, Display 1 pinned.
454
- expect(rule?.tokens['--font-size-base']).toBe('1rem');
455
- expect(rule?.tokens['--font-size-lg']).toBe('1.1875rem');
456
- expect(rule?.tokens['--font-size-5xl']).toBe('2.625rem');
457
- // Desktop scale is untouched.
458
- expect(neutralTheme.tokens['--font-size-base']).toBe('0.875rem');
459
- expect(neutralTheme.tokens['--font-size-5xl']).toBe('2.625rem');
460
- expect(generateThemeCSS(neutralTheme).component).toContain(
461
- '@media (width < 1024px) and (pointer: coarse)',
462
- );
463
- });
464
- });