@seekalfred/primitives 0.7.3 → 0.8.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 (149) hide show
  1. package/README.md +315 -4
  2. package/dist/alert/alert.d.ts +2 -2
  3. package/dist/alert/alert.js +1 -1
  4. package/dist/alert/alert.js.map +1 -1
  5. package/dist/alert-dialog/alert-dialog.d.ts +14 -0
  6. package/dist/alert-dialog/alert-dialog.js +52 -27
  7. package/dist/alert-dialog/alert-dialog.js.map +1 -1
  8. package/dist/announcement-bar/announcement-bar.d.ts +15 -4
  9. package/dist/announcement-bar/announcement-bar.js +31 -6
  10. package/dist/announcement-bar/announcement-bar.js.map +1 -1
  11. package/dist/avatar/avatar.d.ts +7 -0
  12. package/dist/avatar/avatar.js +33 -12
  13. package/dist/avatar/avatar.js.map +1 -1
  14. package/dist/badge/badge.d.ts +16 -1
  15. package/dist/badge/badge.js +12 -5
  16. package/dist/badge/badge.js.map +1 -1
  17. package/dist/bar-chart/bar-chart-series.d.ts +75 -0
  18. package/dist/bar-chart/bar-chart-series.js +401 -0
  19. package/dist/bar-chart/bar-chart-series.js.map +1 -0
  20. package/dist/bar-chart/bar-chart.d.ts +25 -1
  21. package/dist/bar-chart/bar-chart.js +26 -1
  22. package/dist/bar-chart/bar-chart.js.map +1 -1
  23. package/dist/bar-chart/index.d.ts +2 -1
  24. package/dist/button/button.d.ts +24 -2
  25. package/dist/button/button.js +62 -13
  26. package/dist/button/button.js.map +1 -1
  27. package/dist/card/card.js +1 -1
  28. package/dist/card/card.js.map +1 -1
  29. package/dist/checkbox/checkbox.d.ts +25 -1
  30. package/dist/checkbox/checkbox.js +103 -28
  31. package/dist/checkbox/checkbox.js.map +1 -1
  32. package/dist/checkbox/index.d.ts +2 -2
  33. package/dist/checkbox/index.js +2 -2
  34. package/dist/chip-group/chip-group.d.ts +11 -0
  35. package/dist/chip-group/chip-group.js +50 -34
  36. package/dist/chip-group/chip-group.js.map +1 -1
  37. package/dist/chip-group/index.d.ts +2 -2
  38. package/dist/code-block/code-block.d.ts +45 -0
  39. package/dist/code-block/code-block.js +74 -0
  40. package/dist/code-block/code-block.js.map +1 -0
  41. package/dist/code-block/index.d.ts +2 -0
  42. package/dist/code-block/index.js +2 -0
  43. package/dist/command-palette/command-palette.d.ts +128 -0
  44. package/dist/command-palette/command-palette.js +371 -0
  45. package/dist/command-palette/command-palette.js.map +1 -0
  46. package/dist/command-palette/index.d.ts +2 -0
  47. package/dist/command-palette/index.js +2 -0
  48. package/dist/copy-button/copy-button.d.ts +19 -3
  49. package/dist/copy-button/copy-button.js +7 -4
  50. package/dist/copy-button/copy-button.js.map +1 -1
  51. package/dist/dialog/dialog.js +1 -1
  52. package/dist/dialog/dialog.js.map +1 -1
  53. package/dist/empty-state/empty-state.d.ts +17 -0
  54. package/dist/empty-state/empty-state.js +9 -7
  55. package/dist/empty-state/empty-state.js.map +1 -1
  56. package/dist/file-drop-zone/file-drop-zone.d.ts +29 -3
  57. package/dist/file-drop-zone/file-drop-zone.js +26 -8
  58. package/dist/file-drop-zone/file-drop-zone.js.map +1 -1
  59. package/dist/gauge/gauge.d.ts +18 -1
  60. package/dist/gauge/gauge.js +35 -21
  61. package/dist/gauge/gauge.js.map +1 -1
  62. package/dist/gauge/index.d.ts +2 -2
  63. package/dist/index.d.ts +14 -9
  64. package/dist/index.js +14 -10
  65. package/dist/info-tooltip/index.d.ts +2 -0
  66. package/dist/info-tooltip/index.js +2 -0
  67. package/dist/info-tooltip/info-tooltip.d.ts +35 -0
  68. package/dist/info-tooltip/info-tooltip.js +49 -0
  69. package/dist/info-tooltip/info-tooltip.js.map +1 -0
  70. package/dist/input/input.d.ts +7 -0
  71. package/dist/input/input.js +4 -2
  72. package/dist/input/input.js.map +1 -1
  73. package/dist/lib/field-shell.js +2 -6
  74. package/dist/lib/field-shell.js.map +1 -1
  75. package/dist/lib/required-marker.js +20 -0
  76. package/dist/lib/required-marker.js.map +1 -0
  77. package/dist/lib/selection-control.js +32 -1
  78. package/dist/lib/selection-control.js.map +1 -1
  79. package/dist/line-chart/index.d.ts +2 -2
  80. package/dist/line-chart/line-chart.d.ts +106 -3
  81. package/dist/line-chart/line-chart.js +185 -46
  82. package/dist/line-chart/line-chart.js.map +1 -1
  83. package/dist/mention-input/mention-input.js +1 -1
  84. package/dist/money-input/index.d.ts +2 -2
  85. package/dist/money-input/money-input.d.ts +14 -0
  86. package/dist/money-input/money-input.js +17 -7
  87. package/dist/money-input/money-input.js.map +1 -1
  88. package/dist/morph-select/morph-select.d.ts +8 -1
  89. package/dist/morph-select/morph-select.js +8 -5
  90. package/dist/morph-select/morph-select.js.map +1 -1
  91. package/dist/multi-select/multi-select.d.ts +31 -0
  92. package/dist/multi-select/multi-select.js +123 -6
  93. package/dist/multi-select/multi-select.js.map +1 -1
  94. package/dist/number-field/number-field.js +1 -1
  95. package/dist/password-field/password-field.js +1 -1
  96. package/dist/password-strength/password-strength.js +1 -1
  97. package/dist/phone-input/phone-input.js +1 -1
  98. package/dist/popover/popover.js +1 -1
  99. package/dist/popover/popover.js.map +1 -1
  100. package/dist/progress/index.d.ts +2 -2
  101. package/dist/progress/index.js +2 -2
  102. package/dist/progress/progress.d.ts +53 -2
  103. package/dist/progress/progress.js +146 -36
  104. package/dist/progress/progress.js.map +1 -1
  105. package/dist/radio-group/radio-group.d.ts +12 -3
  106. package/dist/radio-group/radio-group.js +62 -52
  107. package/dist/radio-group/radio-group.js.map +1 -1
  108. package/dist/rich-text-editor/rich-text-editor.js +1 -1
  109. package/dist/scroll-area/scroll-area.d.ts +1 -1
  110. package/dist/scrub-input/scrub-input.d.ts +11 -1
  111. package/dist/scrub-input/scrub-input.js +22 -10
  112. package/dist/scrub-input/scrub-input.js.map +1 -1
  113. package/dist/search-field/search-field.js +1 -1
  114. package/dist/select/select.d.ts +10 -1
  115. package/dist/select/select.js +4 -3
  116. package/dist/select/select.js.map +1 -1
  117. package/dist/separator/separator.d.ts +1 -1
  118. package/dist/shortcut-recorder/shortcut-recorder.js +1 -1
  119. package/dist/sparkline/sparkline.d.ts +27 -2
  120. package/dist/sparkline/sparkline.js +115 -18
  121. package/dist/sparkline/sparkline.js.map +1 -1
  122. package/dist/stat-card/index.d.ts +2 -2
  123. package/dist/stat-card/index.js +2 -2
  124. package/dist/stat-card/stat-card.d.ts +32 -1
  125. package/dist/stat-card/stat-card.js +52 -9
  126. package/dist/stat-card/stat-card.js.map +1 -1
  127. package/dist/styles.css +1 -1
  128. package/dist/table/index.d.ts +2 -2
  129. package/dist/table/index.js +2 -2
  130. package/dist/table/table.d.ts +211 -3
  131. package/dist/table/table.js +473 -93
  132. package/dist/table/table.js.map +1 -1
  133. package/dist/tabs/tabs.d.ts +4 -2
  134. package/dist/tabs/tabs.js +7 -5
  135. package/dist/tabs/tabs.js.map +1 -1
  136. package/dist/tag-input/tag-input.js +1 -1
  137. package/dist/text-shimmer/text-shimmer.d.ts +18 -2
  138. package/dist/text-shimmer/text-shimmer.js +5 -2
  139. package/dist/text-shimmer/text-shimmer.js.map +1 -1
  140. package/dist/textarea/textarea.d.ts +7 -0
  141. package/dist/textarea/textarea.js +4 -2
  142. package/dist/textarea/textarea.js.map +1 -1
  143. package/dist/toast/toast.d.ts +2 -2
  144. package/dist/toggle-button/index.d.ts +2 -0
  145. package/dist/toggle-button/index.js +2 -0
  146. package/dist/toggle-button/toggle-button.d.ts +93 -0
  147. package/dist/toggle-button/toggle-button.js +199 -0
  148. package/dist/toggle-button/toggle-button.js.map +1 -0
  149. package/package.json +19 -3
@@ -3,8 +3,8 @@ import { cn, cnAll } from "../lib/cn.js";
3
3
  import { useAfter, useCountTo } from "../lib/count-to.js";
4
4
  import { SwapText } from "../lib/swap-text.js";
5
5
  import { cva } from "class-variance-authority";
6
- import { forwardRef } from "react";
7
- import { Label, ProgressBar } from "react-aria-components";
6
+ import { forwardRef, useId, useMemo } from "react";
7
+ import { Label, ProgressBar, useLocale } from "react-aria-components";
8
8
  import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
9
9
  //#region src/progress/progress.tsx
10
10
  /**
@@ -36,6 +36,17 @@ import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
36
36
  * the track against the page at 5.25:1.
37
37
  *
38
38
  * Both are in the build-failing contrast contract.
39
+ *
40
+ * A TARGET MARKER is opt-in (`target`): a short upright tick that stands
41
+ * proud of the track at the value the bar should have reached -- "where the
42
+ * goal should be by now". The tick is decorative; its meaning is read to
43
+ * assistive technology as the bar's description (`targetLabel` plus the
44
+ * formatted value), and `showTargetLabel` also prints it under the tick.
45
+ *
46
+ * `surface="inverse"` is for a bar on the inverted (foreground-coloured)
47
+ * surface, such as a dark summary strip: the track is the inverse text colour
48
+ * mixed into the surface, the default fill and the copy take the inverse text
49
+ * colour, so the bar keeps its contrast where the default greys would vanish.
39
50
  */
