@teamblind-chorus/ui 1.1.0 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (148) hide show
  1. package/README.md +3 -3
  2. package/agents/AGENTS.md +6 -6
  3. package/agents/DESIGN.md +245 -244
  4. package/agents/LOVABLE.md +40 -11
  5. package/agents/catalog.md +10 -8
  6. package/agents/components/avatar-rail/avatar-rail.md +2 -4
  7. package/agents/components/avatar-rail/avatar-rail.spec.json +27 -12
  8. package/agents/components/badge/role.md +7 -9
  9. package/agents/components/badge/role.spec.json +6 -6
  10. package/agents/components/badge/update.md +6 -8
  11. package/agents/components/badge/update.spec.json +5 -5
  12. package/agents/components/banner/banner.family.json +3 -1
  13. package/agents/components/banner/banner.md +66 -15
  14. package/agents/components/banner/banner.spec.json +37 -14
  15. package/agents/components/bottom-sheet/bottom-sheet.md +4 -6
  16. package/agents/components/bottom-sheet/bottom-sheet.spec.json +5 -5
  17. package/agents/components/bubble/bubble.md +8 -10
  18. package/agents/components/bubble/bubble.spec.json +11 -11
  19. package/agents/components/button/button.md +1 -1
  20. package/agents/components/button/check.md +9 -11
  21. package/agents/components/button/check.spec.json +25 -8
  22. package/agents/components/button/fab.md +7 -9
  23. package/agents/components/button/fab.spec.json +27 -10
  24. package/agents/components/button/group.spec.json +4 -4
  25. package/agents/components/button/icon.md +21 -23
  26. package/agents/components/button/icon.spec.json +29 -12
  27. package/agents/components/button/standard.md +40 -42
  28. package/agents/components/button/standard.spec.json +37 -20
  29. package/agents/components/button/text.md +21 -23
  30. package/agents/components/button/text.spec.json +30 -13
  31. package/agents/components/button/toggle.md +7 -9
  32. package/agents/components/button/toggle.spec.json +27 -10
  33. package/agents/components/button/toolbar.md +24 -26
  34. package/agents/components/button/toolbar.spec.json +10 -12
  35. package/agents/components/carousel/carousel.md +1 -1
  36. package/agents/components/carousel/post.md +15 -21
  37. package/agents/components/carousel/post.spec.json +17 -17
  38. package/agents/components/carousel/profile.md +9 -45
  39. package/agents/components/carousel/profile.spec.json +17 -17
  40. package/agents/components/chip/chip.md +1 -1
  41. package/agents/components/chip/filter.md +22 -24
  42. package/agents/components/chip/filter.spec.json +34 -11
  43. package/agents/components/chip/tag.md +22 -24
  44. package/agents/components/chip/tag.spec.json +36 -13
  45. package/agents/components/dialog/dialog.md +1 -3
  46. package/agents/components/dialog/dialog.spec.json +3 -3
  47. package/agents/components/directory-list/directory-list.md +1 -3
  48. package/agents/components/directory-list/directory-list.spec.json +2 -2
  49. package/agents/components/divider/divider.family.json +1 -1
  50. package/agents/components/divider/divider.md +12 -14
  51. package/agents/components/divider/divider.spec.json +8 -8
  52. package/agents/components/empty-state/empty-state.family.json +28 -0
  53. package/agents/components/empty-state/empty-state.md +69 -0
  54. package/agents/components/empty-state/empty-state.spec.json +87 -0
  55. package/agents/components/feed/ad.md +2 -4
  56. package/agents/components/feed/ad.spec.json +10 -10
  57. package/agents/components/feed/post.md +41 -43
  58. package/agents/components/feed/post.spec.json +35 -39
  59. package/agents/components/form-field/form-field.md +1 -1
  60. package/agents/components/form-field/input.md +32 -34
  61. package/agents/components/form-field/input.spec.json +39 -31
  62. package/agents/components/form-field/search.md +2 -4
  63. package/agents/components/form-field/search.spec.json +24 -16
  64. package/agents/components/form-field/select.md +18 -20
  65. package/agents/components/form-field/select.spec.json +36 -27
  66. package/agents/components/form-field/textarea.md +3 -5
  67. package/agents/components/form-field/textarea.spec.json +37 -29
  68. package/agents/components/header/main.md +4 -6
  69. package/agents/components/header/main.spec.json +3 -3
  70. package/agents/components/header/sub.md +6 -8
  71. package/agents/components/header/sub.spec.json +3 -3
  72. package/agents/components/list/accordion.md +34 -45
  73. package/agents/components/list/accordion.spec.json +26 -17
  74. package/agents/components/list/entry.md +59 -81
  75. package/agents/components/list/entry.spec.json +37 -21
  76. package/agents/components/list/list.md +2 -2
  77. package/agents/components/list/radio.md +13 -20
  78. package/agents/components/list/radio.spec.json +33 -18
  79. package/agents/components/list/standard.md +88 -64
  80. package/agents/components/list/standard.spec.json +52 -20
  81. package/agents/components/metadata/compact.md +4 -6
  82. package/agents/components/metadata/compact.spec.json +6 -6
  83. package/agents/components/metadata/metadata.md +1 -1
  84. package/agents/components/metadata/standard.md +12 -14
  85. package/agents/components/metadata/standard.spec.json +10 -10
  86. package/agents/components/nav-card/nav-card.md +25 -27
  87. package/agents/components/nav-card/nav-card.spec.json +25 -16
  88. package/agents/components/nav-list/nav-list.md +2 -8
  89. package/agents/components/nav-list/nav-list.spec.json +3 -3
  90. package/agents/components/navigation-bar/main.md +9 -11
  91. package/agents/components/navigation-bar/main.spec.json +6 -6
  92. package/agents/components/navigation-bar/search.md +6 -8
  93. package/agents/components/navigation-bar/search.spec.json +9 -9
  94. package/agents/components/navigation-bar/sub.md +9 -11
  95. package/agents/components/navigation-bar/sub.spec.json +7 -7
  96. package/agents/components/page-shell/page-shell.family.json +1 -1
  97. package/agents/components/page-shell/page-shell.md +33 -0
  98. package/agents/components/page-shell/page-shell.spec.json +85 -0
  99. package/agents/components/pagination/pagination.family.json +1 -1
  100. package/agents/components/pagination/pagination.md +3 -3
  101. package/agents/components/pagination/pagination.spec.json +5 -5
  102. package/agents/components/profile-header/profile-header.md +9 -11
  103. package/agents/components/profile-header/profile-header.spec.json +9 -9
  104. package/agents/components/progress/progress.family.json +1 -1
  105. package/agents/components/progress/progress.md +5 -5
  106. package/agents/components/progress/progress.spec.json +8 -8
  107. package/agents/components/side-sheet/side-sheet.md +11 -13
  108. package/agents/components/side-sheet/side-sheet.spec.json +3 -3
  109. package/agents/components/skeleton/skeleton.md +7 -9
  110. package/agents/components/skeleton/skeleton.spec.json +5 -5
  111. package/agents/components/spinner/spinner.family.json +27 -0
  112. package/agents/components/spinner/spinner.md +96 -0
  113. package/agents/components/spinner/spinner.spec.json +82 -0
  114. package/agents/components/status-tag/status-tag.md +7 -9
  115. package/agents/components/status-tag/status-tag.spec.json +5 -5
  116. package/agents/components/suggestion-list/suggestion-list.md +3 -7
  117. package/agents/components/suggestion-list/suggestion-list.spec.json +8 -12
  118. package/agents/components/switch/switch.md +12 -14
  119. package/agents/components/switch/switch.spec.json +23 -15
  120. package/agents/components/tab-bar/tab-bar.md +9 -11
  121. package/agents/components/tab-bar/tab-bar.spec.json +37 -23
  122. package/agents/components/tabs/rounded.md +6 -8
  123. package/agents/components/tabs/rounded.spec.json +34 -13
  124. package/agents/components/tabs/segmented.md +4 -6
  125. package/agents/components/tabs/segmented.spec.json +4 -8
  126. package/agents/components/tabs/underline.md +9 -11
  127. package/agents/components/tabs/underline.spec.json +31 -14
  128. package/agents/components/thumbnail/thumbnail.md +5 -7
  129. package/agents/components/thumbnail/thumbnail.spec.json +8 -8
  130. package/agents/components/toast/toast.md +5 -7
  131. package/agents/components/toast/toast.spec.json +3 -3
  132. package/agents/components/tooltip/tooltip.md +6 -8
  133. package/agents/components/tooltip/tooltip.spec.json +4 -4
  134. package/agents/manifest.json +8 -6
  135. package/agents/tokens.usage.json +71 -226
  136. package/agents/usage.json +12 -0
  137. package/dist/index.cjs +531 -262
  138. package/dist/index.cjs.map +1 -1
  139. package/dist/index.d.cts +57 -13
  140. package/dist/index.d.ts +57 -13
  141. package/dist/index.js +530 -263
  142. package/dist/index.js.map +1 -1
  143. package/dist/styles.css +560 -379
  144. package/eslint/rules.js +7 -7
  145. package/package.json +2 -3
  146. package/agents/anti-patterns.md +0 -533
  147. package/agents/compose.md +0 -240
  148. package/agents/images.md +0 -66
package/dist/index.cjs CHANGED
@@ -7,11 +7,11 @@ var reactDom = require('react-dom');
7
7
  // ../../schema/components/badge/update.spec.json
