@teamblind-chorus/ui 1.1.0 → 1.2.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 (46) hide show
  1. package/agents/catalog.md +6 -4
  2. package/agents/components/avatar-rail/avatar-rail.spec.json +19 -0
  3. package/agents/components/banner/banner.family.json +3 -1
  4. package/agents/components/banner/banner.md +54 -1
  5. package/agents/components/banner/banner.spec.json +24 -1
  6. package/agents/components/button/check.spec.json +19 -0
  7. package/agents/components/button/fab.spec.json +19 -0
  8. package/agents/components/button/icon.spec.json +19 -0
  9. package/agents/components/button/standard.spec.json +19 -0
  10. package/agents/components/button/text.spec.json +19 -0
  11. package/agents/components/button/toggle.spec.json +19 -0
  12. package/agents/components/chip/filter.spec.json +19 -0
  13. package/agents/components/chip/tag.spec.json +19 -0
  14. package/agents/components/empty-state/empty-state.family.json +28 -0
  15. package/agents/components/empty-state/empty-state.md +69 -0
  16. package/agents/components/empty-state/empty-state.spec.json +87 -0
  17. package/agents/components/form-field/input.spec.json +8 -1
  18. package/agents/components/form-field/search.spec.json +8 -1
  19. package/agents/components/form-field/select.spec.json +9 -1
  20. package/agents/components/form-field/textarea.spec.json +8 -1
  21. package/agents/components/list/accordion.spec.json +9 -0
  22. package/agents/components/list/entry.spec.json +19 -0
  23. package/agents/components/list/radio.spec.json +19 -0
  24. package/agents/components/list/standard.md +46 -0
  25. package/agents/components/list/standard.spec.json +37 -2
  26. package/agents/components/nav-card/nav-card.spec.json +9 -0
  27. package/agents/components/page-shell/page-shell.family.json +1 -1
  28. package/agents/components/page-shell/page-shell.md +33 -0
  29. package/agents/components/page-shell/page-shell.spec.json +85 -0
  30. package/agents/components/spinner/spinner.family.json +27 -0
  31. package/agents/components/spinner/spinner.md +98 -0
  32. package/agents/components/spinner/spinner.spec.json +82 -0
  33. package/agents/components/switch/switch.spec.json +9 -0
  34. package/agents/components/tab-bar/tab-bar.spec.json +16 -0
  35. package/agents/components/tabs/rounded.spec.json +19 -0
  36. package/agents/components/tabs/underline.spec.json +19 -0
  37. package/agents/manifest.json +8 -6
  38. package/agents/usage.json +12 -0
  39. package/dist/index.cjs +340 -60
  40. package/dist/index.cjs.map +1 -1
  41. package/dist/index.d.cts +46 -2
  42. package/dist/index.d.ts +46 -2
  43. package/dist/index.js +339 -61
  44. package/dist/index.js.map +1 -1
  45. package/dist/styles.css +182 -0
  46. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -106,13 +106,13 @@ function formatCount(value) {
106
106
  return String(Math.floor(value));
107
107
  }
108
108
  function sizingStyle(spec, size) {
109
- const s = spec.sizes[size] ?? spec.sizes.medium;
109
+ const s2 = spec.sizes[size] ?? spec.sizes.medium;
110
110
  return {
111
- "--badge-min-height": tokenToCss(s.minHeight),
112
- "--badge-min-width": tokenToCss(s.minWidth),
113
- "--badge-padding-block": tokenToCss(s.paddingBlock),
114
- "--badge-padding-inline": tokenToCss(s.paddingInline),
115
- ...s.labelTypo ? typoStyles(s.labelTypo) : null
111
+ "--badge-min-height": tokenToCss(s2.minHeight),
112
+ "--badge-min-width": tokenToCss(s2.minWidth),
113
+ "--badge-padding-block": tokenToCss(s2.paddingBlock),
114
+ "--badge-padding-inline": tokenToCss(s2.paddingInline),
115
+ ...s2.labelTypo ? typoStyles(s2.labelTypo) : null
116
116
  };
117
117
  }
