@sableui/react 0.13.2 → 0.15.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 (203) hide show
  1. package/AGENTS.md +2 -2
  2. package/CHANGELOG.md +172 -0
  3. package/CONTRIBUTING.md +6 -4
  4. package/DESIGN.md +10 -3
  5. package/dist/blocks/approval-block.js +93 -0
  6. package/dist/blocks/approval-block.meta.js +23 -0
  7. package/dist/blocks/index.d.ts +27 -1
  8. package/dist/blocks/index.js +8 -0
  9. package/dist/blocks/message-reactions-block.js +87 -0
  10. package/dist/blocks/message-reactions-block.meta.js +23 -0
  11. package/dist/components/accordion/accordion.meta.js +8 -2
  12. package/dist/components/area-chart/area-chart.meta.js +10 -3
  13. package/dist/components/attachment/attachment.meta.js +26 -0
  14. package/dist/components/bar-chart/bar-chart.meta.js +11 -4
  15. package/dist/components/bottom-nav/bottom-nav.js +3 -1
  16. package/dist/components/bottom-nav/bottom-nav.meta.js +32 -0
  17. package/dist/components/breadcrumbs/breadcrumbs.meta.js +26 -0
  18. package/dist/components/bubble/bubble.js +105 -5
  19. package/dist/components/bubble/bubble.meta.js +42 -6
  20. package/dist/components/bubble/bubble.variants.js +1 -1
  21. package/dist/components/bubble/index.js +2 -0
  22. package/dist/components/button/button.js +21 -4
  23. package/dist/components/button/button.meta.js +7 -7
  24. package/dist/components/calendar/calendar.js +18 -1
  25. package/dist/components/calendar/calendar.meta.js +38 -0
  26. package/dist/components/carousel/carousel.js +6 -4
  27. package/dist/components/carousel/carousel.meta.js +42 -0
  28. package/dist/components/chat-composer/chat-composer.js +21 -21
  29. package/dist/components/chat-composer/chat-composer.meta.js +38 -0
  30. package/dist/components/chat-composer/use-eased-growth.js +70 -0
  31. package/dist/components/checkbox/checkbox-mark.js +8 -1
  32. package/dist/components/checkbox/checkbox.js +3 -1
  33. package/dist/components/checkbox/checkbox.meta.js +32 -0
  34. package/dist/components/checkbox-card/checkbox-card.js +3 -0
  35. package/dist/components/checkbox-card/checkbox-card.meta.js +32 -0
  36. package/dist/components/code-block/code-block.js +14 -3
  37. package/dist/components/code-block/code-block.meta.js +28 -1
  38. package/dist/components/collapsible/collapsible.meta.js +32 -0
  39. package/dist/components/color-field/color-field.meta.js +26 -0
  40. package/dist/components/combobox/combobox.meta.js +44 -0
  41. package/dist/components/command-menu/command-menu.js +8 -3
  42. package/dist/components/command-menu/command-menu.meta.js +38 -0
  43. package/dist/components/confirm-button/confirm-button.js +179 -0
  44. package/dist/components/confirm-button/confirm-button.meta.js +143 -0
  45. package/dist/components/confirm-button/index.js +7 -0
  46. package/dist/components/data-table/data-table.js +6 -2
  47. package/dist/components/data-table/data-table.meta.js +32 -0
  48. package/dist/components/entity/entity.meta.js +26 -0
  49. package/dist/components/feedback/feedback.js +120 -47
  50. package/dist/components/feedback/feedback.meta.js +55 -4
  51. package/dist/components/input-group/input-group.meta.js +27 -1
  52. package/dist/components/like-button/index.js +7 -0
  53. package/dist/components/like-button/like-button.js +89 -0
  54. package/dist/components/like-button/like-button.meta.js +132 -0
  55. package/dist/components/line-chart/line-chart.meta.js +10 -3
  56. package/dist/components/middle-truncate/middle-truncate.meta.js +1 -1
  57. package/dist/components/note/note.meta.js +32 -0
  58. package/dist/components/notification-badge/notification-badge.js +17 -5
  59. package/dist/components/notification-badge/notification-badge.meta.js +34 -2
  60. package/dist/components/number-ticker/index.js +7 -0
  61. package/dist/components/number-ticker/number-ticker.js +246 -0
  62. package/dist/components/number-ticker/number-ticker.meta.js +159 -0
  63. package/dist/components/otp-field/otp-field.js +23 -3
  64. package/dist/components/otp-field/otp-field.meta.js +23 -2
  65. package/dist/components/panel-group/panel-group.meta.js +26 -0
  66. package/dist/components/panel-section/panel-section.js +37 -10
  67. package/dist/components/panel-section/panel-section.meta.js +32 -0
  68. package/dist/components/pie-chart/pie-chart.js +20 -11
  69. package/dist/components/pie-chart/pie-chart.meta.js +17 -4
  70. package/dist/components/pill/pill.meta.js +32 -0
  71. package/dist/components/radar-chart/radar-chart.js +4 -3
  72. package/dist/components/radar-chart/radar-chart.meta.js +10 -3
  73. package/dist/components/radial-chart/radial-chart.js +22 -22
  74. package/dist/components/radial-chart/radial-chart.meta.js +17 -4
  75. package/dist/components/radio/radio.meta.js +26 -0
  76. package/dist/components/radio-card/radio-card.meta.js +38 -0
  77. package/dist/components/reaction-picker/index.js +13 -0
  78. package/dist/components/reaction-picker/reaction-picker.js +104 -0
  79. package/dist/components/reaction-picker/reaction-picker.meta.js +156 -0
  80. package/dist/components/relative-time/relative-time.meta.js +32 -0
  81. package/dist/components/scroller/scroller.meta.js +32 -0
  82. package/dist/components/scrub-field/scrub-field.meta.js +32 -0
  83. package/dist/components/search-input/search-input.js +9 -7
  84. package/dist/components/search-input/search-input.meta.js +26 -0
  85. package/dist/components/show-more/show-more.js +1 -4
  86. package/dist/components/show-more/show-more.meta.js +38 -0
  87. package/dist/components/side-nav/side-nav.js +21 -1
  88. package/dist/components/side-nav/side-nav.meta.js +32 -0
  89. package/dist/components/side-nav/side-nav.variants.js +3 -1
  90. package/dist/components/sidebar/sidebar.js +31 -8
  91. package/dist/components/sidebar/sidebar.meta.js +38 -0
  92. package/dist/components/slider/slider.meta.js +26 -0
  93. package/dist/components/sparkline/sparkline.js +7 -4
  94. package/dist/components/sparkline/sparkline.meta.js +9 -2
  95. package/dist/components/table/table.meta.js +26 -0
  96. package/dist/components/tabs/tabs.js +27 -8
  97. package/dist/components/tabs/tabs.meta.js +46 -3
  98. package/dist/components/theme-selector/theme-selector.meta.js +32 -0
  99. package/dist/components/theme-switcher/theme-switcher.js +4 -1
  100. package/dist/components/theme-switcher/theme-switcher.meta.js +42 -0
  101. package/dist/components/toast/toast.js +25 -13
  102. package/dist/components/toast/toast.meta.js +7 -1
  103. package/dist/components/toggle/toggle.js +28 -5
  104. package/dist/components/toggle/toggle.meta.js +44 -2
  105. package/dist/components/tool-call/index.js +8 -0
  106. package/dist/components/tool-call/tool-call.js +117 -0
  107. package/dist/components/tool-call/tool-call.meta.js +147 -0
  108. package/dist/components/tool-call/tool-call.variants.js +33 -0
  109. package/dist/components/tooltip/index.js +3 -1
  110. package/dist/components/tooltip/tooltip.js +84 -0
  111. package/dist/components/tooltip/tooltip.meta.js +16 -1
  112. package/dist/components/tree/tree.js +71 -6
  113. package/dist/components/tree/tree.meta.js +32 -0
  114. package/dist/components/video/video.meta.js +32 -0
  115. package/dist/file-hashes.json +130 -104
  116. package/dist/foundations/motion.js +69 -3
  117. package/dist/icon-sets/reicon.js +11 -1
  118. package/dist/index.d.ts +907 -104
  119. package/dist/index.js +6 -1
  120. package/dist/lib/chart/cartesian.js +9 -10
  121. package/dist/lib/chart/chart-center.js +30 -0
  122. package/dist/lib/chart/chart-frame.js +15 -8
  123. package/dist/lib/chart/dim.js +13 -0
  124. package/dist/lib/chart/format.js +7 -1
  125. package/dist/lib/chart/motion.js +6 -4
  126. package/dist/lib/highlight-glide.js +68 -10
  127. package/dist/lib/taxonomy.js +5 -0
  128. package/dist/motion/Burst.js +79 -0
  129. package/dist/motion/MotionProvider.js +45 -3
  130. package/dist/motion/Reveal.js +1 -1
  131. package/dist/motion/TextReveal.js +1 -1
  132. package/dist/motion/index.js +17 -2
  133. package/dist/motion/useAutoSize.js +42 -0
  134. package/dist/motion/useShake.js +25 -0
  135. package/dist/styles.css +439 -5
  136. package/llms/blocks/approval-block.md +128 -0
  137. package/llms/blocks/message-reactions-block.md +116 -0
  138. package/llms/components/accordion.md +3 -2
  139. package/llms/components/area-chart.md +4 -3
  140. package/llms/components/attachment.md +7 -0
  141. package/llms/components/bar-chart.md +5 -4
  142. package/llms/components/bottom-nav.md +8 -0
  143. package/llms/components/breadcrumbs.md +7 -0
  144. package/llms/components/bubble.md +17 -7
  145. package/llms/components/button.md +6 -6
  146. package/llms/components/calendar.md +9 -0
  147. package/llms/components/carousel.md +17 -0
  148. package/llms/components/chat-composer.md +9 -0
  149. package/llms/components/checkbox-card.md +8 -0
  150. package/llms/components/checkbox.md +8 -0
  151. package/llms/components/code-block.md +8 -0
  152. package/llms/components/collapsible.md +8 -0
  153. package/llms/components/color-field.md +7 -0
  154. package/llms/components/combobox.md +10 -0
  155. package/llms/components/command-menu.md +9 -0
  156. package/llms/components/confirm-button.md +92 -0
  157. package/llms/components/data-table.md +8 -0
  158. package/llms/components/entity.md +7 -0
  159. package/llms/components/feedback.md +23 -3
  160. package/llms/components/input-group.md +8 -1
  161. package/llms/components/like-button.md +91 -0
  162. package/llms/components/line-chart.md +4 -3
  163. package/llms/components/middle-truncate.md +1 -1
  164. package/llms/components/note.md +8 -0
  165. package/llms/components/notification-badge.md +10 -2
  166. package/llms/components/number-ticker.md +86 -0
  167. package/llms/components/otp-field.md +15 -1
  168. package/llms/components/panel-group.md +7 -0
  169. package/llms/components/panel-section.md +8 -0
  170. package/llms/components/pie-chart.md +6 -4
  171. package/llms/components/pill.md +8 -0
  172. package/llms/components/radar-chart.md +4 -3
  173. package/llms/components/radial-chart.md +6 -4
  174. package/llms/components/radio-card.md +9 -0
  175. package/llms/components/radio.md +7 -0
  176. package/llms/components/reaction-picker.md +115 -0
  177. package/llms/components/relative-time.md +8 -0
  178. package/llms/components/scroller.md +8 -0
  179. package/llms/components/scrub-field.md +8 -0
  180. package/llms/components/search-input.md +7 -0
  181. package/llms/components/show-more.md +9 -0
  182. package/llms/components/side-nav.md +8 -0
  183. package/llms/components/sidebar.md +9 -0
  184. package/llms/components/slider.md +7 -0
  185. package/llms/components/sparkline.md +3 -2
  186. package/llms/components/table.md +7 -0
  187. package/llms/components/tabs.md +20 -2
  188. package/llms/components/theme-selector.md +8 -0
  189. package/llms/components/theme-switcher.md +17 -0
  190. package/llms/components/toast.md +2 -1
  191. package/llms/components/toggle.md +19 -2
  192. package/llms/components/tool-call.md +115 -0
  193. package/llms/components/tooltip.md +11 -1
  194. package/llms/components/tree.md +8 -0
  195. package/llms/components/video.md +8 -0
  196. package/llms/foundations/motion.md +64 -3
  197. package/llms/reference/utilities.md +26 -1
  198. package/llms/tasks/reviewing-motion.md +14 -0
  199. package/llms-full.txt +1337 -69
  200. package/llms.txt +19 -11
  201. package/package.json +1 -1
  202. package/registry.json +3040 -124
  203. package/targets/ios.json +45 -0