40
51
  const progressVariants = cva(["flex w-full flex-col box-border font-sans"], {
41
52
  variants: { headerEmpty: {
@@ -44,7 +55,28 @@ const progressVariants = cva(["flex w-full flex-col box-border font-sans"], {
44
55
  } },
45
56
  defaultVariants: { headerEmpty: false }
46
57
  });
47
- const progressTrackVariants = cva(["relative h-2 w-full overflow-hidden rounded-pill box-border", "bg-muted border-(length:--border-width) border-border"]);
58
+ const progressTrackVariants = cva(["relative h-2 w-full overflow-hidden rounded-pill box-border", "border-(length:--border-width)"], {
59
+ variants: {
60
+ /** The surface the bar sits on. */
61
+ surface: {
62
+ default: "bg-muted border-border",
63
+ inverse: "bg-[color-mix(in_oklab,var(--color-text-inverse)_28%,var(--color-foreground))] border-transparent"
64
+ } },
65
+ defaultVariants: { surface: "default" }
66
+ });
67
+ /**
68
+ * The target tick: 2px wide, standing 4px proud of the track on both sides,
69
+ * with a 1px halo of the surface colour so it reads over the fill as well as
70
+ * over the track. Placed by an inline `inset-inline-start`, so it mirrors in
71
+ * right-to-left text.
72
+ */
73
+ const progressTargetVariants = cva("pointer-events-none absolute -top-1 -bottom-1 w-0.5 -ms-px rounded-pill", {
74
+ variants: { surface: {
75
+ default: "bg-foreground shadow-[0_0_0_1px_var(--color-surface)]",
76
+ inverse: "bg-text-inverse shadow-[0_0_0_1px_var(--color-foreground)]"
77
+ } },
78
+ defaultVariants: { surface: "default" }
79
+ });
48
80
  const progressFillVariants = cva(["h-full rounded-pill", "progress-fill"], {
49
81
  variants: { tone: {
50
82
  neutral: "bg-primary-strong",
@@ -86,47 +118,125 @@ function ProgressValue({ percentage, valueText, showCompletion }) {
86
118
  })
87
119
  }) : null] });
88
120
  }