118
118
  function appearanceStyle(spec) {
@@ -236,10 +236,12 @@ function useFullBleedGuard(ref, name) {
236
236
  function Banner({
237
237
  appearance = "default",
238
238
  outlined = false,
239
+ neutralBody = false,
239
240
  title,
240
241
  icon,
241
242
  thumbnail,
242
243
  action,
244
+ trailingAction,
243
245
  trailingIcon,
244
246
  children,
245
247
  className,
@@ -255,6 +257,7 @@ function Banner({
255
257
  "chorus-banner",
256
258
  `chorus-banner--${appearance}`,
257
259
  outlined && "chorus-banner--outlined",
260
+ neutralBody && "chorus-banner--neutral-body",
258
261
  className
259
262
  ),
260
263
  role: "note",
@@ -275,7 +278,7 @@ function Banner({
275
278
  }
276
279
  ) : null
277
280
  ] }),
278
- trailingIcon ? /* @__PURE__ */ jsx("span", { className: "chorus-banner__trailing-icon", "aria-hidden": "true", children: trailingIcon }) : null
281
+ trailingAction ? /* @__PURE__ */ jsx("span", { className: "chorus-banner__trailing-action", children: trailingAction }) : trailingIcon ? /* @__PURE__ */ jsx("span", { className: "chorus-banner__trailing-icon", "aria-hidden": "true", children: trailingIcon }) : null
279
282
  ]
280
283
  }
281
284
  );
@@ -372,16 +375,16 @@ var standard_spec_default = {
372
375
  insetColor: "sys.color.focusInset"
373
376
  }}};
374
377
  function sizeStyle(size) {
375
- const s = standard_spec_default.sizes[size] ?? standard_spec_default.sizes[standard_spec_default.props.size.default];
378
+ const s2 = standard_spec_default.sizes[size] ?? standard_spec_default.sizes[standard_spec_default.props.size.default];
376
379
  return {
377
- "--button-standard-padding-block": tokenToCss(s.paddingBlock),
378
- "--button-standard-padding-inline": tokenToCss(s.paddingInline),
379
- "--button-standard-gap": tokenToCss(s.gap),
380
- "--button-standard-min-height": tokenToCss(s.minHeight),
381
- "--button-standard-min-width": tokenToCss(s.minWidth),
382
- "--button-standard-radius": tokenToCss(s.radius),
383
- "--button-standard-icon-size": tokenToCss(s.iconSize),
384
- ...typoStyles(s.labelTypo)
380
+ "--button-standard-padding-block": tokenToCss(s2.paddingBlock),
381
+ "--button-standard-padding-inline": tokenToCss(s2.paddingInline),
382
+ "--button-standard-gap": tokenToCss(s2.gap),
383
+ "--button-standard-min-height": tokenToCss(s2.minHeight),
384
+ "--button-standard-min-width": tokenToCss(s2.minWidth),
385
+ "--button-standard-radius": tokenToCss(s2.radius),
386
+ "--button-standard-icon-size": tokenToCss(s2.iconSize),
387
+ ...typoStyles(s2.labelTypo)
385
388
  };
386
389
  }