package/dist/index.js CHANGED
@@ -43,6 +43,8 @@ export * from "./icon/index.js";
43
43
  export * from "./motion/index.js";
44
44
  export * from "./components/button/index.js";
45
45
  export * from "./components/button-group/index.js";
46
+ export * from "./components/confirm-button/index.js";
47
+ export * from "./components/like-button/index.js";
46
48
  export * from "./components/card/index.js";
47
49
  export * from "./components/caret-menu/index.js";
48
50
  export * from "./components/badge/index.js";
@@ -63,10 +65,12 @@ export * from "./components/avatar/index.js";
63
65
  export * from "./components/skeleton/index.js";
64
66
  export * from "./components/spinner/index.js";
65
67
  export * from "./components/thinking-orb/index.js";
68
+ export * from "./components/tool-call/index.js";
66
69
  export * from "./components/streaming-text/index.js";
67
70
  export * from "./components/gliding-highlight/index.js";
68
71
  export * from "./components/progress/index.js";
69
72
  export * from "./components/meter/index.js";
73
+ export * from "./components/number-ticker/index.js";
70
74
  export * from "./components/segmented-bar/index.js";
71
75
  export * from "./components/area-chart/index.js";
72
76
  export * from "./components/bar-chart/index.js";
@@ -160,6 +164,7 @@ export * from "./components/chat-composer/index.js";
160
164
  export * from "./components/settings-menu/index.js";
