gridsmith-ui 0.30.1 → 0.31.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 (80) hide show
  1. package/CHANGELOG.md +73 -1
  2. package/dist/charts.d.ts +114 -1
  3. package/dist/charts.js +1113 -206
  4. package/dist/charts.js.map +1 -1
  5. package/dist/chat.js +24 -10
  6. package/dist/chat.js.map +1 -1
  7. package/dist/{chunk-ULCACQSA.js → chunk-2Q4HOGEI.js} +16 -51
  8. package/dist/chunk-2Q4HOGEI.js.map +1 -0
  9. package/dist/{chunk-T52LAFKG.js → chunk-3J67NISC.js} +13 -15
  10. package/dist/chunk-3J67NISC.js.map +1 -0
  11. package/dist/{chunk-L6HWACK7.js → chunk-53F7GVBQ.js} +6 -3
  12. package/dist/chunk-53F7GVBQ.js.map +1 -0
  13. package/dist/{chunk-SLRTSBCJ.js → chunk-BKYWKMUN.js} +73 -678
  14. package/dist/chunk-BKYWKMUN.js.map +1 -0
  15. package/dist/{chunk-KMKBOTFR.js → chunk-EKWXPSTI.js} +10 -12
  16. package/dist/chunk-EKWXPSTI.js.map +1 -0
  17. package/dist/{chunk-SJZWEBHL.js → chunk-GP2COVJC.js} +24 -11
  18. package/dist/chunk-GP2COVJC.js.map +1 -0
  19. package/dist/{chunk-OVHBQT3Z.js → chunk-GT6M74IZ.js} +6 -6
  20. package/dist/{chunk-OVHBQT3Z.js.map → chunk-GT6M74IZ.js.map} +1 -1
  21. package/dist/{chunk-IELIF4PJ.js → chunk-HUNEJIKV.js} +3 -3
  22. package/dist/{chunk-IELIF4PJ.js.map → chunk-HUNEJIKV.js.map} +1 -1
  23. package/dist/{chunk-NEUWZUK6.js → chunk-LE72KHIE.js} +3 -4
  24. package/dist/chunk-LE72KHIE.js.map +1 -0
  25. package/dist/{chunk-OX3NNMWG.js → chunk-OFACAHRV.js} +10 -6
  26. package/dist/chunk-OFACAHRV.js.map +1 -0
  27. package/dist/{chunk-R5QXMNW7.js → chunk-OWQ7M6R7.js} +177 -27
  28. package/dist/chunk-OWQ7M6R7.js.map +1 -0
  29. package/dist/{chunk-TYNDXMBV.js → chunk-QOMP4D6V.js} +3 -3
  30. package/dist/{chunk-TYNDXMBV.js.map → chunk-QOMP4D6V.js.map} +1 -1
  31. package/dist/chunk-U7WBI4AV.js +285 -0
  32. package/dist/chunk-U7WBI4AV.js.map +1 -0
  33. package/dist/chunk-V2ARGHJN.js +755 -0
  34. package/dist/chunk-V2ARGHJN.js.map +1 -0
  35. package/dist/chunk-V73LHNRL.js +51 -0
  36. package/dist/chunk-V73LHNRL.js.map +1 -0
  37. package/dist/{chunk-UDJOIVKQ.js → chunk-XSQD3UGY.js} +77 -7
  38. package/dist/chunk-XSQD3UGY.js.map +1 -0
  39. package/dist/{chunk-ISONVOA7.js → chunk-YFHTREH5.js} +6 -8
  40. package/dist/chunk-YFHTREH5.js.map +1 -0
  41. package/dist/data-grid.js +10 -13
  42. package/dist/data-grid.js.map +1 -1
  43. package/dist/editor-mentions.js +2 -4
  44. package/dist/editor-mentions.js.map +1 -1
  45. package/dist/editor.js +3 -7
  46. package/dist/editor.js.map +1 -1
  47. package/dist/icons.js +1 -1
  48. package/dist/index.d.ts +104 -16
  49. package/dist/index.js +1075 -654
  50. package/dist/index.js.map +1 -1
  51. package/dist/kanban.js +9 -13
  52. package/dist/kanban.js.map +1 -1
  53. package/dist/resizable.js +9 -13
  54. package/dist/resizable.js.map +1 -1
  55. package/dist/virtualized.js +10 -13
  56. package/dist/virtualized.js.map +1 -1
  57. package/package.json +1 -1
  58. package/dist/chunk-6C2IUMY7.js +0 -46
  59. package/dist/chunk-6C2IUMY7.js.map +0 -1
  60. package/dist/chunk-7Q46JXSK.js +0 -51
  61. package/dist/chunk-7Q46JXSK.js.map +0 -1
  62. package/dist/chunk-HTXP5BMM.js +0 -38
  63. package/dist/chunk-HTXP5BMM.js.map +0 -1
  64. package/dist/chunk-ISONVOA7.js.map +0 -1
  65. package/dist/chunk-KMKBOTFR.js.map +0 -1
  66. package/dist/chunk-L6HWACK7.js.map +0 -1
  67. package/dist/chunk-NEUWZUK6.js.map +0 -1
  68. package/dist/chunk-NKUCS67T.js +0 -13
  69. package/dist/chunk-NKUCS67T.js.map +0 -1
  70. package/dist/chunk-OX3NNMWG.js.map +0 -1
  71. package/dist/chunk-R5QXMNW7.js.map +0 -1
  72. package/dist/chunk-SJZWEBHL.js.map +0 -1
  73. package/dist/chunk-SLRTSBCJ.js.map +0 -1
  74. package/dist/chunk-T52LAFKG.js.map +0 -1
  75. package/dist/chunk-UDJOIVKQ.js.map +0 -1
  76. package/dist/chunk-ULCACQSA.js.map +0 -1
  77. package/dist/chunk-WIUGPZOH.js +0 -88
  78. package/dist/chunk-WIUGPZOH.js.map +0 -1
  79. package/dist/chunk-YL5KD5RC.js +0 -49
  80. package/dist/chunk-YL5KD5RC.js.map +0 -1