89
- const Progress = forwardRef(function Progress({ label, labelHidden, showValue, showCompletion = true, tone, trackClassName, fillClassName, className, ...props }, ref) {
121
+ /** The track's frame and the tick. With no marker it renders the track alone, as before. */
122
+ function TargetFrame({ marker, surface, children }) {
123
+ if (!marker) return /* @__PURE__ */ jsx(Fragment$1, { children });
124
+ return /* @__PURE__ */ jsxs("div", {
125
+ "data-slot": "progress-target-frame",
126
+ className: "relative",
127
+ children: [children, /* @__PURE__ */ jsx("span", {
128
+ "data-slot": "progress-target",
129
+ "aria-hidden": "true",
130
+ className: progressTargetVariants({ surface }),
131
+ style: { insetInlineStart: `${marker.share * 100}%` }
132
+ })]
133
+ });
134
+ }
135
+ /**
136
+ * The target's meaning, the bar's description. Printed, it hangs from a
137
+ * zero-width anchor at the tick: centred on it, or running inward near either
138
+ * end, in either text direction. Otherwise it is for screen readers only.
139
+ */
140
+ function TargetText({ id, marker, shown, inverse }) {
141
+ if (!shown) return /* @__PURE__ */ jsx("span", {
142
+ id,
143
+ className: "sr-only",
144
+ children: marker.text
145
+ });
146
+ return /* @__PURE__ */ jsx("div", {
147
+ "data-slot": "progress-target-label",
148
+ "aria-hidden": "true",
149
+ className: "relative h-[1lh] text-xs leading-(--leading-normal)",
150
+ children: /* @__PURE__ */ jsx("span", {
151
+ className: cnAll("absolute top-0 flex w-0 whitespace-nowrap", marker.share < .2 ? "justify-start" : marker.share > .8 ? "justify-end" : "justify-center", inverse ? "text-text-inverse" : "text-muted-foreground"),
152
+ style: { insetInlineStart: `${marker.share * 100}%` },
153
+ children: /* @__PURE__ */ jsx("span", {
154
+ id,
155
+ children: marker.text
156
+ })
157
+ })
158
+ });
159
+ }
160
+ const Progress = forwardRef(function Progress({ label, labelHidden, showValue, showCompletion = true, tone, target, targetLabel = "Target", showTargetLabel = false, surface = "default", trackClassName, fillClassName, className, ...props }, ref) {
161
+ const { locale } = useLocale();
162
+ const targetId = useId();
163
+ const { minValue = 0, maxValue = 100, formatOptions, isIndeterminate: indeterminate } = props;
164
+ const marker = useMemo(() => {
165
+ if (target === void 0 || !Number.isFinite(target) || indeterminate) return null;
166
+ const span = maxValue - minValue;
167
+ const share = span > 0 ? Math.min(1, Math.max(0, (target - minValue) / span)) : 0;
168
+ const options = formatOptions ?? { style: "percent" };
169
+ const shown = options.style === "percent" ? share : Math.min(maxValue, Math.max(minValue, target));
170
+ return {
171
+ share,
172
+ text: `${targetLabel}: ${new Intl.NumberFormat(locale, options).format(shown)}`
173
+ };
174
+ }, [
175
+ target,
176
+ indeterminate,
177
+ minValue,
178
+ maxValue,
179
+ formatOptions,
180
+ locale,
181
+ targetLabel
182
+ ]);
183
+ const inverse = surface === "inverse";
184
+ const describedBy = marker ? [props["aria-describedby"], targetId].filter(Boolean).join(" ") : props["aria-describedby"];
90
185
  return /* @__PURE__ */ jsx(ProgressBar, {
91
186
  ref,
92
187
  ...props,
188
+ "aria-describedby": describedBy,
93
189
  "data-slot": "progress",
190
+ "data-surface": inverse ? "inverse" : void 0,
94
191
  className: cn(progressVariants({ headerEmpty: Boolean(labelHidden && !showValue) }), className),
95
- children: ({ percentage, isIndeterminate, valueText }) => /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsxs("div", {
96
- "data-slot": "progress-header",
97
- className: cnAll(showValue && "flex items-baseline justify-between gap-2"),
98
- children: [/* @__PURE__ */ jsx(Label, {
99
- "data-slot": "progress-label",
100
- className: cnAll("text-sm leading-(--leading-normal) text-foreground", labelHidden && "sr-only"),
101
- children: typeof label === "string" ? /* @__PURE__ */ jsx(SwapText, {
102
- swapKey: label,
103
- className: "block",
104
- children: label
105
- }) : label
106
- }), showValue ? /* @__PURE__ */ jsx("span", {
107
- "data-slot": "progress-value",
108
- "aria-hidden": "true",
109
- className: "text-sm leading-(--leading-normal) text-muted-foreground tabular-nums",
110
- children: /* @__PURE__ */ jsx(ProgressValue, {
111
- percentage: percentage ?? 0,
112
- valueText,
113
- showCompletion
192
+ children: ({ percentage, isIndeterminate, valueText }) => /* @__PURE__ */ jsxs(Fragment$1, { children: [
193
+ /* @__PURE__ */ jsxs("div", {
194
+ "data-slot": "progress-header",
195
+ className: cnAll(showValue && "flex items-baseline justify-between gap-2"),
196
+ children: [/* @__PURE__ */ jsx(Label, {
197
+ "data-slot": "progress-label",
198
+ className: cnAll("text-sm leading-(--leading-normal)", inverse ? "text-text-inverse" : "text-foreground", labelHidden && "sr-only"),
199
+ children: typeof label === "string" ? /* @__PURE__ */ jsx(SwapText, {
200
+ swapKey: label,
201
+ className: "block",
202
+ children: label
203
+ }) : label
204
+ }), showValue ? /* @__PURE__ */ jsx("span", {
205
+ "data-slot": "progress-value",
206
+ "aria-hidden": "true",
207
+ className: cnAll("text-sm leading-(--leading-normal) tabular-nums", inverse ? "text-text-inverse" : "text-muted-foreground"),
208
+ children: /* @__PURE__ */ jsx(ProgressValue, {
209
+ percentage: percentage ?? 0,
210
+ valueText,
211
+ showCompletion
212
+ })
213
+ }) : null]
214
+ }),
215
+ /* @__PURE__ */ jsx(TargetFrame, {
216
+ marker,
217
+ surface,
218
+ children: /* @__PURE__ */ jsx("div", {
219
+ "data-slot": "progress-track",
220
+ className: cn(progressTrackVariants({ surface }), trackClassName),
221
+ children: /* @__PURE__ */ jsx("div", {
222
+ "data-slot": "progress-fill",
223
+ "data-indeterminate": isIndeterminate || void 0,
224
+ "data-complete": showCompletion && (!tone || tone === "neutral") && !isIndeterminate && (percentage ?? 0) >= 100 ? "" : void 0,
225
+ className: cn([progressFillVariants({ tone }), inverse && (!tone || tone === "neutral") && "bg-text-inverse"], fillClassName),
226
+ style: isIndeterminate ? void 0 : { width: `${percentage ?? 0}%` }
227
+ })
114
228
  })
115
- }) : null]
116
- }), /* @__PURE__ */ jsx("div", {
117
- "data-slot": "progress-track",
118
- className: cn(progressTrackVariants(), trackClassName),
119
- children: /* @__PURE__ */ jsx("div", {
120
- "data-slot": "progress-fill",
121
- "data-indeterminate": isIndeterminate || void 0,
122
- "data-complete": showCompletion && (!tone || tone === "neutral") && !isIndeterminate && (percentage ?? 0) >= 100 ? "" : void 0,
123
- className: cn(progressFillVariants({ tone }), fillClassName),
124
- style: isIndeterminate ? void 0 : { width: `${percentage ?? 0}%` }
125
- })
126
- })] })
229
+ }),
230
+ marker ? /* @__PURE__ */ jsx(TargetText, {
231
+ id: targetId,
232
+ marker,
233
+ shown: showTargetLabel,
234
+ inverse
235
+ }) : null
236
+ ] })
127
237
  });
128
238
  });
129
239
  //#endregion
130
- export { Progress, progressFillVariants, progressTrackVariants, progressVariants };
240
+ export { Progress, progressFillVariants, progressTargetVariants, progressTrackVariants, progressVariants };
131
241
 
132
242
  //# sourceMappingURL=progress.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"progress.js","names":["AriaProgressBar","AriaLabel"],"sources":["../../src/progress/progress.tsx"],"sourcesContent":["\"use client\";\n\nimport { cva, type VariantProps } from \"class-variance-authority\";\nimport { forwardRef, type ReactNode } from \"react\";\nimport {\n Label as AriaLabel,\n ProgressBar as AriaProgressBar,\n type ProgressBarProps as AriaProgressBarProps,\n} from \"react-aria-components\";\n\nimport { cn, cnAll } from \"../lib/cn.js\";\nimport { useAfter, useCountTo } from \"../lib/count-to.js\";\nimport { SwapText } from \"../lib/swap-text.js\";\n\n/**\n * Progress, ported from the legacy shadcn/Radix component.\n *\n * Census: seven files import it and only TWO render anything --\n * `ResponseOption.tsx` and `LiveQA.tsx`. Both pass `value` and\n * `className` and nothing else.\n *\n * `label` IS REQUIRED, and that is the point. Neither live call site\n * passes `aria-label`, `aria-labelledby` or an `id` a label targets, and\n * `role=\"progressbar\"` does not take its name from content -- so both\n * rendered progress bars are announced as an unnamed progressbar. React\n * Aria supplies the plumbing for three naming routes but no name of its\n * own: `<ProgressBar value={60}/>` is just as nameless there. Making the\n * prop required is what turns it into something you cannot forget.\n *\n * The migration plan said this component \"adds the missing\n * aria-valuenow\". That is wrong -- Radix already supplies `aria-valuenow`,\n * `aria-valuemin`, `aria-valuemax` and `aria-valuetext`. The missing thing\n * was always the NAME.\n *\n * THE COLOURS ARE NOT THE LEGACY'S. Its track is `bg-primary/20`, an alpha\n * fill that composites against unknown page content and therefore has no\n * fixed contrast pair at all -- over white it is roughly 1.05:1, an\n * invisible boundary. Its indicator is `bg-primary`, 2.44:1 on the page.\n * Here the track is `muted` with a `border` boundary and the fill is\n * `primary-strong`, which puts the fill against the track at 4.61:1 and\n * the track against the page at 5.25:1.\n *\n * Both are in the build-failing contrast contract.\n */\nexport const progressVariants = cva([\"flex w-full flex-col box-border font-sans\"], {\n variants: {\n /*\n * True when nothing is printed above the track (`labelHidden` and no\n * `showValue`). The header is then an empty row -- its sr-only label is\n * out of flow -- and the column gap would leave 6px of nothing above the\n * bar. The gap lives here and not in the base, so the raw\n * `progressVariants()` output never carries both `gap-1.5` and `gap-0`.\n */\n headerEmpty: { true: \"gap-0\", false: \"gap-1.5\" },\n },\n defaultVariants: { headerEmpty: false },\n});\n\nexport const progressTrackVariants = cva([\n \"relative h-2 w-full overflow-hidden rounded-pill box-border\",\n // muted, NOT `border` as the Switch uses. A switch has no fill; a\n // progress bar does, and primary-strong on a `border` track is 1.01:1 --\n // the two halves are the same grey and the value disappears.\n \"bg-muted border-(length:--border-width) border-border\",\n]);\n\nexport const progressFillVariants = cva(\n [\n \"h-full rounded-pill\",\n // The fill follows the value on the smooth spring and turns success-coloured when it completes; indeterminate, it sweeps (see progress-fill).\n \"progress-fill\",\n ],\n {\n variants: {\n /*\n * What the value MEANS, which for a progress bar is usually a score\n * rather than a completion. The product's bar carried exactly this axis\n * (`default`/`success`/`warning`/`error`/`info`) and reached for it to\n * colour performance metrics.\n *\n * `-strong` throughout: the fill sits on the `muted` track and has to\n * clear the 3:1 non-text floor of WCAG 1.4.11 against it, which is the\n * same reason the track is muted rather than `border` -- primary-strong\n * on a `border` track measures 1.01:1 and the value disappears.\n */\n tone: {\n neutral: \"bg-primary-strong\",\n success: \"bg-success-strong\",\n warning: \"bg-warning-strong\",\n danger: \"bg-destructive-strong\",\n info: \"bg-info-strong\",\n },\n },\n defaultVariants: { tone: \"neutral\" },\n },\n);\n\n/**\n * A determinate or indeterminate progress bar.\n *\n * @example\n * <Progress label=\"Upload\" value={62} />\n * <Progress label=\"Loading results\" />\n */\nexport interface ProgressProps\n extends Omit<AriaProgressBarProps, \"className\" | \"style\" | \"children\">,\n VariantProps<typeof progressFillVariants> {\n /**\n * What is progressing, and the bar's accessible name.\n *\n * **Required.** Both legacy call sites render an unnamed progressbar.\n * Pass `labelHidden` when the surrounding UI already says it.\n */\n label: ReactNode;\n /**\n * Render the label for screen readers only.\n *\n * Both legacy call sites sit beside visible text that was never\n * associated with the bar -- this is how that text becomes the name\n * without printing it twice.\n */\n labelHidden?: boolean;\n /**\n * Print the value beside the label.\n *\n * The text itself comes from React Aria, which already formats it from\n * `value`/`minValue`/`maxValue` and honours `formatOptions` and\n * `valueLabel` -- so a percentage, a currency or \"3 of 7\" are all reachable\n * without a second prop here. It was never RENDERED before: the bar\n * announced its value to assistive tech and showed a sighted reader nothing.\n */\n showValue?: boolean;\n /**\n * Mark a full bar as done: a default-toned fill eases to the success\n * colour at 100% and, with `showValue`, a check lands beside the count.\n *\n * Pass `false` for a bar that can reach 100% before the work is finished\n * -- a timer-driven estimate -- so it does not say \"done\" early. The fill\n * keeps its tone and no check is shown. An explicit `tone` is never\n * overridden either way.\n *\n * @default true\n */\n showCompletion?: boolean;\n /** Extra classes for the track. */\n trackClassName?: string;\n /** Extra classes for the fill -- a per-call-site colour the tone axis cannot express. */\n fillClassName?: string;\n /** Extra classes. Merged last, so they win conflicts with ours. */\n className?: string;\n}\n\n/**\n * The printed value. A percentage counts to its new number on the same spring the\n * fill follows, and a check lands beside the final count once the bar is full.\n * Any other text (a price, a step count) is shown as it is.\n */\nfunction ProgressValue({\n percentage,\n valueText,\n showCompletion,\n}: {\n percentage: number;\n valueText: string | undefined;\n showCompletion: boolean;\n}) {\n const percent = Math.round(percentage);\n const counted = useCountTo(percent);\n const done = useAfter(showCompletion && percent >= 100, 240);\n const isPercent = valueText === `${percent}%`;\n return (\n <>\n {isPercent ? `${counted}%` : valueText}\n {isPercent ? (\n <span data-slot=\"progress-done\" className=\"ms-1 inline-flex align-middle text-success-strong\">\n <SwapText swapKey={done ? \"done\" : \"\"} motion=\"glyph\" className=\"inline-flex\">\n <svg\n viewBox=\"0 0 16 16\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n className=\"size-3.5\"\n aria-hidden=\"true\"\n >\n <path d=\"m3.5 8.5 3 3 6-6\" />\n </svg>\n </SwapText>\n </span>\n ) : null}\n </>\n );\n}\n\nexport const Progress = forwardRef<HTMLDivElement, ProgressProps>(function Progress(\n {\n label,\n labelHidden,\n showValue,\n showCompletion = true,\n tone,\n trackClassName,\n fillClassName,\n className,\n ...props\n },\n ref,\n) {\n return (\n <AriaProgressBar\n ref={ref}\n {...props}\n data-slot=\"progress\"\n className={cn(progressVariants({ headerEmpty: Boolean(labelHidden && !showValue) }), className)}\n >\n {({ percentage, isIndeterminate, valueText }) => (\n <>\n {/*\n The row exists only when the value is printed. Wrapping the label\n in a flex row unconditionally would change the label's box for\n every existing consumer to no purpose.\n */}\n <div\n data-slot=\"progress-header\"\n className={cnAll(showValue && \"flex items-baseline justify-between gap-2\")}\n >\n <AriaLabel\n data-slot=\"progress-label\"\n className={cnAll(\n \"text-sm leading-(--leading-normal) text-foreground\",\n labelHidden && \"sr-only\",\n )}\n >\n {typeof label === \"string\" ? (\n <SwapText swapKey={label} className=\"block\">\n {label}\n </SwapText>\n ) : (\n label\n )}\n </AriaLabel>\n {showValue ? (\n <span\n data-slot=\"progress-value\"\n // aria-hidden: React Aria already announces the value on the\n // progressbar itself. Without this a screen reader reads it\n // twice, once as the widget's value and once as loose text.\n aria-hidden=\"true\"\n className=\"text-sm leading-(--leading-normal) text-muted-foreground tabular-nums\"\n >\n <ProgressValue\n percentage={percentage ?? 0}\n valueText={valueText}\n showCompletion={showCompletion}\n />\n </span>\n ) : null}\n </div>\n <div\n data-slot=\"progress-track\"\n className={cn(progressTrackVariants(), trackClassName)}\n >\n {/*\n width, not the legacy translateX(-(100 - value)%). That\n overflowed for value > 100 -- a positive translate slid the\n indicator off to the right, while the announced value stayed\n whatever was passed. React Aria clamps the value, so the two\n can no longer disagree.\n\n An indeterminate bar is not the legacy's silent 0%:\n `value={undefined}` became `(value || 0)` there, so a sighted\n user saw \"0% done\" while a screen reader said \"indeterminate\".\n Its fill is a segment that sweeps the track on a loop (a slow\n pulse of the whole track under reduced motion), sized and moved\n by progress-fill, so it carries no inline width.\n */}\n <div\n data-slot=\"progress-fill\"\n data-indeterminate={isIndeterminate || undefined}\n // A default-toned bar turns success-coloured when it completes, unless showCompletion is off; an explicit tone is never overridden.\n data-complete={\n showCompletion && (!tone || tone === \"neutral\") && !isIndeterminate && (percentage ?? 0) >= 100\n ? \"\"\n : undefined\n }\n className={cn(progressFillVariants({ tone }), fillClassName)}\n style={isIndeterminate ? undefined : { width: `${percentage ?? 0}%` }}\n />\n </div>\n </>\n )}\n </AriaProgressBar>\n );\n});\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA4CA,MAAa,mBAAmB,IAAI,CAAC,2CAA2C,GAAG;CACjF,UAAU,EAQR,aAAa;EAAE,MAAM;EAAS,OAAO;CAAU,EACjD;CACA,iBAAiB,EAAE,aAAa,MAAM;AACxC,CAAC;AAED,MAAa,wBAAwB,IAAI,CACvC,+DAIA,uDACF,CAAC;AAED,MAAa,uBAAuB,IAClC,CACE,uBAEA,eACF,GACA;CACE,UAAU,EAYR,MAAM;EACJ,SAAS;EACT,SAAS;EACT,SAAS;EACT,QAAQ;EACR,MAAM;CACR,EACF;CACA,iBAAiB,EAAE,MAAM,UAAU;AACrC,CACF;;;;;;AA8DA,SAAS,cAAc,EACrB,YACA,WACA,kBAKC;CACD,MAAM,UAAU,KAAK,MAAM,UAAU;CACrC,MAAM,UAAU,WAAW,OAAO;CAClC,MAAM,OAAO,SAAS,kBAAkB,WAAW,KAAK,GAAG;CAC3D,MAAM,YAAY,cAAc,GAAG,QAAQ;CAC3C,OACE,qBAAA,YAAA,EAAA,UAAA,CACG,YAAY,GAAG,QAAQ,KAAK,WAC5B,YACC,oBAAC,QAAD;EAAM,aAAU;EAAgB,WAAU;EACxC,UAAA,oBAAC,UAAD;GAAU,SAAS,OAAO,SAAS;GAAI,QAAO;GAAQ,WAAU;GAC9D,UAAA,oBAAC,OAAD;IACE,SAAQ;IACR,MAAK;IACL,QAAO;IACP,aAAY;IACZ,eAAc;IACd,gBAAe;IACf,WAAU;IACV,eAAY;IAEZ,UAAA,oBAAC,QAAD,EAAM,GAAE,mBAAoB,CAAA;GACzB,CAAA;EACG,CAAA;CACN,CAAA,IACJ,IACJ,EAAA,CAAA;AAEN;AAEA,MAAa,WAAW,WAA0C,SAAS,SACzE,EACE,OACA,aACA,WACA,iBAAiB,MACjB,MACA,gBACA,eACA,WACA,GAAG,SAEL,KACA;CACA,OACE,oBAACA,aAAD;EACO;EACL,GAAI;EACJ,aAAU;EACV,WAAW,GAAG,iBAAiB,EAAE,aAAa,QAAQ,eAAe,CAAC,SAAS,EAAE,CAAC,GAAG,SAAS;EAE5F,WAAA,EAAE,YAAY,iBAAiB,gBAC/B,qBAAA,YAAA,EAAA,UAAA,CAME,qBAAC,OAAD;GACE,aAAU;GACV,WAAW,MAAM,aAAa,2CAA2C;GAF3E,UAAA,CAIE,oBAACC,OAAD;IACE,aAAU;IACV,WAAW,MACT,sDACA,eAAe,SACjB;IAEC,UAAA,OAAO,UAAU,WAChB,oBAAC,UAAD;KAAU,SAAS;KAAO,WAAU;KACjC,UAAA;IACO,CAAA,IAEV;GAEO,CAAA,GACV,YACC,oBAAC,QAAD;IACE,aAAU;IAIV,eAAY;IACZ,WAAU;IAEV,UAAA,oBAAC,eAAD;KACE,YAAY,cAAc;KACf;KACK;IACjB,CAAA;GACG,CAAA,IACJ,IACD;EACL,CAAA,GAAA,oBAAC,OAAD;GACE,aAAU;GACV,WAAW,GAAG,sBAAsB,GAAG,cAAc;GAgBrD,UAAA,oBAAC,OAAD;IACE,aAAU;IACV,sBAAoB,mBAAmB,KAAA;IAEvC,iBACE,mBAAmB,CAAC,QAAQ,SAAS,cAAc,CAAC,oBAAoB,cAAc,MAAM,MACxF,KACA,KAAA;IAEN,WAAW,GAAG,qBAAqB,EAAE,KAAK,CAAC,GAAG,aAAa;IAC3D,OAAO,kBAAkB,KAAA,IAAY,EAAE,OAAO,GAAG,cAAc,EAAE,GAAG;GACrE,CAAA;EACE,CAAA,CACL,EAAA,CAAA;CAEW,CAAA;AAErB,CAAC"}
1
+ {"version":3,"file":"progress.js","names":["AriaProgressBar","AriaLabel"],"sources":["../../src/progress/progress.tsx"],"sourcesContent":["\"use client\";\n\nimport { cva, type VariantProps } from \"class-variance-authority\";\nimport { forwardRef, useId, useMemo, type ReactNode } from \"react\";\nimport {\n Label as AriaLabel,\n ProgressBar as AriaProgressBar,\n useLocale,\n type ProgressBarProps as AriaProgressBarProps,\n} from \"react-aria-components\";\n\nimport { cn, cnAll } from \"../lib/cn.js\";\nimport { useAfter, useCountTo } from \"../lib/count-to.js\";\nimport { SwapText } from \"../lib/swap-text.js\";\n\n/**\n * Progress, ported from the legacy shadcn/Radix component.\n *\n * Census: seven files import it and only TWO render anything --\n * `ResponseOption.tsx` and `LiveQA.tsx`. Both pass `value` and\n * `className` and nothing else.\n *\n * `label` IS REQUIRED, and that is the point. Neither live call site\n * passes `aria-label`, `aria-labelledby` or an `id` a label targets, and\n * `role=\"progressbar\"` does not take its name from content -- so both\n * rendered progress bars are announced as an unnamed progressbar. React\n * Aria supplies the plumbing for three naming routes but no name of its\n * own: `<ProgressBar value={60}/>` is just as nameless there. Making the\n * prop required is what turns it into something you cannot forget.\n *\n * The migration plan said this component \"adds the missing\n * aria-valuenow\". That is wrong -- Radix already supplies `aria-valuenow`,\n * `aria-valuemin`, `aria-valuemax` and `aria-valuetext`. The missing thing\n * was always the NAME.\n *\n * THE COLOURS ARE NOT THE LEGACY'S. Its track is `bg-primary/20`, an alpha\n * fill that composites against unknown page content and therefore has no\n * fixed contrast pair at all -- over white it is roughly 1.05:1, an\n * invisible boundary. Its indicator is `bg-primary`, 2.44:1 on the page.\n * Here the track is `muted` with a `border` boundary and the fill is\n * `primary-strong`, which puts the fill against the track at 4.61:1 and\n * the track against the page at 5.25:1.\n *\n * Both are in the build-failing contrast contract.\n *\n * A TARGET MARKER is opt-in (`target`): a short upright tick that stands\n * proud of the track at the value the bar should have reached -- \"where the\n * goal should be by now\". The tick is decorative; its meaning is read to\n * assistive technology as the bar's description (`targetLabel` plus the\n * formatted value), and `showTargetLabel` also prints it under the tick.\n *\n * `surface=\"inverse\"` is for a bar on the inverted (foreground-coloured)\n * surface, such as a dark summary strip: the track is the inverse text colour\n * mixed into the surface, the default fill and the copy take the inverse text\n * colour, so the bar keeps its contrast where the default greys would vanish.\n */\nexport const progressVariants = cva([\"flex w-full flex-col box-border font-sans\"], {\n variants: {\n /*\n * True when nothing is printed above the track (`labelHidden` and no\n * `showValue`). The header is then an empty row -- its sr-only label is\n * out of flow -- and the column gap would leave 6px of nothing above the\n * bar. The gap lives here and not in the base, so the raw\n * `progressVariants()` output never carries both `gap-1.5` and `gap-0`.\n */\n headerEmpty: { true: \"gap-0\", false: \"gap-1.5\" },\n },\n defaultVariants: { headerEmpty: false },\n});\n\nexport const progressTrackVariants = cva(\n [\"relative h-2 w-full overflow-hidden rounded-pill box-border\", \"border-(length:--border-width)\"],\n {\n variants: {\n /** The surface the bar sits on. */\n surface: {\n // muted, NOT `border` as the Switch uses. A switch has no fill; a\n // progress bar does, and primary-strong on a `border` track is 1.01:1 --\n // the two halves are the same grey and the value disappears.\n default: \"bg-muted border-border\",\n // On the inverted surface: the inverse text colour mixed into that surface, a quiet step off it.\n inverse:\n \"bg-[color-mix(in_oklab,var(--color-text-inverse)_28%,var(--color-foreground))] border-transparent\",\n },\n },\n defaultVariants: { surface: \"default\" },\n },\n);\n\n/**\n * The target tick: 2px wide, standing 4px proud of the track on both sides,\n * with a 1px halo of the surface colour so it reads over the fill as well as\n * over the track. Placed by an inline `inset-inline-start`, so it mirrors in\n * right-to-left text.\n */\nexport const progressTargetVariants = cva(\n \"pointer-events-none absolute -top-1 -bottom-1 w-0.5 -ms-px rounded-pill\",\n {\n variants: {\n surface: {\n default: \"bg-foreground shadow-[0_0_0_1px_var(--color-surface)]\",\n inverse: \"bg-text-inverse shadow-[0_0_0_1px_var(--color-foreground)]\",\n },\n },\n defaultVariants: { surface: \"default\" },\n },\n);\n\nexport const progressFillVariants = cva(\n [\n \"h-full rounded-pill\",\n // The fill follows the value on the smooth spring and turns success-coloured when it completes; indeterminate, it sweeps (see progress-fill).\n \"progress-fill\",\n ],\n {\n variants: {\n /*\n * What the value MEANS, which for a progress bar is usually a score\n * rather than a completion. The product's bar carried exactly this axis\n * (`default`/`success`/`warning`/`error`/`info`) and reached for it to\n * colour performance metrics.\n *\n * `-strong` throughout: the fill sits on the `muted` track and has to\n * clear the 3:1 non-text floor of WCAG 1.4.11 against it, which is the\n * same reason the track is muted rather than `border` -- primary-strong\n * on a `border` track measures 1.01:1 and the value disappears.\n */\n tone: {\n neutral: \"bg-primary-strong\",\n success: \"bg-success-strong\",\n warning: \"bg-warning-strong\",\n danger: \"bg-destructive-strong\",\n info: \"bg-info-strong\",\n },\n },\n defaultVariants: { tone: \"neutral\" },\n },\n);\n\n/**\n * A determinate or indeterminate progress bar.\n *\n * @example\n * <Progress label=\"Upload\" value={62} />\n * <Progress label=\"Loading results\" />\n */\nexport interface ProgressProps\n extends Omit<AriaProgressBarProps, \"className\" | \"style\" | \"children\">,\n VariantProps<typeof progressFillVariants> {\n /**\n * What is progressing, and the bar's accessible name.\n *\n * **Required.** Both legacy call sites render an unnamed progressbar.\n * Pass `labelHidden` when the surrounding UI already says it.\n */\n label: ReactNode;\n /**\n * Render the label for screen readers only.\n *\n * Both legacy call sites sit beside visible text that was never\n * associated with the bar -- this is how that text becomes the name\n * without printing it twice.\n */\n labelHidden?: boolean;\n /**\n * Print the value beside the label.\n *\n * The text itself comes from React Aria, which already formats it from\n * `value`/`minValue`/`maxValue` and honours `formatOptions` and\n * `valueLabel` -- so a percentage, a currency or \"3 of 7\" are all reachable\n * without a second prop here. It was never RENDERED before: the bar\n * announced its value to assistive tech and showed a sighted reader nothing.\n */\n showValue?: boolean;\n /**\n * Mark a full bar as done: a default-toned fill eases to the success\n * colour at 100% and, with `showValue`, a check lands beside the count.\n *\n * Pass `false` for a bar that can reach 100% before the work is finished\n * -- a timer-driven estimate -- so it does not say \"done\" early. The fill\n * keeps its tone and no check is shown. An explicit `tone` is never\n * overridden either way.\n *\n * @default true\n */\n showCompletion?: boolean;\n /**\n * Where the bar should be by now, on the same scale as `value`\n * (`minValue`..`maxValue`, clamped). Draws an upright tick across the track\n * at that point. The tick is decorative; `targetLabel` and the formatted\n * target are read as the bar's description. Leave it out (the default) for\n * no marker and the bar exactly as before. Ignored while indeterminate.\n */\n target?: number;\n /**\n * What the target means, such as \"Expected by now\". Read with the formatted\n * target (\"Expected by now: 40%\") as the bar's description, and printed\n * under the tick with `showTargetLabel`.\n * @default \"Target\"\n */\n targetLabel?: string;\n /**\n * Print `targetLabel` and the target under the tick. It follows the tick and\n * stays inside the bar near either end.\n * @default false\n */\n showTargetLabel?: boolean;\n /**\n * The surface the bar sits on. `inverse` is the inverted, foreground-coloured\n * surface (a dark strip in the light theme, a light one in the dark theme):\n * the track, the default-toned fill, the target tick and the copy take\n * colours that hold their contrast there. Explicit tones keep their colours.\n * @default \"default\"\n */\n surface?: \"default\" | \"inverse\";\n /** Extra classes for the track. */\n trackClassName?: string;\n /** Extra classes for the fill -- a per-call-site colour the tone axis cannot express. */\n fillClassName?: string;\n /** Extra classes. Merged last, so they win conflicts with ours. */\n className?: string;\n}\n\n/**\n * The printed value. A percentage counts to its new number on the same spring the\n * fill follows, and a check lands beside the final count once the bar is full.\n * Any other text (a price, a step count) is shown as it is.\n */\nfunction ProgressValue({\n percentage,\n valueText,\n showCompletion,\n}: {\n percentage: number;\n valueText: string | undefined;\n showCompletion: boolean;\n}) {\n const percent = Math.round(percentage);\n const counted = useCountTo(percent);\n const done = useAfter(showCompletion && percent >= 100, 240);\n const isPercent = valueText === `${percent}%`;\n return (\n <>\n {isPercent ? `${counted}%` : valueText}\n {isPercent ? (\n <span data-slot=\"progress-done\" className=\"ms-1 inline-flex align-middle text-success-strong\">\n <SwapText swapKey={done ? \"done\" : \"\"} motion=\"glyph\" className=\"inline-flex\">\n <svg\n viewBox=\"0 0 16 16\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n className=\"size-3.5\"\n aria-hidden=\"true\"\n >\n <path d=\"m3.5 8.5 3 3 6-6\" />\n </svg>\n </SwapText>\n </span>\n ) : null}\n </>\n );\n}\n\ninterface Marker {\n /** Where the target sits along the track, 0 to 1. */\n share: number;\n /** \"Target: 40%\": what is read, and printed with showTargetLabel. */\n text: string;\n}\n\n/** The track's frame and the tick. With no marker it renders the track alone, as before. */\nfunction TargetFrame({\n marker,\n surface,\n children,\n}: {\n marker: Marker | null;\n surface: \"default\" | \"inverse\";\n children: ReactNode;\n}) {\n if (!marker) return <>{children}</>;\n return (\n <div data-slot=\"progress-target-frame\" className=\"relative\">\n {children}\n <span\n data-slot=\"progress-target\"\n aria-hidden=\"true\"\n className={progressTargetVariants({ surface })}\n style={{ insetInlineStart: `${marker.share * 100}%` }}\n />\n </div>\n );\n}\n\n/**\n * The target's meaning, the bar's description. Printed, it hangs from a\n * zero-width anchor at the tick: centred on it, or running inward near either\n * end, in either text direction. Otherwise it is for screen readers only.\n */\nfunction TargetText({\n id,\n marker,\n shown,\n inverse,\n}: {\n id: string;\n marker: Marker;\n shown: boolean;\n inverse: boolean;\n}) {\n if (!shown) {\n return (\n <span id={id} className=\"sr-only\">\n {marker.text}\n </span>\n );\n }\n return (\n // aria-hidden: it is read once, as the description, not again as loose text.\n <div data-slot=\"progress-target-label\" aria-hidden=\"true\" className=\"relative h-[1lh] text-xs leading-(--leading-normal)\">\n <span\n className={cnAll(\n \"absolute top-0 flex w-0 whitespace-nowrap\",\n marker.share < 0.2 ? \"justify-start\" : marker.share > 0.8 ? \"justify-end\" : \"justify-center\",\n inverse ? \"text-text-inverse\" : \"text-muted-foreground\",\n )}\n style={{ insetInlineStart: `${marker.share * 100}%` }}\n >\n <span id={id}>{marker.text}</span>\n </span>\n </div>\n );\n}\n\nexport const Progress = forwardRef<HTMLDivElement, ProgressProps>(function Progress(\n {\n label,\n labelHidden,\n showValue,\n showCompletion = true,\n tone,\n target,\n targetLabel = \"Target\",\n showTargetLabel = false,\n surface = \"default\",\n trackClassName,\n fillClassName,\n className,\n ...props\n },\n ref,\n) {\n const { locale } = useLocale();\n const targetId = useId();\n const { minValue = 0, maxValue = 100, formatOptions, isIndeterminate: indeterminate } = props;\n const marker = useMemo(() => {\n if (target === undefined || !Number.isFinite(target) || indeterminate) return null;\n const span = maxValue - minValue;\n const share = span > 0 ? Math.min(1, Math.max(0, (target - minValue) / span)) : 0;\n // Formatted the way React Aria formats the bar's own value: the share under the default percent style, else the value.\n const options = formatOptions ?? { style: \"percent\" };\n const shown = options.style === \"percent\" ? share : Math.min(maxValue, Math.max(minValue, target));\n return { share, text: `${targetLabel}: ${new Intl.NumberFormat(locale, options).format(shown)}` };\n }, [target, indeterminate, minValue, maxValue, formatOptions, locale, targetLabel]);\n const inverse = surface === \"inverse\";\n const describedBy = marker\n ? [props[\"aria-describedby\"], targetId].filter(Boolean).join(\" \")\n : props[\"aria-describedby\"];\n return (\n <AriaProgressBar\n ref={ref}\n {...props}\n aria-describedby={describedBy}\n data-slot=\"progress\"\n data-surface={inverse ? \"inverse\" : undefined}\n className={cn(progressVariants({ headerEmpty: Boolean(labelHidden && !showValue) }), className)}\n >\n {({ percentage, isIndeterminate, valueText }) => (\n <>\n {/*\n The row exists only when the value is printed. Wrapping the label\n in a flex row unconditionally would change the label's box for\n every existing consumer to no purpose.\n */}\n <div\n data-slot=\"progress-header\"\n className={cnAll(showValue && \"flex items-baseline justify-between gap-2\")}\n >\n <AriaLabel\n data-slot=\"progress-label\"\n className={cnAll(\n \"text-sm leading-(--leading-normal)\",\n inverse ? \"text-text-inverse\" : \"text-foreground\",\n labelHidden && \"sr-only\",\n )}\n >\n {typeof label === \"string\" ? (\n <SwapText swapKey={label} className=\"block\">\n {label}\n </SwapText>\n ) : (\n label\n )}\n </AriaLabel>\n {showValue ? (\n <span\n data-slot=\"progress-value\"\n // aria-hidden: React Aria already announces the value on the\n // progressbar itself. Without this a screen reader reads it\n // twice, once as the widget's value and once as loose text.\n aria-hidden=\"true\"\n className={cnAll(\n \"text-sm leading-(--leading-normal) tabular-nums\",\n inverse ? \"text-text-inverse\" : \"text-muted-foreground\",\n )}\n >\n <ProgressValue\n percentage={percentage ?? 0}\n valueText={valueText}\n showCompletion={showCompletion}\n />\n </span>\n ) : null}\n </div>\n {/* The track is wrapped only when there is a marker, so a bar without one keeps its DOM exactly. */}\n <TargetFrame marker={marker} surface={surface}>\n <div\n data-slot=\"progress-track\"\n className={cn(progressTrackVariants({ surface }), trackClassName)}\n >\n {/*\n width, not the legacy translateX(-(100 - value)%). That\n overflowed for value > 100 -- a positive translate slid the\n indicator off to the right, while the announced value stayed\n whatever was passed. React Aria clamps the value, so the two\n can no longer disagree.\n \n An indeterminate bar is not the legacy's silent 0%:\n `value={undefined}` became `(value || 0)` there, so a sighted\n user saw \"0% done\" while a screen reader said \"indeterminate\".\n Its fill is a segment that sweeps the track on a loop (a slow\n pulse of the whole track under reduced motion), sized and moved\n by progress-fill, so it carries no inline width.\n */}\n <div\n data-slot=\"progress-fill\"\n data-indeterminate={isIndeterminate || undefined}\n // A default-toned bar turns success-coloured when it completes, unless showCompletion is off; an explicit tone is never overridden.\n data-complete={\n showCompletion && (!tone || tone === \"neutral\") && !isIndeterminate && (percentage ?? 0) >= 100\n ? \"\"\n : undefined\n }\n className={cn(\n [progressFillVariants({ tone }), inverse && (!tone || tone === \"neutral\") && \"bg-text-inverse\"],\n fillClassName,\n )}\n style={isIndeterminate ? undefined : { width: `${percentage ?? 0}%` }}\n />\n </div>\n </TargetFrame>\n {marker ? <TargetText id={targetId} marker={marker} shown={showTargetLabel} inverse={inverse} /> : null}\n </>\n )}\n </AriaProgressBar>\n );\n});\n"],"mappings":"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"}
@@ -20,7 +20,7 @@ import { RadioGroupProps, RadioProps } from "react-aria-components";
20
20
  * so a radio and a checkbox in the same form agree.
21
21
  */
22
22
  export declare const radioGroupVariants: (props?: ({
23
- variant?: "default" | "card" | null | undefined;
23
+ variant?: "default" | "card" | "framed" | null | undefined;
24
24
  } & import("class-variance-authority/types").ClassProp) | undefined) => string;
25
25
  export declare const radioVariants: (props?: import("class-variance-authority/types").ClassProp | undefined) => string;
26
26
  /**
@@ -34,7 +34,7 @@ export declare const radioVariants: (props?: import("class-variance-authority/ty
34
34
  */
35
35
  export declare const radioCardVariants: (props?: import("class-variance-authority/types").ClassProp | undefined) => string;
36
36
  /** Which look the group's radios take. */
37
- export type RadioGroupVariant = "default" | "card";
37
+ export type RadioGroupVariant = "default" | "card" | "framed";
38
38
  /**
39
39
  * A set of mutually exclusive choices.
40
40
  *
@@ -48,7 +48,9 @@ interface RadioGroupProps$1 extends Omit<RadioGroupProps, "className" | "style">
48
48
  /**
49
49
  * `card` draws each choice as a selectable card, with a title, an optional
50
50
  * `description` and `icon` on each `Radio`. The keyboard model is the same:
51
- * one tab stop, arrow keys move and select.
51
+ * one tab stop, arrow keys move and select. `framed` draws a single bordered
52
+ * frame around ordinary radios (laid out by `orientation`); its focus is the
53
+ * frame's border turning dark.
52
54
  *
53
55
  * @default "default"
54
56
  */
@@ -74,6 +76,13 @@ interface RadioProps$1 extends Omit<RadioProps, "className" | "style" | "childre
74
76
  description?: ReactNode;
75
77
  /** A decorative icon before the title. Card variant only; hidden from assistive tech. */
76
78
  icon?: ReactNode;
79
+ /**
80
+ * Content above the title, edge to edge in the card: a thumbnail, a preview.
81
+ * Card variant only. With it the card stacks (media, then the icon, title and
82
+ * dot row). Give an `<img>` its own `alt`, or `alt=""` when the title says it
83
+ * all; the slot adds no name of its own.
84
+ */
85
+ media?: ReactNode;
77
86
  /** Extra classes for the label (the whole card in the card variant). Merged last, so they win conflicts. */
78
87
  className?: string;
79
88
  }
