@astryxdesign/theme-neutral 0.7.6-canary.d29fe0f → 0.7.6-canary.d545bc5

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@astryxdesign/theme-neutral",
3
- "version": "0.7.6-canary.d29fe0f",
3
+ "version": "0.7.6-canary.d545bc5",
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,20 +52,34 @@
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.d29fe0f",
74
+ "@astryxdesign/core": "0.7.6-canary.d545bc5",
62
75
  "react": ">=19"
63
76
  },
64
77
  "devDependencies": {
65
- "@astryxdesign/cli": "0.7.6-canary.d29fe0f"
78
+ "@astryxdesign/cli": "0.7.6-canary.d545bc5"
66
79
  },
67
80
  "module": "./dist/source.mjs",
68
81
  "scripts": {
82
+ "api:report": "node ../../../scripts/api-report/cli.mjs --package .",
69
83
  "build": "node ../../../scripts/clean-dist.mjs && node ../../../scripts/generate-neutral-palette-refs.mjs --check && astryx theme build src/neutralTheme.ts -o dist/theme.css --icons-specifier ./icons.mjs && tsup && tsc --project tsconfig.build.json && node ../../../scripts/check-fully-specified.mjs"
70
84
  }
71
85
  }
@@ -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
- });