@@ -0,0 +1,285 @@
1
+ "use client";
2
+ import { useFieldControl, Dropdown } from './chunk-BKYWKMUN.js';
3
+ import { useReportedChange } from './chunk-72SDAX54.js';
4
+ import { ControlSizeContext } from './chunk-2Q4HOGEI.js';
5
+ import { cn, rovingTabStop, FOCUS_RING } from './chunk-OWQ7M6R7.js';
6
+ import { jsx, jsxs } from 'react/jsx-runtime';
7
+ import { useContext, useRef, useState, useCallback, useLayoutEffect, useEffect } from 'react';
8
+
9
+ function Bone({ className, style, "data-gs": marker }) {
10
+ return /* @__PURE__ */ jsx(
11
+ "div",
12
+ {
13
+ "data-gs": marker,
14
+ className: cn("rounded-[var(--ds-radius-sm)] bg-[var(--ds-bg-emphasis)] animate-pulse", className),
15
+ style
16
+ }
17
+ );
18
+ }
19
+ function Skeleton({ variant = "text", lines = 3, width, height, className }) {
20
+ if (variant === "custom") {
21
+ return /* @__PURE__ */ jsx(Bone, { "data-gs": "skeleton", className, style: { width, height } });
22
+ }
23
+ if (variant === "avatar") {
24
+ return /* @__PURE__ */ jsx(Bone, { "data-gs": "skeleton", className: cn("h-10 w-10 rounded-[var(--ds-radius-full)]", className) });
25
+ }
26
+ if (variant === "card") {
27
+ return /* @__PURE__ */ jsxs("div", { "data-gs": "skeleton", className: cn("space-y-ds-3", className), children: [
28
+ /* @__PURE__ */ jsx(Bone, { className: "h-32 w-full rounded-[var(--ds-radius-md)]" }),
29
+ /* @__PURE__ */ jsx(Bone, { className: "h-4 w-3/4" }),
30
+ /* @__PURE__ */ jsx(Bone, { className: "h-4 w-1/2" })
31
+ ] });
32
+ }
33
+ if (variant === "table-row") {
34
+ return /* @__PURE__ */ jsxs("div", { "data-gs": "skeleton", className: cn("flex gap-ds-4", className), children: [
35
+ /* @__PURE__ */ jsx(Bone, { className: "h-4 flex-[3]" }),
36
+ /* @__PURE__ */ jsx(Bone, { className: "h-4 flex-[2]" }),
37
+ /* @__PURE__ */ jsx(Bone, { className: "h-4 flex-1" }),
38
+ /* @__PURE__ */ jsx(Bone, { className: "h-4 flex-1" })
39
+ ] });
40
+ }
41
+ const widths = ["w-3/4", "w-1/2", "w-5/6", "w-2/3", "w-4/5"];
42
+ return /* @__PURE__ */ jsx("div", { "data-gs": "skeleton", className: cn("space-y-ds-3", className), children: Array.from({ length: lines }, (_, i) => /* @__PURE__ */ jsx(Bone, { className: cn("h-4", widths[i % widths.length]) }, i)) });
43
+ }
44
+ function optionsKeyForFit(options) {
45
+ return options.map((o) => `${o.value}:${o.label}`).join("|");
46
+ }
47
+ var sizes = {
48
+ xs: "text-xs px-ds-2",
49
+ sm: "text-xs px-ds-3",
50
+ md: "text-sm px-ds-3",
51
+ lg: "text-base px-ds-3"
52
+ };
53
+ var containerPadding = {
54
+ xs: "p-[2px] gap-[2px]",
55
+ sm: "p-ds-1 gap-ds-0.5",
56
+ md: "p-ds-1 gap-ds-0.5",
57
+ lg: "p-ds-1 gap-ds-0.5"
58
+ };
59
+ var indicatorInset = {
60
+ xs: "2px",
61
+ sm: "4px",
62
+ md: "4px",
63
+ lg: "4px"
64
+ };
65
+ function segmentRadius(inset, bordered) {
66
+ return `max(var(--ds-radius-sm), calc(var(--ds-radius-md) - ${inset}${bordered ? " - var(--ds-border-width)" : ""}))`;
67
+ }
68
+ function SegmentedControl({
69
+ value,
70
+ onChange: onChangeProp,
71
+ options,
72
+ size: sizeProp,
73
+ variant = "default",
74
+ fullWidth = false,
75
+ onHoverChange,
76
+ className,
77
+ "aria-label": ariaLabel,
78
+ maxOptions = 6,
79
+ ...wiring
80
+ }) {
81
+ const onChange = useReportedChange(onChangeProp, value);
82
+ const contextSize = useContext(ControlSizeContext);
83
+ const control = useFieldControl({ ...wiring, "aria-label": ariaLabel }, { component: "SegmentedControl" });
84
+ const { "aria-labelledby": _pageLabelledBy, ...dropdownWiring } = wiring;
85
+ const size = sizeProp ?? contextSize ?? "md";
86
+ const containerRef = useRef(null);
87
+ const [indicator, setIndicator] = useState({ left: 0, width: 0 });
88
+ const parentRef = useRef(null);
89
+ const neededRef = useRef(0);
90
+ const [overflows, setOverflows] = useState(false);
91
+ const measureFit = useCallback(() => {
92
+ const row = containerRef.current;
93
+ if (row) {
94
+ parentRef.current = row.parentElement;
95
+ if (row.scrollWidth > 0) neededRef.current = row.scrollWidth;
96
+ }
97
+ const parent = parentRef.current;
98
+ const needed = neededRef.current;
99
+ if (!parent || !needed) return;
100
+ const style = getComputedStyle(parent);
101
+ const available = parent.clientWidth - (parseFloat(style.paddingLeft) || 0) - (parseFloat(style.paddingRight) || 0);
102
+ if (available <= 0) return;
103
+ setOverflows((prev) => prev ? needed > available - 8 : needed > available);
104
+ }, []);
105
+ useLayoutEffect(() => {
106
+ measureFit();
107
+ const parent = parentRef.current;
108
+ if (!parent || typeof ResizeObserver === "undefined") return;
109
+ const observer = new ResizeObserver(measureFit);
110
+ observer.observe(parent);
111
+ return () => observer.disconnect();
112
+ }, [measureFit, optionsKeyForFit(options)]);
113
+ const collapsed = options.length > maxOptions || overflows;
114
+ const updateIndicator = useCallback(() => {
115
+ if (!containerRef.current) return;
116
+ const active = containerRef.current.querySelector("[data-active='true']");
117
+ if (active) {
118
+ setIndicator({
119
+ left: active.offsetLeft,
120
+ width: active.offsetWidth
121
+ });
122
+ }
123
+ }, []);
124
+ const optionsKey = options.map((o) => `${o.value}:${o.label}`).join("|");
125
+ useLayoutEffect(() => {
126
+ updateIndicator();
127
+ }, [value, optionsKey, updateIndicator]);
128
+ useEffect(() => {
129
+ const fonts = document.fonts;
130
+ if (!fonts?.ready) return;
131
+ let live = true;
132
+ fonts.ready.then(() => {
133
+ if (live) updateIndicator();
134
+ });
135
+ return () => {
136
+ live = false;
137
+ };
138
+ }, [updateIndicator]);
139
+ const [animate, setAnimate] = useState(false);
140
+ const select = useCallback((v) => {
141
+ setAnimate(true);
142
+ onChange(v);
143
+ }, [onChange]);
144
+ useLayoutEffect(() => {
145
+ if (collapsed || !containerRef.current || typeof ResizeObserver === "undefined") return;
146
+ const observer = new ResizeObserver(updateIndicator);
147
+ observer.observe(containerRef.current);
148
+ containerRef.current.querySelectorAll("button").forEach((b) => observer.observe(b));
149
+ return () => observer.disconnect();
150
+ }, [updateIndicator, optionsKey, collapsed]);
151
+ const containerHeight = {
152
+ xs: "h-ds-6",
153
+ sm: "h-[var(--ds-control-height-sm)]",
154
+ md: "h-[var(--ds-control-height-md)]",
155
+ lg: "h-[var(--ds-control-height-lg)]"
156
+ };
157
+ const tabStop = rovingTabStop(options, (o) => o.value === value, (o) => !!o.disabled);
158
+ const handleKeyDown = (e) => {
159
+ const enabled = options.filter((o) => !o.disabled);
160
+ if (enabled.length === 0) return;
161
+ const idx = Math.max(0, enabled.findIndex((o) => o.value === value));
162
+ let next = null;
163
+ if (e.key === "ArrowRight" || e.key === "ArrowDown") next = (idx + 1) % enabled.length;
164
+ else if (e.key === "ArrowLeft" || e.key === "ArrowUp") next = (idx - 1 + enabled.length) % enabled.length;
165
+ else if (e.key === "Home") next = 0;
166
+ else if (e.key === "End") next = enabled.length - 1;
167
+ if (next === null) return;
168
+ e.preventDefault();
169
+ select(enabled[next].value);
170
+ const buttons = containerRef.current?.querySelectorAll('[role="radio"]');
171
+ buttons?.forEach((b) => {
172
+ if (b.dataset.value === enabled[next].value) b.focus();
173
+ });
174
+ };
175
+ if (collapsed) {
176
+ return /* @__PURE__ */ jsx(
177
+ Dropdown,
178
+ {
179
+ ...dropdownWiring,
180
+ "aria-label": control.shown ? void 0 : control.label,
181
+ "aria-labelledby": control.shown ? void 0 : wiring["aria-labelledby"],
182
+ size: size === "md" || size === "lg" ? "md" : "sm",
183
+ options: options.filter((o) => !o.disabled).map((o) => ({ id: o.value, label: o.label })),
184
+ value,
185
+ onChange: (v) => onChange(v),
186
+ className: cn("inline-block", fullWidth ? "w-full" : "w-auto", className)
187
+ }
188
+ );
189
+ }
190
+ return /* @__PURE__ */ jsxs(
191
+ "div",
192
+ {
193
+ ref: containerRef,
194
+ role: "radiogroup",
195
+ id: control.id,
196
+ "aria-label": control.labelId ? void 0 : control.label,
197
+ "aria-labelledby": control.labelId,
198
+ "aria-describedby": control.describedBy,
199
+ "aria-invalid": control.invalid || void 0,
200
+ "aria-required": control.required || void 0,
201
+ onKeyDown: handleKeyDown,
202
+ className: cn(
203
+ "relative items-center",
204
+ // Sprint 25.14 A21: in a Field's column a track that isn't full width keeps its own width; w-fit, not self-start,
205
+ // so a row or a centred column still aligns it (the review of 30 September)
206
+ fullWidth ? "flex w-full" : "inline-flex w-fit",
207
+ containerPadding[size],
208
+ containerHeight[size],
209
+ "bg-[var(--ds-bg-muted)]",
210
+ // The owner's review of 1 October 2026: the frame's corner is a field's, so the two change together with the
211
+ // theme's radius; the segments inside sit concentric with it (SEGMENT_RADIUS)
212
+ "rounded-[var(--ds-radius-md)]",
213
+ // The frame's edge is a button's: each segment is a labelled button, so the frame isn't what identifies the
214
+ // control, and WCAG 1.4.11 asks no 3:1 of it
215
+ variant === "default" && "border-[length:var(--ds-border-width)] border-[var(--ds-border-default)]",
216
+ control.invalid && "border-[length:var(--ds-border-width)] border-[var(--ds-status-error)]",
217
+ className
218
+ ),
219
+ style: { "--ds-segment-radius": segmentRadius(indicatorInset[size], variant === "default" || !!control.invalid) },
220
+ children: [
221
+ /* @__PURE__ */ jsx(
222
+ "div",
223
+ {
224
+ className: cn(
225
+ "absolute rounded-[var(--ds-segment-radius)]",
226
+ "bg-[var(--ds-bg-surface)]",
227
+ // The selected segment is a kind of button, so it takes a secondary button's surface and edge, as the frame
228
+ // takes a button's edge (the owner, 3 October 2026: the 3:1 strong edge it kept from Sprint 25.14 drew the
229
+ // darker outline 25.19 took off the fields). It shows its state as the raised surface on the muted track, with
230
+ // the default variant's shadow and its label's stronger colour. While its segment has keyboard focus the shadow
231
+ // steps aside, as a button's does, so the segment's outline sits clear (Sprint 30, D7)
232
+ "border-[length:var(--ds-border-width)] border-[var(--ds-border-default)]",
233
+ variant === "default" && "shadow-[var(--ds-shadow-sm)]",
234
+ "[[role=radiogroup]:has([aria-checked=true]:focus-visible)_&]:shadow-none",
235
+ "transition-all motion-reduce:transition-none"
236
+ ),
237
+ "data-animate": animate ? "true" : "false",
238
+ style: {
239
+ left: indicator.left,
240
+ width: indicator.width,
241
+ top: indicatorInset[size],
242
+ bottom: indicatorInset[size],
243
+ transitionDuration: animate ? "var(--ds-transition-speed)" : "0ms",
244
+ transitionTimingFunction: "var(--ds-transition-ease)"
245
+ }
246
+ }
247
+ ),
248
+ options.map((opt, i) => /* @__PURE__ */ jsx(
249
+ "button",
250
+ {
251
+ type: "button",
252
+ role: "radio",
253
+ "aria-checked": opt.value === value,
254
+ "data-active": opt.value === value,
255
+ "data-value": opt.value,
256
+ tabIndex: i === tabStop ? 0 : -1,
257
+ disabled: opt.disabled,
258
+ onClick: () => !opt.disabled && select(opt.value),
259
+ onMouseEnter: onHoverChange && !opt.disabled ? () => onHoverChange(opt.value) : void 0,
260
+ onMouseLeave: onHoverChange ? () => onHoverChange(null) : void 0,
261
+ className: cn(
262
+ "relative z-10 inline-flex items-center justify-center self-stretch font-medium whitespace-nowrap",
263
+ fullWidth && "flex-1",
264
+ "rounded-[var(--ds-segment-radius)]",
265
+ "transition-colors cursor-pointer",
266
+ FOCUS_RING,
267
+ sizes[size],
268
+ opt.value === value ? "text-[var(--ds-text-primary)]" : "text-[var(--ds-text-muted)] hover:text-[var(--ds-text-secondary)]",
269
+ opt.disabled && "opacity-disabled cursor-not-allowed"
270
+ ),
271
+ children: opt.label
272
+ },
273
+ opt.value
274
+ ))
275
+ ]
276
+ }
277
+ );
278
+ }
279
+
280
+ // ../../showcase/src/lib/title-text.ts
281
+ var TITLE_TEXT = "font-[family-name:var(--ds-font-display)] text-lg font-[var(--ds-weight-heading)] text-[var(--ds-text-primary)] break-words";
282
+
283
+ export { SegmentedControl, Skeleton, TITLE_TEXT };
284
+ //# sourceMappingURL=chunk-U7WBI4AV.js.map
285
+ //# sourceMappingURL=chunk-U7WBI4AV.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../showcase/src/components/Skeleton.tsx","../../../showcase/src/components/SegmentedControl.tsx","../../../showcase/src/lib/title-text.ts"],"names":["jsx","jsxs"],"mappings":"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","file":"chunk-U7WBI4AV.js","sourcesContent":["import { cn } from \"../lib/utils\";\n\ninterface SkeletonProps {\n variant?: \"text\" | \"avatar\" | \"card\" | \"table-row\" | \"custom\";\n lines?: number;\n width?: string;\n height?: string;\n className?: string;\n}\n\nfunction Bone({ className, style, \"data-gs\": marker }: { className?: string; style?: React.CSSProperties; \"data-gs\"?: string }) {\n return (\n <div\n data-gs={marker}\n className={cn(\"rounded-[var(--ds-radius-sm)] bg-[var(--ds-bg-emphasis)] animate-pulse\", className)}\n style={style}\n />\n );\n}\n\nexport function Skeleton({ variant = \"text\", lines = 3, width, height, className }: SkeletonProps) {\n if (variant === \"custom\") {\n return <Bone data-gs=\"skeleton\" className={className} style={{ width, height }} />;\n }\n\n if (variant === \"avatar\") {\n return <Bone data-gs=\"skeleton\" className={cn(\"h-10 w-10 rounded-[var(--ds-radius-full)]\", className)} />;\n }\n\n if (variant === \"card\") {\n return (\n <div data-gs=\"skeleton\" className={cn(\"space-y-ds-3\", className)}>\n <Bone className=\"h-32 w-full rounded-[var(--ds-radius-md)]\" />\n <Bone className=\"h-4 w-3/4\" />\n <Bone className=\"h-4 w-1/2\" />\n </div>\n );\n }\n\n if (variant === \"table-row\") {\n return (\n <div data-gs=\"skeleton\" className={cn(\"flex gap-ds-4\", className)}>\n <Bone className=\"h-4 flex-[3]\" />\n <Bone className=\"h-4 flex-[2]\" />\n <Bone className=\"h-4 flex-1\" />\n <Bone className=\"h-4 flex-1\" />\n </div>\n );\n }\n\n // text variant\n const widths = [\"w-3/4\", \"w-1/2\", \"w-5/6\", \"w-2/3\", \"w-4/5\"];\n return (\n <div data-gs=\"skeleton\" className={cn(\"space-y-ds-3\", className)}>\n {Array.from({ length: lines }, (_, i) => (\n <Bone key={i} className={cn(\"h-4\", widths[i % widths.length])} />\n ))}\n </div>\n );\n}\n","import { useRef, useState, useLayoutEffect, useEffect, useCallback, useContext } from \"react\";\nimport { cn, FOCUS_RING } from \"../lib/utils\";\nimport { rovingTabStop } from \"../lib/focus\";\nimport { Dropdown } from \"./Dropdown\";\nimport { ControlSizeContext } from \"./Button\";\nimport { useFieldControl, type FieldControlWiring } from \"./Field\";\nimport { useReportedChange } from \"../lib/form-context\";\n\ninterface SegmentedControlOption {\n value: string;\n label: string;\n disabled?: boolean;\n}\n\ntype SegmentedControlSize = \"xs\" | \"sm\" | \"md\" | \"lg\";\n\ninterface SegmentedControlProps extends FieldControlWiring {\n value: string;\n onChange: (value: string) => void;\n options: SegmentedControlOption[];\n /** xs is for dense tool chrome (toolbars, inspectors): 24px tall. */\n size?: SegmentedControlSize;\n variant?: \"default\" | \"ghost\";\n /** Stretch to the container and share the width equally between options. */\n fullWidth?: boolean;\n /**\n * Called with an option's value while the pointer is over it and with null\n * when it leaves, so a picker can preview a choice before committing it.\n */\n onHoverChange?: (value: string | null) => void;\n className?: string;\n /**\n * Accessible name for the radiogroup, for when there is no visible label (e.g. in a toolbar). A control with a visible\n * label sits in a Field instead, which names it and stacks the label above: `<Field label=\"Status\"><SegmentedControl …/></Field>`.\n */\n \"aria-label\"?: string;\n /**\n * Above this many options the control renders a Dropdown with the same value, onChange and options — a row of\n * eleven segments runs off the page (bench round 3). Default 6; raise it only where the row is known to fit. A row\n * that does not fit its container (a four-option range on a phone) renders the Dropdown too, and comes back as a\n * row when the container widens (round 10).\n */\n maxOptions?: number;\n}\n\n/** The options as one string, so the fit re-measures when the labels change. */\nfunction optionsKeyForFit(options: SegmentedControlOption[]): string { return options.map((o) => `${o.value}:${o.label}`).join(\"|\"); }\n\n// Option padding is part of the size (tailwind-merge cannot resolve two ds-\n// spacing classes against each other, so it is not appended as an override).\nconst sizes: Record<SegmentedControlSize, string> = {\n xs: \"text-xs px-ds-2\",\n sm: \"text-xs px-ds-3\",\n md: \"text-sm px-ds-3\",\n lg: \"text-base px-ds-3\",\n};\n\n// xs uses a 2px inset so the whole control stays 24px tall.\nconst containerPadding: Record<SegmentedControlSize, string> = {\n xs: \"p-[2px] gap-[2px]\",\n sm: \"p-ds-1 gap-ds-0.5\",\n md: \"p-ds-1 gap-ds-0.5\",\n lg: \"p-ds-1 gap-ds-0.5\",\n};\n\nconst indicatorInset: Record<SegmentedControlSize, string> = {\n xs: \"2px\",\n sm: \"4px\",\n md: \"4px\",\n lg: \"4px\",\n};\n\n/**\n * The segments' corner: the frame's (a field's, --ds-radius-md) less the gap between the two edges, the selected\n * segment's inset and the frame's border when it draws one, so the curves sit evenly inside each other; never sharper\n * than the theme's small radius, so on a theme of small corners the segments stay slightly rounded, not square (a\n * square theme stays square).\n */\nfunction segmentRadius(inset: string, bordered: boolean): string {\n return `max(var(--ds-radius-sm), calc(var(--ds-radius-md) - ${inset}${bordered ? \" - var(--ds-border-width)\" : \"\"}))`;\n}\n\nexport function SegmentedControl({\n value,\n onChange: onChangeProp,\n options,\n size: sizeProp,\n variant = \"default\",\n fullWidth = false,\n onHoverChange,\n className,\n \"aria-label\": ariaLabel,\n maxOptions = 6,\n ...wiring\n}: SegmentedControlProps) {\n // Sprint 25.12 A15: a new value the user gives this control marks the form's overlay; the page's onChange runs as given\n const onChange = useReportedChange(onChangeProp, value);\n const contextSize = useContext(ControlSizeContext);\n // Inside a Field it takes the Field's label (round 27, R27-10): a radio group is named by aria-labelledby, since\n // <label for> cannot name it, and the Field stacks its label above. It takes the Field's id, hint, error and\n // required state too (round 27 review: without them the summary had no link and focus skipped it); a radio group\n // carries aria-invalid and aria-required itself. As a Dropdown, the Dropdown reads the same Field. The name follows the\n // one rule (useFieldControl; the final review of Sprint 25.31, 2): the Field's label on screen leads it, where a\n // page's aria-label won before (\"Calendar view\" over \"View\"), and a page's name beside it is left out, with a warning\n const control = useFieldControl({ ...wiring, \"aria-label\": ariaLabel }, { component: \"SegmentedControl\" });\n // as a Dropdown, only the names the rule left: the page gave them to this control, not to a Dropdown, which would warn\n const { \"aria-labelledby\": _pageLabelledBy, ...dropdownWiring } = wiring;\n const size: SegmentedControlSize = sizeProp ?? (contextSize as SegmentedControlSize | null) ?? \"md\";\n const containerRef = useRef<HTMLDivElement>(null);\n const [indicator, setIndicator] = useState({ left: 0, width: 0 });\n\n // Round 10 (the owner's review of the analytics overview on a phone): a row that does not fit its container renders\n // the Dropdown too, not only a row of too many options. The row's natural width is remembered from the last time\n // it was laid out; the container (the filters row, the toolbar) is observed in both states, so the row comes back\n // when the container widens. 8 px of hysteresis keeps a row sitting on the edge from flapping.\n const parentRef = useRef<HTMLElement | null>(null);\n const neededRef = useRef(0);\n const [overflows, setOverflows] = useState(false);\n const measureFit = useCallback(() => {\n const row = containerRef.current;\n if (row) {\n parentRef.current = row.parentElement;\n if (row.scrollWidth > 0) neededRef.current = row.scrollWidth;\n }\n const parent = parentRef.current;\n const needed = neededRef.current;\n if (!parent || !needed) return;\n const style = getComputedStyle(parent);\n const available = parent.clientWidth - (parseFloat(style.paddingLeft) || 0) - (parseFloat(style.paddingRight) || 0);\n if (available <= 0) return;\n setOverflows((prev) => (prev ? needed > available - 8 : needed > available));\n }, []);\n useLayoutEffect(() => {\n measureFit();\n const parent = parentRef.current;\n if (!parent || typeof ResizeObserver === \"undefined\") return;\n const observer = new ResizeObserver(measureFit);\n observer.observe(parent);\n return () => observer.disconnect();\n }, [measureFit, optionsKeyForFit(options)]); // eslint-disable-line react-hooks/exhaustive-deps\n const collapsed = options.length > maxOptions || overflows;\n\n const updateIndicator = useCallback(() => {\n if (!containerRef.current) return;\n const active = containerRef.current.querySelector<HTMLElement>(\"[data-active='true']\");\n if (active) {\n setIndicator({\n left: active.offsetLeft,\n width: active.offsetWidth,\n });\n }\n }, []);\n\n // The option set can change under the control (a configurator hiding an\n // option), which moves the active segment without changing `value`: re-measure\n // on that too, and watch every segment, not just the container, so a label\n // that changes width also moves the thumb.\n const optionsKey = options.map((o) => `${o.value}:${o.label}`).join(\"|\");\n useLayoutEffect(() => {\n updateIndicator();\n }, [value, optionsKey, updateIndicator]);\n\n // The web font can land after the first measurement and widen the segments (round 5: the thumb was caught\n // mid-slide on every load). Re-measure when the fonts are ready, and animate only after the user's first change:\n // the initial position and the font re-measure snap into place.\n useEffect(() => {\n const fonts = (document as Document & { fonts?: { ready?: Promise<unknown> } }).fonts;\n if (!fonts?.ready) return;\n let live = true;\n fonts.ready.then(() => { if (live) updateIndicator(); });\n return () => { live = false; };\n }, [updateIndicator]);\n const [animate, setAnimate] = useState(false);\n const select = useCallback((v: string) => { setAnimate(true); onChange(v); }, [onChange]);\n\n useLayoutEffect(() => {\n if (collapsed || !containerRef.current || typeof ResizeObserver === \"undefined\") return;\n const observer = new ResizeObserver(updateIndicator);\n observer.observe(containerRef.current);\n containerRef.current.querySelectorAll<HTMLElement>(\"button\").forEach((b) => observer.observe(b));\n return () => observer.disconnect();\n }, [updateIndicator, optionsKey, collapsed]);\n\n const containerHeight: Record<SegmentedControlSize, string> = {\n xs: \"h-ds-6\",\n sm: \"h-[var(--ds-control-height-sm)]\",\n md: \"h-[var(--ds-control-height-md)]\",\n lg: \"h-[var(--ds-control-height-lg)]\",\n };\n\n // Radio-group keyboard pattern: one tab stop (the checked option, or the\n // first enabled one when nothing is checked), arrows move selection between\n // enabled options, Home/End jump to the ends.\n const tabStop = rovingTabStop(options, (o) => o.value === value, (o) => !!o.disabled);\n const handleKeyDown = (e: React.KeyboardEvent<HTMLDivElement>) => {\n const enabled = options.filter((o) => !o.disabled);\n if (enabled.length === 0) return;\n const idx = Math.max(0, enabled.findIndex((o) => o.value === value));\n let next: number | null = null;\n if (e.key === \"ArrowRight\" || e.key === \"ArrowDown\") next = (idx + 1) % enabled.length;\n else if (e.key === \"ArrowLeft\" || e.key === \"ArrowUp\") next = (idx - 1 + enabled.length) % enabled.length;\n else if (e.key === \"Home\") next = 0;\n else if (e.key === \"End\") next = enabled.length - 1;\n if (next === null) return;\n e.preventDefault();\n select(enabled[next].value);\n const buttons = containerRef.current?.querySelectorAll<HTMLButtonElement>('[role=\"radio\"]');\n buttons?.forEach((b) => { if (b.dataset.value === enabled[next!].value) b.focus(); });\n };\n\n if (collapsed) {\n return (\n <Dropdown\n {...dropdownWiring}\n aria-label={control.shown ? undefined : control.label}\n aria-labelledby={control.shown ? undefined : wiring[\"aria-labelledby\"]}\n size={size === \"md\" || size === \"lg\" ? \"md\" : \"sm\"}\n options={options.filter((o) => !o.disabled).map((o) => ({ id: o.value, label: o.label }))}\n value={value}\n onChange={(v) => onChange(v as string)}\n className={cn(\"inline-block\", fullWidth ? \"w-full\" : \"w-auto\", className)}\n />\n );\n }\n\n return (\n <div\n ref={containerRef}\n role=\"radiogroup\"\n id={control.id}\n aria-label={control.labelId ? undefined : control.label}\n aria-labelledby={control.labelId}\n aria-describedby={control.describedBy}\n aria-invalid={control.invalid || undefined}\n aria-required={control.required || undefined}\n onKeyDown={handleKeyDown}\n className={cn(\n \"relative items-center\",\n // Sprint 25.14 A21: in a Field's column a track that isn't full width keeps its own width; w-fit, not self-start,\n // so a row or a centred column still aligns it (the review of 30 September)\n fullWidth ? \"flex w-full\" : \"inline-flex w-fit\",\n containerPadding[size],\n containerHeight[size],\n \"bg-[var(--ds-bg-muted)]\",\n // The owner's review of 1 October 2026: the frame's corner is a field's, so the two change together with the\n // theme's radius; the segments inside sit concentric with it (SEGMENT_RADIUS)\n \"rounded-[var(--ds-radius-md)]\",\n // The frame's edge is a button's: each segment is a labelled button, so the frame isn't what identifies the\n // control, and WCAG 1.4.11 asks no 3:1 of it\n variant === \"default\" && \"border-[length:var(--ds-border-width)] border-[var(--ds-border-default)]\",\n control.invalid && \"border-[length:var(--ds-border-width)] border-[var(--ds-status-error)]\",\n className,\n )}\n style={{ \"--ds-segment-radius\": segmentRadius(indicatorInset[size], variant === \"default\" || !!control.invalid) } as React.CSSProperties}\n >\n <div\n className={cn(\n \"absolute rounded-[var(--ds-segment-radius)]\",\n \"bg-[var(--ds-bg-surface)]\",\n // The selected segment is a kind of button, so it takes a secondary button's surface and edge, as the frame\n // takes a button's edge (the owner, 3 October 2026: the 3:1 strong edge it kept from Sprint 25.14 drew the\n // darker outline 25.19 took off the fields). It shows its state as the raised surface on the muted track, with\n // the default variant's shadow and its label's stronger colour. While its segment has keyboard focus the shadow\n // steps aside, as a button's does, so the segment's outline sits clear (Sprint 30, D7)\n \"border-[length:var(--ds-border-width)] border-[var(--ds-border-default)]\",\n variant === \"default\" && \"shadow-[var(--ds-shadow-sm)]\",\n \"[[role=radiogroup]:has([aria-checked=true]:focus-visible)_&]:shadow-none\",\n \"transition-all motion-reduce:transition-none\",\n )}\n data-animate={animate ? \"true\" : \"false\"}\n style={{\n left: indicator.left,\n width: indicator.width,\n top: indicatorInset[size],\n bottom: indicatorInset[size],\n transitionDuration: animate ? \"var(--ds-transition-speed)\" : \"0ms\",\n transitionTimingFunction: \"var(--ds-transition-ease)\",\n }}\n />\n {options.map((opt, i) => (\n <button\n key={opt.value}\n type=\"button\"\n role=\"radio\"\n aria-checked={opt.value === value}\n data-active={opt.value === value}\n data-value={opt.value}\n tabIndex={i === tabStop ? 0 : -1}\n disabled={opt.disabled}\n onClick={() => !opt.disabled && select(opt.value)}\n onMouseEnter={onHoverChange && !opt.disabled ? () => onHoverChange(opt.value) : undefined}\n onMouseLeave={onHoverChange ? () => onHoverChange(null) : undefined}\n className={cn(\n \"relative z-10 inline-flex items-center justify-center self-stretch font-medium whitespace-nowrap\",\n fullWidth && \"flex-1\",\n \"rounded-[var(--ds-segment-radius)]\",\n \"transition-colors cursor-pointer\",\n FOCUS_RING,\n sizes[size],\n opt.value === value\n ? \"text-[var(--ds-text-primary)]\"\n : \"text-[var(--ds-text-muted)] hover:text-[var(--ds-text-secondary)]\",\n opt.disabled && \"opacity-disabled cursor-not-allowed\",\n )}\n >\n {opt.label}\n </button>\n ))}\n </div>\n );\n}\n","/**\n * A title's text classes: CardTitle's, which a titled Table's band and a ChartContainer's title share (Sprint 25.9, A1),\n * one definition so they cannot drift. A word or a name longer than its line breaks at the line's edge (round 27,\n * R27-16: run 70's \"primary-logo-navy.svg\" ran out of its card); break-word, not anywhere, so a title's words still\n * set its minimum and the actions beside it wrap under it before a word is broken (HEADER_LAYOUT.floor).\n */\nexport const TITLE_TEXT = \"font-[family-name:var(--ds-font-display)] text-lg font-[var(--ds-weight-heading)] text-[var(--ds-text-primary)] break-words\";\n"]}