@sableui/react 0.13.1 → 0.14.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 (231) hide show
  1. package/AGENTS.md +2 -2
  2. package/CHANGELOG.md +132 -0
  3. package/CONTRIBUTING.md +7 -5
  4. package/DESIGN.md +8 -3
  5. package/bin/sable.mjs +17 -1
  6. package/dist/blocks/approval-block.js +93 -0
  7. package/dist/blocks/approval-block.meta.js +23 -0
  8. package/dist/blocks/index.d.ts +17 -2
  9. package/dist/blocks/index.js +4 -0
  10. package/dist/components/accordion/accordion.meta.js +10 -3
  11. package/dist/components/area-chart/area-chart.meta.js +10 -3
  12. package/dist/components/attachment/attachment.meta.js +26 -0
  13. package/dist/components/badge/badge.meta.js +2 -1
  14. package/dist/components/banner/banner.meta.js +4 -2
  15. package/dist/components/bar-chart/bar-chart.meta.js +11 -4
  16. package/dist/components/bottom-nav/bottom-nav.js +3 -1
  17. package/dist/components/bottom-nav/bottom-nav.meta.js +32 -0
  18. package/dist/components/breadcrumbs/breadcrumbs.meta.js +26 -0
  19. package/dist/components/button/button.js +21 -4
  20. package/dist/components/button/button.meta.js +7 -7
  21. package/dist/components/button-group/button-group.meta.js +2 -1
  22. package/dist/components/calendar/calendar.js +18 -1
  23. package/dist/components/calendar/calendar.meta.js +38 -0
  24. package/dist/components/carousel/carousel.js +6 -4
  25. package/dist/components/carousel/carousel.meta.js +42 -0
  26. package/dist/components/chat-composer/chat-composer.js +21 -21
  27. package/dist/components/chat-composer/chat-composer.meta.js +38 -0
  28. package/dist/components/chat-composer/use-eased-growth.js +70 -0
  29. package/dist/components/checkbox/checkbox-mark.js +8 -1
  30. package/dist/components/checkbox/checkbox.js +3 -1
  31. package/dist/components/checkbox/checkbox.meta.js +34 -1
  32. package/dist/components/checkbox-card/checkbox-card.js +3 -0
  33. package/dist/components/checkbox-card/checkbox-card.meta.js +32 -0
  34. package/dist/components/code-block/code-block.js +14 -3
  35. package/dist/components/code-block/code-block.meta.js +28 -1
  36. package/dist/components/collapsible/collapsible.meta.js +34 -1
  37. package/dist/components/color-field/color-field.meta.js +26 -0
  38. package/dist/components/combobox/combobox.meta.js +48 -2
  39. package/dist/components/command-menu/command-menu.js +8 -3
  40. package/dist/components/command-menu/command-menu.meta.js +44 -3
  41. package/dist/components/confirm-button/confirm-button.js +179 -0
  42. package/dist/components/confirm-button/confirm-button.meta.js +143 -0
  43. package/dist/components/confirm-button/index.js +7 -0
  44. package/dist/components/context-card/context-card.meta.js +2 -1
  45. package/dist/components/data-table/data-table.js +6 -2
  46. package/dist/components/data-table/data-table.meta.js +32 -0
  47. package/dist/components/dialog/dialog.meta.js +6 -3
  48. package/dist/components/disclosure-picker/disclosure-picker.meta.js +2 -1
  49. package/dist/components/entity/entity.meta.js +26 -0
  50. package/dist/components/feedback/feedback.js +113 -47
  51. package/dist/components/feedback/feedback.meta.js +49 -4
  52. package/dist/components/inline-status/inline-status.meta.js +2 -1
  53. package/dist/components/input-group/input-group.meta.js +27 -1
  54. package/dist/components/line-chart/line-chart.meta.js +10 -3
  55. package/dist/components/menu/menu.meta.js +2 -1
  56. package/dist/components/menubar/menubar.meta.js +2 -1
  57. package/dist/components/meter/meter.meta.js +2 -1
  58. package/dist/components/middle-truncate/middle-truncate.meta.js +1 -1
  59. package/dist/components/note/note.meta.js +34 -1
  60. package/dist/components/notification-badge/notification-badge.js +17 -5
  61. package/dist/components/notification-badge/notification-badge.meta.js +34 -2
  62. package/dist/components/number-ticker/index.js +7 -0
  63. package/dist/components/number-ticker/number-ticker.js +246 -0
  64. package/dist/components/number-ticker/number-ticker.meta.js +159 -0
  65. package/dist/components/otp-field/otp-field.js +23 -3
  66. package/dist/components/otp-field/otp-field.meta.js +23 -2
  67. package/dist/components/page-loader/page-loader.meta.js +2 -1
  68. package/dist/components/panel-group/panel-group.meta.js +26 -0
  69. package/dist/components/panel-section/panel-section.js +37 -10
  70. package/dist/components/panel-section/panel-section.meta.js +32 -0
  71. package/dist/components/pie-chart/pie-chart.js +20 -11
  72. package/dist/components/pie-chart/pie-chart.meta.js +17 -4
  73. package/dist/components/pill/pill.meta.js +34 -1
  74. package/dist/components/popover/popover.meta.js +4 -2
  75. package/dist/components/progress/progress.meta.js +2 -1
  76. package/dist/components/radar-chart/radar-chart.js +4 -3
  77. package/dist/components/radar-chart/radar-chart.meta.js +10 -3
  78. package/dist/components/radial-chart/radial-chart.js +22 -22
  79. package/dist/components/radial-chart/radial-chart.meta.js +17 -4
  80. package/dist/components/radio/radio.meta.js +28 -1
  81. package/dist/components/radio-card/radio-card.meta.js +38 -0
  82. package/dist/components/relative-time/relative-time.meta.js +32 -0
  83. package/dist/components/scroller/scroller.meta.js +32 -0
  84. package/dist/components/scrub-field/scrub-field.meta.js +32 -0
  85. package/dist/components/search-input/search-input.js +9 -7
  86. package/dist/components/search-input/search-input.meta.js +26 -0
  87. package/dist/components/segmented-bar/segmented-bar.meta.js +2 -1
  88. package/dist/components/select/select.meta.js +4 -2
  89. package/dist/components/sheet/sheet.meta.js +4 -2
  90. package/dist/components/show-more/show-more.js +1 -4
  91. package/dist/components/show-more/show-more.meta.js +38 -0
  92. package/dist/components/side-nav/side-nav.js +21 -1
  93. package/dist/components/side-nav/side-nav.meta.js +34 -1
  94. package/dist/components/side-nav/side-nav.variants.js +3 -1
  95. package/dist/components/sidebar/sidebar.js +31 -8
  96. package/dist/components/sidebar/sidebar.meta.js +38 -0
  97. package/dist/components/skeleton/skeleton.meta.js +2 -1
  98. package/dist/components/slider/slider.meta.js +26 -0
  99. package/dist/components/sparkline/sparkline.js +7 -4
  100. package/dist/components/sparkline/sparkline.meta.js +9 -2
  101. package/dist/components/spinner/spinner.meta.js +2 -1
  102. package/dist/components/stepper/stepper.meta.js +4 -2
  103. package/dist/components/switch/switch.meta.js +2 -1
  104. package/dist/components/table/table.meta.js +28 -1
  105. package/dist/components/tabs/tabs.js +27 -8
  106. package/dist/components/tabs/tabs.meta.js +50 -5
  107. package/dist/components/theme-selector/theme-selector.meta.js +32 -0
  108. package/dist/components/theme-switcher/theme-switcher.js +4 -1
  109. package/dist/components/theme-switcher/theme-switcher.meta.js +42 -0
  110. package/dist/components/thinking-orb/thinking-orb.meta.js +4 -2
  111. package/dist/components/toast/toast.meta.js +2 -1
  112. package/dist/components/toggle/toggle.js +28 -5
  113. package/dist/components/toggle/toggle.meta.js +46 -3
  114. package/dist/components/tool-call/index.js +8 -0
  115. package/dist/components/tool-call/tool-call.js +117 -0
  116. package/dist/components/tool-call/tool-call.meta.js +147 -0
  117. package/dist/components/tool-call/tool-call.variants.js +33 -0
  118. package/dist/components/toolbar/toolbar.meta.js +2 -1
  119. package/dist/components/tooltip/index.js +3 -1
  120. package/dist/components/tooltip/tooltip.js +84 -0
  121. package/dist/components/tooltip/tooltip.meta.js +18 -2
  122. package/dist/components/tree/tree.js +71 -6
  123. package/dist/components/tree/tree.meta.js +32 -0
  124. package/dist/components/video/video.meta.js +32 -0
  125. package/dist/file-hashes.json +139 -122
  126. package/dist/foundations/motion.js +49 -3
  127. package/dist/index.d.ts +616 -46
  128. package/dist/index.js +4 -1
  129. package/dist/lib/chart/cartesian.js +9 -10
  130. package/dist/lib/chart/chart-center.js +30 -0
  131. package/dist/lib/chart/chart-frame.js +15 -8
  132. package/dist/lib/chart/dim.js +13 -0
  133. package/dist/lib/chart/format.js +7 -1
  134. package/dist/lib/chart/motion.js +6 -4
  135. package/dist/lib/highlight-glide.js +68 -10
  136. package/dist/lib/taxonomy.js +3 -0
  137. package/dist/{meta-DVG8ceze.d.ts → meta-DX5r5wQL.d.ts} +8 -0
  138. package/dist/motion/MotionProvider.js +45 -3
  139. package/dist/motion/Reveal.js +1 -1
  140. package/dist/motion/TextReveal.js +1 -1
  141. package/dist/motion/index.js +15 -2
  142. package/dist/motion/useAutoSize.js +42 -0
  143. package/dist/motion/useShake.js +25 -0
  144. package/dist/patterns/index.d.ts +1 -1
  145. package/dist/styles.css +298 -5
  146. package/llms/blocks/approval-block.md +128 -0
  147. package/llms/components/accordion.md +4 -3
  148. package/llms/components/area-chart.md +4 -3
  149. package/llms/components/attachment.md +7 -0
  150. package/llms/components/badge.md +1 -1
  151. package/llms/components/banner.md +2 -2
  152. package/llms/components/bar-chart.md +5 -4
  153. package/llms/components/bottom-nav.md +8 -0
  154. package/llms/components/breadcrumbs.md +7 -0
  155. package/llms/components/button-group.md +1 -1
  156. package/llms/components/button.md +6 -6
  157. package/llms/components/calendar.md +9 -0
  158. package/llms/components/carousel.md +17 -0
  159. package/llms/components/chat-composer.md +9 -0
  160. package/llms/components/checkbox-card.md +8 -0
  161. package/llms/components/checkbox.md +9 -1
  162. package/llms/components/code-block.md +8 -0
  163. package/llms/components/collapsible.md +9 -1
  164. package/llms/components/color-field.md +7 -0
  165. package/llms/components/combobox.md +12 -2
  166. package/llms/components/command-menu.md +12 -3
  167. package/llms/components/confirm-button.md +92 -0
  168. package/llms/components/context-card.md +1 -1
  169. package/llms/components/data-table.md +8 -0
  170. package/llms/components/dialog.md +3 -3
  171. package/llms/components/disclosure-picker.md +1 -1
  172. package/llms/components/entity.md +7 -0
  173. package/llms/components/feedback.md +22 -3
  174. package/llms/components/inline-status.md +1 -1
  175. package/llms/components/input-group.md +8 -1
  176. package/llms/components/line-chart.md +4 -3
  177. package/llms/components/menu.md +1 -1
  178. package/llms/components/menubar.md +1 -1
  179. package/llms/components/meter.md +1 -1
  180. package/llms/components/middle-truncate.md +1 -1
  181. package/llms/components/note.md +9 -1
  182. package/llms/components/notification-badge.md +10 -2
  183. package/llms/components/number-ticker.md +86 -0
  184. package/llms/components/otp-field.md +15 -1
  185. package/llms/components/page-loader.md +1 -1
  186. package/llms/components/panel-group.md +7 -0
  187. package/llms/components/panel-section.md +8 -0
  188. package/llms/components/pie-chart.md +6 -4
  189. package/llms/components/pill.md +9 -1
  190. package/llms/components/popover.md +2 -2
  191. package/llms/components/progress.md +1 -1
  192. package/llms/components/radar-chart.md +4 -3
  193. package/llms/components/radial-chart.md +6 -4
  194. package/llms/components/radio-card.md +9 -0
  195. package/llms/components/radio.md +8 -1
  196. package/llms/components/relative-time.md +8 -0
  197. package/llms/components/scroller.md +8 -0
  198. package/llms/components/scrub-field.md +8 -0
  199. package/llms/components/search-input.md +7 -0
  200. package/llms/components/segmented-bar.md +1 -1
  201. package/llms/components/select.md +2 -2
  202. package/llms/components/sheet.md +2 -2
  203. package/llms/components/show-more.md +9 -0
  204. package/llms/components/side-nav.md +9 -1
  205. package/llms/components/sidebar.md +9 -0
  206. package/llms/components/skeleton.md +1 -1
  207. package/llms/components/slider.md +7 -0
  208. package/llms/components/sparkline.md +3 -2
  209. package/llms/components/spinner.md +1 -1
  210. package/llms/components/stepper.md +2 -2
  211. package/llms/components/switch.md +1 -1
  212. package/llms/components/table.md +8 -1
  213. package/llms/components/tabs.md +22 -4
  214. package/llms/components/theme-selector.md +8 -0
  215. package/llms/components/theme-switcher.md +17 -0
  216. package/llms/components/thinking-orb.md +2 -2
  217. package/llms/components/toast.md +1 -1
  218. package/llms/components/toggle.md +20 -3
  219. package/llms/components/tool-call.md +115 -0
  220. package/llms/components/toolbar.md +1 -1
  221. package/llms/components/tooltip.md +12 -2
  222. package/llms/components/tree.md +8 -0
  223. package/llms/components/video.md +8 -0
  224. package/llms/foundations/motion.md +48 -3
  225. package/llms/reference/utilities.md +23 -1
  226. package/llms/tasks/reviewing-motion.md +14 -0
  227. package/llms-full.txt +1004 -98
  228. package/llms.txt +16 -11
  229. package/package.json +1 -1
  230. package/registry.json +2708 -187
  231. package/targets/ios.json +27 -0