@@ -1,6 +1,6 @@
1
1
  "use client";
2
2
  import { cn, cnAll } from "../lib/cn.js";
3
- import { selectionLabelVariants, selectionRingVariants } from "../lib/selection-control.js";
3
+ import { selectionCardVariants, selectionLabelVariants, selectionRingVariants } from "../lib/selection-control.js";
4
4
  import { cva } from "class-variance-authority";
5
5
  import { createContext, forwardRef, useContext, useId } from "react";
6
6
  import { Radio, RadioGroup } from "react-aria-components";
@@ -26,10 +26,24 @@ import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
26
26
  */
27
27
  const radioGroupVariants = cva(["box-border font-sans"], {
28
28
  variants: {
29
- /** `card` lays the choices out as a grid; add `sm:grid-cols-2` and the like. */
29
+ /**
30
+ * `card` lays the choices out as a grid; add `sm:grid-cols-2` and the like.
31
+ * `framed` keeps ordinary radios and draws one bordered frame around the row.
32
+ */
30
33
  variant: {
31
34
  default: "flex flex-col gap-2",
32
- card: "grid gap-3"
35
+ card: "grid gap-3",
36
+ framed: [
37
+ "inline-flex flex-col gap-2 px-2 py-0.5",
38
+ "rounded-control border-(length:--border-width) border-border bg-surface",
39
+ "data-[orientation=horizontal]:flex-row data-[orientation=horizontal]:items-center data-[orientation=horizontal]:gap-3",
40
+ "has-[[data-focus-visible]]:border-foreground",
41
+ "[&_[data-slot=radio-dot]]:outline-hidden!",
42
+ "forced-colors:has-[[data-focus-visible]]:outline-solid",
43
+ "forced-colors:has-[[data-focus-visible]]:outline-(length:--focus-ring-width)",
44
+ "forced-colors:has-[[data-focus-visible]]:outline-offset-(--focus-ring-offset)",
45
+ "motion-safe:transition-[border-color] motion-safe:duration-(--duration-quick) motion-safe:ease-settle"
46
+ ]
33
47
  } },
34
48
  defaultVariants: { variant: "default" }
35
49
  });