8
8
  var update_spec_default = {
9
9
  appearance: {
10
- background: "sys.color.brand",
11
- label: "sys.color.onBrand",
10
+ background: "sys.color.text.brand",
11
+ label: "sys.color.text.onFill",
12
12
  radius: "sys.radius.full",
13
13
  dotOutline: {
14
- color: "sys.color.surface",
14
+ color: "sys.color.surface.default",
15
15
  width: "sys.borderWidth.thin",
16
16
  rendering: "box-shadow",
17
17
  note: "Update Dot rungs paint a 2px `surface`-color outline as a `box-shadow` so the dot stays a discrete chip on any host (image, icon, row) without enlarging its bounding box. The outline carves the dot out of whatever sits beside it; without it the brand fill blends into surrounding fills with similar luminance."
@@ -30,7 +30,7 @@ var update_spec_default = {
30
30
  minWidth: "ref.space.200",
31
31
  paddingBlock: "0",
32
32
  paddingInline: "sys.layout.container.2xs",
33
- labelTypo: "sys.typo.caption"
33
+ labelTypo: "sys.typo.label.xs"
34
34
  },
35
35
  "dot-md": {
36
36
  minHeight: "ref.space.100",
@@ -54,15 +54,15 @@ var update_spec_default = {
54
54
  var role_spec_default = {
55
55
  appearances: {
56
56
  default: {
57
- background: "sys.color.primaryContainer",
58
- label: "sys.color.onPrimaryContainer",
57
+ background: "sys.color.background.selected",
58
+ label: "sys.color.text.link",
59
59
  radius: "sys.radius.full",
60
60
  default: true,
61
61
  note: "Tonal informational pair \u2014 pale primary container fill with the deep primary-container label. Theme-aware sys tokens, so no separate dark binding. Never the brand pair: brand is reserved for the Update sub's activity marker, and a brand-filled role mark would read as an alert, not an identity."
62
62
  },
63
63
  inverse: {
64
- background: "sys.color.inverseSurface",
65
- label: "sys.color.inverseOnSurface",
64
+ background: "sys.color.background.inverse",
65
+ label: "sys.color.text.inverse",
66
66
  radius: "sys.radius.full",
67
67
  note: "Inverse pair \u2014 the strongest mark in the row: near-black pill with light label (theme-aware; flips in dark mode). Reserved for the PRO mark on paid professional users. Same geometry as default; only the fill pair changes."
68
68
  }
@@ -73,7 +73,7 @@ var role_spec_default = {
73
73
  minWidth: "ref.space.200",
74
74
  paddingBlock: "ref.space.25",
75
75
  paddingInline: "ref.space.75",
76
- labelTypo: "sys.typo.caption",
76
+ labelTypo: "sys.typo.label.xs",
77
77
  labelLineHeight: "1.2",
78
78
  note: "Single 16-rung (`ref.space.200`) \u2014 sized to ride inline beside `caption`/`label` metadata text without stretching the row. 2 \xD7 6 padding (`ref.space.25` \xD7 `ref.space.75`); the reference steps bind raw where `sys.*` exposes no step, in lockstep with the Update sub's rungs. Label line-height is the structural `1.2` (same device as StatusTag): 10px \xD7 1.2 + 2 \xD7 2px padding = exactly 16px \u2014 `caption`'s 15px running-text line would push the pill to 19."
79
79
  }
@@ -108,13 +108,13 @@ function formatCount(value) {
108
108
  return String(Math.floor(value));
109
109
  }
110
110
  function sizingStyle(spec, size) {
111
- const s = spec.sizes[size] ?? spec.sizes.medium;
111
+ const s2 = spec.sizes[size] ?? spec.sizes.medium;
112
112
  return {
113
- "--badge-min-height": tokenToCss(s.minHeight),
114
- "--badge-min-width": tokenToCss(s.minWidth),
115
- "--badge-padding-block": tokenToCss(s.paddingBlock),
116
- "--badge-padding-inline": tokenToCss(s.paddingInline),
117
- ...s.labelTypo ? typoStyles(s.labelTypo) : null
113
+ "--badge-min-height": tokenToCss(s2.minHeight),
114
+ "--badge-min-width": tokenToCss(s2.minWidth),
115
+ "--badge-padding-block": tokenToCss(s2.paddingBlock),
116
+ "--badge-padding-inline": tokenToCss(s2.paddingInline),
117
+ ...s2.labelTypo ? typoStyles(s2.labelTypo) : null
118
118
  };
119
119
  }
120
120
  function appearanceStyle(spec) {
@@ -238,10 +238,12 @@ function useFullBleedGuard(ref, name) {
238
238
  function Banner({
239
239
  appearance = "default",
240
240
  outlined = false,
241
+ neutralBody = false,
241
242
  title,
242
243
  icon,
243
244
  thumbnail,
244
245
  action,
246
+ trailingAction,
245
247
  trailingIcon,
246
248
  children,
247
249
  className,
@@ -257,6 +259,7 @@ function Banner({
257
259
  "chorus-banner",
258
260
  `chorus-banner--${appearance}`,
259
261
  outlined && "chorus-banner--outlined",
262
+ neutralBody && "chorus-banner--neutral-body",
260
263
  className
261
264
  ),
262
265
  role: "note",
@@ -277,7 +280,7 @@ function Banner({
277
280
  }
278
281
  ) : null
279
282
  ] }),
280
- trailingIcon ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "chorus-banner__trailing-icon", "aria-hidden": "true", children: trailingIcon }) : null
283
+ trailingAction ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "chorus-banner__trailing-action", children: trailingAction }) : trailingIcon ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "chorus-banner__trailing-icon", "aria-hidden": "true", children: trailingIcon }) : null
281
284
  ]
282
285
  }
283
286
  );
@@ -326,27 +329,27 @@ var standard_spec_default = {
326
329
  },
327
330
  appearances: {
328
331
  primary: {
329
- background: "sys.color.primary",
332
+ background: "sys.color.background.primary",
330
333
  border: null,
331
- label: "sys.color.onPrimary"
334
+ label: "sys.color.text.onFill"
332
335
  },
333
336
  secondary: {
334
- background: "sys.color.secondaryContainer",
337
+ background: "sys.color.background.neutral",
335
338
  border: null,
336
- label: "sys.color.onSecondaryContainer"
339
+ label: "sys.color.text.default"
337
340
  },
338
341
  outlined: {
339
342
  background: "transparent",
340
343
  border: {
341
344
  width: "sys.borderWidth.hairline",
342
- color: "sys.color.primary"
345
+ color: "sys.color.text.link"
343
346
  },
344
- label: "sys.color.primary"
347
+ label: "sys.color.text.link"
345
348
  },
346
349
  tertiary: {
347
350
  background: "transparent",
348
351
  border: null,
349
- label: "sys.color.onSurfaceVariant"
352
+ label: "sys.color.text.subtle"
350
353
  }
351
354
  },
352
355
  states: {
@@ -368,22 +371,19 @@ var standard_spec_default = {
368
371
  opacity: "sys.state.focus"
369
372
  },
370
373
  ring: {
371
- outerWidth: "sys.borderWidth.thin",
372
- outerColor: "sys.color.focus",
373
- insetWidth: "sys.borderWidth.hairline",
374
- insetColor: "sys.color.focusInset"
374
+ color: "sys.color.border.focused"
375
375
  }}};
376
376
  function sizeStyle(size) {
377
- const s = standard_spec_default.sizes[size] ?? standard_spec_default.sizes[standard_spec_default.props.size.default];
377
+ const s2 = standard_spec_default.sizes[size] ?? standard_spec_default.sizes[standard_spec_default.props.size.default];
378
378
  return {
379
- "--button-standard-padding-block": tokenToCss(s.paddingBlock),
380
- "--button-standard-padding-inline": tokenToCss(s.paddingInline),
381
- "--button-standard-gap": tokenToCss(s.gap),
382
- "--button-standard-min-height": tokenToCss(s.minHeight),
383
- "--button-standard-min-width": tokenToCss(s.minWidth),
384
- "--button-standard-radius": tokenToCss(s.radius),
385
- "--button-standard-icon-size": tokenToCss(s.iconSize),
386
- ...typoStyles(s.labelTypo)
379
+ "--button-standard-padding-block": tokenToCss(s2.paddingBlock),
380
+ "--button-standard-padding-inline": tokenToCss(s2.paddingInline),
381
+ "--button-standard-gap": tokenToCss(s2.gap),
382
+ "--button-standard-min-height": tokenToCss(s2.minHeight),
383
+ "--button-standard-min-width": tokenToCss(s2.minWidth),
384
+ "--button-standard-radius": tokenToCss(s2.radius),
385
+ "--button-standard-icon-size": tokenToCss(s2.iconSize),
386
+ ...typoStyles(s2.labelTypo)
387
387
  };
388
388
  }
389
389
  function appearanceStyle2(appearance) {
@@ -401,10 +401,7 @@ function stateStyle() {
401
401
  "--button-standard-overlay-pressed": tokenToCss(standard_spec_default.states.pressed.overlay.opacity),
402
402
  "--button-standard-overlay-focus": tokenToCss(standard_spec_default.focusIndicator.overlay.opacity),
403
403
  "--button-standard-disabled-opacity": tokenToCss(standard_spec_default.states.disabled.containerOpacity),
404
- "--button-standard-focus-outer-width": tokenToCss(standard_spec_default.focusIndicator.ring.outerWidth),
405
- "--button-standard-focus-outer-color": tokenToCss(standard_spec_default.focusIndicator.ring.outerColor),
406
- "--button-standard-focus-inset-width": tokenToCss(standard_spec_default.focusIndicator.ring.insetWidth),
407
- "--button-standard-focus-inset-color": tokenToCss(standard_spec_default.focusIndicator.ring.insetColor)
404
+ "--button-standard-focus-outer-color": tokenToCss(standard_spec_default.focusIndicator.ring.color)
408
405
  };
409
406
  }
410
407
  var FORCEABLE_STATES = /* @__PURE__ */ new Set(["hovered", "pressed", "focused"]);
@@ -472,12 +469,12 @@ var fab_spec_default = {
472
469
  },
473
470
  appearances: {
474
471
  primary: {
475
- background: "sys.color.brand",
476
- label: "sys.color.onBrand"
472
+ background: "sys.color.text.brand",
473
+ label: "sys.color.text.onFill"
477
474
  },
478
475
  secondary: {
479
- background: "sys.color.surfaceContainerHigh",
480
- label: "sys.color.onSurface"
476
+ background: "sys.color.surface.default",
477
+ label: "sys.color.text.default"
481
478
  }
482
479
  },
483
480
  states: {
@@ -490,17 +487,13 @@ var fab_spec_default = {
490
487
  overlay: {
491
488
  opacity: "sys.state.pressed"
492
489
  }
493
- }
494
- },
490
+ }},
495
491
  focusIndicator: {
496
492
  overlay: {
497
493
  opacity: "sys.state.focus"
498
494
  },
499
495
  ring: {
500
- outerWidth: "sys.borderWidth.thin",
501
- outerColor: "sys.color.focus",
502
- insetWidth: "sys.borderWidth.hairline",
503
- insetColor: "sys.color.focusInset"
496
+ color: "sys.color.border.focused"
504
497
  }}};
505
498
  var FORCEABLE_STATES2 = /* @__PURE__ */ new Set(["hovered", "pressed", "focused"]);
