@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
@@ -0,0 +1,70 @@
1
+ "use client";
2
+
3
+ // src/components/chat-composer/use-eased-growth.ts
4
+ import { useCallback, useEffect, useRef } from "react";
5
+ import { useIsomorphicLayoutEffect } from "../../lib/use-isomorphic-layout-effect.js";
6
+ var GROW_TRANSITION = "height calc(var(--motion-duration-fast) * var(--motion-expressive, 1)) var(--motion-ease-standard), opacity var(--motion-duration-fast) var(--motion-ease-standard)";
7
+ function useEasedGrowth(ref, value) {
8
+ const mounted = useRef(false);
9
+ const width = useRef(0);
10
+ const fit = useCallback(
11
+ (animate) => {
12
+ const el = ref.current;
13
+ if (!el) return;
14
+ const style = getComputedStyle(el);
15
+ const from = Number.parseFloat(style.height);
16
+ const scrollTop = el.scrollTop;
17
+ el.style.transition = "none";
18
+ el.style.height = "auto";
19
+ if (el.scrollHeight === 0) {
20
+ el.style.height = "";
21
+ el.style.overflowY = "";
22
+ el.style.transition = GROW_TRANSITION;
23
+ return;
24
+ }
25
+ const content = el.scrollHeight + (Number.parseFloat(style.borderTopWidth) || 0) + (Number.parseFloat(style.borderBottomWidth) || 0);
26
+ const max = Number.parseFloat(style.maxHeight);
27
+ const target = Number.isNaN(max) ? content : Math.min(content, max);
28
+ const capped = content > target + 0.5;
29
+ el.style.overflowY = capped ? "" : "hidden";
30
+ if (animate && Number.isFinite(from)) {
31
+ el.style.height = `${from}px`;
32
+ void el.offsetHeight;
33
+ el.style.transition = GROW_TRANSITION;
34
+ el.style.height = `${target}px`;
35
+ } else {
36
+ el.style.height = `${target}px`;
37
+ void el.offsetHeight;
38
+ el.style.transition = GROW_TRANSITION;
39
+ }
40
+ el.scrollTop = capped ? scrollTop : 0;
41
+ },
42
+ [ref]
43
+ );
44
+ useIsomorphicLayoutEffect(() => {
45
+ fit(mounted.current);
46
+ mounted.current = true;
47
+ }, [value, fit]);
48
+ useEffect(() => {
49
+ const el = ref.current;
50
+ if (!el || typeof ResizeObserver === "undefined") return;
51
+ width.current = el.clientWidth;
52
+ const ro = new ResizeObserver(() => {
53
+ if (el.clientWidth === width.current) return;
54
+ width.current = el.clientWidth;
55
+ fit(false);
56
+ });
57
+ ro.observe(el);
58
+ let live = true;
59
+ document.fonts?.ready.then(() => {
60
+ if (live) fit(false);
61
+ });
62
+ return () => {
63
+ live = false;
64
+ ro.disconnect();
65
+ };
66
+ }, [ref, fit]);
67
+ }
68
+ export {
69
+ useEasedGrowth
70
+ };
@@ -9,7 +9,14 @@ function CheckboxMark({ className }) {
9
9
  Base.Indicator,
10
10
  {
11
11
  className: cn(
12
- "flex items-center justify-center text-current motion-interactive ease-spring data-[starting-style]:scale-0 data-[starting-style]:opacity-0 data-[ending-style]:scale-0 data-[ending-style]:opacity-0",
12
+ // motion-tick, not a scale from 0: the mark wipes in from its leading
13
+ // stroke, so checking reads as a tick being drawn rather than a dot
14
+ // swelling into a check. The dash takes the same wipe, which for a
15
+ // horizontal bar is simply a line drawn left to right. It declares its
16
+ // own transition list, so motion-interactive must not sit here too —
17
+ // two transition-property declarations on one element and the later
18
+ // class silently wins, dropping the clip-path.
19
+ "flex items-center justify-center text-current motion-tick",
13
20
  className
14
21
  ),
15
22
  render: (indicatorProps, state) => /* @__PURE__ */ jsx("span", { ...indicatorProps, children: /* @__PURE__ */ jsx(
@@ -1,13 +1,15 @@
1
1
  // src/components/checkbox/checkbox.tsx
2
2
  import { Checkbox as Base } from "@base-ui/react/checkbox";
3
3
  import { cn } from "../../lib/cn.js";
4
+ import { motionAttribute } from "../../motion/MotionProvider.js";
4
5
  import { CHECK_BOX } from "../../lib/selection-card.js";
5
6
  import { CheckboxMark } from "./checkbox-mark.js";
6
7
  import { jsx } from "react/jsx-runtime";
7
- function Checkbox({ className, children, ...props }) {
8
+ function Checkbox({ className, children, motion, ...props }) {
8
9
  return /* @__PURE__ */ jsx(
9
10
  Base.Root,
10
11
  {
12
+ "data-motion": motionAttribute(motion),
11
13
  className: cn(
12
14
  CHECK_BOX,
13
15
  "text-primary-foreground",
@@ -22,7 +22,8 @@ var checkboxMeta = {
22
22
  },
23
23
  {
24
24
  guidance: "dont",
25
- description: "Do not use Checkbox for mutually exclusive choices; use Radio or Select instead."
25
+ description: "Do not use Checkbox for mutually exclusive choices; use Radio or Select instead.",
26
+ instead: ["Radio", "Select"]
26
27
  },
27
28
  {
28
29
  guidance: "dont",
@@ -90,6 +91,12 @@ var checkboxMeta = {
90
91
  name: "render",
91
92
  type: "ReactElement | (props, state) => ReactElement",
92
93
  description: "Compose the root with another element through the Base UI render prop."
94
+ },
95
+ {
96
+ name: "motion",
97
+ type: '"full" | "subtle" | "off"',
98
+ default: '"full"',
99
+ 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. Under `subtle` the tick fades in instead of drawing itself."
93
100
  }
94
101
  ],
95
102
  tokensUsed: [
@@ -99,6 +106,32 @@ var checkboxMeta = {
99
106
  "primary-foreground",
100
107
  "ring"
101
108
  ],
109
+ motion: [
110
+ {
111
+ slot: "arrival",
112
+ recipe: "none",
113
+ part: "Root",
114
+ why: "It arrives with the form it belongs to. Animating each box in turns a settings list into a title sequence."
115
+ },
116
+ {
117
+ slot: "response",
118
+ recipe: "motion-interactive",
119
+ part: "Box",
120
+ why: "The box fills with the primary colour as it is checked; easing the fill keeps the mark and the box changing as one event rather than two frames apart."
121
+ },
122
+ {
123
+ slot: "transition",
124
+ recipe: "motion-tick",
125
+ part: "Mark",
126
+ why: "The check draws itself in from its leading stroke, so checking reads as the box being ticked rather than a glyph appearing. The dash wipes the same way. Unchecking only fades, so clearing a list is quick; under `subtle` checking fades too."
127
+ },
128
+ {
129
+ slot: "activity",
130
+ recipe: "none",
131
+ part: "Root",
132
+ why: "A checkbox commits instantly. A slow write belongs to the surrounding form's pending state, not the control."
133
+ }
134
+ ],
102
135
  a11y: [
103
136
  "Base UI renders a hidden native <input type=checkbox> for keyboard, focus, and form semantics.",
104
137
  "Space toggles; focus-visible draws a 2px ring in the --ring token.",
@@ -2,6 +2,7 @@
2
2
  import { Checkbox as Base } from "@base-ui/react/checkbox";
3
3
  import { CheckboxGroup as BaseGroup } from "@base-ui/react/checkbox-group";
4
4
  import { cn } from "../../lib/cn.js";
5
+ import { motionAttribute } from "../../motion/MotionProvider.js";
5
6
  import {
6
7
  CHECK_BOX,
7
8
  SELECTION_CARD_GLYPH,
@@ -52,6 +53,7 @@ function CheckboxCard({
52
53
  description,
53
54
  icon,
54
55
  tone = "neutral",
56
+ motion,
55
57
  ...props
56
58
  }) {
57
59
  const t = checkboxCardToneClasses[tone];
@@ -59,6 +61,7 @@ function CheckboxCard({
59
61
  return /* @__PURE__ */ jsxs(
60
62
  Base.Root,
61
63
  {
64
+ "data-motion": motionAttribute(motion),
62
65
  className: cn(
63
66
  SELECTION_CARD_SHELL,
64
67
  icon != null ? SELECTION_CARD_LAYOUT.tile : SELECTION_CARD_LAYOUT.row,
@@ -92,6 +92,12 @@ var checkboxCardMeta = {
92
92
  name: "checked / onCheckedChange / defaultChecked / disabled",
93
93
  type: "Base UI Checkbox.Root props",
94
94
  description: "Standard checkbox state. Without a group-owned value, each card is independent \u2014 drive checked/onCheckedChange per item for multi-select."
95
+ },
96
+ {
97
+ name: "motion",
98
+ type: '"full" | "subtle" | "off"',
99
+ default: '"full"',
100
+ 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. Under `subtle` the tick fades in instead of drawing itself."
95
101
  }
96
102
  ],
97
103
  subcomponents: [
@@ -124,6 +130,32 @@ var checkboxCardMeta = {
124
130
  "tone-warning",
125
131
  "tone-warning-foreground"
126
132
  ],
133
+ motion: [
134
+ {
135
+ slot: "arrival",
136
+ recipe: "none",
137
+ part: "Root",
138
+ why: "It arrives with the form it belongs to. Animating each box in turns a settings list into a title sequence."
139
+ },
140
+ {
141
+ slot: "response",
142
+ recipe: "motion-interactive",
143
+ part: "Box",
144
+ why: "The box fills with the primary colour as it is checked; easing the fill keeps the mark and the box changing as one event rather than two frames apart."
145
+ },
146
+ {
147
+ slot: "transition",
148
+ recipe: "motion-tick",
149
+ part: "Mark",
150
+ why: "The check draws itself in from its leading stroke, so checking reads as the box being ticked rather than a glyph appearing. The dash wipes the same way. Unchecking only fades, so clearing a list is quick; under `subtle` checking fades too."
151
+ },
152
+ {
153
+ slot: "activity",
154
+ recipe: "none",
155
+ part: "Root",
156
+ why: "A checkbox commits instantly. A slow write belongs to the surrounding form's pending state, not the control."
157
+ }
158
+ ],
127
159
  a11y: [
128
160
  "Base UI provides the checkbox role, aria-checked, hidden native input, keyboard toggle, and data-[checked]/data-disabled state.",
129
161
  "The whole card is the hit target; focus draws a 2px outline outside the card (not a ring, so the checked tone ring survives focus); Space toggles the focused card.",
@@ -7,6 +7,15 @@ import { Button } from "../button/button.js";
7
7
  import { cn } from "../../lib/cn.js";
8
8
  import { jsx, jsxs } from "react/jsx-runtime";
9
9
  var CodeHighlightContext = createContext(null);
10
+ function CopyGlyph({ copied }) {
11
+ return /* @__PURE__ */ jsxs("span", { "aria-hidden": "true", className: "motion-icon-swap", children: [
12
+ /* @__PURE__ */ jsx("span", { "data-active": !copied, className: "inline-flex", children: /* @__PURE__ */ jsx(Icon, { name: "copy" }) }),
13
+ /* @__PURE__ */ jsx("span", { "data-active": copied, className: "inline-flex", children: /* @__PURE__ */ jsx(Icon, { name: "check" }) })
14
+ ] });
15
+ }
16
+ function CopiedStatus({ copied }) {
17
+ return /* @__PURE__ */ jsx("span", { role: "status", className: "sr-only", children: copied ? "Copied" : "" });
18
+ }
10
19
  function CodeHighlightProvider({ highlight, children }) {
11
20
  return /* @__PURE__ */ jsx(CodeHighlightContext.Provider, { value: highlight, children });
12
21
  }
@@ -39,7 +48,8 @@ function CodeBlock({
39
48
  ] }),
40
49
  code
41
50
  ] }),
42
- showCopy ? /* @__PURE__ */ jsx(Button, { variant: "ghost", size: "icon-sm", "aria-label": "Copy command", onClick: copy, className: "shrink-0 text-muted-foreground", children: /* @__PURE__ */ jsx(Icon, { name: copied ? "check" : "copy" }) }) : null
51
+ showCopy ? /* @__PURE__ */ jsx(Button, { variant: "ghost", size: "icon-sm", "aria-label": "Copy command", onClick: copy, className: "shrink-0 text-muted-foreground", children: /* @__PURE__ */ jsx(CopyGlyph, { copied }) }) : null,
52
+ showCopy ? /* @__PURE__ */ jsx(CopiedStatus, { copied }) : null
43
53
  ]
44
54
  }
45
55
  );
@@ -57,7 +67,7 @@ function CodeBlock({
57
67
  children: [
58
68
  hasHeader ? /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-3 border-b border-border px-4 py-2", children: [
59
69
  /* @__PURE__ */ jsx("span", { className: "text-mono-caption text-muted-foreground", children: filename }),
60
- showCopy ? /* @__PURE__ */ jsx(Button, { variant: "ghost", size: "icon-sm", "aria-label": "Copy code", onClick: copy, className: "text-muted-foreground", children: /* @__PURE__ */ jsx(Icon, { name: copied ? "check" : "copy" }) }) : null
70
+ showCopy ? /* @__PURE__ */ jsx(Button, { variant: "ghost", size: "icon-sm", "aria-label": "Copy code", onClick: copy, className: "text-muted-foreground", children: /* @__PURE__ */ jsx(CopyGlyph, { copied }) }) : null
61
71
  ] }) : null,
62
72
  /* @__PURE__ */ jsx(
63
73
  "pre",
@@ -72,7 +82,8 @@ function CodeBlock({
72
82
  children: /* @__PURE__ */ jsx("code", { children: highlighter ? highlighter(code) : code })
73
83
  }
74
84
  ),
75
- copyColumn ? /* @__PURE__ */ jsx(Button, { variant: "ghost", size: "icon-sm", "aria-label": "Copy code", onClick: copy, className: "m-2 ms-0 shrink-0 text-muted-foreground", children: /* @__PURE__ */ jsx(Icon, { name: copied ? "check" : "copy" }) }) : null
85
+ copyColumn ? /* @__PURE__ */ jsx(Button, { variant: "ghost", size: "icon-sm", "aria-label": "Copy code", onClick: copy, className: "m-2 ms-0 shrink-0 text-muted-foreground", children: /* @__PURE__ */ jsx(CopyGlyph, { copied }) }) : null,
86
+ showCopy ? /* @__PURE__ */ jsx(CopiedStatus, { copied }) : null
76
87
  ]
77
88
  }
78
89
  );
@@ -79,9 +79,36 @@ var codeBlockMeta = {
79
79
  }
80
80
  ],
81
81
  tokensUsed: ["border", "muted", "muted-foreground", "foreground"],
82
+ motion: [
83
+ {
84
+ slot: "arrival",
85
+ recipe: "none",
86
+ part: "Root",
87
+ why: "Code is reading content and arrives with the page or message it belongs to. An entrance of its own would delay the text someone came to copy."
88
+ },
89
+ {
90
+ slot: "response",
91
+ recipe: "motion-interactive",
92
+ part: "Copy button",
93
+ why: "The copy button is a quiet ghost icon in the corner of a block. Its hover fill is what tells the pointer it is a control and not decoration."
94
+ },
95
+ {
96
+ slot: "transition",
97
+ recipe: "motion-icon-swap",
98
+ part: "Copy glyph",
99
+ why: "The copy mark turning into a check is the only proof the clipboard took the text. Snapped between frames it was easy to miss at the edge of a block; the swap reads as one mark being replaced."
100
+ },
101
+ {
102
+ slot: "activity",
103
+ recipe: "none",
104
+ part: "Root",
105
+ why: "Copying is instant. There is no in-between worth showing, and a spinner for a clipboard write would read as a slow one."
106
+ }
107
+ ],
82
108
  a11y: [
83
109
  "Code renders inside a <pre><code> for correct semantics and screen-reader handling.",
84
- "Copy is the ghost icon Button with `useCopy`, which guards clipboard access for SSR safety."
110
+ "Copy is the ghost icon Button with `useCopy`, which guards clipboard access for SSR safety.",
111
+ 'A successful copy is announced as "Copied" through a polite role=status region that is always mounted; the button keeps its label, which names what it does. Both glyphs are aria-hidden.'
85
112
  ],
86
113
  examples: [
87
114
  {
@@ -22,7 +22,8 @@ var collapsibleMeta = {
22
22
  },
23
23
  {
24
24
  guidance: "dont",
25
- description: "Do not use Collapsible for multi-section navigation; use Accordion or Tabs instead."
25
+ description: "Do not use Collapsible for multi-section navigation; use Accordion or Tabs instead.",
26
+ instead: ["Accordion", "Tabs"]
26
27
  },
27
28
  {
28
29
  guidance: "dont",
@@ -91,6 +92,38 @@ var collapsibleMeta = {
91
92
  }
92
93
  ],
93
94
  tokensUsed: ["foreground", "muted-foreground"],
95
+ motion: [
96
+ {
97
+ slot: "arrival",
98
+ recipe: "none",
99
+ part: "Root",
100
+ why: "A collapsible is part of the page it sits in, present from first paint. Its trigger is what the reader notices, not an entrance."
101
+ },
102
+ {
103
+ slot: "response",
104
+ recipe: "none",
105
+ part: "CollapsibleTrigger",
106
+ why: "The trigger is whatever the consumer renders (usually a Button), and that control brings its own response. Adding one here would double it."
107
+ },
108
+ {
109
+ slot: "transition",
110
+ recipe: "motion-disclosure",
111
+ part: "CollapsiblePanel",
112
+ why: "The height change ties the content that appears to the trigger that was pressed; snapped open, everything below jumps with nothing linking cause to effect."
113
+ },
114
+ {
115
+ slot: "transition",
116
+ recipe: "motion-interactive",
117
+ part: "CollapsibleIndicator",
118
+ why: "The chevron turns with the panel rather than flipping in one frame: motion-interactive lists `rotate`, the property rotate-180 writes."
119
+ },
120
+ {
121
+ slot: "activity",
122
+ recipe: "none",
123
+ part: "Root",
124
+ why: "Nothing here is indeterminate; content that loads on open should put its own loader inside the panel."
125
+ }
126
+ ],
94
127
  a11y: [
95
128
  "Base UI wires aria-expanded on the trigger and aria-controls/id between trigger and panel.",
96
129
  "Trigger is a real <button>; toggles on click and Enter/Space.",
@@ -95,6 +95,32 @@ var colorFieldMeta = {
95
95
  description: "Accessible name for the trigger; the current value is appended to it."
96
96
  }
97
97
  ],
98
+ motion: [
99
+ {
100
+ slot: "arrival",
101
+ recipe: "motion-pop",
102
+ part: "Popover",
103
+ why: "The picker grows out of the swatch it edits (PopoverContent's recipe, its origin on the anchor). In a property panel with a column of swatches, that origin is what tells you which colour you are now editing."
104
+ },
105
+ {
106
+ slot: "response",
107
+ recipe: "motion-interactive",
108
+ part: "Swatch",
109
+ why: "Preset swatches lift slightly under the pointer. They are 20px chips of colour with no label, and the eased lift is what tells you which one a click will take."
110
+ },
111
+ {
112
+ slot: "transition",
113
+ recipe: "none",
114
+ part: "Handles",
115
+ why: "The saturation, hue and alpha handles follow the pointer exactly. A handle that trailed the drag would misreport the colour under the cursor, and a swatch or a typed hex moving them is the value changing, which the field should show at once."
116
+ },
117
+ {
118
+ slot: "activity",
119
+ recipe: "none",
120
+ part: "Root",
121
+ why: "Picking is synchronous. The EyeDropper is the browser's own modal, with its own feedback."
122
+ }
123
+ ],
98
124
  a11y: [
99
125
  `The trigger's accessible name includes the current value, so a screen reader announces the colour rather than just "button" \u2014 the swatch itself conveys nothing without sight.`,
100
126
  "The Transparent entry is a toggle with aria-pressed, because it is a state of the value and not a navigation action.",
@@ -32,7 +32,8 @@ var comboboxMeta = {
32
32
  },
33
33
  {
34
34
  guidance: "dont",
35
- description: "Use Combobox for tiny option sets where Select or Radio would be easier to scan."
35
+ description: "Use Combobox for tiny option sets where Select or Radio would be easier to scan.",
36
+ instead: ["Select", "Radio"]
36
37
  },
37
38
  {
38
39
  guidance: "do",
@@ -44,7 +45,8 @@ var comboboxMeta = {
44
45
  },
45
46
  {
46
47
  guidance: "dont",
47
- description: "Use Combobox as a command launcher. Use CommandMenu for actions and navigation."
48
+ description: "Use Combobox as a command launcher. Use CommandMenu for actions and navigation.",
49
+ instead: ["CommandMenu"]
48
50
  },
49
51
  {
50
52
  guidance: "dont",
@@ -201,6 +203,50 @@ var comboboxMeta = {
201
203
  "secondary",
202
204
  "secondary-foreground"
203
205
  ],
206
+ motion: [
207
+ {
208
+ slot: "arrival",
209
+ recipe: "motion-pop",
210
+ part: "ComboboxContent",
211
+ why: "The list grows from the field it filters, its origin on Base UI's anchor. With several comboboxes in a form, that is what attaches the open list to the one being typed in."
212
+ },
213
+ {
214
+ slot: "response",
215
+ recipe: "motion-interactive",
216
+ part: "ComboboxItem",
217
+ why: "A row's ink eases as the pointer or the arrow keys reach it, so moving through a long filtered list reads as continuous movement rather than rows blinking."
218
+ },
219
+ {
220
+ slot: "response",
221
+ recipe: "custom",
222
+ part: "Highlight",
223
+ why: "One highlight travels to the active row (lib/highlight-glide: a measured translate on the motion tokens) instead of each row painting its own fill. When the list re-filters under the caret, the highlight sliding to the new match is what shows the keyboard target moved."
224
+ },
225
+ {
226
+ slot: "response",
227
+ recipe: "motion-interactive",
228
+ part: "Toggle",
229
+ why: "The chevron toggle at the field's edge eases its ink on hover; the change is what tells you it is a separate target from the text box."
230
+ },
231
+ {
232
+ slot: "transition",
233
+ recipe: "motion-interactive",
234
+ part: "Toggle",
235
+ why: "The chevron turns 180\xB0 as the list opens and closes (motion-interactive lists `rotate`), in step with the popup, so it reads as one control changing direction."
236
+ },
237
+ {
238
+ slot: "transition",
239
+ recipe: "motion-pop",
240
+ part: "ComboboxContent",
241
+ why: "Closing shrinks back toward the field, returning the eye to the input that now holds the value."
242
+ },
243
+ {
244
+ slot: "activity",
245
+ recipe: "none",
246
+ part: "Root",
247
+ why: "Combobox ships no loading part. A list fetched as you type shows a Spinner (motion-spin) in ComboboxEmpty or the content \u2014 where the results will land \u2014 and only the caller knows a fetch is in flight."
248
+ }
249
+ ],
204
250
  a11y: [
205
251
  "Base UI provides role=combobox/listbox/option, roving tabindex, arrow-key navigation, and type-to-filter.",
206
252
  "Esc and outside-press dismiss; focus returns to the input on close.",
@@ -187,8 +187,13 @@ function CommandMenuBody({
187
187
  ),
188
188
  query ? (
189
189
  // Empties the query and hands focus back to the field, which owns
190
- // the keyboard (the list is driven from it).
191
- /* @__PURE__ */ jsx(
190
+ // the keyboard (the list is driven from it). It mounts on the first
191
+ // keystroke, beside the caret the eye is on, so the wrapper fades it
192
+ // in (motion-appear) rather than letting it blink into the header.
193
+ // On the wrapper, not the Button: motion-appear declares its own
194
+ // transition list and would fight motion-interactive for the one
195
+ // transition-property slot. Removal stays instant.
196
+ /* @__PURE__ */ jsx("span", { className: "flex motion-appear", children: /* @__PURE__ */ jsx(
192
197
  Button,
193
198
  {
194
199
  variant: "ghost",
@@ -203,7 +208,7 @@ function CommandMenuBody({
203
208
  },
204
209
  children: /* @__PURE__ */ jsx(Icon, { name: "xmark" })
205
210
  }
206
- )
211
+ ) })
207
212
  ) : null
208
213
  ] }),
209
214
  /* @__PURE__ */ jsx("div", { role: "status", "aria-live": "polite", className: "sr-only", children: resultCount === 0 ? "No results" : `${resultCount} result${resultCount === 1 ? "" : "s"}` }),
@@ -28,7 +28,8 @@ var commandMenuMeta = {
28
28
  },
29
29
  {
30
30
  guidance: "dont",
31
- description: "Hand-style a <button> to look like a search field, or put a SearchInput in the header that opens a dialog when focused. The first drifts from the field it imitates; the second is a text box you cannot type in. A header search that opens the palette is CommandMenuTrigger."
31
+ description: "Hand-style a <button> to look like a search field, or put a SearchInput in the header that opens a dialog when focused. The first drifts from the field it imitates; the second is a text box you cannot type in. A header search that opens the palette is CommandMenuTrigger.",
32
+ instead: ["CommandMenuTrigger"]
32
33
  },
33
34
  {
34
35
  guidance: "do",
@@ -44,7 +45,8 @@ var commandMenuMeta = {
44
45
  },
45
46
  {
46
47
  guidance: "dont",
47
- description: "Use CommandMenu as a replacement for every dropdown. Simple choices belong in Select, Menu, or Tabs."
48
+ description: "Use CommandMenu as a replacement for every dropdown. Simple choices belong in Select, Menu, or Tabs.",
49
+ instead: ["Select", "Menu", "Tabs"]
48
50
  },
49
51
  {
50
52
  guidance: "do",
@@ -64,7 +66,8 @@ var commandMenuMeta = {
64
66
  },
65
67
  {
66
68
  guidance: "dont",
67
- description: "Hand-build a palette mock from a search glyph, a row of buttons and a highlight. That is CommandMenuPanel, and the hand-built one has no combobox, no filtering and no keyboard model."
69
+ description: "Hand-build a palette mock from a search glyph, a row of buttons and a highlight. That is CommandMenuPanel, and the hand-built one has no combobox, no filtering and no keyboard model.",
70
+ instead: ["CommandMenuPanel"]
68
71
  },
69
72
  {
70
73
  guidance: "dont",
@@ -226,6 +229,44 @@ var commandMenuMeta = {
226
229
  "accent",
227
230
  "accent-foreground"
228
231
  ],
232
+ motion: [
233
+ {
234
+ slot: "arrival",
235
+ recipe: "motion-pop",
236
+ part: "Dialog",
237
+ why: "The palette opens on DialogContent's recipe over a fading backdrop. It is summoned by a keystroke from nowhere on screen, and the short scale-in is what says a layer arrived rather than the page changing. The inline CommandMenuPanel has no entrance: it is part of the page."
238
+ },
239
+ {
240
+ slot: "arrival",
241
+ recipe: "motion-appear",
242
+ part: "Clear button",
243
+ why: "The clear button mounts on the first keystroke, beside the caret the eye is already on. Fading in, it reads as the field offering a way back instead of a control blinking into the header. Removal is instant, so an emptied query does not wait on its X."
244
+ },
245
+ {
246
+ slot: "response",
247
+ recipe: "motion-interactive",
248
+ part: "CommandItem",
249
+ why: "The active row follows both the arrow keys and the pointer, and its fill eases between rows, so scanning results while typing reads as one highlight moving rather than rows flashing in turn."
250
+ },
251
+ {
252
+ slot: "response",
253
+ recipe: "motion-interactive",
254
+ part: "CommandMenuTrigger",
255
+ why: "The search-shaped trigger eases its ink on hover like the field shell it imitates, so it reads as the field it opens."
256
+ },
257
+ {
258
+ slot: "transition",
259
+ recipe: "motion-pop",
260
+ part: "Dialog",
261
+ why: "Closing on Enter or Esc shrinks the palette away fast and returns focus where it came from; the exit is the confirmation that the command ran or was dismissed."
262
+ },
263
+ {
264
+ slot: "activity",
265
+ recipe: "none",
266
+ part: "List",
267
+ why: "Results are filtered synchronously from the items passed in. Async results are the caller's to mark with a Spinner (motion-spin) in the list, where the person is already looking."
268
+ }
269
+ ],
229
270
  a11y: [
230
271
  "CommandMenuTrigger is a real <button> named by its visible placeholder, with aria-haspopup=dialog and aria-keyshortcuts (Meta+K on macOS, Control+K elsewhere). Its Kbd hint is aria-hidden, so the shortcut is announced once rather than read as part of the name.",
231
272
  "The shortcut never fires from inside a text field, textarea, select or contenteditable (Ctrl+K is delete-to-end-of-line in macOS text views), except the palette's own input.",