@@ -2,10 +2,12 @@
2
2
 
3
3
  // src/components/pie-chart/pie-chart.tsx
4
4
  import { useId } from "react";
5
- import { ChartFrame } from "../../lib/chart/chart-frame.js";
5
+ import { ChartCenter } from "../../lib/chart/chart-center.js";
6
+ import { ChartFrame, chartMotion } from "../../lib/chart/chart-frame.js";
6
7
  import { ChartLegend } from "../../lib/chart/chart-legend.js";
7
8
  import { ChartTooltipContent } from "../../lib/chart/chart-tooltip.js";
8
- import { categoryText, centerValueText, chartFormatters } from "../../lib/chart/format.js";
9
+ import { FILL_HOVER, dimmed } from "../../lib/chart/dim.js";
10
+ import { categoryText, chartFormatters } from "../../lib/chart/format.js";
9
11
  import { arcPath, polar } from "../../lib/chart/geometry.js";
10
12
  import { DRAW_DURATION, morphTransition, pathStyle } from "../../lib/chart/motion.js";
11
13
  import {
@@ -30,7 +32,8 @@ function PieChart({
30
32
  tooltip = true,
31
33
  valueFormatter,
32
34
  locale,
33
- animate = true,
35
+ motion,
36
+ animate,
34
37
  activeIndex,
35
38
  defaultActiveIndex,
36
39
  onActiveIndexChange
@@ -52,6 +55,7 @@ function PieChart({
52
55
  });
53
56
  });
54
57
  const tooltipOptions = typeof tooltip === "object" ? tooltip : {};
58
+ const level = chartMotion(motion, animate);
55
59
  const ratio = donut === true ? 0.6 : typeof donut === "number" ? Math.min(0.9, Math.max(0, donut)) : 0;
56
60
  const geometry = (size) => {
57
61
  const margin = labels === "outside" ? 30 : 8;
@@ -105,7 +109,7 @@ function PieChart({
105
109
  className: cn("h-64", className),
106
110
  count: data.length,
107
111
  interactive: tooltip !== false,
108
- animate,
112
+ motion: level,
109
113
  activeIndex,
110
114
  defaultActiveIndex,
111
115
  onActiveIndexChange,
@@ -117,6 +121,10 @@ function PieChart({
117
121
  rows: data.map((_, i) => [styles[i].label, ...keys.map((_2, r) => fmt.full(rings[r][i].v))])
118
122
  },
119
123
  legend: legend ?? labels === "none" ? /* @__PURE__ */ jsx(ChartLegend, { items: names.map((n, i) => ({ key: n, label: styles[i].label, color: styles[i].color, icon: styles[i].icon })) }) : null,
124
+ overlay: centerLabel && ratio > 0 ? ({ size }) => {
125
+ const g = geometry(size);
126
+ return /* @__PURE__ */ jsx(ChartCenter, { center: centerLabel, x: g.cx, y: g.cy, locale, motion: level });
127
+ } : void 0,
120
128
  children: ({ size, activeIndex: active }) => {
121
129
  const g = geometry(size);
122
130
  const maskId = `${uid}-sweep`;
@@ -148,8 +156,13 @@ function PieChart({
148
156
  d,
149
157
  strokeWidth: separator ? 2 : 0,
150
158
  strokeLinejoin: "round",
151
- className: cn(separator && "stroke-(--sable-surface-bg,var(--background))", active != null && active !== i && "opacity-60"),
152
- style: { ...pathStyle(d), fill: styles[i].color, transition: morphTransition() }
159
+ className: cn(separator && "stroke-(--sable-surface-bg,var(--background))"),
160
+ style: {
161
+ ...pathStyle(d),
162
+ // Stepped back toward the surface rather than faded (see lib/chart/dim).
163
+ fill: active != null && active !== i ? dimmed(styles[i].color, 60) : styles[i].color,
164
+ transition: `${morphTransition()}, ${FILL_HOVER}`
165
+ }
153
166
  },
154
167
  `${r}-${names[i]}`
155
168
  );
@@ -175,11 +188,7 @@ function PieChart({
175
188
  },
176
189
  `l-${names[i]}`
177
190
  );
178
- }) : null,
179
- centerLabel && g.inner > 0 ? /* @__PURE__ */ jsxs("text", { x: g.cx, y: g.cy, textAnchor: "middle", className: "[[data-reveal=out]_&]:opacity-0", style: { transition: morphTransition() }, children: [
180
- /* @__PURE__ */ jsx("tspan", { x: g.cx, dy: "0.35em", className: "fill-foreground text-heading-2 tabular-nums", children: centerValueText(centerLabel.value, locale) }),
181
- centerLabel.label ? /* @__PURE__ */ jsx("tspan", { x: g.cx, dy: "1.9em", className: "fill-muted-foreground text-caption", children: centerLabel.label }) : null
182
- ] }) : null
191
+ }) : null
183
192
  ] });