506
499
  function appearanceStyle3(appearance) {
@@ -521,10 +514,7 @@ var sizingStyle2 = () => ({
521
514
  "--button-fab-overlay-hover": tokenToCss(fab_spec_default.states.hovered.overlay.opacity),
522
515
  "--button-fab-overlay-pressed": tokenToCss(fab_spec_default.states.pressed.overlay.opacity),
523
516
  "--button-fab-overlay-focus": tokenToCss(fab_spec_default.focusIndicator.overlay.opacity),
524
- "--button-fab-focus-outer-width": tokenToCss(fab_spec_default.focusIndicator.ring.outerWidth),
525
- "--button-fab-focus-outer-color": tokenToCss(fab_spec_default.focusIndicator.ring.outerColor),
526
- "--button-fab-focus-inset-width": tokenToCss(fab_spec_default.focusIndicator.ring.insetWidth),
527
- "--button-fab-focus-inset-color": tokenToCss(fab_spec_default.focusIndicator.ring.insetColor),
517
+ "--button-fab-focus-outer-color": tokenToCss(fab_spec_default.focusIndicator.ring.color),
528
518
  ...typoStyles(fab_spec_default.sizing.labelTypo)
529
519
  });
530
520
  function ButtonFab({
@@ -1562,16 +1552,16 @@ var filter_spec_default = {
1562
1552
  selectionStates: {
1563
1553
  unselected: {
1564
1554
  background: "transparent",
1565
- label: "sys.color.onSurface",
1555
+ label: "sys.color.text.default",
1566
1556
  border: {
1567
1557
  width: "sys.borderWidth.hairline",
1568
- color: "sys.color.outlineVariant"
1558
+ color: "sys.color.border.default"
1569
1559
  },
1570
1560
  note: "Transparent fill so the chip adopts whatever surface sits behind it (page, raised card, sheet) without pinning to a fixed neutral step."
1571
1561
  },
1572
1562
  selected: {
1573
- background: "sys.color.inverseSurface",
1574
- label: "sys.color.inverseOnSurface",
1563
+ background: "sys.color.background.inverse",
1564
+ label: "sys.color.text.inverse",
1575
1565
  border: null
1576
1566
  }
1577
1567
  },
@@ -1591,26 +1581,49 @@ var filter_spec_default = {
1591
1581
  opacity: "sys.state.pressed"
1592
1582
  }
1593
1583
  },
1584
+ focused: {
1585
+ overlay: {
1586
+ color: "label",
1587
+ opacity: "sys.state.focus"
1588
+ },
1589
+ focusRing: {
1590
+ composition: "outward",
1591
+ layer: "::after overlay \u2014 position:absolute, inset:0, no reflow (DESIGN.md Focus ring composition)",
1592
+ innerCounterRing: {
1593
+ width: "sys.borderWidth.hairline",
1594
+ color: "sys.color.border.focused"
1595
+ },
1596
+ outerRing: {
1597
+ width: "sys.borderWidth.thin",
1598
+ color: "sys.color.border.focused"
1599
+ }
1600
+ },
1601
+ note: "Keyboard-focus (:focus-visible) visual. Mirrors the `focusIndicator` block (the external-reader contract); kept here so spec-only renderers see focus in the states map. Composes over the lifecycle state the chip is in; never via plain mouse click."
1602
+ },
1594
1603
  disabled: {
1595
1604
  overlay: null,
1596
- containerOpacity: "sys.state.disabled",
1605
+ background: "sys.color.background.disabled",
1606
+ label: "sys.color.text.disabled",
1607
+ border: {
1608
+ width: "sys.borderWidth.hairline",
1609
+ color: "sys.color.border.bold"
1610
+ },
1597
1611
  suppressFocusRing: true,
1598
- cursor: "not-allowed"
1612
+ cursor: "not-allowed",
1613
+ note: "Explicit disabled (no opacity): neutral disabled fill + bold border so the shape reads on any surface, plus disabled label. Overrides the rest/selected appearance."
1599
1614
  }
1600
1615
  },
1601
1616
  focusIndicator: {
1602
1617
  description: "Keyboard-focus visual \u2014 an accessibility indicator, not a lifecycle state. Composes over whichever lifecycle state the chip is in. The `states.focused` block above is kept for JSX runtime consumers; this block is the parallel external-reader contract.",
1603
1618
  composition: "outward",
1604
- compositionReason: "Action affordance with breathing room around it; the 3px outward extent is reserved by the surrounding layout.",
1619
+ compositionReason: "Action affordance with breathing room around it; the 1px outward ring is reserved by the surrounding layout.",
1605
1620
  overlay: {
1606
1621
  color: "label",
1607
1622
  opacity: "sys.state.focus"
1608
1623
  },
1609
1624
  ring: {
1610
- outerWidth: "sys.borderWidth.thin",
1611
- outerColor: "sys.color.focus",
1612
- insetWidth: "sys.borderWidth.hairline",
1613
- insetColor: "sys.color.focusInset"
1625
+ width: "sys.borderWidth.hairline",
1626
+ color: "sys.color.border.focused"
1614
1627
  },
1615
1628
  trigger: ":focus-visible (keyboard / programmatic focus, never plain mouse click)"
1616
1629
  },
@@ -1628,7 +1641,7 @@ var tag_spec_default = {
1628
1641
  name: "Chip",
1629
1642
  family: "chip",
1630
1643
  subcomponent: "tag",
1631
- description: "Informational chip. Square-cornered metadata label. Passive (or dismissable via trailing \xD7). No leading icon. Two appearances: `default` paints the translucent `sys.color.scrimSubtle` scrim (~8% inverse-tone overlay \u2014 black in light, white in dark) so the tag adopts whatever surface sits behind it; `accent` paints a tonal pale-primary container (`sys.color.primaryContainer`) with primary label for tags that need to pop against the surface.",
1644
+ description: "Informational chip. Square-cornered metadata label. Passive (or dismissable via trailing \xD7). No leading icon. Two appearances: `default` paints the translucent `sys.color.background.neutral` scrim (~8% inverse-tone overlay \u2014 black in light, white in dark) so the tag adopts whatever surface sits behind it; `accent` paints a tonal pale-primary container (`sys.color.background.selected`) with primary label for tags that need to pop against the surface.",
1632
1645
  element: "span",
1633
1646
  props: {
1634
1647
  variant: {
@@ -1669,15 +1682,15 @@ var tag_spec_default = {
1669
1682
  },
1670
1683
  appearances: {
1671
1684
  default: {
1672
- background: "sys.color.scrimSubtle",
1673
- label: "sys.color.onSurface",
1685
+ background: "sys.color.background.neutral",
1686
+ label: "sys.color.text.default",
1674
1687
  border: null,
1675
1688
  default: true,
1676
- note: "Background is the translucent inverse-tone scrim (`sys.color.scrimSubtle` \u2014 black 8% in light mode, white 8% in dark) so the tag harmonises with whatever surface sits behind it \u2014 body, raised card, BottomSheet \u2014 instead of pinning to a fixed neutral step. Same Banner-style fill used by Progress track, StatusTag neutral, and Skeleton; sys-color is theme-aware so a single token resolves correctly in both modes."
1689
+ note: "Background is the translucent inverse-tone scrim (`sys.color.background.neutral` \u2014 black 8% in light mode, white 8% in dark) so the tag harmonises with whatever surface sits behind it \u2014 body, raised card, BottomSheet \u2014 instead of pinning to a fixed neutral step. Same Banner-style fill used by Progress track, StatusTag neutral, and Skeleton; sys-color is theme-aware so a single token resolves correctly in both modes."
1677
1690
  },
1678
1691
  accent: {
1679
- background: "sys.color.primaryContainer",
1680
- label: "sys.color.primary",
1692
+ background: "sys.color.background.selected",
1693
+ label: "sys.color.text.link",
1681
1694
  border: null,
1682
1695
  note: "Tonal accent: pale primary container background with primary label. Sys-color tokens are theme-aware, so no separate dark binding is needed \u2014 the resolved tokens pick the right values per theme. Use for tags that should pop against the surface (e.g. Popular Tags in compose, highlighted hashtags) where the default overlay is too quiet."
1683
1696
  }
@@ -1698,26 +1711,49 @@ var tag_spec_default = {
1698
1711
  opacity: "sys.state.pressed"
1699
1712
  }
1700
1713
  },
1714
+ focused: {
1715
+ overlay: {
1716
+ color: "label",
1717
+ opacity: "sys.state.focus"
1718
+ },
1719
+ focusRing: {
1720
+ composition: "outward",
1721
+ layer: "::after overlay \u2014 position:absolute, inset:0, no reflow (DESIGN.md Focus ring composition)",
1722
+ innerCounterRing: {
1723
+ width: "sys.borderWidth.hairline",
1724
+ color: "sys.color.border.focused"
1725
+ },
1726
+ outerRing: {
1727
+ width: "sys.borderWidth.thin",
1728
+ color: "sys.color.border.focused"
1729
+ }
1730
+ },
1731
+ note: "Keyboard-focus (:focus-visible) visual. Mirrors the `focusIndicator` block (the external-reader contract); kept here so spec-only renderers see focus in the states map. Composes over the lifecycle state the chip is in; never via plain mouse click."
1732
+ },
1701
1733
  disabled: {
1702
1734
  overlay: null,
1703
- containerOpacity: "sys.state.disabled",
1735
+ background: "sys.color.background.disabled",
1736
+ label: "sys.color.text.disabled",
1737
+ border: {
1738
+ width: "sys.borderWidth.hairline",
1739
+ color: "sys.color.border.bold"
1740
+ },
1704
1741
  suppressFocusRing: true,
1705
- cursor: "not-allowed"
1742
+ cursor: "not-allowed",
1743
+ note: "Explicit disabled (no opacity): neutral disabled fill + bold border so the shape reads on any surface, plus disabled label. Overrides the rest/selected appearance."
1706
1744
  }
1707
1745
  },
1708
1746
  focusIndicator: {
1709
1747
  description: "Keyboard-focus visual \u2014 an accessibility indicator, not a lifecycle state. Composes over whichever lifecycle state the chip is in. Applies only when the chip is interactive (a dismiss trailingIcon is present). The `states.focused` block above is kept for JSX runtime consumers; this block is the parallel external-reader contract.",
1710
1748
  composition: "outward",
1711
- compositionReason: "Action affordance with breathing room around it; the 3px outward extent is reserved by the surrounding layout.",
1749
+ compositionReason: "Action affordance with breathing room around it; the 1px outward ring is reserved by the surrounding layout.",
1712
1750
  overlay: {
1713
1751
  color: "label",
1714
1752
  opacity: "sys.state.focus"
1715
1753
  },
1716
1754
  ring: {
1717
- outerWidth: "sys.borderWidth.thin",
1718
- outerColor: "sys.color.focus",
1719
- insetWidth: "sys.borderWidth.hairline",
1720
- insetColor: "sys.color.focusInset"
1755
+ width: "sys.borderWidth.hairline",
1756
+ color: "sys.color.border.focused"
1721
1757
  },
1722
1758
  trigger: ":focus-visible (keyboard / programmatic focus, never plain mouse click)"
1723
1759
  },
@@ -1794,16 +1830,16 @@ var toggle_spec_default = {
1794
1830
  },
1795
1831
  selectionStates: {
1796
1832
  unselected: {
1797
- background: "sys.color.primary",
1798
- label: "sys.color.onPrimary",
1833
+ background: "sys.color.background.primary",
1834
+ label: "sys.color.text.onFill",
1799
1835
  border: null
1800
1836
  },
1801
1837
  selected: {
1802
1838
  background: "transparent",
1803
- label: "sys.color.onSurface",
1839
+ label: "sys.color.text.default",
1804
1840
  border: {
1805
1841
  width: "sys.borderWidth.hairline",
1806
- color: "sys.color.outlineVariant"
1842
+ color: "sys.color.border.default"
1807
1843
  }
1808
1844
  }
1809
1845
  },
@@ -1823,6 +1859,25 @@ var toggle_spec_default = {
1823
1859
  opacity: "sys.state.pressed"
1824
1860
  }
1825
1861
  },
1862
+ focused: {
1863
+ overlay: {
1864
+ color: "label",
1865
+ opacity: "sys.state.focus"
1866
+ },
1867
+ focusRing: {
1868
+ composition: "outward",
1869
+ layer: "::after overlay \u2014 position:absolute, inset:0, no reflow (DESIGN.md Focus ring composition)",
1870
+ innerCounterRing: {
1871
+ width: "sys.borderWidth.hairline",
1872
+ color: "sys.color.border.focused"
1873
+ },
1874
+ outerRing: {
1875
+ width: "sys.borderWidth.thin",
1876
+ color: "sys.color.border.focused"
1877
+ }
1878
+ },
1879
+ note: "Keyboard-focus (:focus-visible) visual. Mirrors the `focusIndicator` block (the external-reader contract); kept here so spec-only renderers see focus in the states map. Composes over the lifecycle state the button is in; never via plain mouse click."
1880
+ },
1826
1881
  disabled: {
1827
1882
  overlay: null,
1828
1883
  containerOpacity: "sys.state.disabled",
@@ -1833,21 +1888,19 @@ var toggle_spec_default = {
1833
1888
  focusIndicator: {
1834
1889
  description: "Keyboard-focus visual \u2014 an accessibility indicator, not a lifecycle state. Composes over whichever lifecycle state the button is in. The `states.focused` block above is kept for JSX runtime consumers; this block is the parallel external-reader contract.",
1835
1890
  composition: "outward",
1836
- compositionReason: "Action affordance with breathing room around it; the 3px outward extent is reserved by the surrounding layout.",
1891
+ compositionReason: "Action affordance with breathing room around it; the 1px outward ring is reserved by the surrounding layout.",
1837
1892
  overlay: {
1838
1893
  color: "label",
1839
1894
  opacity: "sys.state.focus"
1840
1895
  },
1841
1896
  ring: {
1842
- outerWidth: "sys.borderWidth.thin",
1843
- outerColor: "sys.color.focus",
1844
- insetWidth: "sys.borderWidth.hairline",
1845
- insetColor: "sys.color.focusInset"
1897
+ width: "sys.borderWidth.hairline",
1898
+ color: "sys.color.border.focused"
1846
1899
  },
1847
1900
  trigger: ":focus-visible (keyboard / programmatic focus, never plain mouse click)"
1848
1901
  },
1849
1902
  forbidden: [
1850
- "active state painted with sys.color.primary fill \u2014 active is transparent + hairline outline (the active state recedes, not asserts)",
1903
+ "active state painted with sys.color.background.primary fill \u2014 active is transparent + hairline outline (the active state recedes, not asserts)",
1851
1904
  "active state painted with any opaque surface fill (surface, surfaceContainer, surfaceContainerHigh) \u2014 the committed form is transparent so the host surface shows through; do not re-bind to a tier'd fill",
1852
1905
  "rest state without an explicit `active={false}` \u2014 toggle is a binary contract, never tristate",
1853
1906
  "manual width override that breaks the full-card stretch when used inside ProfileCarousel.followAction"
@@ -1894,10 +1947,7 @@ function sizingStyle3(spec) {
1894
1947
  "--chip-overlay-pressed": tokenToCss(spec.states.pressed.overlay.opacity),
1895
1948
  "--chip-overlay-focus": tokenToCss(spec.focusIndicator.overlay.opacity),
1896
1949
  "--chip-disabled-opacity": tokenToCss(spec.states.disabled.containerOpacity),
1897
- "--chip-focus-outer-width": tokenToCss(spec.focusIndicator.ring.outerWidth),
1898
- "--chip-focus-outer-color": tokenToCss(spec.focusIndicator.ring.outerColor),
1899
- "--chip-focus-inset-width": tokenToCss(spec.focusIndicator.ring.insetWidth),
1900
- "--chip-focus-inset-color": tokenToCss(spec.focusIndicator.ring.insetColor),
1950
+ "--chip-focus-outer-color": tokenToCss(spec.focusIndicator.ring.color),
1901
1951
  ...typoStyles(spec.sizing.labelTypo)
1902
1952
  };
1903
1953
  }
@@ -1973,14 +2023,14 @@ function ButtonToolbar({ appearance = "default", className, style, ...rest }) {
1973
2023
  let pairStyle = null;
1974
2024
  if (appearance === "accent") {
1975
2025
  pairStyle = {
1976
- "--chip-bg": "var(--sys-color-primary)",
1977
- "--chip-label": "var(--sys-color-onPrimary)",
2026
+ "--chip-bg": "var(--sys-color-background-primary)",
2027
+ "--chip-label": "var(--sys-color-text-onFill)",
1978
2028
  "--chip-border-color": "transparent"
1979
2029
  };
1980
2030
  } else if (appearance === "inverse") {
1981
2031
  pairStyle = {
1982
- "--chip-bg": "var(--sys-color-inverseSurface)",
1983
- "--chip-label": "var(--sys-color-inverseOnSurface)",
2032
+ "--chip-bg": "var(--sys-color-background-inverse)",
2033
+ "--chip-label": "var(--sys-color-text-inverse)",
1984
2034
  "--chip-border-color": "transparent"
1985
2035
  };
1986
2036
  }
@@ -2009,6 +2059,46 @@ var Button = react.forwardRef(function Button2({ variant, ...rest }, ref) {
2009
2059
  const Impl = variant && VARIANTS[variant] || ButtonStandard;
2010
2060
  return /* @__PURE__ */ jsxRuntime.jsx(Impl, { ref, ...rest });
2011
2061
  });
2062
+ var FOCUSABLE_SELECTOR = [
2063
+ "a[href]",
2064
+ "button",
2065
+ "input",
2066
+ "select",
2067
+ "textarea",
2068
+ '[tabindex]:not([tabindex="-1"])'
2069
+ ].join(",");
2070
+ function useFocusTrap(ref, active) {
2071
+ react.useEffect(() => {
2072
+ if (!active) return void 0;
2073
+ const onKey = (e) => {
2074
+ if (e.key !== "Tab") return;
2075
+ const container = ref.current;
2076
+ if (!container) return;
2077
+ const focusable = Array.from(
2078
+ container.querySelectorAll(FOCUSABLE_SELECTOR)
2079
+ ).filter((el) => !el.disabled && el.offsetParent !== null);
2080
+ if (focusable.length === 0) {
2081
+ e.preventDefault();
2082
+ container.focus({ preventScroll: true });
2083
+ return;
2084
+ }
2085
+ const first = focusable[0];
2086
+ const last = focusable[focusable.length - 1];
2087
+ const activeEl = document.activeElement;
2088
+ if (e.shiftKey) {
2089
+ if (activeEl === first || !container.contains(activeEl)) {
2090
+ e.preventDefault();
2091
+ last.focus({ preventScroll: true });
2092
+ }
2093
+ } else if (activeEl === last || !container.contains(activeEl)) {
2094
+ e.preventDefault();
2095
+ first.focus({ preventScroll: true });
2096
+ }
2097
+ };
2098
+ document.addEventListener("keydown", onKey);
2099
+ return () => document.removeEventListener("keydown", onKey);
2100
+ }, [ref, active]);
2101
+ }
2012
2102
  function useBodyScrollLock(locked) {
2013
2103
  react.useEffect(() => {
2014
2104
  if (!locked) return void 0;
@@ -2048,6 +2138,7 @@ function BottomSheet({
2048
2138
  const lastFocusedRef = react.useRef(null);
2049
2139
  const [overflowing, setOverflowing] = react.useState(false);
2050
2140
  useBodyScrollLock(open && !inline);
2141
+ useFocusTrap(cardRef, open && !inline);
2051
2142
  react.useEffect(() => {
2052
2143
  if (!open || inline) return void 0;
2053
2144
  const vv = typeof window !== "undefined" ? window.visualViewport : null;
@@ -2111,6 +2202,7 @@ function BottomSheet({
2111
2202
  className: "chorus-bottom-sheet__card",
2112
2203
  role: "dialog",
2113
2204
  "aria-modal": "true",
2205
+ tabIndex: -1,
2114
2206
  "aria-label": ariaLabel ?? title,
2115
2207
  onClick: (e) => e.stopPropagation(),
2116
2208
  ...rest,
@@ -2429,6 +2521,26 @@ function List({
2429
2521
  if (isRadio) onChange == null ? void 0 : onChange(item.value);
2430
2522
  (_a = item.onClick) == null ? void 0 : _a.call(item);
2431
2523
  };
2524
+ const rowMain = isEntry ? null : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
2525
+ isRadio ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "chorus-list__leading", children: /* @__PURE__ */ jsxRuntime.jsx(RadioIndicator, { selected }) }) : item.thumbnail ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "chorus-list__leading chorus-list__leading--image", children: /* @__PURE__ */ jsxRuntime.jsx(Thumbnail, { size: 40, ...item.thumbnail }) }) : item.icon ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "chorus-list__leading chorus-list__leading--icon", "aria-hidden": "true", children: item.icon }) : null,
2526
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "chorus-list__label-col", children: [
2527
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "chorus-list__primary-row", children: [
2528
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "chorus-list__label", children: item.label }),
2529
+ item.count != null ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "chorus-list__count", children: item.count }) : null
2530
+ ] }),
2531
+ item.supportingText ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "chorus-list__supporting", children: item.supportingText }) : null
2532
+ ] }),
2533
+ item.nav && !item.trailingIcon ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "chorus-list__trailing chorus-list__nav-chevron", "aria-hidden": "true", children: /* @__PURE__ */ jsxRuntime.jsx(ChevronDownIcon, { size: 16 }) }) : item.trailingIcon ? /* @__PURE__ */ jsxRuntime.jsx(
2534
+ "span",
2535
+ {
2536
+ className: "chorus-list__trailing",
2537
+ "data-nested-action": "",
2538
+ onClick: (e) => e.stopPropagation(),
2539
+ onKeyDown: (e) => e.stopPropagation(),
2540
+ children: item.trailingIcon
2541
+ }
2542
+ ) : null
2543
+ ] });
2432
2544
  return /* @__PURE__ */ jsxRuntime.jsx(
2433
2545
  "div",
2434
2546
  {
@@ -2463,26 +2575,26 @@ function List({
2463
2575
  description: item.description,
2464
2576
  trailing: item.trailingIcon
2465
2577
  }
2466
- ) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
2467
- isRadio ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "chorus-list__leading", children: /* @__PURE__ */ jsxRuntime.jsx(RadioIndicator, { selected }) }) : item.thumbnail ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "chorus-list__leading chorus-list__leading--image", children: /* @__PURE__ */ jsxRuntime.jsx(Thumbnail, { size: 40, ...item.thumbnail }) }) : item.icon ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "chorus-list__leading chorus-list__leading--icon", "aria-hidden": "true", children: item.icon }) : null,
2468
- /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "chorus-list__label-col", children: [
2469
- /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "chorus-list__primary-row", children: [
2470
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "chorus-list__label", children: item.label }),
2471
- item.count != null ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "chorus-list__count", children: item.count }) : null
2472
- ] }),
2473
- item.supportingText ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "chorus-list__supporting", children: item.supportingText }) : null
2474
- ] }),
2475
- item.nav && !item.trailingIcon ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "chorus-list__trailing chorus-list__nav-chevron", "aria-hidden": "true", children: /* @__PURE__ */ jsxRuntime.jsx(ChevronDownIcon, { size: 16 }) }) : item.trailingIcon ? /* @__PURE__ */ jsxRuntime.jsx(
2476
- "span",
2477
- {
2478
- className: "chorus-list__trailing",
2479
- "data-nested-action": "",
2480
- onClick: (e) => e.stopPropagation(),
2481
- onKeyDown: (e) => e.stopPropagation(),
2482
- children: item.trailingIcon
2483
- }
2484
- ) : null
2485
- ] })
2578
+ ) : item.banner ? (
2579
+ // Embedded-Banner row the text group stacks over a Banner
2580
+ // that spans the row's full content width, 8px (stack.xs)
2581
+ // below. The Banner is a nested action region: its own
2582
+ // controls never commit the row, and the row's hover/press
2583
+ // overlay is suppressed over it (see styles.css).
2584
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "chorus-list__stack", children: [
2585
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "chorus-list__row-main", children: rowMain }),
2586
+ /* @__PURE__ */ jsxRuntime.jsx(
2587
+ "span",
2588
+ {
2589
+ className: "chorus-list__banner",
2590
+ "data-nested-action": "",
2591
+ onClick: (e) => e.stopPropagation(),
2592
+ onKeyDown: (e) => e.stopPropagation(),
2593
+ children: item.banner
2594
+ }
2595
+ )
2596
+ ] })
2597
+ ) : rowMain
2486
2598
  },