387
390
  function appearanceStyle2(appearance) {
@@ -488,8 +491,7 @@ var fab_spec_default = {
488
491
  overlay: {
489
492
  opacity: "sys.state.pressed"
490
493
  }
491
- }
492
- },
494
+ }},
493
495
  focusIndicator: {
494
496
  overlay: {
495
497
  opacity: "sys.state.focus"
@@ -1589,6 +1591,25 @@ var filter_spec_default = {
1589
1591
  opacity: "sys.state.pressed"
1590
1592
  }
1591
1593
  },
1594
+ focused: {
1595
+ overlay: {
1596
+ color: "label",
1597
+ opacity: "sys.state.focus"
1598
+ },
1599
+ focusRing: {
1600
+ composition: "outward",
1601
+ layer: "::after overlay \u2014 position:absolute, inset:0, no reflow (DESIGN.md Focus ring composition)",
1602
+ innerCounterRing: {
1603
+ width: "sys.borderWidth.hairline",
1604
+ color: "sys.color.focusInset"
1605
+ },
1606
+ outerRing: {
1607
+ width: "sys.borderWidth.thin",
1608
+ color: "sys.color.focus"
1609
+ }
1610
+ },
1611
+ 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."
1612
+ },
1592
1613
  disabled: {
1593
1614
  overlay: null,
1594
1615
  containerOpacity: "sys.state.disabled",
@@ -1696,6 +1717,25 @@ var tag_spec_default = {
1696
1717
  opacity: "sys.state.pressed"
1697
1718
  }
1698
1719
  },
1720
+ focused: {
1721
+ overlay: {
1722
+ color: "label",
1723
+ opacity: "sys.state.focus"
1724
+ },
1725
+ focusRing: {
1726
+ composition: "outward",
1727
+ layer: "::after overlay \u2014 position:absolute, inset:0, no reflow (DESIGN.md Focus ring composition)",
1728
+ innerCounterRing: {
1729
+ width: "sys.borderWidth.hairline",
1730
+ color: "sys.color.focusInset"
1731
+ },
1732
+ outerRing: {
1733
+ width: "sys.borderWidth.thin",
1734
+ color: "sys.color.focus"
1735
+ }
1736
+ },
1737
+ 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."
1738
+ },
1699
1739
  disabled: {
1700
1740
  overlay: null,
1701
1741
  containerOpacity: "sys.state.disabled",
@@ -1821,6 +1861,25 @@ var toggle_spec_default = {
1821
1861
  opacity: "sys.state.pressed"
1822
1862
  }
1823
1863
  },
1864
+ focused: {
1865
+ overlay: {
1866
+ color: "label",
1867
+ opacity: "sys.state.focus"
1868
+ },
1869
+ focusRing: {
1870
+ composition: "outward",
1871
+ layer: "::after overlay \u2014 position:absolute, inset:0, no reflow (DESIGN.md Focus ring composition)",
1872
+ innerCounterRing: {
1873
+ width: "sys.borderWidth.hairline",
1874
+ color: "sys.color.focusInset"
1875
+ },
1876
+ outerRing: {
1877
+ width: "sys.borderWidth.thin",
1878
+ color: "sys.color.focus"
1879
+ }
1880
+ },
1881
+ 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."
1882
+ },
1824
1883
  disabled: {
1825
1884
  overlay: null,
1826
1885
  containerOpacity: "sys.state.disabled",
@@ -2007,6 +2066,46 @@ var Button = forwardRef(function Button2({ variant, ...rest }, ref) {
2007
2066
  const Impl = variant && VARIANTS[variant] || ButtonStandard;
2008
2067
  return /* @__PURE__ */ jsx(Impl, { ref, ...rest });
2009
2068
  });
2069
+ var FOCUSABLE_SELECTOR = [
2070
+ "a[href]",
2071
+ "button",
2072
+ "input",
2073
+ "select",
2074
+ "textarea",
2075
+ '[tabindex]:not([tabindex="-1"])'
2076
+ ].join(",");
2077
+ function useFocusTrap(ref, active) {
2078
+ useEffect(() => {
2079
+ if (!active) return void 0;
2080
+ const onKey = (e) => {
2081
+ if (e.key !== "Tab") return;
2082
+ const container = ref.current;
2083
+ if (!container) return;
2084
+ const focusable = Array.from(
2085
+ container.querySelectorAll(FOCUSABLE_SELECTOR)
2086
+ ).filter((el) => !el.disabled && el.offsetParent !== null);
2087
+ if (focusable.length === 0) {
2088
+ e.preventDefault();
2089
+ container.focus({ preventScroll: true });
2090
+ return;
2091
+ }
2092
+ const first = focusable[0];
2093
+ const last = focusable[focusable.length - 1];
2094
+ const activeEl = document.activeElement;
2095
+ if (e.shiftKey) {
2096
+ if (activeEl === first || !container.contains(activeEl)) {
2097
+ e.preventDefault();
2098
+ last.focus({ preventScroll: true });
2099
+ }
2100
+ } else if (activeEl === last || !container.contains(activeEl)) {
2101
+ e.preventDefault();
2102
+ first.focus({ preventScroll: true });
2103
+ }
2104
+ };
2105
+ document.addEventListener("keydown", onKey);
2106
+ return () => document.removeEventListener("keydown", onKey);
2107
+ }, [ref, active]);
2108
+ }
2010
2109
  function useBodyScrollLock(locked) {
2011
2110
  useEffect(() => {
2012
2111
  if (!locked) return void 0;
@@ -2046,6 +2145,7 @@ function BottomSheet({
2046
2145
  const lastFocusedRef = useRef(null);
2047
2146
  const [overflowing, setOverflowing] = useState(false);
2048
2147
  useBodyScrollLock(open && !inline);
2148
+ useFocusTrap(cardRef, open && !inline);
2049
2149
  useEffect(() => {
2050
2150
  if (!open || inline) return void 0;
2051
2151
  const vv = typeof window !== "undefined" ? window.visualViewport : null;
@@ -2109,6 +2209,7 @@ function BottomSheet({
2109
2209
  className: "chorus-bottom-sheet__card",
2110
2210
  role: "dialog",
2111
2211
  "aria-modal": "true",
2212
+ tabIndex: -1,
2112
2213
  "aria-label": ariaLabel ?? title,
2113
2214
  onClick: (e) => e.stopPropagation(),
2114
2215
  ...rest,
@@ -2427,6 +2528,26 @@ function List({
2427
2528
  if (isRadio) onChange == null ? void 0 : onChange(item.value);
2428
2529
  (_a = item.onClick) == null ? void 0 : _a.call(item);
2429
2530
  };
2531
+ const rowMain = isEntry ? null : /* @__PURE__ */ jsxs(Fragment, { children: [
2532
+ isRadio ? /* @__PURE__ */ jsx("span", { className: "chorus-list__leading", children: /* @__PURE__ */ jsx(RadioIndicator, { selected }) }) : item.thumbnail ? /* @__PURE__ */ jsx("span", { className: "chorus-list__leading chorus-list__leading--image", children: /* @__PURE__ */ jsx(Thumbnail, { size: 40, ...item.thumbnail }) }) : item.icon ? /* @__PURE__ */ jsx("span", { className: "chorus-list__leading chorus-list__leading--icon", "aria-hidden": "true", children: item.icon }) : null,
2533
+ /* @__PURE__ */ jsxs("span", { className: "chorus-list__label-col", children: [
2534
+ /* @__PURE__ */ jsxs("span", { className: "chorus-list__primary-row", children: [
2535
+ /* @__PURE__ */ jsx("span", { className: "chorus-list__label", children: item.label }),
2536
+ item.count != null ? /* @__PURE__ */ jsx("span", { className: "chorus-list__count", children: item.count }) : null
2537
+ ] }),
2538
+ item.supportingText ? /* @__PURE__ */ jsx("span", { className: "chorus-list__supporting", children: item.supportingText }) : null
2539
+ ] }),
2540
+ item.nav && !item.trailingIcon ? /* @__PURE__ */ jsx("span", { className: "chorus-list__trailing chorus-list__nav-chevron", "aria-hidden": "true", children: /* @__PURE__ */ jsx(ChevronDownIcon, { size: 16 }) }) : item.trailingIcon ? /* @__PURE__ */ jsx(
2541
+ "span",
2542
+ {
2543
+ className: "chorus-list__trailing",
2544
+ "data-nested-action": "",
2545
+ onClick: (e) => e.stopPropagation(),
2546
+ onKeyDown: (e) => e.stopPropagation(),
2547
+ children: item.trailingIcon
2548
+ }
2549
+ ) : null
2550
+ ] });
2430
2551
  return /* @__PURE__ */ jsx(
2431
2552
  "div",
2432
2553
  {
@@ -2461,26 +2582,26 @@ function List({
2461
2582
  description: item.description,
2462
2583
  trailing: item.trailingIcon
2463
2584
  }
2464
- ) : /* @__PURE__ */ jsxs(Fragment, { children: [
2465
- isRadio ? /* @__PURE__ */ jsx("span", { className: "chorus-list__leading", children: /* @__PURE__ */ jsx(RadioIndicator, { selected }) }) : item.thumbnail ? /* @__PURE__ */ jsx("span", { className: "chorus-list__leading chorus-list__leading--image", children: /* @__PURE__ */ jsx(Thumbnail, { size: 40, ...item.thumbnail }) }) : item.icon ? /* @__PURE__ */ jsx("span", { className: "chorus-list__leading chorus-list__leading--icon", "aria-hidden": "true", children: item.icon }) : null,
2466
- /* @__PURE__ */ jsxs("span", { className: "chorus-list__label-col", children: [
2467
- /* @__PURE__ */ jsxs("span", { className: "chorus-list__primary-row", children: [
2468
- /* @__PURE__ */ jsx("span", { className: "chorus-list__label", children: item.label }),
2469
- item.count != null ? /* @__PURE__ */ jsx("span", { className: "chorus-list__count", children: item.count }) : null
2470
- ] }),
2471
- item.supportingText ? /* @__PURE__ */ jsx("span", { className: "chorus-list__supporting", children: item.supportingText }) : null
2472
- ] }),
2473
- item.nav && !item.trailingIcon ? /* @__PURE__ */ jsx("span", { className: "chorus-list__trailing chorus-list__nav-chevron", "aria-hidden": "true", children: /* @__PURE__ */ jsx(ChevronDownIcon, { size: 16 }) }) : item.trailingIcon ? /* @__PURE__ */ jsx(
2474
- "span",
2475
- {
2476
- className: "chorus-list__trailing",
2477
- "data-nested-action": "",
2478
- onClick: (e) => e.stopPropagation(),
2479
- onKeyDown: (e) => e.stopPropagation(),
2480
- children: item.trailingIcon
2481
- }
2482
- ) : null
2483
- ] })
2585
+ ) : item.banner ? (
2586
+ // Embedded-Banner row the text group stacks over a Banner
2587
+ // that spans the row's full content width, 8px (stack.xs)
2588
+ // below. The Banner is a nested action region: its own
2589
+ // controls never commit the row, and the row's hover/press
2590
+ // overlay is suppressed over it (see styles.css).
2591
+ /* @__PURE__ */ jsxs("span", { className: "chorus-list__stack", children: [
2592
+ /* @__PURE__ */ jsx("span", { className: "chorus-list__row-main", children: rowMain }),
2593
+ /* @__PURE__ */ jsx(
2594
+ "span",
2595
+ {
2596
+ className: "chorus-list__banner",
2597
+ "data-nested-action": "",
2598
+ onClick: (e) => e.stopPropagation(),
2599
+ onKeyDown: (e) => e.stopPropagation(),
2600
+ children: item.banner
2601
+ }
2602
+ )
2603
+ ] })
2604
+ ) : rowMain
2484
2605
  },
2485
2606
  item.value ?? idx
2486
2607
  );
@@ -2740,6 +2861,7 @@ function Dialog({
2740
2861
  const cardRef = useRef(null);
2741
2862
  const lastFocusedRef = useRef(null);
2742
2863
  useBodyScrollLock(open && !inline);
2864
+ useFocusTrap(cardRef, open && !inline);
2743
2865
  useEffect(() => {
2744
2866
  var _a;
2745
2867
  if (!open) return void 0;
@@ -2785,6 +2907,7 @@ function Dialog({
2785
2907
  className: joinClasses("chorus-dialog__card", image && "chorus-dialog__card--with-image"),
2786
2908
  role: "dialog",
2787
2909
  "aria-modal": "true",
2910
+ tabIndex: -1,
2788
2911
  "aria-label": ariaLabel ?? title,
2789
2912
  onClick: (e) => e.stopPropagation(),
2790
2913
  ...rest,
@@ -2818,6 +2941,80 @@ function Divider({
2818
2941
  }
2819
2942
  );
2820
2943
  }
2944
+
2945
+ // ../../schema/components/empty-state/empty-state.spec.json
2946
+ var empty_state_spec_default = {
2947
+ sizing: {
2948
+ illustrationSize: "ref.space.600",
2949
+ illustrationColor: "sys.color.onSurfaceVariant",
2950
+ illustrationGap: "sys.layout.stack.sm",
2951
+ headlineTypo: "sys.typo.heading.sm",
2952
+ headlineColor: "sys.color.onSurface",
2953
+ bodyTypo: "sys.typo.body.sm",
2954
+ bodyColor: "sys.color.onSurfaceVariant",
2955
+ bodyGap: "sys.layout.stack.2xs",
2956
+ actionGap: "sys.layout.stack.md"
2957
+ }};
2958
+ var s = empty_state_spec_default.sizing;
2959
+ var CONTAINER_STYLE = {
2960
+ "--empty-state-illustration-gap": tokenToCss(s.illustrationGap),
2961
+ "--empty-state-body-gap": tokenToCss(s.bodyGap),
2962
+ "--empty-state-action-gap": tokenToCss(s.actionGap)
2963
+ };
2964
+ var ILLUSTRATION_STYLE = {
2965
+ "--empty-state-illustration-size": tokenToCss(s.illustrationSize),
2966
+ "--empty-state-illustration-color": tokenToCss(s.illustrationColor)
2967
+ };
2968
+ var HEADLINE_STYLE = {
2969
+ "--empty-state-headline-color": tokenToCss(s.headlineColor),
2970
+ ...typoStyles(s.headlineTypo)
2971
+ };
2972
+ var BODY_STYLE = {
2973
+ "--empty-state-body-color": tokenToCss(s.bodyColor),
2974
+ ...typoStyles(s.bodyTypo)
2975
+ };
2976
+ function EmptyState({
2977
+ illustration,
2978
+ headline,
2979
+ body,
2980
+ action,
2981
+ className,
2982
+ style,
2983
+ ...rest
2984
+ }) {
2985
+ return /* @__PURE__ */ jsxs(
2986
+ "div",
2987
+ {
2988
+ role: "status",
2989
+ className: joinClasses("chorus-empty-state", className),
2990
+ style: { ...CONTAINER_STYLE, ...style },
2991
+ ...rest,
2992
+ children: [
2993
+ illustration ? /* @__PURE__ */ jsx(
2994
+ "span",
2995
+ {
2996
+ className: "chorus-empty-state__illustration",
2997
+ "aria-hidden": "true",
2998
+ style: ILLUSTRATION_STYLE,
2999
+ children: illustration
3000
+ }
3001
+ ) : null,
3002
+ /* @__PURE__ */ jsx("p", { className: "chorus-empty-state__headline", style: HEADLINE_STYLE, children: headline }),
3003
+ body ? /* @__PURE__ */ jsx("p", { className: "chorus-empty-state__body", style: BODY_STYLE, children: body }) : null,
3004
+ action ? /* @__PURE__ */ jsx("div", { className: "chorus-empty-state__action", children: /* @__PURE__ */ jsx(
3005
+ Button,
3006
+ {
3007
+ appearance: "primary",
3008
+ onClick: action.onClick ?? (action.href ? () => {
3009
+ window.location.assign(action.href);
3010
+ } : void 0),
3011
+ children: action.label
3012
+ }
3013
+ ) }) : null
3014
+ ]
3015
+ }
3016
+ );
3017
+ }
2821
3018
  var TabsContext = createContext({
2822
3019
  variant: "underline",
2823
3020
  value: null,
@@ -2940,19 +3137,19 @@ function TabsUnderline({ className, style, children, ...rest }) {
2940
3137
  useScrollOverflow(ref);
2941
3138
  useSlidingIndicator(ref, indicatorRef, value);
2942
3139
  useFullBleedGuard(ref, "Tabs");
2943
- const s = underline_spec_default.sizing;
3140
+ const s2 = underline_spec_default.sizing;
2944
3141
  const composedStyle = {
2945
- "--tabs-container-padding-inline": tokenToCss(s.containerPaddingInline),
2946
- "--tabs-tab-min-height": tokenToCss(s.minHeight),
2947
- "--tabs-tab-padding-block": tokenToCss(s.paddingBlock),
2948
- "--tabs-tab-padding-inline": tokenToCss(s.paddingInline),
2949
- "--tabs-inter-tab-gap": tokenToCss(s.interTabGap),
2950
- "--tabs-slot-gap": tokenToCss(s.slotGap),
2951
- "--tabs-icon-size": tokenToCss(s.iconSize),
2952
- "--tabs-indicator-height": tokenToCss(s.indicatorHeight),
2953
- "--tabs-divider-width": tokenToCss(s.dividerWidth),
2954
- "--tabs-divider-color": tokenToCss(s.dividerColor),
2955
- "--tabs-fade-width": tokenToCss(s.fadeWidth),
3142
+ "--tabs-container-padding-inline": tokenToCss(s2.containerPaddingInline),
3143
+ "--tabs-tab-min-height": tokenToCss(s2.minHeight),
3144
+ "--tabs-tab-padding-block": tokenToCss(s2.paddingBlock),
3145
+ "--tabs-tab-padding-inline": tokenToCss(s2.paddingInline),
3146
+ "--tabs-inter-tab-gap": tokenToCss(s2.interTabGap),
3147
+ "--tabs-slot-gap": tokenToCss(s2.slotGap),
3148
+ "--tabs-icon-size": tokenToCss(s2.iconSize),
3149
+ "--tabs-indicator-height": tokenToCss(s2.indicatorHeight),
3150
+ "--tabs-divider-width": tokenToCss(s2.dividerWidth),
3151
+ "--tabs-divider-color": tokenToCss(s2.dividerColor),
3152
+ "--tabs-fade-width": tokenToCss(s2.fadeWidth),
2956
3153
  "--tabs-label-unselected": tokenToCss(underline_spec_default.selectionStates.unselected.label),
2957
3154
  "--tabs-label-selected": tokenToCss(underline_spec_default.selectionStates.selected.label),
2958
3155
  "--tabs-indicator-color": tokenToCss(underline_spec_default.selectionStates.selected.indicator),
@@ -2964,7 +3161,7 @@ function TabsUnderline({ className, style, children, ...rest }) {
2964
3161
  "--tabs-focus-outer-color": tokenToCss(underline_spec_default.focusIndicator.ring.outerColor),
2965
3162
  "--tabs-focus-inset-width": tokenToCss(underline_spec_default.focusIndicator.ring.insetWidth),
2966
3163
  "--tabs-focus-inset-color": tokenToCss(underline_spec_default.focusIndicator.ring.insetColor),
2967
- ...typoStyles(s.labelTypo),
3164
+ ...typoStyles(s2.labelTypo),
2968
3165
  ...style
2969
3166
  };
2970
3167
  return /* @__PURE__ */ jsxs(
@@ -3026,12 +3223,12 @@ function TabsSegmented({ className, style, children, ...rest }) {
3026
3223
  const ref = useRef(null);
3027
3224
  useScrollOverflow(ref);
3028
3225
  useFullBleedGuard(ref, "Tabs");
3029
- const s = segmented_spec_default.sizing;
3226
+ const s2 = segmented_spec_default.sizing;
3030
3227
  const composedStyle = {
3031
- "--tabs-container-padding-block": tokenToCss(s.containerPaddingBlock),
3032
- "--tabs-container-padding-inline": tokenToCss(s.containerPaddingInline),
3033
- "--tabs-inter-segment-gap": tokenToCss(s.interSegmentGap),
3034
- "--tabs-fade-width": tokenToCss(s.fadeWidth),
3228
+ "--tabs-container-padding-block": tokenToCss(s2.containerPaddingBlock),
3229
+ "--tabs-container-padding-inline": tokenToCss(s2.containerPaddingInline),
3230
+ "--tabs-inter-segment-gap": tokenToCss(s2.interSegmentGap),
3231
+ "--tabs-fade-width": tokenToCss(s2.fadeWidth),
3035
3232
  ...style
3036
3233
  };
3037
3234
  return /* @__PURE__ */ jsx(
@@ -4371,12 +4568,19 @@ var input_spec_default = {
4371
4568
  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."
4372
4569
  },
4373
4570
  active: {
4571
+ isFocusState: true,
4374
4572
  overlay: null,
4375
4573
  border: "borderActive",
4376
4574
  strokeWeight: "activeStrokeWeight",
4377
4575
  caret: "visible",
4378
4576
  showsClearWhenValue: true,
4379
- 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)."
4577
+ focusRing: {
4578
+ composition: "outward",
4579
+ layer: "::after overlay \u2014 position:absolute, inset:0, no reflow (DESIGN.md Focus ring composition)",
4580
+ innerCounterRing: { width: "sys.borderWidth.hairline", color: "sys.color.focusInset" },
4581
+ outerRing: { width: "sys.borderWidth.thin", color: "sys.color.focus" }
4582
+ },
4583
+ 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 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)."
4380
4584
  },
4381
4585
  disabled: {
4382
4586
  overlay: null,
@@ -4558,11 +4762,18 @@ var textarea_spec_default = {
4558
4762
  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."
4559
4763
  },
4560
4764
  active: {
4765
+ isFocusState: true,
4561
4766
  overlay: null,
4562
4767
  border: "borderActive",
4563
4768
  strokeWeight: "activeStrokeWeight",
4564
4769
  caret: "visible",
4565
- note: "Stroke steps from `hairline` (1px) to `activeStrokeWeight` (2px) as an inset box-shadow \u2014 same pixel-stable contract as input."
4770
+ focusRing: {
4771
+ composition: "outward",
4772
+ layer: "::after overlay \u2014 position:absolute, inset:0, no reflow (DESIGN.md Focus ring composition)",
4773
+ innerCounterRing: { width: "sys.borderWidth.hairline", color: "sys.color.focusInset" },
4774
+ outerRing: { width: "sys.borderWidth.thin", color: "sys.color.focus" }
4775
+ },
4776
+ 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 steps from `hairline` (1px) to `activeStrokeWeight` (2px) as an inset box-shadow \u2014 same pixel-stable contract as input."
4566
4777
  },
4567
4778
  disabled: {
4568
4779
  overlay: null,
@@ -4696,12 +4907,19 @@ var search_spec_default = {
4696
4907
  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."
4697
4908
  },
4698
4909
  active: {
4910
+ isFocusState: true,
4699
4911
  overlay: null,
4700
4912
  border: "borderActive",
4701
4913
  strokeWeight: "activeStrokeWeight",
4702
4914
  caret: "visible",
4703
4915
  showsClearWhenValue: true,
4704
- 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)."
4916
+ focusRing: {
4917
+ composition: "outward",
4918
+ layer: "::after overlay \u2014 position:absolute, inset:0, no reflow (DESIGN.md Focus ring composition)",
4919
+ innerCounterRing: { width: "sys.borderWidth.hairline", color: "sys.color.focusInset" },
4920
+ outerRing: { width: "sys.borderWidth.thin", color: "sys.color.focus" }
4921
+ },
4922
+ 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 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)."
4705
4923
  },
4706
4924
  disabled: {
4707
4925
  overlay: null,
@@ -4888,9 +5106,17 @@ var select_spec_default = {
4888
5106
  }
4889
5107
  },
4890
5108
  active: {
5109
+ isFocusState: true,
4891
5110
  overlay: null,
4892
5111
  border: "borderActive",
4893
- strokeWeight: "activeStrokeWeight"
5112
+ strokeWeight: "activeStrokeWeight",
5113
+ focusRing: {
5114
+ composition: "outward",
5115
+ layer: "::after overlay \u2014 position:absolute, inset:0, no reflow (DESIGN.md Focus ring composition)",
5116
+ innerCounterRing: { width: "sys.borderWidth.hairline", color: "sys.color.focusInset" },
5117
+ outerRing: { width: "sys.borderWidth.thin", color: "sys.color.focus" }
5118
+ },
5119
+ 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` (2px) as an inset box-shadow, pixel-stable (no reflow)."
4894
5120
  },
4895
5121
  disabled: {
4896
5122
  overlay: null,
@@ -5511,6 +5737,58 @@ function SkeletonGroup({
5511
5737
  }
5512
5738
  );
5513
5739
  }
5740
+
5741
+ // ../../schema/components/spinner/spinner.spec.json
5742
+ var spinner_spec_default = {
5743
+ sizes: {
5744
+ medium: {
5745
+ diameter: "sys.icon.lg",
5746
+ labelTypo: "sys.typo.body.sm",
5747
+ gap: "sys.layout.inline.sm"
5748
+ },
5749
+ small: {
5750
+ diameter: "sys.icon.md",
5751
+ labelTypo: "sys.typo.body.sm",
5752
+ gap: "sys.layout.inline.sm"
5753
+ }
5754
+ }};
5755
+ function sizingStyle4(spec, size) {
5756
+ const s2 = spec.sizes[size] ?? spec.sizes.medium;
5757
+ return {
5758
+ "--spinner-diameter": tokenToCss(s2.diameter),
5759
+ "--spinner-gap": tokenToCss(s2.gap)
5760
+ };
5761
+ }
5762
+ function Spinner({
5763
+ size = "medium",
5764
+ label,
5765
+ className,
5766
+ style,
5767
+ "aria-label": ariaLabel,
5768
+ ...rest
5769
+ }) {
5770
+ var _a;
5771
+ const labelTypo = ((_a = spinner_spec_default.sizes[size]) == null ? void 0 : _a.labelTypo) ?? spinner_spec_default.sizes.medium.labelTypo;
5772
+ const a11yLabel = label != null ? void 0 : ariaLabel ?? "Loading";
5773
+ return /* @__PURE__ */ jsxs(
5774
+ "span",
5775
+ {
5776
+ role: "status",
5777
+ "aria-label": a11yLabel,
5778
+ className: joinClasses(
5779
+ "chorus-spinner",
5780
+ `chorus-spinner--${size}`,
5781
+ className
5782
+ ),
5783
+ style: { ...sizingStyle4(spinner_spec_default, size), ...style },
5784
+ ...rest,
5785
+ children: [
5786
+ /* @__PURE__ */ jsx("span", { className: "chorus-spinner__arc", "aria-hidden": "true" }),
5787
+ label != null ? /* @__PURE__ */ jsx("span", { className: "chorus-spinner__label", style: typoStyles(labelTypo), children: label }) : null
5788
+ ]
5789
+ }
5790
+ );
5791
+ }
5514
5792
  function StatusTag({
5515
5793
  appearance = "neutral",
5516
5794
  children,
@@ -5896,6 +6174,6 @@ function Tooltip({
5896
6174
  );
5897
6175
  }
5898
6176
 
5899
- export { Accordion, Banner as Alert, NavigationBar as AppBar, Thumbnail as Avatar, AvatarRail, Badge, Banner, TabBar as BottomNav, BottomSheet, Bubble, Button, ButtonGroup, Carousel, SuggestionList as ChannelList, AvatarRail as ChannelRail, Chip, Dialog, DirectoryList, Divider, BottomSheet as Drawer, Feed, FeedAd, FeedGroup, FormField, FormFieldGroup, Header, Input, List, Metadata, NavCard, NavCardGroup, NavList, NavigationBar, PageShell, Pagination, PostCarousel, ProfileCarousel, ProfileHeader, Progress, SearchBar2 as SearchBar, Carousel as Section, Select, BottomSheet as Sheet, SideSheet as SideDrawer, SideSheet, SideSheetGroup, Skeleton, SkeletonGroup, StatusTag, SubHeader, SuggestionList, Switch, Tab, TabBar, Tabs, Textarea, Thumbnail, Toast, Tooltip };
6177
+ export { Accordion, Banner as Alert, NavigationBar as AppBar, Thumbnail as Avatar, AvatarRail, Badge, Banner, TabBar as BottomNav, BottomSheet, Bubble, Button, ButtonGroup, Carousel, SuggestionList as ChannelList, AvatarRail as ChannelRail, Chip, Dialog, DirectoryList, Divider, BottomSheet as Drawer, EmptyState, Feed, FeedAd, FeedGroup, FormField, FormFieldGroup, Header, Input, List, Metadata, NavCard, NavCardGroup, NavList, NavigationBar, PageShell, Pagination, PostCarousel, ProfileCarousel, ProfileHeader, Progress, SearchBar2 as SearchBar, Carousel as Section, Select, BottomSheet as Sheet, SideSheet as SideDrawer, SideSheet, SideSheetGroup, Skeleton, SkeletonGroup, Spinner, StatusTag, SubHeader, SuggestionList, Switch, Tab, TabBar, Tabs, Textarea, Thumbnail, Toast, Tooltip };
5900
6178
  //# sourceMappingURL=index.js.map
5901
6179
  //# sourceMappingURL=index.js.map