184
193
  }
185
194
  }
@@ -127,11 +127,18 @@ var pieChartMeta = {
127
127
  default: "runtime locale",
128
128
  description: "BCP 47 locale for default number and date formatting."
129
129
  },
130
+ {
131
+ name: "motion",
132
+ type: '"full" | "subtle" | "off"',
133
+ default: '"full"',
134
+ description: "How much motion this component uses: `full` (default) inherits the app's policy, `subtle` keeps feedback and drops the flourish, `off` stills it. It can only turn motion down \u2014 it never overrides a reader's reduce-motion setting. The sweep, the slice morph and the rolling centre number are the flourish; the hover pull-out and dim are the feedback `subtle` keeps."
135
+ },
130
136
  {
131
137
  name: "animate",
132
138
  type: "boolean",
133
139
  default: "true",
134
- description: "Sweep the slices in clockwise when the chart scrolls into view."
140
+ description: 'Sweep the slices in clockwise when the chart scrolls into view. `animate={false}` is `motion="subtle"`.',
141
+ deprecated: 'motion="subtle" (what animate={false} now maps to: no arrival, hover kept) or motion="off"'
135
142
  },
136
143
  {
137
144
  name: "activeIndex",
@@ -168,19 +175,25 @@ var pieChartMeta = {
168
175
  slot: "arrival",
169
176
  recipe: "custom",
170
177
  part: "Slices",
171
- why: "Once on screen the slices are revealed by a mask that sweeps clockwise from 12 o'clock \u2014 the whole is read as it fills. On the motion tokens; reduced motion shows the finished chart."
178
+ why: 'Once on screen the slices are revealed by a mask that sweeps clockwise from 12 o\'clock \u2014 the whole is read as it fills. On the motion tokens; reduced motion shows the finished chart. The arrival is expressive: it plays at the full motion level only, so `motion="subtle"` (or a subtle app policy) shows the finished chart while hover feedback stays.'
172
179
  },
173
180
  {
174
181
  slot: "response",
175
182
  recipe: "none",
176
183
  part: "Active slice",
177
- why: "The hovered slice pulls outward and the rest dim at once; the geometry change itself eases (see transition)."
184
+ why: "The hovered slice pulls outward and the rest dim at once (mixed toward the surface, not faded); the geometry change itself eases (see transition). Essential: `subtle` keeps it."
185
+ },
186
+ {
187
+ slot: "transition",
188
+ recipe: "custom",
189
+ part: "Centre value",
190
+ why: "A numeric centre value rolls to its new total digit by digit (NumberTicker) when the data changes, so the headline is seen moving with the slices instead of being found different afterwards. Expressive: under `subtle` it updates in place."
178
191
  },
179
192
  {
180
193
  slot: "transition",
181
194
  recipe: "custom",
182
195
  part: "Slices",
183
- why: "Geometry eases from the old shape to the new one through a CSS `d` transition on the motion tokens (no recipe animates path geometry); browsers without `d` transitions update in place."
196
+ why: "Geometry eases from the old shape to the new one through a CSS `d` transition on the motion tokens (no recipe animates path geometry); browsers without `d` transitions update in place. Expressive, like the arrival: under `subtle` new data swaps in place."
184
197
  },
185
198
  {
186
199
  slot: "activity",
@@ -24,7 +24,8 @@ var pillMeta = {
24
24
  },
25
25
  {
26
26
  guidance: "dont",
27
- description: "Do not use Pill as a static label. A pressable-looking chip that does nothing is the most common cause of dead clicks in a filter row \u2014 that is a Badge."
27
+ description: "Do not use Pill as a static label. A pressable-looking chip that does nothing is the most common cause of dead clicks in a filter row \u2014 that is a Badge.",
28
+ instead: ["Badge"]
28
29
  },
29
30
  {
30
31
  guidance: "dont",
@@ -97,6 +98,38 @@ var pillMeta = {
97
98
  "muted-foreground",
98
99
  "foreground"
99
100
  ],
101
+ motion: [
102
+ {
103
+ slot: "arrival",
104
+ recipe: "none",
105
+ part: "Root",
106
+ why: "Pills arrive with the filter row or the content they annotate. A chip that animated in alone would read as a filter someone just added."
107
+ },
108
+ {
109
+ slot: "response",
110
+ recipe: "motion-interactive",
111
+ part: "Root",
112
+ why: "The hover wash eases, so sweeping the pointer along a filter row reads as one cursor moving rather than chips flashing in turn."
113
+ },
114
+ {
115
+ slot: "response",
116
+ recipe: "motion-press",
117
+ part: "Root",
118
+ why: "A pill often toggles a filter that takes a moment to reach the results; the settle on press confirms the click landed before they change."
119
+ },
120
+ {
121
+ slot: "transition",
122
+ recipe: "motion-interactive",
123
+ part: "Root",
124
+ why: "`selected` lifts the chip onto its selected surface on the same ease, so toggling reads as this chip changing state, not a different chip appearing in its place."
125
+ },
126
+ {
127
+ slot: "activity",
128
+ recipe: "none",
129
+ part: "Root",
130
+ why: "A chip is a choice, not a process. If applying it is slow, the results show it."
131
+ }
132
+ ],
100
133
  a11y: [
101
134
  'Renders a native <button type="button"> for focus and keyboard activation.',
102
135
  "Selected state is exposed via aria-pressed."
@@ -22,11 +22,13 @@ var popoverMeta = {
22
22
  },
23
23
  {
24
24
  guidance: "dont",
25
- description: "Do not use Popover for blocking confirmations, long forms, or critical information."
25
+ description: "Do not use Popover for blocking confirmations, long forms, or critical information.",
26
+ instead: ["Dialog"]
26
27
  },
27
28
  {
28
29
  guidance: "dont",
29
- description: "Do not put a form that can fail validation inside one. A popover dismisses on outside click, which discards what the user typed with no warning \u2014 that is a Dialog."
30
+ description: "Do not put a form that can fail validation inside one. A popover dismisses on outside click, which discards what the user typed with no warning \u2014 that is a Dialog.",
31
+ instead: ["Dialog"]
30
32
  }
31
33
  ],
32
34
  anatomy: [
@@ -26,7 +26,8 @@ var progressMeta = {
26
26
  },
27
27
  {
28
28
  guidance: "dont",
29
- description: "Do not use a determinate Progress for an unknown duration. A bar that sits at 90% is worse than a Spinner, because it makes a promise about time that you cannot keep."
29
+ description: "Do not use a determinate Progress for an unknown duration. A bar that sits at 90% is worse than a Spinner, because it makes a promise about time that you cannot keep.",
30
+ instead: ["Spinner"]
30
31
  }
31
32
  ],
32
33
  anatomy: [
@@ -1,7 +1,7 @@
1
1
  "use client";
2
2
 
3
3
  // src/components/radar-chart/radar-chart.tsx
4
- import { ChartFrame } from "../../lib/chart/chart-frame.js";
4
+ import { ChartFrame, chartMotion } from "../../lib/chart/chart-frame.js";
5
5
  import { ChartLegend } from "../../lib/chart/chart-legend.js";
6
6
  import { ChartTooltipContent } from "../../lib/chart/chart-tooltip.js";
7
7
  import { approxTextWidth, categoryText, chartFormatters } from "../../lib/chart/format.js";
@@ -29,7 +29,8 @@ function RadarChart({
29
29
  tooltip = true,
30
30
  valueFormatter,
31
31
  locale,
32
- animate = true,
32
+ motion,
33
+ animate,
33
34
  activeIndex,
34
35
  defaultActiveIndex,
35
36
  onActiveIndexChange
@@ -80,7 +81,7 @@ function RadarChart({
80
81
  className: cn("h-72", className),
81
82
  count: n,
82
83
  interactive: tooltip !== false,
83
- animate,
84
+ motion: chartMotion(motion, animate),
84
85
  activeIndex,
85
86
  defaultActiveIndex,
86
87
  onActiveIndexChange,
@@ -121,11 +121,18 @@ var radarChartMeta = {
121
121
  default: "runtime locale",
122
122
  description: "BCP 47 locale for default number and date formatting."
123
123
  },
124
+ {
125
+ name: "motion",
126
+ type: '"full" | "subtle" | "off"',
127
+ default: '"full"',
128
+ description: "How much motion this component uses: `full` (default) inherits the app's policy, `subtle` keeps feedback and drops the flourish, `off` stills it. It can only turn motion down \u2014 it never overrides a reader's reduce-motion setting. The polygons growing in and morphing are the flourish; the hover highlight is the feedback `subtle` keeps."
129
+ },
124
130
  {
125
131
  name: "animate",
126
132
  type: "boolean",
127
133
  default: "true",
128
- description: "Grow the polygons from the centre when the chart scrolls into view."
134
+ description: 'Grow the polygons from the centre when the chart scrolls into view. `animate={false}` is `motion="subtle"`.',
135
+ deprecated: 'motion="subtle" (what animate={false} now maps to: no arrival, hover kept) or motion="off"'
129
136
  },
130
137
  {
131
138
  name: "activeIndex",
@@ -162,7 +169,7 @@ var radarChartMeta = {
162
169
  slot: "arrival",
163
170
  recipe: "custom",
164
171
  part: "Polygon",
165
- why: "Once on screen each polygon grows from the centre and fades in, series staggered, on the emphasized easing. On the motion tokens; reduced motion shows the finished chart."
172
+ why: 'Once on screen each polygon grows from the centre and fades in, series staggered, on the emphasized easing. On the motion tokens; reduced motion shows the finished chart. The arrival is expressive: it plays at the full motion level only, so `motion="subtle"` (or a subtle app policy) shows the finished chart while hover feedback stays.'
166
173
  },
167
174
  {
168
175
  slot: "response",
@@ -174,7 +181,7 @@ var radarChartMeta = {
174
181
  slot: "transition",
175
182
  recipe: "custom",
176
183
  part: "Polygon",
177
- why: "Geometry eases from the old shape to the new one through a CSS `d` transition on the motion tokens (no recipe animates path geometry); browsers without `d` transitions update in place."
184
+ why: "Geometry eases from the old shape to the new one through a CSS `d` transition on the motion tokens (no recipe animates path geometry); browsers without `d` transitions update in place. Expressive, like the arrival: under `subtle` new data swaps in place."
178
185
  },
179
186
  {
180
187
  slot: "activity",
@@ -1,12 +1,14 @@
1
1
  "use client";
2
2
 
3
3
  // src/components/radial-chart/radial-chart.tsx
4
- import { ChartFrame } from "../../lib/chart/chart-frame.js";
4
+ import { ChartCenter } from "../../lib/chart/chart-center.js";
5
+ import { ChartFrame, chartMotion } from "../../lib/chart/chart-frame.js";
5
6
  import { ChartLegend } from "../../lib/chart/chart-legend.js";
6
7
  import { ChartTooltipContent } from "../../lib/chart/chart-tooltip.js";
7
- import { categoryText, centerValueText, chartFormatters } from "../../lib/chart/format.js";
8
+ import { STROKE_HOVER, dimmed } from "../../lib/chart/dim.js";
9
+ import { categoryText, chartFormatters } from "../../lib/chart/format.js";
8
10
  import { polar, strokeArcPath } from "../../lib/chart/geometry.js";
9
- import { drawTransition, morphTransition, pathStyle, stagger } from "../../lib/chart/motion.js";
11
+ import { drawTransition, pathStyle, stagger } from "../../lib/chart/motion.js";
10
12
  import {
11
13
  categoryStyle,
12
14
  numberValue
@@ -30,7 +32,8 @@ function RadialChart({
30
32
  tooltip = true,
31
33
  valueFormatter,
32
34
  locale,
33
- animate = true,
35
+ motion,
36
+ animate,
34
37
  activeIndex,
35
38
  defaultActiveIndex,
36
39
  onActiveIndexChange
@@ -45,6 +48,7 @@ function RadialChart({
45
48
  const start = -span / 2 + (sweep >= 360 ? span / 2 : 0);
46
49
  const tooltipOptions = typeof tooltip === "object" ? tooltip : {};
47
50
  const gauge = sweep < 360;
51
+ const level = chartMotion(motion, animate);
48
52
  const geometry = (size) => {
49
53
  const outer = gauge ? Math.min(size.width / 2, size.height * (sweep <= 180 ? 0.92 : 0.6)) - 8 : Math.min(size.width, size.height) / 2 - 8;
50
54
  const cx = size.width / 2;
@@ -102,7 +106,7 @@ function RadialChart({
102
106
  className: cn("h-64", className),
103
107
  count: data.length,
104
108
  interactive: tooltip !== false,
105
- animate,
109
+ motion: level,
106
110
  activeIndex,
107
111
  defaultActiveIndex,
108
112
  onActiveIndexChange,
@@ -114,6 +118,10 @@ function RadialChart({
114
118
  rows: data.map((_, i) => [styles[i].label, fmt.full(values[i])])
115
119
  },
116
120
  legend: legend ?? !labels ? /* @__PURE__ */ jsx(ChartLegend, { items: names.map((n, i) => ({ key: n, label: styles[i].label, color: styles[i].color, icon: styles[i].icon })) }) : null,
121
+ overlay: centerLabel ? ({ size }) => {
122
+ const g = geometry(size);
123
+ return gauge && sweep <= 180 ? /* @__PURE__ */ jsx(ChartCenter, { center: centerLabel, x: g.cx, y: g.cy - 6, placement: "gauge", locale, motion: level }) : /* @__PURE__ */ jsx(ChartCenter, { center: centerLabel, x: g.cx, y: g.cy, locale, motion: level });
124
+ } : void 0,
117
125
  children: ({ size, activeIndex: active }) => {
118
126
  const g = geometry(size);
119
127
  const ringCount = stacked ? 1 : data.length;
@@ -140,8 +148,14 @@ function RadialChart({
140
148
  fill: "none",
141
149
  strokeWidth: g.thickness,
142
150
  strokeLinecap: stacked ? "butt" : "round",
143
- className: cn("[stroke-dasharray:1] [stroke-dashoffset:0] [[data-reveal=out]_&]:[stroke-dashoffset:1]", active != null && active !== a.i && "opacity-60"),
144
- style: { ...pathStyle(d), stroke: styles[a.i].color, transition: drawTransition(stagger(a.i, 90)) }
151
+ className: "[stroke-dasharray:1] [stroke-dashoffset:0] [[data-reveal=out]_&]:[stroke-dashoffset:1]",
152
+ style: {
153
+ ...pathStyle(d),
154
+ // Stepped back toward the surface rather than faded: a faded ring
155
+ // lets its muted track show through and turns muddy (lib/chart/dim).
156
+ stroke: active != null && active !== a.i ? dimmed(styles[a.i].color, 60) : styles[a.i].color,
157
+ transition: `${drawTransition(stagger(a.i, 90))}, ${STROKE_HOVER}`
158
+ }
145
159
  },
146
160
  names[a.i]
147
161
  );
@@ -149,21 +163,7 @@ function RadialChart({
149
163
  labels && !stacked ? arcs.map((a) => {
150
164
  const [x, y] = polar(g.cx, g.cy, radiusOf(g, a.ring), start);
151
165
  return /* @__PURE__ */ jsx("text", { x: x - 8, y, dy: "0.32em", textAnchor: "end", className: "fill-muted-foreground text-caption", children: styles[a.i].label }, `l${a.i}`);
152
- }) : null,
153
- centerLabel ? /* @__PURE__ */ jsxs(
154
- "text",
155
- {
156
- x: g.cx,
157
- y: gauge && sweep <= 180 ? g.cy - 6 : g.cy,
158
- textAnchor: "middle",
159
- className: "[[data-reveal=out]_&]:opacity-0",
160
- style: { transition: morphTransition() },
161
- children: [
162
- /* @__PURE__ */ jsx("tspan", { x: g.cx, dy: gauge && sweep <= 180 ? centerLabel.label ? "-1.4em" : "0" : "0.35em", className: "fill-foreground text-heading-2 tabular-nums", children: centerValueText(centerLabel.value, locale) }),
163
- centerLabel.label ? /* @__PURE__ */ jsx("tspan", { x: g.cx, dy: gauge && sweep <= 180 ? "1.6em" : "1.9em", className: "fill-muted-foreground text-caption", children: centerLabel.label }) : null
164
- ]
165
- }
166
- ) : null
166
+ }) : null
167
167
  ] });
168
168
  }
169
169
  }
@@ -133,11 +133,18 @@ var radialChartMeta = {
133
133
  default: "runtime locale",
134
134
  description: "BCP 47 locale for default number and date formatting."
135
135
  },
136
+ {
137
+ name: "motion",
138
+ type: '"full" | "subtle" | "off"',
139
+ default: '"full"',
140
+ description: "How much motion this component uses: `full` (default) inherits the app's policy, `subtle` keeps feedback and drops the flourish, `off` stills it. It can only turn motion down \u2014 it never overrides a reader's reduce-motion setting. The draw-on, the ring morph and the rolling centre number are the flourish; the hover dim is the feedback `subtle` keeps."
141
+ },
136
142
  {
137
143
  name: "animate",
138
144
  type: "boolean",
139
145
  default: "true",
140
- description: "Draw the rings round from their start when the chart scrolls into view."
146
+ description: 'Draw the rings round from their start when the chart scrolls into view. `animate={false}` is `motion="subtle"`.',
147
+ deprecated: 'motion="subtle" (what animate={false} now maps to: no arrival, hover kept) or motion="off"'
141
148
  },
142
149
  {
143
150
  name: "activeIndex",
@@ -174,19 +181,25 @@ var radialChartMeta = {
174
181
  slot: "arrival",
175
182
  recipe: "custom",
176
183
  part: "Ring",
177
- why: "Once on screen each ring draws round from its start (stroke-dashoffset on a round-capped arc), staggered outside in. On the motion tokens; reduced motion shows the finished chart."
184
+ why: 'Once on screen each ring draws round from its start (stroke-dashoffset on a round-capped arc), staggered outside in. On the motion tokens; reduced motion shows the finished chart. The arrival is expressive: it plays at the full motion level only, so `motion="subtle"` (or a subtle app policy) shows the finished chart while hover feedback stays.'
178
185
  },
179
186
  {
180
187
  slot: "response",
181
188
  recipe: "none",
182
189
  part: "Active ring",
183
- why: "The hovered ring stays full strength and the others dim at once."
190
+ why: "The hovered ring stays full strength and the others dim at once \u2014 mixed toward the surface rather than faded, so a dimmed ring stays solid over its track instead of turning muddy. Essential: `subtle` keeps it."
191
+ },
192
+ {
193
+ slot: "transition",
194
+ recipe: "custom",
195
+ part: "Centre value",
196
+ why: "A numeric centre value rolls to its new total digit by digit (NumberTicker) when the data changes, so the headline is seen moving with the rings instead of being found different afterwards. Expressive: under `subtle` it updates in place."
184
197
  },
185
198
  {
186
199
  slot: "transition",
187
200
  recipe: "custom",
188
201
  part: "Ring",
189
- why: "Geometry eases from the old shape to the new one through a CSS `d` transition on the motion tokens (no recipe animates path geometry); browsers without `d` transitions update in place."
202
+ why: "Geometry eases from the old shape to the new one through a CSS `d` transition on the motion tokens (no recipe animates path geometry); browsers without `d` transitions update in place. Expressive, like the arrival: under `subtle` new data swaps in place."
190
203
  },
191
204
  {
192
205
  slot: "activity",
@@ -22,7 +22,8 @@ var radioMeta = {
22
22
  },
23
23
  {
24
24
  guidance: "dont",
25
- description: "Do not use Radio for independent toggles or multiple selection; use Checkbox or Switch."
25
+ description: "Do not use Radio for independent toggles or multiple selection; use Checkbox or Switch.",
26
+ instead: ["Checkbox", "Switch"]
26
27
  },
27
28
  {
28
29
  guidance: "dont",
@@ -66,6 +67,32 @@ var radioMeta = {
66
67
  }
67
68
  ],
68
69
  tokensUsed: ["input", "card", "primary", "ring"],
70
+ motion: [
71
+ {
72
+ slot: "arrival",
73
+ recipe: "none",
74
+ part: "Root",
75
+ why: "Radios arrive with the form they belong to. Animating each one in turns a question into a title sequence."
76
+ },
77
+ {
78
+ slot: "response",
79
+ recipe: "motion-interactive",
80
+ part: "Root",
81
+ why: "The ring's edge and focus ring ease, so arrowing through a group \u2014 which moves the selection with focus \u2014 reads as one choice moving down the list."
82
+ },
83
+ {
84
+ slot: "transition",
85
+ recipe: "motion-interactive",
86
+ part: "Indicator",
87
+ why: "The dot grows from the centre on a spring and shrinks out of the radio being left. A group's selection moves from one option to another, and the paired grow and shrink is what makes it read as moving rather than two dots blinking."
88
+ },
89
+ {
90
+ slot: "activity",
91
+ recipe: "none",
92
+ part: "Root",
93
+ why: "A radio commits instantly. A slow write belongs to the surrounding form's pending state, not the control."
94
+ }
95
+ ],
69
96
  a11y: [
70
97
  "Base UI provides the radiogroup/radio roles, aria-checked, hidden native inputs, and roving tabindex.",
71
98
  "Arrow keys move and select between items; Space selects the focused item; Tab enters/leaves the group.",
@@ -122,6 +122,44 @@ var radioCardMeta = {
122
122
  "tone-info",
123
123
  "tone-warning"
124
124
  ],
125
+ motion: [
126
+ {
127
+ slot: "arrival",
128
+ recipe: "none",
129
+ part: "Root",
130
+ why: "Cards arrive with the form. A plan picker that animated each card in would delay the choice it exists to make."
131
+ },
132
+ {
133
+ slot: "response",
134
+ recipe: "motion-interactive",
135
+ part: "Root",
136
+ why: "The edge steps toward the ring colour on hover, eased. On a card-sized target a hard snap across the whole border reads as the layout flickering."
137
+ },
138
+ {
139
+ slot: "response",
140
+ recipe: "motion-press",
141
+ part: "Root",
142
+ why: "The settle on press confirms a click anywhere in the card \u2014 the radio is small, and without it a click in the card's body can feel like it missed the control."
143
+ },
144
+ {
145
+ slot: "transition",
146
+ recipe: "motion-interactive",
147
+ part: "Root",
148
+ why: "Checking eases the tone ring and wash onto the new card as the old one releases, so the choice reads as moving between cards rather than two cards repainting."
149
+ },
150
+ {
151
+ slot: "transition",
152
+ recipe: "motion-interactive",
153
+ part: "Indicator",
154
+ why: "The radio dot grows in on a spring, the same mark as Radio, so a card and a plain radio confirm a choice the same way."
155
+ },
156
+ {
157
+ slot: "activity",
158
+ recipe: "none",
159
+ part: "Root",
160
+ why: "A choice commits instantly. A slow write belongs to the surrounding form's pending state."
161
+ }
162
+ ],
125
163
  a11y: [
126
164
  "Base UI provides the radiogroup/radio roles, aria-checked, hidden native inputs, and roving tabindex.",
127
165
  "Arrow keys move and select between cards; Space selects the focused card; Tab enters/leaves the group.",
@@ -54,6 +54,38 @@ var relativeTimeMeta = {
54
54
  }
55
55
  ],
56
56
  tokensUsed: ["popover", "popover-foreground", "border", "ring"],
57
+ motion: [
58
+ {
59
+ slot: "arrival",
60
+ recipe: "none",
61
+ part: "Root",
62
+ why: "It is a word in a line of text and arrives with that line."
63
+ },
64
+ {
65
+ slot: "response",
66
+ recipe: "none",
67
+ part: "Root",
68
+ why: "The only hover change on the timestamp is a dotted underline, and text-decoration-line is discrete: CSS cannot tween it, so a transition here would animate nothing and imply the snap had been handled."
69
+ },
70
+ {
71
+ slot: "response",
72
+ recipe: "motion-pop",
73
+ part: "Tooltip",
74
+ why: "The pointer's real answer is the absolute time, which arrives in a Tooltip on its own fast pop. That short entrance is what makes the full date read as belonging to the word under the cursor."
75
+ },
76
+ {
77
+ slot: "transition",
78
+ recipe: "none",
79
+ part: "Root",
80
+ why: "The label does not tick: it is computed when the component renders, so it never changes in front of the reader. A timestamp rolling from one minute to two would pull the eye off the content it dates."
81
+ },
82
+ {
83
+ slot: "activity",
84
+ recipe: "none",
85
+ part: "Root",
86
+ why: "A timestamp has nothing to wait for."
87
+ }
88
+ ],
57
89
  a11y: [
58
90
  "Renders a semantic <time> element with a machine-readable dateTime (ISO) attribute.",
59
91
  "The <time> carries tabIndex=0 (it is not focusable natively), so the absolute time \u2014 exposed via Base UI's Tooltip, which opens on keyboard focus as well as hover and wires aria-describedby \u2014 is reachable from the keyboard.",
@@ -92,6 +92,38 @@ var scrollerMeta = {
92
92
  wheelScroll: ["true", "false"]
93
93
  },
94
94
  tokensUsed: ["ring"],
95
+ motion: [
96
+ {
97
+ slot: "arrival",
98
+ recipe: "none",
99
+ part: "Root",
100
+ why: "It is a region, not an event; its content arrives with whatever fills it."
101
+ },
102
+ {
103
+ slot: "response",
104
+ recipe: "motion-interactive",
105
+ part: "Viewport",
106
+ why: "The region is focusable so the keyboard can scroll it, and its focus ring eases in like any control's. It is the one sign the region, not the page, now takes the arrow keys."
107
+ },
108
+ {
109
+ slot: "response",
110
+ recipe: "custom",
111
+ part: "Scrollbar",
112
+ why: "The vertical bar (`scrollbar-quiet`) stays hidden until the region is hovered, focused or scrolled, then fades its thumb in on the motion tokens. A bar that appeared in one frame would flash every time the pointer crossed the list."
113
+ },
114
+ {
115
+ slot: "transition",
116
+ recipe: "none",
117
+ part: "Edge fade",
118
+ why: "The edge fades switch on and off as you scroll, in the frame the content is already moving, so the change is carried by the scroll itself. They are a mask gradient, which CSS cannot interpolate without registered custom properties."
119
+ },
120
+ {
121
+ slot: "activity",
122
+ recipe: "none",
123
+ part: "Root",
124
+ why: "A scroll region does no work of its own. Content still loading inside it brings its own Skeleton or Spinner."
125
+ }
126
+ ],
95
127
  a11y: [
96
128
  "The edge fades are a CSS mask-image on the scrolling element, not overlay elements \u2014 there is nothing in the DOM to intercept a pointer or be read by assistive tech, and the content under a fade stays fully interactive.",
97
129
  'Two boxes: a chrome box carrying `className` (border, background, radius, sizing) and, inside it, the element that actually scrolls and carries the fade mask. They are split because a `mask-image` applies to its element\'s own border \u2014 so with both on one box, a bordered Scroller watched its frame dissolve at the scrolling edge. The axis is published as `data-orientation`, not `aria-orientation`, which is invalid on `role="group"`.',
@@ -99,6 +99,38 @@ var scrubFieldMeta = {
99
99
  { name: "disabled", type: "boolean", description: "Disables typing and scrubbing." },
100
100
  { name: "id", type: "string", description: "Input id, for a PropertyRow's htmlFor." }
101
101
  ],
102
+ motion: [
103
+ {
104
+ slot: "arrival",
105
+ recipe: "none",
106
+ part: "Root",
107
+ why: "It arrives with the inspector row it sits in."
108
+ },
109
+ {
110
+ slot: "response",
111
+ recipe: "motion-interactive",
112
+ part: "Root",
113
+ why: "The shell eases its focus ring like every field, so tabbing through a column of numbers shows where the keyboard is without flicker."
114
+ },
115
+ {
116
+ slot: "response",
117
+ recipe: "motion-interactive",
118
+ part: "Handle",
119
+ why: "The handle's ink brightens while the field is hovered. It is the only sign that the icon or prefix can be dragged, which nobody would guess from a label."
120
+ },
121
+ {
122
+ slot: "transition",
123
+ recipe: "none",
124
+ part: "Value",
125
+ why: "The number follows the drag pixel for pixel. Easing it \u2014 or rolling it with a NumberTicker \u2014 would show a value the pointer has already left, in a control whose whole point is precision."
126
+ },
127
+ {
128
+ slot: "activity",
129
+ recipe: "none",
130
+ part: "Root",
131
+ why: "Scrubbing is synchronous; there is nothing to wait for."
132
+ }
133
+ ],
102
134
  a11y: [
103
135
  "The input carries the accessible name via `label`; the glyph is aria-hidden because it is decorative.",
104
136
  "Every scrub gesture is reachable from the keyboard: ArrowUp/ArrowDown step by `step`, Shift+Arrow by ten steps.",