@@ -56,21 +70,7 @@ const radioVariants = cva([...radioMark, selectionRingVariants()]);
56
70
  * pair (`border-primary-strong`, hover darkens an unselected edge), so a
57
71
  * selected card and a selected stat card read the same.
58
72
  */
59
- const radioCardVariants = cva([
60
- "group flex items-start gap-3 p-4",
61
- "rounded-panel border-(length:--border-width) border-border bg-surface",
62
- "box-border font-sans text-sm leading-(--leading-normal) text-foreground text-start",
63
- "cursor-pointer select-none",
64
- "motion-safe:transition-[border-color] motion-safe:duration-(--duration-quick) motion-safe:ease-settle",
65
- "data-[hovered]:not-data-[selected]:border-muted-foreground",
66
- "data-[selected]:border-primary-strong",
67
- "data-[disabled]:cursor-not-allowed data-[disabled]:opacity-(--opacity-disabled)",
68
- "outline-none",
69
- "data-[focus-visible]:outline-solid",
70
- "data-[focus-visible]:outline-(length:--focus-ring-width)",
71
- "data-[focus-visible]:outline-offset-(--focus-ring-offset)",
72
- "data-[focus-visible]:outline-(--color-ring)"
73
- ]);
73
+ const radioCardVariants = selectionCardVariants;
74
74
  const radioCardDotVariants = cva([...radioMark, "mt-0.5"]);