2487
2599
  item.value ?? idx
2488
2600
  );
@@ -2742,6 +2854,7 @@ function Dialog({
2742
2854
  const cardRef = react.useRef(null);
2743
2855
  const lastFocusedRef = react.useRef(null);
2744
2856
  useBodyScrollLock(open && !inline);
2857
+ useFocusTrap(cardRef, open && !inline);
2745
2858
  react.useEffect(() => {
2746
2859
  var _a;
2747
2860
  if (!open) return void 0;
@@ -2787,6 +2900,7 @@ function Dialog({
2787
2900
  className: joinClasses("chorus-dialog__card", image && "chorus-dialog__card--with-image"),
2788
2901
  role: "dialog",
2789
2902
  "aria-modal": "true",
2903
+ tabIndex: -1,
2790
2904
  "aria-label": ariaLabel ?? title,
2791
2905
  onClick: (e) => e.stopPropagation(),
2792
2906
  ...rest,
@@ -2820,6 +2934,80 @@ function Divider({
2820
2934
  }
2821
2935
  );
2822
2936
  }
2937
+
2938
+ // ../../schema/components/empty-state/empty-state.spec.json
2939
+ var empty_state_spec_default = {
2940
+ sizing: {
2941
+ illustrationSize: "ref.space.600",
2942
+ illustrationColor: "sys.color.text.subtle",
2943
+ illustrationGap: "sys.layout.stack.sm",
2944
+ headlineTypo: "sys.typo.heading.sm",
2945
+ headlineColor: "sys.color.text.default",
2946
+ bodyTypo: "sys.typo.body.sm",
2947
+ bodyColor: "sys.color.text.subtle",
2948
+ bodyGap: "sys.layout.stack.2xs",
2949
+ actionGap: "sys.layout.stack.md"
2950
+ }};
2951
+ var s = empty_state_spec_default.sizing;
2952
+ var CONTAINER_STYLE = {
2953
+ "--empty-state-illustration-gap": tokenToCss(s.illustrationGap),
2954
+ "--empty-state-body-gap": tokenToCss(s.bodyGap),
2955
+ "--empty-state-action-gap": tokenToCss(s.actionGap)
2956
+ };
2957
+ var ILLUSTRATION_STYLE = {
2958
+ "--empty-state-illustration-size": tokenToCss(s.illustrationSize),
2959
+ "--empty-state-illustration-color": tokenToCss(s.illustrationColor)
2960
+ };
2961
+ var HEADLINE_STYLE = {
2962
+ "--empty-state-headline-color": tokenToCss(s.headlineColor),
2963
+ ...typoStyles(s.headlineTypo)
2964
+ };
2965
+ var BODY_STYLE = {
2966
+ "--empty-state-body-color": tokenToCss(s.bodyColor),
2967
+ ...typoStyles(s.bodyTypo)
2968
+ };
2969
+ function EmptyState({
2970
+ illustration,
2971
+ headline,
2972
+ body,
2973
+ action,
2974
+ className,
2975
+ style,
2976
+ ...rest
2977
+ }) {
2978
+ return /* @__PURE__ */ jsxRuntime.jsxs(
2979
+ "div",
2980
+ {
2981
+ role: "status",
2982
+ className: joinClasses("chorus-empty-state", className),
2983
+ style: { ...CONTAINER_STYLE, ...style },
2984
+ ...rest,
2985
+ children: [
2986
+ illustration ? /* @__PURE__ */ jsxRuntime.jsx(
2987
+ "span",
2988
+ {
2989
+ className: "chorus-empty-state__illustration",
2990
+ "aria-hidden": "true",
2991
+ style: ILLUSTRATION_STYLE,
2992
+ children: illustration
2993
+ }
2994
+ ) : null,
2995
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "chorus-empty-state__headline", style: HEADLINE_STYLE, children: headline }),
2996
+ body ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: "chorus-empty-state__body", style: BODY_STYLE, children: body }) : null,
2997
+ action ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "chorus-empty-state__action", children: /* @__PURE__ */ jsxRuntime.jsx(
2998
+ Button,
2999
+ {
3000
+ appearance: "primary",
3001
+ onClick: action.onClick ?? (action.href ? () => {
3002
+ window.location.assign(action.href);
3003
+ } : void 0),
3004
+ children: action.label
3005
+ }
3006
+ ) }) : null
3007
+ ]
3008
+ }
3009
+ );
3010
+ }
2823
3011
  var TabsContext = react.createContext({
2824
3012
  variant: "underline",
2825
3013
  value: null,
@@ -2846,17 +3034,17 @@ var underline_spec_default = {
2846
3034
  slotGap: "sys.layout.inline.sm",
2847
3035
  indicatorHeight: "sys.borderWidth.thin",
2848
3036
  dividerWidth: "sys.borderWidth.hairline",
2849
- dividerColor: "sys.color.outlineVariant",
3037
+ dividerColor: "sys.color.border.default",
2850
3038
  labelTypo: "sys.typo.label.md",
2851
3039
  iconSize: "sys.icon.md",
2852
3040
  fadeWidth: "ref.space.600"
2853
3041
  },
2854
3042
  selectionStates: {
2855
3043
  unselected: {
2856
- label: "sys.color.outline"},
3044
+ label: "sys.color.text.subtle"},
2857
3045
  selected: {
2858
- label: "sys.color.onSurface",
2859
- indicator: "sys.color.onSurface"
3046
+ label: "sys.color.text.default",
3047
+ indicator: "sys.color.border.selected"
2860
3048
  }
2861
3049
  },
2862
3050
  states: {
@@ -2871,17 +3059,14 @@ var underline_spec_default = {
2871
3059
  }
2872
3060
  },
2873
3061
  disabled: {
2874
- containerOpacity: "sys.state.disabled"}
3062
+ }
2875
3063
  },