161
165
  export * from "./components/message/index.js";
162
166
  export * from "./components/bubble/index.js";
167
+ export * from "./components/reaction-picker/index.js";
163
168
  export * from "./components/attachment/index.js";
164
169
  export * from "./components/marker/index.js";
165
170
  import { Density, DensityProvider, useDensity, useAnchoredDensity, useTypeTier } from "./lib/density.js";
@@ -172,7 +177,7 @@ import {
172
177
  } from "./lib/use-caret-trigger.js";
173
178
  import { STATUS_TONES, STATUS_TONE_ICON } from "./lib/status-tone.js";
174
179
  import { PALETTE_COLORS, PALETTE_FAMILIES, PALETTE_SCALES, PALETTE_STEPS, PALETTE_TONES, isPaletteColor } from "./lib/palette.js";
175
- var SABLE_UI_VERSION = "0.13.2";
180
+ var SABLE_UI_VERSION = "0.15.0";
176
181
  export {
177
182
  ART_TOKENS,
178
183
  CHART_SERIES_COUNT,
@@ -3,10 +3,11 @@
3
3
  // src/lib/chart/cartesian.tsx
4
4
  import { useId } from "react";
5
5
  import { cn } from "../cn.js";
6
- import { ChartFrame } from "./chart-frame.js";
6
+ import { ChartFrame, chartMotion } from "./chart-frame.js";
7
7
  import { ChartLegend } from "./chart-legend.js";
8
8
  import { ChartTooltipContent } from "./chart-tooltip.js";
9
9
  import { approxTextWidth, categoryText, chartFormatters } from "./format.js";
10
+ import { FILL_HOVER, dimmed } from "./dim.js";
10
11
  import { areaPath, linePath, rectPath } from "./geometry.js";
11
12
  import { GLIDE, barTransition, drawTransition, morphTransition, pathStyle, riseTransition, stagger } from "./motion.js";
12
13
  import { bandScale, extent, linearScale, niceTicks, pointScale, stack } from "./scale.js";
@@ -34,7 +35,8 @@ function CartesianChart(props) {
34
35
  locale,
35
36
  tooltip = true,
36
37
  legend,
37
- animate = true,
38
+ motion,
39
+ animate,
38
40
  activeIndex,
39
41
  defaultActiveIndex,
40
42
  onActiveIndexChange,
@@ -150,7 +152,7 @@ function CartesianChart(props) {
150
152
  className,
151
153
  count: n,
152
154
  interactive: tooltip !== false,
153
- animate,
155
+ motion: chartMotion(motion, animate),
154
156
  activeIndex,
155
157
  defaultActiveIndex,
156
158
  onActiveIndexChange,
@@ -232,15 +234,12 @@ function CartesianChart(props) {
232
234
  "path",
233
235
  {
234
236
  d,
235
- className: cn(
236
- horizontal ? "[[data-reveal=out]_&]:scale-x-0" : "[[data-reveal=out]_&]:scale-y-0",
237
- dim && "opacity-40"
238
- ),
237
+ className: horizontal ? "[[data-reveal=out]_&]:scale-x-0" : "[[data-reveal=out]_&]:scale-y-0",
239
238
  style: {
240
239
  ...pathStyle(d),
241
- fill: colorAt(k, i),
240
+ fill: dim ? dimmed(colorAt(k, i), 38) : colorAt(k, i),
242
241
  transformOrigin: origin,
243
- transition: barTransition(stagger(i))
242
+ transition: `${barTransition(stagger(i))}, ${FILL_HOVER}`
244
243
  }
245
244
  },
246
245
  `b${k}-${i}`
@@ -329,7 +328,7 @@ function CartesianChart(props) {
329
328
  r: 4,
330
329
  strokeWidth: 2,
331
330
  className: cn(SURFACE, "[[data-reveal=out]_&]:opacity-0"),
332
- style: { fill: colorBy === "index" ? colorAt(k, i) : s.colorValue, transition: morphTransition(), transitionDelay: `calc(${300 + i * 25}ms * var(--motion-scale, 1))` }
331
+ style: { fill: colorBy === "index" ? colorAt(k, i) : s.colorValue, transition: morphTransition(), transitionDelay: `calc(${300 + i * 25}ms * var(--motion-scale, 1) * var(--motion-expressive, 1))` }
333
332
  },
334
333
  `p${k}-${i}`
335
334
  )
@@ -0,0 +1,30 @@
1
+ "use client";
2
+
3
+ // src/lib/chart/chart-center.tsx
4
+ import { NumberTicker } from "../../components/number-ticker/number-ticker.js";
5
+ import { cn } from "../cn.js";
6
+ import { centerValueFormat } from "./format.js";
7
+ import { morphTransition } from "./motion.js";
8
+ import { jsx, jsxs } from "react/jsx-runtime";
9
+ function ChartCenter({ center, x, y, placement = "middle", locale, motion }) {
10
+ const value = typeof center.value === "number" ? /* @__PURE__ */ jsx(NumberTicker, { value: center.value, format: centerValueFormat(center.value), locales: locale, motion, className: "text-heading-2 text-foreground" }) : /* @__PURE__ */ jsx("span", { className: "text-heading-2 text-foreground tabular-nums", children: center.value });
11
+ const middle = placement === "middle";
12
+ return /* @__PURE__ */ jsx(
13
+ "div",
14
+ {
15
+ className: "absolute whitespace-nowrap [[data-reveal=out]_&]:opacity-0",
16
+ style: { left: x, top: y, transform: middle ? "translate(-50%, -50%)" : "translate(-50%, -100%)", transition: morphTransition() },
17
+ children: /* @__PURE__ */ jsxs("div", { className: cn("relative flex flex-col items-center", !middle && center.label && "pb-4"), children: [
18
+ value,
19
+ center.label ? middle ? (
20
+ // Out of flow, so the number alone is centred; centred by flex so a
21
+ // caption wider than the number overhangs it evenly on both sides.
22
+ /* @__PURE__ */ jsx("span", { className: "absolute inset-x-0 top-full mt-1 flex justify-center", children: /* @__PURE__ */ jsx("span", { className: "text-caption text-muted-foreground", children: center.label }) })
23
+ ) : /* @__PURE__ */ jsx("span", { className: "text-caption text-muted-foreground", children: center.label }) : null
24
+ ] })
25
+ }
26
+ );
27
+ }
28
+ export {
29
+ ChartCenter
30
+ };
@@ -2,16 +2,19 @@
2
2
 
3
3
  // src/lib/chart/chart-frame.tsx
4
4
  import { useEffect, useId, useRef, useState } from "react";
5
- import { useMotion } from "../../motion/MotionProvider.js";
5
+ import { motionAttribute, useMotionLevel } from "../../motion/MotionProvider.js";
6
6
  import { useArrival } from "./use-arrival.js";
7
7
  import { cn } from "../cn.js";
8
8
  import { jsx, jsxs } from "react/jsx-runtime";
9
+ function chartMotion(motion, animate) {
10
+ return motion ?? (animate === false ? "subtle" : void 0);
11
+ }
9
12
  function ChartFrame({
10
13
  label,
11
14
  className,
12
15
  count,
13
16
  interactive,
14
- animate,
17
+ motion,
15
18
  activeIndex: controlled,
16
19
  defaultActiveIndex = null,
17
20
  onActiveIndexChange,
@@ -20,13 +23,14 @@ function ChartFrame({
20
23
  describe,
21
24
  legend,
22
25
  table,
23
- children
26
+ children,
27
+ overlay
24
28
  }) {
25
- const { enabled } = useMotion();
29
+ const { expressive } = useMotionLevel(motion);
26
30
  const [viewRef, inView] = useArrival();
27
31
  const plotRef = useRef(null);
28
32
  const [size, setSize] = useState(null);
29
- const [shown, setShown] = useState(!animate);
33
+ const [shown, setShown] = useState(motion === "off" || motion === "subtle");
30
34
  const [uncontrolled, setUncontrolled] = useState(defaultActiveIndex);
31
35
  const [viaKeyboard, setViaKeyboard] = useState(false);
32
36
  const appeared = useRef(true);
@@ -54,7 +58,7 @@ function ChartFrame({
54
58
  }, []);
55
59
  useEffect(() => {
56
60
  if (shown) return;
57
- if (!animate || !enabled) {
61
+ if (!expressive) {
58
62
  setShown(true);
59
63
  return;
60
64
  }
@@ -67,7 +71,7 @@ function ChartFrame({
67
71
  cancelAnimationFrame(outer);
68
72
  cancelAnimationFrame(inner);
69
73
  };
70
- }, [shown, animate, enabled, size, inView]);
74
+ }, [shown, expressive, size, inView]);
71
75
  const local = (event) => {
72
76
  const r = event.currentTarget.getBoundingClientRect();
73
77
  return { x: event.clientX - r.left, y: event.clientY - r.top };
@@ -98,6 +102,7 @@ function ChartFrame({
98
102
  {
99
103
  ref: viewRef,
100
104
  "data-reveal": shown ? "in" : "out",
105
+ "data-motion": motionAttribute(motion),
101
106
  className: cn("m-0 flex h-60 w-full min-w-0 flex-col gap-3", className),
102
107
  children: [
103
108
  /* @__PURE__ */ jsxs(
@@ -129,6 +134,7 @@ function ChartFrame({
129
134
  children: children({ size, activeIndex: interactive ? active : null })
130
135
  }
131
136
  ) : null,
137
+ overlay && size && size.width > 0 && size.height > 0 ? /* @__PURE__ */ jsx("div", { "aria-hidden": true, className: "pointer-events-none absolute inset-0", children: overlay({ size, activeIndex: interactive ? active : null }) }) : null,
132
138
  placement ? /* @__PURE__ */ jsx(
133
139
  "div",
134
140
  {
@@ -159,5 +165,6 @@ function ChartFrame({
159
165
  );
160
166
  }
161
167
  export {
162
- ChartFrame
168
+ ChartFrame,
169
+ chartMotion
163
170
  };
@@ -0,0 +1,13 @@
1
+ // src/lib/chart/dim.ts
2
+ import { HOVER } from "./motion.js";
3
+ var SURFACE = "var(--sable-surface-bg, var(--background))";
4
+ function dimmed(color, keep) {
5
+ return `color-mix(in oklab, ${color} ${keep}%, ${SURFACE})`;
6
+ }
7
+ var FILL_HOVER = `fill ${HOVER}`;
8
+ var STROKE_HOVER = `stroke ${HOVER}`;
9
+ export {
10
+ FILL_HOVER,
11
+ STROKE_HOVER,
12
+ dimmed
13
+ };
@@ -14,11 +14,17 @@ function approxTextWidth(text) {
14
14
  }
15
15
  function centerValueText(value, locale) {
16
16
  if (typeof value === "string") return value;
17
- return Math.abs(value) < 1e4 ? new Intl.NumberFormat(locale, { maximumFractionDigits: 1 }).format(value) : new Intl.NumberFormat(locale, { notation: "compact", maximumFractionDigits: 1 }).format(value);
17
+ return new Intl.NumberFormat(locale, centerValueFormat(value)).format(value);
18
18
  }
19
+ function centerValueFormat(value) {
20
+ return Math.abs(value) < 1e4 ? FULL_CENTER : COMPACT_CENTER;
21
+ }
22
+ var FULL_CENTER = { maximumFractionDigits: 1 };
23
+ var COMPACT_CENTER = { notation: "compact", maximumFractionDigits: 1 };
19
24
  export {
20
25
  approxTextWidth,
21
26
  categoryText,
27
+ centerValueFormat,
22
28
  centerValueText,
23
29
  chartFormatters
24
30
  };
@@ -1,14 +1,15 @@
1
1
  // src/lib/chart/motion.ts
2
- var DRAW_DURATION = "calc(var(--motion-duration-slower) * 1.8)";
3
- var RISE = "var(--motion-duration-slower) var(--motion-ease-emphasized)";
4
- var MORPH = "var(--motion-duration-slow) var(--motion-ease-standard)";
2
+ var expressive = (token, factor = 1) => `calc(var(${token}) * ${factor} * var(--motion-expressive, 1))`;
3
+ var DRAW_DURATION = expressive("--motion-duration-slower", 1.8);
4
+ var RISE = `${expressive("--motion-duration-slower")} var(--motion-ease-emphasized)`;
5
+ var MORPH = `${expressive("--motion-duration-slow")} var(--motion-ease-standard)`;
5
6
  var HOVER = "var(--motion-duration-fast) var(--motion-ease-standard)";
6
7
  var GLIDE = `transform ${HOVER}, opacity ${HOVER}, cx ${HOVER}, cy ${HOVER}`;
7
8
  function barTransition(delay = "0ms") {
8
9
  return `scale ${RISE} ${delay}, opacity ${HOVER}, d ${MORPH}`;
9
10
  }
10
11
  function stagger(index, stepMs = 35) {
11
- return `calc(${Math.min(index, 24) * stepMs}ms * var(--motion-scale, 1))`;
12
+ return `calc(${Math.min(index, 24) * stepMs}ms * var(--motion-scale, 1) * var(--motion-expressive, 1))`;
12
13
  }
13
14
  function drawTransition(delay = "0ms") {
14
15
  return `stroke-dashoffset ${DRAW_DURATION} var(--motion-ease-decelerate) ${delay}, d ${MORPH}, opacity ${HOVER}`;
@@ -25,6 +26,7 @@ function pathStyle(d) {
25
26
  export {
26
27
  DRAW_DURATION,
27
28
  GLIDE,
29
+ HOVER,
28
30
  barTransition,
29
31
  drawTransition,
30
32
  morphTransition,
@@ -3,17 +3,26 @@
3
3
  // src/lib/highlight-glide.tsx
4
4
  import { useLayoutEffect, useRef } from "react";
5
5
  import { jsx } from "react/jsx-runtime";
6
- function HighlightGlide({ className = "rounded-popup-item bg-accent" }) {
6
+ var expressive = (token) => `calc(var(${token}) * var(--motion-expressive, 1))`;
7
+ function HighlightGlide({
8
+ className = "rounded-popup-item bg-accent",
9
+ follow = "data-highlighted",
10
+ timing = "highlight"
11
+ }) {
7
12
  const ref = useRef(null);
8
13
  useLayoutEffect(() => {
9
14
  const glide = ref.current;
10
15
  const host = glide?.parentElement;
11
16
  if (!glide || !host) return;
12
17
  host.setAttribute("data-glide", "");
18
+ const selection = timing === "selection";
13
19
  let shown = false;
20
+ let placed = false;
21
+ let lastRow = null;
22
+ let lastTarget = "";
14
23
  let frame = 0;
15
24
  const place = () => {
16
- const row = host.querySelector("[data-highlighted]:not([data-highlight-glide])");
25
+ const row = host.querySelector(`[${follow}]:not([data-highlight-glide])`);
17
26
  if (!row) {
18
27
  shown = false;
19
28
  glide.style.opacity = "0";
@@ -21,20 +30,59 @@ function HighlightGlide({ className = "rounded-popup-item bg-accent" }) {
21
30
  }
22
31
  const hostBox = host.getBoundingClientRect();
23
32
  const rowBox = row.getBoundingClientRect();
24
- const scale = host.offsetWidth > 0 ? hostBox.width / host.offsetWidth || 1 : 1;
33
+ const layoutWidth = selection ? Number.parseFloat(getComputedStyle(host).width) || host.offsetWidth : host.offsetWidth;
34
+ const scale = layoutWidth > 0 ? hostBox.width / layoutWidth || 1 : 1;
25
35
  const x = (rowBox.left - hostBox.left) / scale + host.scrollLeft - host.clientLeft;
26
36
  const y = (rowBox.top - hostBox.top) / scale + host.scrollTop - host.clientTop;
27
37
  const geometry = (s) => {
28
38
  s.width = `${rowBox.width / scale}px`;
29
39
  s.height = `${rowBox.height / scale}px`;
30
- s.transform = `translate(${x}px, ${y}px)`;
40
+ if (selection) s.translate = `${x}px ${y}px`;
41
+ else s.transform = `translate(${x}px, ${y}px)`;
31
42
  };
43
+ if (selection) {
44
+ const target = `${x},${y},${rowBox.width / scale},${rowBox.height / scale}`;
45
+ const sameRow = placed && shown && row === lastRow;
46
+ lastRow = row;
47
+ const moved = target !== lastTarget;
48
+ lastTarget = target;
49
+ if (sameRow) {
50
+ if (moved) {
51
+ glide.style.transitionProperty = "none";
52
+ geometry(glide.style);
53
+ void glide.offsetWidth;
54
+ glide.style.transitionProperty = "";
55
+ }
56
+ return;
57
+ }
58
+ if (!placed) {
59
+ glide.style.transitionProperty = "none";
60
+ geometry(glide.style);
61
+ glide.style.opacity = "1";
62
+ void glide.offsetWidth;
63
+ glide.style.transitionProperty = "";
64
+ placed = true;
65
+ shown = true;
66
+ return;
67
+ }
68
+ if (!shown) {
69
+ glide.style.transitionProperty = "opacity";
70
+ geometry(glide.style);
71
+ void glide.offsetWidth;
72
+ glide.style.transitionProperty = "";
73
+ shown = true;
74
+ } else {
75
+ geometry(glide.style);
76
+ }
77
+ glide.style.opacity = "1";
78
+ return;
79
+ }
32
80
  if (!shown) {
33
81
  glide.style.transitionProperty = "opacity";
34
82
  geometry(glide.style);
35
83
  cancelAnimationFrame(frame);
36
84
  frame = requestAnimationFrame(() => {
37
- glide.style.transitionProperty = "transform, width, height, opacity";
85
+ glide.style.transitionProperty = "opacity, transform, width, height";
38
86
  });
39
87
  shown = true;
40
88
  } else {
@@ -44,7 +92,7 @@ function HighlightGlide({ className = "rounded-popup-item bg-accent" }) {
44
92
  };
45
93
  place();
46
94
  const mutations = new MutationObserver(place);
47
- mutations.observe(host, { attributes: true, attributeFilter: ["data-highlighted"], subtree: true, childList: true });
95
+ mutations.observe(host, { attributes: true, attributeFilter: [follow], subtree: true, childList: true });
48
96
  const resize = new ResizeObserver(place);
49
97
  resize.observe(host);
50
98
  host.addEventListener("scroll", place, true);
@@ -59,7 +107,7 @@ function HighlightGlide({ className = "rounded-popup-item bg-accent" }) {
59
107
  host.removeEventListener("animationend", place);
60
108
  host.removeAttribute("data-glide");
61
109
  };
62
- }, []);
110
+ }, [follow, timing]);
63
111
  return /* @__PURE__ */ jsx(
64
112
  "span",
65
113
  {
@@ -72,9 +120,19 @@ function HighlightGlide({ className = "rounded-popup-item bg-accent" }) {
72
120
  // getBoundingClientRect, which is physical in RTL too.
73
121
  left: 0,
74
122
  opacity: 0,
75
- transitionProperty: "opacity",
76
- transitionDuration: "var(--motion-duration-instant)",
77
- transitionTimingFunction: "var(--motion-ease-standard)"
123
+ ...timing === "highlight" && {
124
+ transitionProperty: "opacity",
125
+ // One duration per property in "opacity, transform, width, height"
126
+ // order. The fade that shows or hides the highlight is essential;
127
+ // the travel is expressive.
128
+ transitionDuration: [
129
+ "var(--motion-duration-instant)",
130
+ expressive("--motion-duration-instant"),
131
+ expressive("--motion-duration-instant"),
132
+ expressive("--motion-duration-instant")
133
+ ].join(", "),
134
+ transitionTimingFunction: "var(--motion-ease-standard)"
135
+ }
78
136
  }
79
137
  }
80
138
  );
@@ -25,8 +25,11 @@ var COMPONENT_TAXONOMY = {
25
25
  what it is. Copying is `useCopy` on a Button, not a component. */
26
26
  Button: ["Actions", "either"],
27
27
  ButtonGroup: ["Actions", "either"],
28
+ ConfirmButton: ["Actions", "either"],
29
+ LikeButton: ["Actions", "either"],
28
30
  Menubar: ["Actions", "either"],
29
31
  OverflowMenu: ["Actions", "either"],
32
+ ReactionPicker: ["Actions", "either"],
30
33
  Toolbar: ["Actions", "either"],
31
34
  /* ── Forms ────────────────────────────────────────────────────────────────
32
35
  Typing or entering a value, and the structure a form is built from.
@@ -136,6 +139,7 @@ var COMPONENT_TAXONOMY = {
136
139
  Entity: ["Data display", "either"],
137
140
  GlidingHighlight: ["Data display", "either"],
138
141
  Meter: ["Data display", "either"],
142
+ NumberTicker: ["Data display", "either"],
139
143
  RelativeTime: ["Data display", "either"],
140
144
  SegmentedBar: ["Data display", "either"],
141
145
  Table: ["Data display", "either"],
@@ -184,6 +188,7 @@ var COMPONENT_TAXONOMY = {
184
188
  MessageScroller: ["Chat & AI", "either"],
185
189
  StreamingText: ["Chat & AI", "either"],
186
190
  ThinkingOrb: ["Chat & AI", "either"],
191
+ ToolCall: ["Chat & AI", "either"],
187
192
  /* ── Media & mockups ──────────────────────────────────────────────────────
188
193
  Presenting images and video: players, galleries, scrolling strips, and the
189
194
  device frames a product image sits in. `Video` was filed as a mockup; it is
@@ -0,0 +1,79 @@
1
+ "use client";
2
+
3
+ // src/motion/Burst.tsx
4
+ import { useRef, useState } from "react";
5
+ import { cn } from "../lib/cn.js";
6
+ import { motionAttribute, useMotionLevel } from "./MotionProvider.js";
7
+ import { jsx, jsxs } from "react/jsx-runtime";
8
+ var DEFAULT_PALETTE = ["pink", "purple", "blue", "orange"];
9
+ function Burst({
10
+ trigger,
11
+ palette = DEFAULT_PALETTE,
12
+ count = 7,
13
+ particle,
14
+ playOnMount = false,
15
+ className,
16
+ motion
17
+ }) {
18
+ const { expressive } = useMotionLevel(motion);
19
+ const [last, setLast] = useState(trigger);
20
+ const [shot, setShot] = useState(playOnMount ? 1 : 0);
21
+ if (!Object.is(trigger, last)) {
22
+ setLast(trigger);
23
+ setShot((n) => n + 1);
24
+ }
25
+ const [done, setDone] = useState(0);
26
+ const landed = useRef({ shot: 0, pieces: 0 });
27
+ if (shot === 0 || shot === done || !expressive || palette.length === 0) return null;
28
+ const places = Math.max(1, Math.round(count));
29
+ const onAnimationEnd = (event) => {
30
+ if (!event.target.classList.contains("sable-burst-dot")) return;
31
+ if (landed.current.shot !== shot) landed.current = { shot, pieces: 0 };
32
+ landed.current.pieces += 1;
33
+ if (landed.current.pieces >= places * 2) setDone(shot);
34
+ };
35
+ const step = 360 / places;
36
+ const scale = particle === void 0 ? 1 : 2.5;
37
+ return /* @__PURE__ */ jsxs(
38
+ "span",
39
+ {
40
+ "aria-hidden": true,
41
+ "data-motion": motionAttribute(motion),
42
+ className: cn("sable-burst [--burst-size:2.25em]", className),
43
+ onAnimationEnd,
44
+ children: [
45
+ particle === void 0 ? /* @__PURE__ */ jsx("span", { className: "sable-burst-ring", "data-palette": palette[0] }) : null,
46
+ Array.from({ length: places }, (_, i) => {
47
+ const angle = i * step - 90;
48
+ return [
49
+ { angle, reach: 0.95, size: `calc(var(--burst-size) * ${0.14 * scale})`, family: palette[i % palette.length] },
50
+ {
51
+ angle: angle + step / 3,
52
+ reach: 0.75,
53
+ size: `calc(var(--burst-size) * ${0.1 * scale})`,
54
+ family: palette[(i + 1) % palette.length]
55
+ }
56
+ ].map((dot, j) => /* @__PURE__ */ jsx(
57
+ "span",
58
+ {
59
+ className: "sable-burst-dot",
60
+ "data-palette": particle === void 0 ? dot.family : void 0,
61
+ "data-particle": particle === void 0 ? void 0 : "",
62
+ style: {
63
+ "--burst-dot-angle": `${dot.angle}deg`,
64
+ "--burst-dot-reach": dot.reach,
65
+ "--burst-dot-size": dot.size
66
+ },
67
+ children: particle
68
+ },
69
+ `${i}-${j}`
70
+ ));
71
+ })
72
+ ]
73
+ },
74
+ shot
75
+ );
76
+ }
77
+ export {
78
+ Burst
79
+ };
@@ -17,6 +17,9 @@ function resolveEnabled(policy, prefersReducedMotion) {
17
17
  if (policy === "full") return true;
18
18
  return !prefersReducedMotion;
19
19
  }
20
+ function resolveExpressive(policy, prefersReducedMotion) {
21
+ return policy !== "subtle" && resolveEnabled(policy, prefersReducedMotion);
22
+ }
20
23
  function MotionProvider({
21
24
  children,
22
25
  defaultPolicy = "system",
@@ -51,12 +54,47 @@ function MotionProvider({
51
54
  policy,
52
55
  setPolicy,
53
56
  prefersReducedMotion,
54
- enabled: resolveEnabled(policy, prefersReducedMotion)
57
+ enabled: resolveEnabled(policy, prefersReducedMotion),
58
+ expressive: resolveExpressive(policy, prefersReducedMotion)
55
59
  }),
56
60
  [policy, setPolicy, prefersReducedMotion]
57
61
  );
58
62
  return /* @__PURE__ */ jsx(MotionContext.Provider, { value, children });
59
63
  }
64
+ var LEVEL_RANK = { off: 0, subtle: 1, full: 2 };
65
+ function lowerLevel(a, b) {
66
+ return LEVEL_RANK[a] <= LEVEL_RANK[b] ? a : b;
67
+ }
68
+ function contextLevel(ctx) {
69
+ if (!ctx.enabled) return "off";
70
+ return ctx.expressive ? "full" : "subtle";
71
+ }
72
+ function motionAttribute(level) {
73
+ return level === void 0 || level === "full" ? void 0 : level;
74
+ }
75
+ function useMotionLevel(level) {
76
+ const ctx = useMotion();
77
+ const resolved = lowerLevel(contextLevel(ctx), level ?? "full");
78
+ return {
79
+ level: resolved,
80
+ enabled: resolved !== "off",
81
+ expressive: resolved === "full",
82
+ attribute: motionAttribute(level)
83
+ };
84
+ }
85
+ function MotionScope({ children, level }) {
86
+ const parent = useMotion();
87
+ const resolved = lowerLevel(contextLevel(parent), level);
88
+ const value = useMemo(
89
+ () => ({
90
+ ...parent,
91
+ enabled: resolved !== "off",
92
+ expressive: resolved === "full"
93
+ }),
94
+ [parent, resolved]
95
+ );
96
+ return /* @__PURE__ */ jsx(MotionContext.Provider, { value, children: /* @__PURE__ */ jsx("div", { "data-motion": level, style: { display: "contents" }, children }) });
97
+ }
60
98
  function useMotion() {
61
99
  const ctx = useContext(MotionContext);
62
100
  const prefersReducedMotion = useReducedMotion();
@@ -66,10 +104,14 @@ function useMotion() {
66
104
  setPolicy: () => {
67
105
  },
68
106
  prefersReducedMotion,
69
- enabled: resolveEnabled("system", prefersReducedMotion)
107
+ enabled: resolveEnabled("system", prefersReducedMotion),
108
+ expressive: resolveExpressive("system", prefersReducedMotion)
70
109
  };
71
110
  }
72
111
  export {
73
112
  MotionProvider,
74
- useMotion
113
+ MotionScope,
114
+ motionAttribute,
115
+ useMotion,
116
+ useMotionLevel
75
117
  };
@@ -18,7 +18,7 @@ function Reveal({
18
18
  ...props
19
19
  }) {
20
20
  const Comp = as ?? "div";
21
- const { enabled } = useMotion();
21
+ const { expressive: enabled } = useMotion();
22
22
  const { ref, inView } = useInView({ once, amount, rootMargin });
23
23
  const shown = inView && when || !enabled;
24
24
  return /* @__PURE__ */ jsx(
@@ -18,7 +18,7 @@ function TextReveal({
18
18
  ...props
19
19
  }) {
20
20
  const Comp = as ?? "span";
21
- const { enabled } = useMotion();
21
+ const { expressive: enabled } = useMotion();
22
22
  const { ref, inView } = useInView({ once, amount, rootMargin });
23
23
  const shown = inView && when || !enabled;
24
24
  const units = split === "char" ? typeof Intl.Segmenter === "function" ? Array.from(new Intl.Segmenter(void 0, { granularity: "grapheme" }).segment(text), ({ segment }) => segment) : Array.from(text) : text.split(/(\s+)/).filter(Boolean);
@@ -1,24 +1,39 @@
1
1
  // src/motion/index.ts
2
- import { MotionProvider, useMotion } from "./MotionProvider.js";
2
+ import {
3
+ MotionProvider,
4
+ MotionScope,
5
+ motionAttribute,
6
+ useMotion,
7
+ useMotionLevel
8
+ } from "./MotionProvider.js";
3
9
  import { useReducedMotion } from "./useReducedMotion.js";
10
+ import { useShake } from "./useShake.js";
11
+ import { useAutoSize } from "./useAutoSize.js";
4
12
  import { useInView } from "./useInView.js";
5
13
  import { Reveal } from "./Reveal.js";
6
14
  import { TextReveal } from "./TextReveal.js";
7
15
  import { Presence } from "./Presence.js";
16
+ import { Burst } from "./Burst.js";
8
17
  import {
9
18
  startViewTransition,
10
19
  supportsViewTransitions,
11
20
  ViewTransitionName
12
21
  } from "./viewTransition.js";
13
22
  export {
23
+ Burst,
14
24
  MotionProvider,
25
+ MotionScope,
15
26
  Presence,
16
27
  Reveal,
17
28
  TextReveal,
18
29
  ViewTransitionName,
30
+ motionAttribute,
19
31
  startViewTransition,
20
32
  supportsViewTransitions,
33
+ useAutoSize,
21
34
  useInView,
22
35
  useMotion,
23
- useReducedMotion
36
+ useMotionLevel,
37
+ useReducedMotion,
38
+ useShake
24
39
  };