75
75
  /** The group hands its variant down, so every Radio inside matches it. */
76
76
  const RadioVariantContext = createContext("default");
@@ -96,7 +96,7 @@ const Radio$1 = forwardRef(function Radio(props, ref) {
96
96
  ref
97
97
  });
98
98
  });
99
- const RadioDefault = forwardRef(function RadioDefault({ className, children, description: _description, icon: _icon, ...props }, ref) {
99
+ const RadioDefault = forwardRef(function RadioDefault({ className, children, description: _description, icon: _icon, media: _media, ...props }, ref) {
100
100
  return /* @__PURE__ */ jsx(Radio, {
101
101
  ...props,
102
102
  inputRef: ref,
@@ -117,7 +117,7 @@ const RadioDefault = forwardRef(function RadioDefault({ className, children, des
117
117
  })] })
118
118
  });
119
119
  });
120
- const RadioCard = forwardRef(function RadioCard({ className, children, description, icon, ...props }, ref) {
120
+ const RadioCard = forwardRef(function RadioCard({ className, children, description, icon, media, ...props }, ref) {
121
121
  const id = useId();
122
122
  const titleId = `${id}-title`;
123
123
  const descriptionId = `${id}-description`;
@@ -128,39 +128,49 @@ const RadioCard = forwardRef(function RadioCard({ className, children, descripti
128
128
  inputRef: ref,
129
129
  "data-slot": "radio",
130
130
  "data-variant": "card",
131
- className: cn(radioCardVariants(), className),
132
- children: ({ isSelected }) => /* @__PURE__ */ jsxs(Fragment$1, { children: [
133
- icon == null ? null : /* @__PURE__ */ jsx("span", {
134
- "aria-hidden": "true",
135
- "data-slot": "radio-icon",
136
- className: "mt-0.5 inline-flex shrink-0 text-text-secondary [&_svg]:size-4",
137
- children: icon
138
- }),
139
- /* @__PURE__ */ jsxs("span", {
140
- className: "flex min-w-0 flex-1 flex-col gap-1",
141
- children: [children == null ? null : /* @__PURE__ */ jsx("span", {
142
- id: titleId,
143
- "data-slot": "radio-label",
144
- className: "font-medium",
145
- children
146
- }), description == null ? null : /* @__PURE__ */ jsx("span", {
147
- id: descriptionId,
148
- "data-slot": "radio-description",
149
- className: "text-text-secondary",
150
- children: description
151
- })]
152
- }),
153
- /* @__PURE__ */ jsx("span", {
154
- "aria-hidden": "true",
155
- "data-slot": "radio-dot",
156
- className: radioCardDotVariants(),
157
- children: /* @__PURE__ */ jsx("span", {
158
- "data-slot": "radio-indicator",
159
- "data-selected": isSelected || void 0,
160
- className: "radio-dot size-1.5 rounded-full bg-current"
131
+ className: cn(cnAll(radioCardVariants(), media != null && "flex-col items-stretch"), className),
132
+ children: ({ isSelected }) => {
133
+ const row = /* @__PURE__ */ jsxs(Fragment$1, { children: [
134
+ icon == null ? null : /* @__PURE__ */ jsx("span", {
135
+ "aria-hidden": "true",
136
+ "data-slot": "radio-icon",
137
+ className: "mt-0.5 inline-flex shrink-0 text-text-secondary [&_svg]:size-4",
138
+ children: icon
139
+ }),
140
+ /* @__PURE__ */ jsxs("span", {
141
+ className: "flex min-w-0 flex-1 flex-col gap-1",
142
+ children: [children == null ? null : /* @__PURE__ */ jsx("span", {
143
+ id: titleId,
144
+ "data-slot": "radio-label",
145
+ className: "font-medium",
146
+ children
147
+ }), description == null ? null : /* @__PURE__ */ jsx("span", {
148
+ id: descriptionId,
149
+ "data-slot": "radio-description",
150
+ className: "text-text-secondary",
151
+ children: description
152
+ })]
153
+ }),
154
+ /* @__PURE__ */ jsx("span", {
155
+ "aria-hidden": "true",
156
+ "data-slot": "radio-dot",
157
+ className: radioCardDotVariants(),
158
+ children: /* @__PURE__ */ jsx("span", {
159
+ "data-slot": "radio-indicator",
160
+ "data-selected": isSelected || void 0,
161
+ className: "radio-dot size-1.5 rounded-full bg-current"
162
+ })
161
163
  })
162
- })
163
- ] })
164
+ ] });
165
+ return media == null ? row : /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx("span", {
166
+ "data-slot": "radio-media",
167
+ className: "block w-full min-w-0",
168
+ children: media
169
+ }), /* @__PURE__ */ jsx("span", {
170
+ className: "flex w-full items-start gap-3",
171
+ children: row
172
+ })] });
173
+ }
164
174
  });
165
175
  });
166
176
  //#endregion
@@ -1 +1 @@
1
- {"version":3,"file":"radio-group.js","names":["RadioGroup","AriaRadioGroup","Radio","AriaRadio"],"sources":["../../src/radio-group/radio-group.tsx"],"sourcesContent":["\"use client\";\n\nimport { cva } from \"class-variance-authority\";\nimport { createContext, forwardRef, useContext, useId, type ReactNode } from \"react\";\nimport {\n Radio as AriaRadio,\n RadioGroup as AriaRadioGroup,\n type RadioGroupProps as AriaRadioGroupProps,\n type RadioProps as AriaRadioProps,\n} from \"react-aria-components\";\n\nimport { cn, cnAll } from \"../lib/cn.js\";\nimport { selectionLabelVariants, selectionRingVariants } from \"../lib/selection-control.js\";\n\n/**\n * RadioGroup, ported from the legacy shadcn/Radix component onto React Aria.\n *\n * Census: 1 file, 3 uses -- `RadioGroup` 1, `RadioGroupItem` 2. The\n * smallest real surface in the migration, so the port is deliberately plain.\n *\n * THE LABEL MOVES INSIDE THE ITEM. Radix's `RadioGroupItem` is a bare\n * button and the caller pairs it with a separate `<Label htmlFor>`; React\n * Aria's `Radio` takes its label as `children` and wires the association\n * itself. That is the one rename a call site has to make, and it removes\n * the id bookkeeping.\n *\n * `border-primary` becomes `border-border` for the same reason it did on\n * Checkbox: brand orange is 2.44:1 on white and cannot legally be the only\n * thing describing a control's edge (WCAG 1.4.11). Selected reuses\n * Checkbox's exact pair -- `bg-primary` with a `primary-strong` boundary --\n * so a radio and a checkbox in the same form agree.\n */\nexport const radioGroupVariants = cva([\"box-border font-sans\"], {\n variants: {\n /** `card` lays the choices out as a grid; add `sm:grid-cols-2` and the like. */\n variant: {\n default: \"flex flex-col gap-2\",\n card: \"grid gap-3\",\n },\n },\n defaultVariants: { variant: \"default\" },\n});\n\n/**\n * The box and dot, shared by both variants. Only the default variant rings\n * the dot itself: a card rings the whole card, and a second ring on the dot\n * inside it would just be noise.\n */\nconst radioMark = [\n \"inline-flex size-4 shrink-0 items-center justify-center\",\n \"rounded-full border-(length:--border-width) box-border\",\n \"bg-surface border-control-edge\",\n // Colours ease, the mark dips under a press and springs back (see radio-mark).\n \"radio-mark\",\n \"group-data-[selected]:bg-primary group-data-[selected]:border-primary-strong\",\n \"group-data-[selected]:text-primary-foreground\",\n];\n\nexport const radioVariants = cva([...radioMark, selectionRingVariants()]);\n\n/**\n * One choice drawn as a selectable card: a bordered surface the whole of\n * which is the hit target, with the title, an optional description and icon.\n *\n * Selection is shown twice, by the border AND by the dot, so it never rests\n * on colour alone (WCAG 1.4.1). The border reuses StatCard's selected filter\n * pair (`border-primary-strong`, hover darkens an unselected edge), so a\n * selected card and a selected stat card read the same.\n */\nexport const radioCardVariants = cva([\n \"group flex items-start gap-3 p-4\",\n \"rounded-panel border-(length:--border-width) border-border bg-surface\",\n \"box-border font-sans text-sm leading-(--leading-normal) text-foreground text-start\",\n \"cursor-pointer select-none\",\n \"motion-safe:transition-[border-color] motion-safe:duration-(--duration-quick) motion-safe:ease-settle\",\n \"data-[hovered]:not-data-[selected]:border-muted-foreground\",\n \"data-[selected]:border-primary-strong\",\n \"data-[disabled]:cursor-not-allowed data-[disabled]:opacity-(--opacity-disabled)\",\n // The same ring as selectionRingVariants, on purpose not reused: that helper\n // reads the state from a `group-data-[focus-visible]:` parent, and here the\n // card IS the group, so its own `data-[focus-visible]:` is the state. Keep\n // the two in step. outline-solid is load-bearing: outline-none sets the\n // style the width utility resolves from (see selectionRingVariants).\n \"outline-none\",\n \"data-[focus-visible]:outline-solid\",\n \"data-[focus-visible]:outline-(length:--focus-ring-width)\",\n \"data-[focus-visible]:outline-offset-(--focus-ring-offset)\",\n \"data-[focus-visible]:outline-(--color-ring)\",\n]);\n\nconst radioCardDotVariants = cva([...radioMark, \"mt-0.5\"]);\n\n/** Which look the group's radios take. */\nexport type RadioGroupVariant = \"default\" | \"card\";\n\n/** The group hands its variant down, so every Radio inside matches it. */\nconst RadioVariantContext = createContext<RadioGroupVariant>(\"default\");\n\n/**\n * A set of mutually exclusive choices.\n *\n * @example\n * <RadioGroup value={plan} onChange={setPlan} aria-label=\"Plan\">\n * <Radio value=\"free\">Free</Radio>\n * <Radio value=\"pro\">Pro</Radio>\n * </RadioGroup>\n */\nexport interface RadioGroupProps extends Omit<AriaRadioGroupProps, \"className\" | \"style\"> {\n /**\n * `card` draws each choice as a selectable card, with a title, an optional\n * `description` and `icon` on each `Radio`. The keyboard model is the same:\n * one tab stop, arrow keys move and select.\n *\n * @default \"default\"\n */\n variant?: RadioGroupVariant;\n /** Extra classes. Merged last, so they win conflicts with ours. */\n className?: string;\n}\n\nexport const RadioGroup = forwardRef<HTMLDivElement, RadioGroupProps>(function RadioGroup(\n { className, variant = \"default\", children, ...props },\n ref,\n) {\n return (\n <AriaRadioGroup\n ref={ref}\n {...props}\n data-slot=\"radio-group\"\n data-variant={variant === \"default\" ? undefined : variant}\n className={cn(radioGroupVariants({ variant }), className)}\n >\n {(renderProps) => (\n <RadioVariantContext.Provider value={variant}>\n {typeof children === \"function\" ? children(renderProps) : children}\n </RadioVariantContext.Provider>\n )}\n </AriaRadioGroup>\n );\n});\n\n/**\n * One choice.\n *\n * @example\n * <Radio value=\"pro\">Pro</Radio>\n */\nexport interface RadioProps extends Omit<AriaRadioProps, \"className\" | \"style\" | \"children\"> {\n /** The label, and this option's accessible name. In a card, the title. */\n children?: ReactNode;\n /**\n * A line under the title, read as the option's description\n * (`aria-describedby`), not as part of its name. Card variant only; the\n * default variant ignores it.\n */\n description?: ReactNode;\n /** A decorative icon before the title. Card variant only; hidden from assistive tech. */\n icon?: ReactNode;\n /** Extra classes for the label (the whole card in the card variant). Merged last, so they win conflicts. */\n className?: string;\n}\n\nexport const Radio = forwardRef<HTMLInputElement, RadioProps>(function Radio(props, ref) {\n const variant = useContext(RadioVariantContext);\n return variant === \"card\" ? <RadioCard {...props} ref={ref} /> : <RadioDefault {...props} ref={ref} />;\n});\n\nconst RadioDefault = forwardRef<HTMLInputElement, RadioProps>(function RadioDefault(\n // description and icon belong to the card; pulled out so they never reach the DOM.\n { className, children, description: _description, icon: _icon, ...props },\n ref,\n) {\n return (\n <AriaRadio\n {...props}\n // inputRef, not ref: AriaRadio's own ref targets the label it renders\n // around a visually-hidden native input, and a label cannot be\n // focused -- ref.current.focus() would silently no-op. inputRef is\n // React Aria's own escape hatch to the real control.\n inputRef={ref}\n data-slot=\"radio\"\n // justify-start, overriding the shared label's centring: a radio sits\n // in a vertical list where every label must start on the same line,\n // not centre itself in a 24px box.\n className={cn(cnAll(selectionLabelVariants(), \"justify-start\"), className)}\n >\n {({ isSelected }) => (\n <>\n <span aria-hidden=\"true\" data-slot=\"radio-dot\" className={radioVariants()}>\n {/*\n Always there: the state decides whether it shows, so the dot can\n spring in and shrink away. data-selected carries the state to a\n test, which can no longer read it from the dot's presence.\n */}\n <span\n data-slot=\"radio-indicator\"\n data-selected={isSelected || undefined}\n className=\"radio-dot size-1.5 rounded-full bg-current\"\n />\n </span>\n {children == null ? null : <span data-slot=\"radio-label\">{children}</span>}\n </>\n )}\n </AriaRadio>\n );\n});\n\nconst RadioCard = forwardRef<HTMLInputElement, RadioProps>(function RadioCard(\n { className, children, description, icon, ...props },\n ref,\n) {\n const id = useId();\n const titleId = `${id}-title`;\n const descriptionId = `${id}-description`;\n return (\n <AriaRadio\n // The title alone names the option; the description describes it. Left\n // to the wrapping <label>, the name would be title and description run\n // together. A caller's own labelling wins: their aria-labelledby or\n // aria-describedby by the spread below, and their aria-label because the\n // title's aria-labelledby is then left off (it would outrank aria-label\n // in the name computation).\n aria-labelledby={children == null || props[\"aria-label\"] != null ? undefined : titleId}\n aria-describedby={description == null ? undefined : descriptionId}\n {...props}\n inputRef={ref}\n data-slot=\"radio\"\n data-variant=\"card\"\n className={cn(radioCardVariants(), className)}\n >\n {({ isSelected }) => (\n <>\n {icon == null ? null : (\n <span\n aria-hidden=\"true\"\n data-slot=\"radio-icon\"\n className=\"mt-0.5 inline-flex shrink-0 text-text-secondary [&_svg]:size-4\"\n >\n {icon}\n </span>\n )}\n <span className=\"flex min-w-0 flex-1 flex-col gap-1\">\n {children == null ? null : (\n <span id={titleId} data-slot=\"radio-label\" className=\"font-medium\">\n {children}\n </span>\n )}\n {description == null ? null : (\n <span id={descriptionId} data-slot=\"radio-description\" className=\"text-text-secondary\">\n {description}\n </span>\n )}\n </span>\n <span aria-hidden=\"true\" data-slot=\"radio-dot\" className={radioCardDotVariants()}>\n <span\n data-slot=\"radio-indicator\"\n data-selected={isSelected || undefined}\n className=\"radio-dot size-1.5 rounded-full bg-current\"\n />\n </span>\n </>\n )}\n </AriaRadio>\n );\n});\n"],"mappings":"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"}
1
+ {"version":3,"file":"radio-group.js","names":["RadioGroup","AriaRadioGroup","Radio","AriaRadio"],"sources":["../../src/radio-group/radio-group.tsx"],"sourcesContent":["\"use client\";\n\nimport { cva } from \"class-variance-authority\";\nimport { createContext, forwardRef, useContext, useId, type ReactNode } from \"react\";\nimport {\n Radio as AriaRadio,\n RadioGroup as AriaRadioGroup,\n type RadioGroupProps as AriaRadioGroupProps,\n type RadioProps as AriaRadioProps,\n} from \"react-aria-components\";\n\nimport { cn, cnAll } from \"../lib/cn.js\";\nimport {\n selectionCardVariants,\n selectionLabelVariants,\n selectionRingVariants,\n} from \"../lib/selection-control.js\";\n\n/**\n * RadioGroup, ported from the legacy shadcn/Radix component onto React Aria.\n *\n * Census: 1 file, 3 uses -- `RadioGroup` 1, `RadioGroupItem` 2. The\n * smallest real surface in the migration, so the port is deliberately plain.\n *\n * THE LABEL MOVES INSIDE THE ITEM. Radix's `RadioGroupItem` is a bare\n * button and the caller pairs it with a separate `<Label htmlFor>`; React\n * Aria's `Radio` takes its label as `children` and wires the association\n * itself. That is the one rename a call site has to make, and it removes\n * the id bookkeeping.\n *\n * `border-primary` becomes `border-border` for the same reason it did on\n * Checkbox: brand orange is 2.44:1 on white and cannot legally be the only\n * thing describing a control's edge (WCAG 1.4.11). Selected reuses\n * Checkbox's exact pair -- `bg-primary` with a `primary-strong` boundary --\n * so a radio and a checkbox in the same form agree.\n */\nexport const radioGroupVariants = cva([\"box-border font-sans\"], {\n variants: {\n /**\n * `card` lays the choices out as a grid; add `sm:grid-cols-2` and the like.\n * `framed` keeps ordinary radios and draws one bordered frame around the row.\n */\n variant: {\n default: \"flex flex-col gap-2\",\n card: \"grid gap-3\",\n framed: [\n \"inline-flex flex-col gap-2 px-2 py-0.5\",\n \"rounded-control border-(length:--border-width) border-border bg-surface\",\n \"data-[orientation=horizontal]:flex-row data-[orientation=horizontal]:items-center data-[orientation=horizontal]:gap-3\",\n // Focus is the FRAME's own border going dark, not a ring on the dot: the\n // approved style. :has() reads React Aria's data-focus-visible off the\n // radio's label. The dot's own ring is switched off in this variant, and\n // forced colours (which override a border colour) get an outline on the frame.\n \"has-[[data-focus-visible]]:border-foreground\",\n \"[&_[data-slot=radio-dot]]:outline-hidden!\",\n \"forced-colors:has-[[data-focus-visible]]:outline-solid\",\n \"forced-colors:has-[[data-focus-visible]]:outline-(length:--focus-ring-width)\",\n \"forced-colors:has-[[data-focus-visible]]:outline-offset-(--focus-ring-offset)\",\n \"motion-safe:transition-[border-color] motion-safe:duration-(--duration-quick) motion-safe:ease-settle\",\n ],\n },\n },\n defaultVariants: { variant: \"default\" },\n});\n\n/**\n * The box and dot, shared by both variants. Only the default variant rings\n * the dot itself: a card rings the whole card, and a second ring on the dot\n * inside it would just be noise.\n */\nconst radioMark = [\n \"inline-flex size-4 shrink-0 items-center justify-center\",\n \"rounded-full border-(length:--border-width) box-border\",\n \"bg-surface border-control-edge\",\n // Colours ease, the mark dips under a press and springs back (see radio-mark).\n \"radio-mark\",\n \"group-data-[selected]:bg-primary group-data-[selected]:border-primary-strong\",\n \"group-data-[selected]:text-primary-foreground\",\n];\n\nexport const radioVariants = cva([...radioMark, selectionRingVariants()]);\n\n/**\n * One choice drawn as a selectable card: a bordered surface the whole of\n * which is the hit target, with the title, an optional description and icon.\n *\n * Selection is shown twice, by the border AND by the dot, so it never rests\n * on colour alone (WCAG 1.4.1). The border reuses StatCard's selected filter\n * pair (`border-primary-strong`, hover darkens an unselected edge), so a\n * selected card and a selected stat card read the same.\n */\nexport const radioCardVariants = selectionCardVariants;\n\nconst radioCardDotVariants = cva([...radioMark, \"mt-0.5\"]);\n\n/** Which look the group's radios take. */\nexport type RadioGroupVariant = \"default\" | \"card\" | \"framed\";\n\n/** The group hands its variant down, so every Radio inside matches it. */\nconst RadioVariantContext = createContext<RadioGroupVariant>(\"default\");\n\n/**\n * A set of mutually exclusive choices.\n *\n * @example\n * <RadioGroup value={plan} onChange={setPlan} aria-label=\"Plan\">\n * <Radio value=\"free\">Free</Radio>\n * <Radio value=\"pro\">Pro</Radio>\n * </RadioGroup>\n */\nexport interface RadioGroupProps extends Omit<AriaRadioGroupProps, \"className\" | \"style\"> {\n /**\n * `card` draws each choice as a selectable card, with a title, an optional\n * `description` and `icon` on each `Radio`. The keyboard model is the same:\n * one tab stop, arrow keys move and select. `framed` draws a single bordered\n * frame around ordinary radios (laid out by `orientation`); its focus is the\n * frame's border turning dark.\n *\n * @default \"default\"\n */\n variant?: RadioGroupVariant;\n /** Extra classes. Merged last, so they win conflicts with ours. */\n className?: string;\n}\n\nexport const RadioGroup = forwardRef<HTMLDivElement, RadioGroupProps>(function RadioGroup(\n { className, variant = \"default\", children, ...props },\n ref,\n) {\n return (\n <AriaRadioGroup\n ref={ref}\n {...props}\n data-slot=\"radio-group\"\n data-variant={variant === \"default\" ? undefined : variant}\n className={cn(radioGroupVariants({ variant }), className)}\n >\n {(renderProps) => (\n <RadioVariantContext.Provider value={variant}>\n {typeof children === \"function\" ? children(renderProps) : children}\n </RadioVariantContext.Provider>\n )}\n </AriaRadioGroup>\n );\n});\n\n/**\n * One choice.\n *\n * @example\n * <Radio value=\"pro\">Pro</Radio>\n */\nexport interface RadioProps extends Omit<AriaRadioProps, \"className\" | \"style\" | \"children\"> {\n /** The label, and this option's accessible name. In a card, the title. */\n children?: ReactNode;\n /**\n * A line under the title, read as the option's description\n * (`aria-describedby`), not as part of its name. Card variant only; the\n * default variant ignores it.\n */\n description?: ReactNode;\n /** A decorative icon before the title. Card variant only; hidden from assistive tech. */\n icon?: ReactNode;\n /**\n * Content above the title, edge to edge in the card: a thumbnail, a preview.\n * Card variant only. With it the card stacks (media, then the icon, title and\n * dot row). Give an `<img>` its own `alt`, or `alt=\"\"` when the title says it\n * all; the slot adds no name of its own.\n */\n media?: ReactNode;\n /** Extra classes for the label (the whole card in the card variant). Merged last, so they win conflicts. */\n className?: string;\n}\n\nexport const Radio = forwardRef<HTMLInputElement, RadioProps>(function Radio(props, ref) {\n const variant = useContext(RadioVariantContext);\n return variant === \"card\" ? <RadioCard {...props} ref={ref} /> : <RadioDefault {...props} ref={ref} />;\n});\n\nconst RadioDefault = forwardRef<HTMLInputElement, RadioProps>(function RadioDefault(\n // description and icon belong to the card; pulled out so they never reach the DOM.\n { className, children, description: _description, icon: _icon, media: _media, ...props },\n ref,\n) {\n return (\n <AriaRadio\n {...props}\n // inputRef, not ref: AriaRadio's own ref targets the label it renders\n // around a visually-hidden native input, and a label cannot be\n // focused -- ref.current.focus() would silently no-op. inputRef is\n // React Aria's own escape hatch to the real control.\n inputRef={ref}\n data-slot=\"radio\"\n // justify-start, overriding the shared label's centring: a radio sits\n // in a vertical list where every label must start on the same line,\n // not centre itself in a 24px box.\n className={cn(cnAll(selectionLabelVariants(), \"justify-start\"), className)}\n >\n {({ isSelected }) => (\n <>\n <span aria-hidden=\"true\" data-slot=\"radio-dot\" className={radioVariants()}>\n {/*\n Always there: the state decides whether it shows, so the dot can\n spring in and shrink away. data-selected carries the state to a\n test, which can no longer read it from the dot's presence.\n */}\n <span\n data-slot=\"radio-indicator\"\n data-selected={isSelected || undefined}\n className=\"radio-dot size-1.5 rounded-full bg-current\"\n />\n </span>\n {children == null ? null : <span data-slot=\"radio-label\">{children}</span>}\n </>\n )}\n </AriaRadio>\n );\n});\n\nconst RadioCard = forwardRef<HTMLInputElement, RadioProps>(function RadioCard(\n { className, children, description, icon, media, ...props },\n ref,\n) {\n const id = useId();\n const titleId = `${id}-title`;\n const descriptionId = `${id}-description`;\n return (\n <AriaRadio\n // The title alone names the option; the description describes it. Left\n // to the wrapping <label>, the name would be title and description run\n // together. A caller's own labelling wins: their aria-labelledby or\n // aria-describedby by the spread below, and their aria-label because the\n // title's aria-labelledby is then left off (it would outrank aria-label\n // in the name computation).\n aria-labelledby={children == null || props[\"aria-label\"] != null ? undefined : titleId}\n aria-describedby={description == null ? undefined : descriptionId}\n {...props}\n inputRef={ref}\n data-slot=\"radio\"\n data-variant=\"card\"\n className={cn(cnAll(radioCardVariants(), media != null && \"flex-col items-stretch\"), className)}\n >\n {({ isSelected }) => {\n const row = (\n <>\n {icon == null ? null : (\n <span\n aria-hidden=\"true\"\n data-slot=\"radio-icon\"\n className=\"mt-0.5 inline-flex shrink-0 text-text-secondary [&_svg]:size-4\"\n >\n {icon}\n </span>\n )}\n <span className=\"flex min-w-0 flex-1 flex-col gap-1\">\n {children == null ? null : (\n <span id={titleId} data-slot=\"radio-label\" className=\"font-medium\">\n {children}\n </span>\n )}\n {description == null ? null : (\n <span\n id={descriptionId}\n data-slot=\"radio-description\"\n className=\"text-text-secondary\"\n >\n {description}\n </span>\n )}\n </span>\n <span aria-hidden=\"true\" data-slot=\"radio-dot\" className={radioCardDotVariants()}>\n <span\n data-slot=\"radio-indicator\"\n data-selected={isSelected || undefined}\n className=\"radio-dot size-1.5 rounded-full bg-current\"\n />\n </span>\n </>\n );\n return media == null ? (\n row\n ) : (\n <>\n <span data-slot=\"radio-media\" className=\"block w-full min-w-0\">\n {media}\n </span>\n <span className=\"flex w-full items-start gap-3\">{row}</span>\n </>\n );\n }}\n </AriaRadio>\n );\n});\n"],"mappings":"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"}
@@ -2,9 +2,9 @@
2
2
  import { cn } from "../lib/cn.js";
3
3
  import { useIsomorphicLayoutEffect } from "../lib/motion.js";
4
4
  import { Button } from "../button/button.js";
5
- import { Tooltip, TooltipTrigger } from "../tooltip/tooltip.js";
6
5
  import { Label } from "../label/label.js";
7
6
  import { FieldShell, useFieldWiring } from "../lib/field-shell.js";
7
+ import { Tooltip, TooltipTrigger } from "../tooltip/tooltip.js";
8
8
  import { isSafeUrl, normalizeUrl } from "./rich-text-html.js";
9
9
  import { $isDocumentEmpty, $readFormats, NO_FORMATS, applyLink, beginLink, createRichTextEditor, loadHtml, loadMarkdown, readValue, setBlock, syncDividers, toggleMark } from "./rich-text-engine.js";
10
10
  import { BackIcon, BoldIcon, CheckIcon, CodeBlockIcon, CodeIcon, DividerIcon, Heading1Icon, Heading2Icon, Heading3Icon, ItalicIcon, LinkIcon, ListIcon, ListOrderedIcon, ParagraphIcon, QuoteIcon, StrikeIcon, UnderlineIcon, UnlinkIcon } from "./rich-text-icons.js";
@@ -32,7 +32,7 @@ export declare const scrollAreaRootVariants: (props?: ({
32
32
  framed?: boolean | null | undefined;
33
33
  } & import("class-variance-authority/types").ClassProp) | undefined) => string;
34
34
  export declare const scrollAreaVariants: (props?: ({
35
- orientation?: "vertical" | "horizontal" | "both" | null | undefined;
35
+ orientation?: "both" | "horizontal" | "vertical" | null | undefined;
36
36
  } & import("class-variance-authority/types").ClassProp) | undefined) => string;
37
37
  /**
38
38
  * A scrolling region.