2876
3064
  focusIndicator: {
2877
3065
  overlay: {
2878
3066
  opacity: "sys.state.focus"
2879
3067
  },
2880
3068
  ring: {
2881
- outerWidth: "sys.borderWidth.thin",
2882
- outerColor: "sys.color.focus",
2883
- insetWidth: "sys.borderWidth.hairline",
2884
- insetColor: "sys.color.focusInset"}}};
3069
+ color: "sys.color.border.focused"}}};
2885
3070
  function useSlidingIndicator(containerRef, indicatorRef, value) {
2886
3071
  react.useLayoutEffect(() => {
2887
3072
  const container = containerRef.current;
@@ -2942,19 +3127,19 @@ function TabsUnderline({ className, style, children, ...rest }) {
2942
3127
  useScrollOverflow(ref);
2943
3128
  useSlidingIndicator(ref, indicatorRef, value);
2944
3129
  useFullBleedGuard(ref, "Tabs");
2945
- const s = underline_spec_default.sizing;
3130
+ const s2 = underline_spec_default.sizing;
2946
3131
  const composedStyle = {
2947
- "--tabs-container-padding-inline": tokenToCss(s.containerPaddingInline),
2948
- "--tabs-tab-min-height": tokenToCss(s.minHeight),
2949
- "--tabs-tab-padding-block": tokenToCss(s.paddingBlock),
2950
- "--tabs-tab-padding-inline": tokenToCss(s.paddingInline),
2951
- "--tabs-inter-tab-gap": tokenToCss(s.interTabGap),
2952
- "--tabs-slot-gap": tokenToCss(s.slotGap),
2953
- "--tabs-icon-size": tokenToCss(s.iconSize),
2954
- "--tabs-indicator-height": tokenToCss(s.indicatorHeight),
2955
- "--tabs-divider-width": tokenToCss(s.dividerWidth),
2956
- "--tabs-divider-color": tokenToCss(s.dividerColor),
2957
- "--tabs-fade-width": tokenToCss(s.fadeWidth),
3132
+ "--tabs-container-padding-inline": tokenToCss(s2.containerPaddingInline),
3133
+ "--tabs-tab-min-height": tokenToCss(s2.minHeight),
3134
+ "--tabs-tab-padding-block": tokenToCss(s2.paddingBlock),
3135
+ "--tabs-tab-padding-inline": tokenToCss(s2.paddingInline),
3136
+ "--tabs-inter-tab-gap": tokenToCss(s2.interTabGap),
3137
+ "--tabs-slot-gap": tokenToCss(s2.slotGap),
3138
+ "--tabs-icon-size": tokenToCss(s2.iconSize),
3139
+ "--tabs-indicator-height": tokenToCss(s2.indicatorHeight),
3140
+ "--tabs-divider-width": tokenToCss(s2.dividerWidth),
3141
+ "--tabs-divider-color": tokenToCss(s2.dividerColor),
3142
+ "--tabs-fade-width": tokenToCss(s2.fadeWidth),
2958
3143
  "--tabs-label-unselected": tokenToCss(underline_spec_default.selectionStates.unselected.label),
2959
3144
  "--tabs-label-selected": tokenToCss(underline_spec_default.selectionStates.selected.label),
2960
3145
  "--tabs-indicator-color": tokenToCss(underline_spec_default.selectionStates.selected.indicator),
@@ -2962,11 +3147,8 @@ function TabsUnderline({ className, style, children, ...rest }) {
2962
3147
  "--tabs-overlay-pressed": tokenToCss(underline_spec_default.states.pressed.overlay.opacity),
2963
3148
  "--tabs-overlay-focus": tokenToCss(underline_spec_default.focusIndicator.overlay.opacity),
2964
3149
  "--tabs-disabled-opacity": tokenToCss(underline_spec_default.states.disabled.containerOpacity),
2965
- "--tabs-focus-outer-width": tokenToCss(underline_spec_default.focusIndicator.ring.outerWidth),
2966
- "--tabs-focus-outer-color": tokenToCss(underline_spec_default.focusIndicator.ring.outerColor),
2967
- "--tabs-focus-inset-width": tokenToCss(underline_spec_default.focusIndicator.ring.insetWidth),
2968
- "--tabs-focus-inset-color": tokenToCss(underline_spec_default.focusIndicator.ring.insetColor),
2969
- ...typoStyles(s.labelTypo),
3150
+ "--tabs-focus-outer-color": tokenToCss(underline_spec_default.focusIndicator.ring.color),
3151
+ ...typoStyles(s2.labelTypo),
2970
3152
  ...style
2971
3153
  };
2972
3154
  return /* @__PURE__ */ jsxRuntime.jsxs(
@@ -3028,12 +3210,12 @@ function TabsSegmented({ className, style, children, ...rest }) {
3028
3210
  const ref = react.useRef(null);
3029
3211
  useScrollOverflow(ref);
3030
3212
  useFullBleedGuard(ref, "Tabs");
3031
- const s = segmented_spec_default.sizing;
3213
+ const s2 = segmented_spec_default.sizing;
3032
3214
  const composedStyle = {
3033
- "--tabs-container-padding-block": tokenToCss(s.containerPaddingBlock),
3034
- "--tabs-container-padding-inline": tokenToCss(s.containerPaddingInline),
3035
- "--tabs-inter-segment-gap": tokenToCss(s.interSegmentGap),
3036
- "--tabs-fade-width": tokenToCss(s.fadeWidth),
3215
+ "--tabs-container-padding-block": tokenToCss(s2.containerPaddingBlock),
3216
+ "--tabs-container-padding-inline": tokenToCss(s2.containerPaddingInline),
3217
+ "--tabs-inter-segment-gap": tokenToCss(s2.interSegmentGap),
3218
+ "--tabs-fade-width": tokenToCss(s2.fadeWidth),
3037
3219
  ...style
3038
3220
  };
3039
3221
  return /* @__PURE__ */ jsxRuntime.jsx(
@@ -3701,9 +3883,9 @@ function Card({ item, innerRef, index }) {
3701
3883
  }
3702
3884
  var MAX_CARDS2 = 5;
3703
3885
  var METRIC_KINDS = {
3704
- star: { Icon: StarFillIcon, tone: "var(--sys-color-icon-yellow)" },
3705
- pulse: { Icon: PulseFillIcon, tone: "var(--sys-color-success)" },
3706
- heart: { Icon: HeartFillIcon, tone: "var(--sys-color-icon-red)" }
3886
+ star: { Icon: StarFillIcon, tone: "var(--sys-color-icon-accent-yellow-default)" },
3887
+ pulse: { Icon: PulseFillIcon, tone: "var(--sys-color-icon-success)" },
3888
+ heart: { Icon: HeartFillIcon, tone: "var(--sys-color-icon-accent-red-default)" }
3707
3889
  };
3708
3890
  var PLACEHOLDER_IMAGE = "/placeholder.png";
3709
3891
  function ProfileCarousel({
@@ -4257,7 +4439,7 @@ var input_spec_default = {
4257
4439
  helper: {
4258
4440
  type: "node",
4259
4441
  optional: true,
4260
- description: "Assistive text rendered below the field box, left-aligned. **Optional on every appearance** \u2014 omit the prop to render the field without an assistive rung; the box and label keep their footprint. On the `error` appearance the helper re-tones to `sys.color.error` so the message reads as the error caption; an error field may still be shown without a helper, in which case only the field box re-tones. Mutually exclusive with `maxLength` \u2014 if both are given, the character count is shown and `helper` is ignored."
4442
+ description: "Assistive text rendered below the field box, left-aligned. **Optional on every appearance** \u2014 omit the prop to render the field without an assistive rung; the box and label keep their footprint. On the `error` appearance the helper re-tones to `sys.color.text.danger` so the message reads as the error caption; an error field may still be shown without a helper, in which case only the field box re-tones. Mutually exclusive with `maxLength` \u2014 if both are given, the character count is shown and `helper` is ignored."
4261
4443
  },
4262
4444
  maxLength: {
4263
4445
  type: "number",
@@ -4277,7 +4459,7 @@ var input_spec_default = {
4277
4459
  },
4278
4460
  label: {
4279
4461
  required: false,
4280
- description: "Visible label above the box. `sys.typo.label.md`, `sys.color.onSurface`. Associated with the input via `htmlFor`.",
4462
+ description: "Visible label above the box. `sys.typo.label.md`, `sys.color.text.default`. Associated with the input via `htmlFor`.",
4281
4463
  accepts: [
4282
4464
  "text"
4283
4465
  ]
@@ -4301,14 +4483,14 @@ var input_spec_default = {
4301
4483
  },
4302
4484
  helper: {
4303
4485
  required: false,
4304
- description: "Assistive text below the box, left-aligned. `sys.typo.body.sm`, `sys.color.onSurfaceVariant`; on the `error` appearance the colour re-tones to `sys.color.error` so the message reads as the error caption. Referenced by the input's `aria-describedby`. Not rendered when a `maxLength` count is present, and intentionally omittable on every appearance (including `error`) \u2014 pass nothing and the field renders without an assistive rung.",
4486
+ description: "Assistive text below the box, left-aligned. `sys.typo.body.sm`, `sys.color.text.subtle`; on the `error` appearance the colour re-tones to `sys.color.text.danger` so the message reads as the error caption. Referenced by the input's `aria-describedby`. Not rendered when a `maxLength` count is present, and intentionally omittable on every appearance (including `error`) \u2014 pass nothing and the field renders without an assistive rung.",
4305
4487
  accepts: [
4306
4488
  "text"
4307
4489
  ]
4308
4490
  },
4309
4491
  count: {
4310
4492
  required: false,
4311
- description: '`current/max` character count below the box, right-aligned, present when `maxLength` is set. `sys.typo.body.sm`, `sys.color.onSurfaceVariant`; the current-count number is `sys.typo.label.md` weight in `sys.color.onSurface`. Referenced by the input\'s `aria-describedby`; updates `aria-live="polite"`.',
4493
+ description: '`current/max` character count below the box, right-aligned, present when `maxLength` is set. `sys.typo.body.sm`, `sys.color.text.subtle`; the current-count number is `sys.typo.label.md` weight in `sys.color.text.default`. Referenced by the input\'s `aria-describedby`; updates `aria-live="polite"`.',
4312
4494
  accepts: [
4313
4495
  "text"
4314
4496
  ]
@@ -4321,7 +4503,7 @@ var input_spec_default = {
4321
4503
  slotGap: "sys.layout.inline.md",
4322
4504
  radius: "sys.radius.md",
4323
4505
  borderWidth: "sys.borderWidth.hairline",
4324
- activeStrokeWeight: "sys.borderWidth.thin",
4506
+ activeStrokeWeight: "sys.borderWidth.hairline",
4325
4507
  groupGap: "sys.layout.stack.xs",
4326
4508
  labelTypo: "sys.typo.label.md",
4327
4509
  helperTypo: "sys.typo.body.sm",
@@ -4331,28 +4513,28 @@ var input_spec_default = {
4331
4513
  iconSize: "sys.icon.md"
4332
4514
  },
4333
4515
  groupColors: {
4334
- label: "sys.color.onSurface",
4335
- helper: "sys.color.onSurfaceVariant",
4336
- helperError: "sys.color.error",
4337
- count: "sys.color.onSurfaceVariant",
4338
- countCurrent: "sys.color.onSurface"
4516
+ label: "sys.color.text.default",
4517
+ helper: "sys.color.text.subtle",
4518
+ helperError: "sys.color.text.danger",
4519
+ count: "sys.color.text.subtle",
4520
+ countCurrent: "sys.color.text.default"
4339
4521
  },
4340
4522
  appearances: {
4341
4523
  default: {
4342
4524
  background: "transparent",
4343
- text: "sys.color.onSurface",
4344
- placeholder: "sys.color.outline",
4345
- borderRest: "sys.color.outlineVariant",
4346
- borderHover: "sys.color.outline",
4347
- borderActive: "sys.color.onSurface"
4525
+ text: "sys.color.text.default",
4526
+ placeholder: "sys.color.border.boldest",
4527
+ borderRest: "sys.color.border.default",
4528
+ borderHover: "sys.color.border.boldest",
4529
+ borderActive: "sys.color.border.focused"
4348
4530
  },
4349
4531
  error: {
4350
- background: "sys.color.errorContainer",
4351
- text: "sys.color.onErrorContainer",
4352
- placeholder: "sys.color.onErrorContainer",
4353
- borderRest: "sys.color.error",
4354
- borderHover: "sys.color.error",
4355
- borderActive: "sys.color.error"
4532
+ background: "sys.color.background.danger",
4533
+ text: "sys.color.text.danger",
4534
+ placeholder: "sys.color.text.danger",
4535
+ borderRest: "sys.color.border.danger",
4536
+ borderHover: "sys.color.border.danger",
4537
+ borderActive: "sys.color.border.danger"
4356
4538
  }
4357
4539
  },
4358
4540
  states: {
@@ -4373,35 +4555,43 @@ var input_spec_default = {
4373
4555
  nestedActionScope: "The pressed overlay (and hover stroke) is suppressed while the pointer presses / hovers the trailing clear button \u2014 that '\xD7' is an independent nested action, so the small control owns the state and the large field does not also read as pressed. The visual-state boundary matches the action boundary."
4374
4556
  },
4375
4557
  active: {
4558
+ isFocusState: true,
4376
4559
  overlay: null,
4377
4560
  border: "borderActive",
4378
4561
  strokeWeight: "activeStrokeWeight",
4379
4562
  caret: "visible",
4380
4563
  showsClearWhenValue: true,
4381
- note: "The stroke steps from its rest `hairline` (1px) to `activeStrokeWeight` (2px) and re-tones to `borderActive` \u2014 but it is an inset `box-shadow`, not a `border`, so the box model is untouched: the field's footprint, caret, and text position are pixel-stable as it goes active. Nothing reflows. The clear button is shown only in this state (and only when the value is non-empty)."
4564
+ focusRing: {
4565
+ composition: "outward",
4566
+ layer: "::after overlay \u2014 position:absolute, inset:0, no reflow (DESIGN.md Focus ring composition)",
4567
+ innerCounterRing: { width: "sys.borderWidth.hairline", color: "sys.color.border.focused" },
4568
+ outerRing: { width: "sys.borderWidth.thin", color: "sys.color.border.focused" }
4569
+ },
4570
+ note: "This IS the field's keyboard/input-focus state \u2014 `active` (caret visible, input engaged) and `:focus-visible` coincide for a text field, so there is no separate `focused` state; the focus ring described here (and in the parallel `focusIndicator` block) is the focus affordance. The stroke stays at `hairline` (1px) and re-tones to `borderActive` on active (no thickening) \u2014 but it is an inset `box-shadow`, not a `border`, so the box model is untouched: the field's footprint, caret, and text position are pixel-stable as it goes active. Nothing reflows. The clear button is shown only in this state (and only when the value is non-empty)."
4382
4571
  },
4383
4572
  disabled: {
4384
4573
  overlay: null,
4385
- background: "sys.color.surfaceContainerLow",
4386
- containerOpacity: "sys.state.disabled",
4574
+ background: "sys.color.background.disabled",
4575
+ text: "sys.color.text.disabled",
4576
+ placeholder: "sys.color.text.disabled",
4577
+ border: "sys.color.border.bold",
4387
4578
  suppressClear: true,
4388
4579
  suppressFocusRing: true,
4389
- cursor: "not-allowed"
4580
+ cursor: "not-allowed",
4581
+ note: "Explicit disabled (no opacity): neutral disabled fill + bold border + disabled text/placeholder."
4390
4582
  }
4391
4583
  },
4392
4584
  focusIndicator: {
4393
4585
  description: "Keyboard-focus visual \u2014 an accessibility indicator, not a lifecycle state. Composes over whichever lifecycle state the field is in (most commonly `active` since focus implies the caret is in the box). The `states.focused` block above is kept for JSX runtime consumers; this block is the parallel external-reader contract.",
4394
4586
  composition: "outward",
4395
- compositionReason: "Action affordance with breathing room around it; the 3px outward extent is reserved by the surrounding layout.",
4587
+ compositionReason: "Action affordance with breathing room around it; the 1px outward ring is reserved by the surrounding layout.",
4396
4588
  overlay: {
4397
4589
  color: "label",
4398
4590
  opacity: "sys.state.focus"
4399
4591
  },
4400
4592
  ring: {
4401
- outerWidth: "sys.borderWidth.thin",
4402
- outerColor: "sys.color.focus",
4403
- insetWidth: "sys.borderWidth.hairline",
4404
- insetColor: "sys.color.focusInset"
4593
+ width: "sys.borderWidth.hairline",
4594
+ color: "sys.color.border.focused"
4405
4595
  },
4406
4596
  trigger: ":focus-visible (keyboard / programmatic focus, never plain mouse click)"
4407
4597
  },
@@ -4414,7 +4604,7 @@ var input_spec_default = {
4414
4604
  },
4415
4605
  forbidden: [
4416
4606
  "raw <input> styled with Tailwind / inline color \u2014 the input is wrapped in the chorus-field chrome that owns the stroke",
4417
- "active-state stroke painted with sys.color.primary / a container tier \u2014 the active stroke is sys.color.onSurface (default appearance) or sys.color.error (error appearance), never primary or a container tier",
4607
+ "active-state stroke painted with sys.color.background.primary / a container tier \u2014 the active stroke is sys.color.text.default (default appearance) or sys.color.text.danger (error appearance), never primary or a container tier",
4418
4608
  "stroke painted via `border:` \u2014 stroke is an inset box-shadow on the field",
4419
4609
  "helper text rendered outside the helperText slot"
4420
4610
  ]
@@ -4459,7 +4649,7 @@ var textarea_spec_default = {
4459
4649
  helper: {
4460
4650
  type: "node",
4461
4651
  optional: true,
4462
- description: "Assistive text rendered below the field box, left-aligned. Same rules as [input.helper](./input.md): mutually exclusive with `maxLength`, optional on every appearance, re-tones to `sys.color.error` on the error appearance."
4652
+ description: "Assistive text rendered below the field box, left-aligned. Same rules as [input.helper](./input.md): mutually exclusive with `maxLength`, optional on every appearance, re-tones to `sys.color.text.danger` on the error appearance."
4463
4653
  },
4464
4654
  maxLength: {
4465
4655
  type: "number",
@@ -4484,7 +4674,7 @@ var textarea_spec_default = {
4484
4674
  },
4485
4675
  label: {
4486
4676
  required: false,
4487
- description: "Visible label above the box. `sys.typo.label.md`, `sys.color.onSurface`. Associated with the textarea via `htmlFor`.",
4677
+ description: "Visible label above the box. `sys.typo.label.md`, `sys.color.text.default`. Associated with the textarea via `htmlFor`.",
4488
4678
  accepts: ["text"]
4489
4679
  },
4490
4680
  container: {
@@ -4515,7 +4705,7 @@ var textarea_spec_default = {
4515
4705
  slotGap: "sys.layout.inline.md",
4516
4706
  radius: "sys.radius.md",
4517
4707
  borderWidth: "sys.borderWidth.hairline",
4518
- activeStrokeWeight: "sys.borderWidth.thin",
4708
+ activeStrokeWeight: "sys.borderWidth.hairline",
4519
4709
  groupGap: "sys.layout.stack.xs",
4520
4710
  labelTypo: "sys.typo.label.md",
4521
4711
  helperTypo: "sys.typo.body.sm",
@@ -4527,28 +4717,28 @@ var textarea_spec_default = {
4527
4717
  resize: "vertical"
4528
4718
  },
4529
4719
  groupColors: {
4530
- label: "sys.color.onSurface",
4531
- helper: "sys.color.onSurfaceVariant",
4532
- helperError: "sys.color.error",
4533
- count: "sys.color.onSurfaceVariant",
4534
- countCurrent: "sys.color.onSurface"
4720
+ label: "sys.color.text.default",
4721
+ helper: "sys.color.text.subtle",
4722
+ helperError: "sys.color.text.danger",
4723
+ count: "sys.color.text.subtle",
4724
+ countCurrent: "sys.color.text.default"
4535
4725
  },
4536
4726
  appearances: {
4537
4727
  default: {
4538
4728
  background: "transparent",
4539
- text: "sys.color.onSurface",
4540
- placeholder: "sys.color.outline",
4541
- borderRest: "sys.color.outlineVariant",
4542
- borderHover: "sys.color.outline",
4543
- borderActive: "sys.color.onSurface"
4729
+ text: "sys.color.text.default",
4730
+ placeholder: "sys.color.border.boldest",
4731
+ borderRest: "sys.color.border.default",
4732
+ borderHover: "sys.color.border.boldest",
4733
+ borderActive: "sys.color.border.focused"
4544
4734
  },
4545
4735
  error: {
4546
- background: "sys.color.errorContainer",
4547
- text: "sys.color.onErrorContainer",
4548
- placeholder: "sys.color.onErrorContainer",
4549
- borderRest: "sys.color.error",
4550
- borderHover: "sys.color.error",
4551
- borderActive: "sys.color.error"
4736
+ background: "sys.color.background.danger",
4737
+ text: "sys.color.text.danger",
4738
+ placeholder: "sys.color.text.danger",
4739
+ borderRest: "sys.color.border.danger",
4740
+ borderHover: "sys.color.border.danger",
4741
+ borderActive: "sys.color.border.danger"
4552
4742
  }
4553
4743
  },
4554
4744
  states: {
@@ -4560,33 +4750,41 @@ var textarea_spec_default = {
4560
4750
  nestedActionScope: "The pressed overlay (and hover stroke) is suppressed while the pointer presses / hovers the trailing clear button \u2014 that '\xD7' is an independent nested action, so the small control owns the state and the large field does not also read as pressed. The visual-state boundary matches the action boundary."
4561
4751
  },
4562
4752
  active: {
4753
+ isFocusState: true,
4563
4754
  overlay: null,
4564
4755
  border: "borderActive",
4565
4756
  strokeWeight: "activeStrokeWeight",
4566
4757
  caret: "visible",
4567
- note: "Stroke steps from `hairline` (1px) to `activeStrokeWeight` (2px) as an inset box-shadow \u2014 same pixel-stable contract as input."
4758
+ focusRing: {
4759
+ composition: "outward",
4760
+ layer: "::after overlay \u2014 position:absolute, inset:0, no reflow (DESIGN.md Focus ring composition)",
4761
+ innerCounterRing: { width: "sys.borderWidth.hairline", color: "sys.color.border.focused" },
4762
+ outerRing: { width: "sys.borderWidth.thin", color: "sys.color.border.focused" }
4763
+ },
4764
+ note: "This IS the field's keyboard/input-focus state \u2014 `active` (caret visible, input engaged) and `:focus-visible` coincide for a text field, so there is no separate `focused` state; the focus ring described here (and in the parallel `focusIndicator` block) is the focus affordance. Stroke stays at `hairline` (1px), re-toning to `borderActive` as an inset box-shadow (no thickening) \u2014 same pixel-stable contract as input."
4568
4765
  },
4569
4766
  disabled: {
4570
4767
  overlay: null,
4571
- background: "sys.color.surfaceContainerLow",
4572
- containerOpacity: "sys.state.disabled",
4768
+ background: "sys.color.background.disabled",
4769
+ text: "sys.color.text.disabled",
4770
+ placeholder: "sys.color.text.disabled",
4771
+ border: "sys.color.border.bold",
4573
4772
  suppressFocusRing: true,
4574
- cursor: "not-allowed"
4773
+ cursor: "not-allowed",
4774
+ note: "Explicit disabled (no opacity): neutral disabled fill + bold border + disabled text/placeholder."
4575
4775
  }
4576
4776
  },
4577
4777
  focusIndicator: {
4578
- description: "Same outward 3-layer ring as input.focusIndicator.",
4778
+ description: "Same outward single ring as input.focusIndicator.",
4579
4779
  composition: "outward",
4580
- compositionReason: "Action affordance with breathing room around it; the 3px outward extent is reserved by the surrounding layout.",
4780
+ compositionReason: "Action affordance with breathing room around it; the 1px outward ring is reserved by the surrounding layout.",
4581
4781
  overlay: {
4582
4782
  color: "label",
4583
4783
  opacity: "sys.state.focus"
4584
4784
  },
4585
4785
  ring: {
4586
- outerWidth: "sys.borderWidth.thin",
4587
- outerColor: "sys.color.focus",
4588
- insetWidth: "sys.borderWidth.hairline",
4589
- insetColor: "sys.color.focusInset"
4786
+ width: "sys.borderWidth.hairline",
4787
+ color: "sys.color.border.focused"
4590
4788
  },
4591
4789
  trigger: ":focus-visible (keyboard / programmatic focus, never plain mouse click)"
4592
4790
  },
@@ -4643,7 +4841,7 @@ var search_spec_default = {
4643
4841
  },
4644
4842
  leading: {
4645
4843
  required: true,
4646
- description: "The leading `SearchIcon` glyph pinned at the box's inner-left edge. Inherits the field's text colour (`sys.color.onSurface`); decorative \u2014 not a real button, has `aria-hidden`. 16px (`sys.icon.md`), matching the clear button's footprint so the two affixes balance.",
4844
+ description: "The leading `SearchIcon` glyph pinned at the box's inner-left edge. Inherits the field's text colour (`sys.color.text.default`); decorative \u2014 not a real button, has `aria-hidden`. 16px (`sys.icon.md`), matching the clear button's footprint so the two affixes balance.",
4647
4845
  intrinsic: true
4648
4846
  },
4649
4847
  input: {
@@ -4666,18 +4864,18 @@ var search_spec_default = {
4666
4864
  slotGap: "sys.layout.inline.md",
4667
4865
  radius: "sys.radius.full",
4668
4866
  borderWidth: "sys.borderWidth.hairline",
4669
- activeStrokeWeight: "sys.borderWidth.thin",
4867
+ activeStrokeWeight: "sys.borderWidth.hairline",
4670
4868
  textTypo: "sys.typo.body.md",
4671
4869
  iconSize: "sys.icon.md"
4672
4870
  },
4673
4871
  appearances: {
4674
4872
  default: {
4675
4873
  background: "transparent",
4676
- text: "sys.color.onSurface",
4677
- placeholder: "sys.color.outline",
4678
- borderRest: "sys.color.outlineVariant",
4679
- borderHover: "sys.color.outline",
4680
- borderActive: "sys.color.onSurface"
4874
+ text: "sys.color.text.default",
4875
+ placeholder: "sys.color.border.boldest",
4876
+ borderRest: "sys.color.border.default",
4877
+ borderHover: "sys.color.border.boldest",
4878
+ borderActive: "sys.color.border.focused"
4681
4879
  }
4682
4880
  },
4683
4881
  states: {
@@ -4698,35 +4896,43 @@ var search_spec_default = {
4698
4896
  nestedActionScope: "The pressed overlay (and hover stroke) is suppressed while the pointer presses / hovers the trailing clear button \u2014 that '\xD7' is an independent nested action, so the small control owns the state and the large field does not also read as pressed. The visual-state boundary matches the action boundary."
4699
4897
  },
4700
4898
  active: {
4899
+ isFocusState: true,
4701
4900
  overlay: null,
4702
4901
  border: "borderActive",
4703
4902
  strokeWeight: "activeStrokeWeight",
4704
4903
  caret: "visible",
4705
4904
  showsClearWhenValue: true,
4706
- note: "The stroke steps from its rest `hairline` (1px) to `activeStrokeWeight` (2px) and re-tones to `borderActive` \u2014 but it is an inset `box-shadow`, not a `border`, so the box model is untouched: the field's footprint, caret, and text position are pixel-stable as it goes active. Nothing reflows. The clear button is shown only in this state (and only when the value is non-empty)."
4905
+ focusRing: {
4906
+ composition: "outward",
4907
+ layer: "::after overlay \u2014 position:absolute, inset:0, no reflow (DESIGN.md Focus ring composition)",
4908
+ innerCounterRing: { width: "sys.borderWidth.hairline", color: "sys.color.border.focused" },
4909
+ outerRing: { width: "sys.borderWidth.thin", color: "sys.color.border.focused" }
4910
+ },
4911
+ note: "This IS the field's keyboard/input-focus state \u2014 `active` (caret visible, input engaged) and `:focus-visible` coincide for a text field, so there is no separate `focused` state; the focus ring described here (and in the parallel `focusIndicator` block) is the focus affordance. The stroke stays at `hairline` (1px) and re-tones to `borderActive` on active (no thickening) \u2014 but it is an inset `box-shadow`, not a `border`, so the box model is untouched: the field's footprint, caret, and text position are pixel-stable as it goes active. Nothing reflows. The clear button is shown only in this state (and only when the value is non-empty)."
4707
4912
  },
4708
4913
  disabled: {
4709
4914
  overlay: null,
4710
- background: "sys.color.surfaceContainerLow",
4711
- containerOpacity: "sys.state.disabled",
4915
+ background: "sys.color.background.disabled",
4916
+ text: "sys.color.text.disabled",
4917
+ placeholder: "sys.color.text.disabled",
4918
+ border: "sys.color.border.bold",
4712
4919
  suppressClear: true,
4713
4920
  suppressFocusRing: true,
4714
- cursor: "not-allowed"
4921
+ cursor: "not-allowed",
4922
+ note: "Explicit disabled (no opacity): neutral disabled fill + bold border + disabled text/placeholder."
4715
4923
  }
4716
4924
  },
4717
4925
  focusIndicator: {
4718
4926
  description: "Keyboard-focus visual \u2014 an accessibility indicator, not a lifecycle state. Composes over whichever lifecycle state the field is in. The `states.focused` block above is kept for JSX runtime consumers; this block is the parallel external-reader contract.",
4719
4927
  composition: "outward",
4720
- compositionReason: "Action affordance with breathing room around it; the 3px outward extent is reserved by the surrounding layout.",
4928
+ compositionReason: "Action affordance with breathing room around it; the 1px outward ring is reserved by the surrounding layout.",
4721
4929
  overlay: {
4722
4930
  color: "label",
4723
4931
  opacity: "sys.state.focus"
4724
4932
  },
4725
4933
  ring: {
4726
- outerWidth: "sys.borderWidth.thin",
4727
- outerColor: "sys.color.focus",
4728
- insetWidth: "sys.borderWidth.hairline",
4729
- insetColor: "sys.color.focusInset"
4934
+ width: "sys.borderWidth.hairline",
4935
+ color: "sys.color.border.focused"
4730
4936
  },
4731
4937
  trigger: ":focus-visible (keyboard / programmatic focus, never plain mouse click)"
4732
4938
  },
@@ -4782,7 +4988,7 @@ var select_spec_default = {
4782
4988
  leadingIcon: {
4783
4989
  type: "node",
4784
4990
  optional: true,
4785
- description: "Optional 16px (`sys.icon.md`) decorative glyph pinned at the inner-left edge of the field. Tracks the field's active text colour (`sys.color.onSurface` on the default appearance, `sys.color.onErrorContainer` on `error`) so the glyph reads as part of the typed content."
4991
+ description: "Optional 16px (`sys.icon.md`) decorative glyph pinned at the inner-left edge of the field. Tracks the field's active text colour (`sys.color.text.default` on the default appearance, `sys.color.text.danger` on `error`) so the glyph reads as part of the typed content."
4786
4992
  },
4787
4993
  onOpen: {
4788
4994
  type: "function",
@@ -4839,7 +5045,7 @@ var select_spec_default = {
4839
5045
  slotGap: "sys.layout.inline.md",
4840
5046
  radius: "sys.radius.md",
4841
5047
  borderWidth: "sys.borderWidth.hairline",
4842
- activeStrokeWeight: "sys.borderWidth.thin",
5048
+ activeStrokeWeight: "sys.borderWidth.hairline",
4843
5049
  groupGap: "sys.layout.stack.xs",
4844
5050
  labelTypo: "sys.typo.label.md",
4845
5051
  helperTypo: "sys.typo.body.sm",
@@ -4849,28 +5055,28 @@ var select_spec_default = {
4849
5055
  iconSize: "sys.icon.md"
4850
5056
  },
4851
5057
  groupColors: {
4852
- label: "sys.color.onSurface",
4853
- helper: "sys.color.onSurfaceVariant",
4854
- helperError: "sys.color.error",
4855
- count: "sys.color.onSurfaceVariant",
4856
- countCurrent: "sys.color.onSurface"
5058
+ label: "sys.color.text.default",
5059
+ helper: "sys.color.text.subtle",
5060
+ helperError: "sys.color.text.danger",
5061
+ count: "sys.color.text.subtle",
5062
+ countCurrent: "sys.color.text.default"
4857
5063
  },
4858
5064
  appearances: {
4859
5065
  default: {
4860
5066
  background: "transparent",
4861
- text: "sys.color.onSurface",
4862
- placeholder: "sys.color.outline",
4863
- borderRest: "sys.color.outlineVariant",
4864
- borderHover: "sys.color.outline",
4865
- borderActive: "sys.color.onSurface"
5067
+ text: "sys.color.text.default",
5068
+ placeholder: "sys.color.border.boldest",
5069
+ borderRest: "sys.color.border.default",
5070
+ borderHover: "sys.color.border.boldest",
5071
+ borderActive: "sys.color.border.focused"
4866
5072
  },
4867
5073
  error: {
4868
- background: "sys.color.errorContainer",
4869
- text: "sys.color.onErrorContainer",
4870
- placeholder: "sys.color.onErrorContainer",
4871
- borderRest: "sys.color.error",
4872
- borderHover: "sys.color.error",
4873
- borderActive: "sys.color.error"
5074
+ background: "sys.color.background.danger",
5075
+ text: "sys.color.text.danger",
5076
+ placeholder: "sys.color.text.danger",
5077
+ borderRest: "sys.color.border.danger",
5078
+ borderHover: "sys.color.border.danger",
5079
+ borderActive: "sys.color.border.danger"
4874
5080
  }
4875
5081
  },
4876
5082
  states: {
@@ -4890,20 +5096,31 @@ var select_spec_default = {
4890
5096
  }
4891
5097
  },
4892
5098
  active: {
5099
+ isFocusState: true,
4893
5100
  overlay: null,
4894
5101
  border: "borderActive",
4895
- strokeWeight: "activeStrokeWeight"
5102
+ strokeWeight: "activeStrokeWeight",
5103
+ focusRing: {
5104
+ composition: "outward",
5105
+ layer: "::after overlay \u2014 position:absolute, inset:0, no reflow (DESIGN.md Focus ring composition)",
5106
+ innerCounterRing: { width: "sys.borderWidth.hairline", color: "sys.color.border.focused" },
5107
+ outerRing: { width: "sys.borderWidth.thin", color: "sys.color.border.focused" }
5108
+ },
5109
+ note: "This IS the trigger's keyboard-focus / open state \u2014 `:focus-visible` and the engaged (open) state coincide for the select trigger, so there is no separate `focused` state; the focus ring described here (and in the parallel `focusIndicator` block) is the focus affordance. The stroke re-tones to `borderActive` at `activeStrokeWeight` (1px, = rest) as an inset box-shadow, pixel-stable (no reflow)."
4896
5110
  },
4897
5111
  disabled: {
4898
5112
  overlay: null,
4899
- background: "sys.color.surfaceContainerLow",
4900
- containerOpacity: "sys.state.disabled",
5113
+ background: "sys.color.background.disabled",
5114
+ text: "sys.color.text.disabled",
5115
+ placeholder: "sys.color.text.disabled",
5116
+ border: "sys.color.border.bold",
4901
5117
  suppressFocusRing: true,
4902
- cursor: "not-allowed"
5118
+ cursor: "not-allowed",
5119
+ note: "Explicit disabled (no opacity): neutral disabled fill + bold border + disabled text/placeholder."
4903
5120
  }
4904
5121
  },
4905
5122
  focusIndicator: {
4906
- description: "Same keyboard-focus indicator as Input \u2014 outward two-layer ring composed over the active stroke.",
5123
+ description: "Same keyboard-focus indicator as Input \u2014 outward single ring composed over the active stroke.",
4907
5124
  composition: "outward",
4908
5125
  compositionReason: "Action affordance with breathing room around it.",
4909
5126
  overlay: {
@@ -4911,10 +5128,8 @@ var select_spec_default = {
4911
5128
  opacity: "sys.state.focus"
4912
5129
  },
4913
5130
  ring: {
4914
- outerWidth: "sys.borderWidth.thin",
4915
- outerColor: "sys.color.focus",
4916
- insetWidth: "sys.borderWidth.hairline",
4917
- insetColor: "sys.color.focusInset"
5131
+ width: "sys.borderWidth.hairline",
5132
+ color: "sys.color.border.focused"
4918
5133
  },
4919
5134
  trigger: ":focus-visible"
4920
5135
  },
@@ -4989,17 +5204,17 @@ function FormFieldBox({
4989
5204
  "--field-group-gap": tokenToCss(spec.sizing.groupGap),
4990
5205
  "--field-bg": tokenToCss(app.background),
4991
5206
  "--field-bg-disabled": tokenToCss(spec.states.disabled.background),
4992
- "--field-text": tokenToCss(app.text),
4993
- "--field-placeholder": tokenToCss(app.placeholder),
4994
- "--field-border": tokenToCss(app.borderRest),
5207
+ // Disabled swaps text / placeholder / border to the explicit disabled
5208
+ // tokens (no opacity) — the field box paints them via these vars.
5209
+ "--field-text": tokenToCss(isDisabled ? spec.states.disabled.text : app.text),
5210
+ "--field-placeholder": tokenToCss(
5211
+ isDisabled ? spec.states.disabled.placeholder : app.placeholder
5212
+ ),
5213
+ "--field-border": tokenToCss(isDisabled ? spec.states.disabled.border : app.borderRest),
4995
5214
  "--field-border-hover": tokenToCss(app.borderHover),
4996
5215
  "--field-border-active": tokenToCss(app.borderActive),
4997
5216
  "--field-overlay-pressed": tokenToCss(spec.states.pressed.overlay.opacity),
4998
- "--field-disabled-opacity": tokenToCss(spec.states.disabled.containerOpacity),
4999
- "--field-focus-outer-width": tokenToCss(spec.focusIndicator.ring.outerWidth),
5000
- "--field-focus-outer-color": tokenToCss(spec.focusIndicator.ring.outerColor),
5001
- "--field-focus-inset-width": tokenToCss(spec.focusIndicator.ring.insetWidth),
5002
- "--field-focus-inset-color": tokenToCss(spec.focusIndicator.ring.insetColor),
5217
+ "--field-focus-outer-color": tokenToCss(spec.focusIndicator.ring.color),
5003
5218
  ...typoStyles(spec.sizing.textTypo)
5004
5219
  };
5005
5220
  const handleChange = (event) => {
@@ -5513,6 +5728,58 @@ function SkeletonGroup({
5513
5728
  }
5514
5729
  );
5515
5730
  }
5731
+
5732
+ // ../../schema/components/spinner/spinner.spec.json
5733
+ var spinner_spec_default = {
5734
+ sizes: {
5735
+ medium: {
5736
+ diameter: "sys.icon.lg",
5737
+ labelTypo: "sys.typo.body.sm",
5738
+ gap: "sys.layout.inline.sm"
5739
+ },
5740
+ small: {
5741
+ diameter: "sys.icon.md",
5742
+ labelTypo: "sys.typo.body.sm",
5743
+ gap: "sys.layout.inline.sm"
5744
+ }
5745
+ }};
5746
+ function sizingStyle4(spec, size) {
5747
+ const s2 = spec.sizes[size] ?? spec.sizes.medium;
5748
+ return {
5749
+ "--spinner-diameter": tokenToCss(s2.diameter),
5750
+ "--spinner-gap": tokenToCss(s2.gap)
5751
+ };
5752
+ }
5753
+ function Spinner({
5754
+ size = "medium",
5755
+ label,
5756
+ className,
5757
+ style,
5758
+ "aria-label": ariaLabel,
5759
+ ...rest
5760
+ }) {
5761
+ var _a;
5762
+ const labelTypo = ((_a = spinner_spec_default.sizes[size]) == null ? void 0 : _a.labelTypo) ?? spinner_spec_default.sizes.medium.labelTypo;
5763
+ const a11yLabel = label != null ? void 0 : ariaLabel ?? "Loading";
5764
+ return /* @__PURE__ */ jsxRuntime.jsxs(
5765
+ "span",
5766
+ {
5767
+ role: "status",
5768
+ "aria-label": a11yLabel,
5769
+ className: joinClasses(
5770
+ "chorus-spinner",
5771
+ `chorus-spinner--${size}`,
5772
+ className
5773
+ ),
5774
+ style: { ...sizingStyle4(spinner_spec_default, size), ...style },
5775
+ ...rest,
5776
+ children: [
5777
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "chorus-spinner__arc", "aria-hidden": "true" }),
5778
+ label != null ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "chorus-spinner__label", style: typoStyles(labelTypo), children: label }) : null
5779
+ ]
5780
+ }
5781
+ );
5782
+ }
5516
5783
  function StatusTag({
5517
5784
  appearance = "neutral",
5518
5785
  children,
@@ -5539,7 +5806,7 @@ var sub_spec_default = {
5539
5806
  appearance: {
5540
5807
  containerFill: "transparent",
5541
5808
  labelTypo: "sys.typo.label.md",
5542
- labelColor: "sys.color.onSurfaceVariant",
5809
+ labelColor: "sys.color.text.subtle",
5543
5810
  paddingInline: "sys.layout.container.md",
5544
5811
  paddingBlockStart: "sys.layout.stack.lg",
5545
5812
  paddingBlockEnd: "sys.layout.stack.xs",
@@ -5918,6 +6185,7 @@ exports.Dialog = Dialog;
5918
6185
  exports.DirectoryList = DirectoryList;
5919
6186
  exports.Divider = Divider;
5920
6187
  exports.Drawer = BottomSheet;
6188
+ exports.EmptyState = EmptyState;
5921
6189
  exports.Feed = Feed;
5922
6190
  exports.FeedAd = FeedAd;
5923
6191
  exports.FeedGroup = FeedGroup;
@@ -5946,6 +6214,7 @@ exports.SideSheet = SideSheet;
5946
6214
  exports.SideSheetGroup = SideSheetGroup;
5947
6215
  exports.Skeleton = Skeleton;
5948
6216
  exports.SkeletonGroup = SkeletonGroup;
6217
+ exports.Spinner = Spinner;
5949
6218
  exports.StatusTag = StatusTag;
5950
6219
  exports.SubHeader = SubHeader;
5951
6220
  exports.SuggestionList = SuggestionList;