@seekalfred/primitives 0.6.0 → 0.7.3

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 (346) hide show
  1. package/README.md +214 -15
  2. package/dist/activity-heatmap/activity-heatmap.d.ts +92 -0
  3. package/dist/activity-heatmap/activity-heatmap.js +699 -0
  4. package/dist/activity-heatmap/activity-heatmap.js.map +1 -0
  5. package/dist/activity-heatmap/index.d.ts +2 -0
  6. package/dist/activity-heatmap/index.js +2 -0
  7. package/dist/alert/alert.d.ts +53 -4
  8. package/dist/alert/alert.js +100 -11
  9. package/dist/alert/alert.js.map +1 -1
  10. package/dist/alert/index.d.ts +2 -2
  11. package/dist/alert/index.js +2 -2
  12. package/dist/alert-dialog/alert-dialog.d.ts +58 -18
  13. package/dist/alert-dialog/alert-dialog.js +152 -9
  14. package/dist/alert-dialog/alert-dialog.js.map +1 -1
  15. package/dist/alert-dialog/index.d.ts +2 -2
  16. package/dist/alert-dialog/index.js +2 -2
  17. package/dist/animated-counter/animated-counter.d.ts +38 -0
  18. package/dist/animated-counter/animated-counter.js +230 -0
  19. package/dist/animated-counter/animated-counter.js.map +1 -0
  20. package/dist/animated-counter/index.d.ts +2 -0
  21. package/dist/animated-counter/index.js +2 -0
  22. package/dist/announcement-bar/announcement-bar.d.ts +67 -0
  23. package/dist/announcement-bar/announcement-bar.js +364 -0
  24. package/dist/announcement-bar/announcement-bar.js.map +1 -0
  25. package/dist/announcement-bar/index.d.ts +2 -0
  26. package/dist/announcement-bar/index.js +2 -0
  27. package/dist/badge/badge.d.ts +1 -1
  28. package/dist/bar-chart/bar-chart.d.ts +51 -0
  29. package/dist/bar-chart/bar-chart.js +565 -0
  30. package/dist/bar-chart/bar-chart.js.map +1 -0
  31. package/dist/bar-chart/index.d.ts +2 -0
  32. package/dist/bar-chart/index.js +2 -0
  33. package/dist/breadcrumb/breadcrumb.d.ts +2 -2
  34. package/dist/breadcrumb/breadcrumb.js +12 -3
  35. package/dist/breadcrumb/breadcrumb.js.map +1 -1
  36. package/dist/brush-chart/brush-chart.d.ts +64 -0
  37. package/dist/brush-chart/brush-chart.js +1056 -0
  38. package/dist/brush-chart/brush-chart.js.map +1 -0
  39. package/dist/brush-chart/index.d.ts +2 -0
  40. package/dist/brush-chart/index.js +2 -0
  41. package/dist/button/button.d.ts +1 -1
  42. package/dist/button/button.js +31 -21
  43. package/dist/button/button.js.map +1 -1
  44. package/dist/button-group/button-group.js +3 -4
  45. package/dist/button-group/button-group.js.map +1 -1
  46. package/dist/calendar/calendar.d.ts +1 -1
  47. package/dist/card/card.d.ts +22 -1
  48. package/dist/card/card.js +60 -7
  49. package/dist/card/card.js.map +1 -1
  50. package/dist/card-stack/card-stack.d.ts +54 -0
  51. package/dist/card-stack/card-stack.js +492 -0
  52. package/dist/card-stack/card-stack.js.map +1 -0
  53. package/dist/card-stack/index.d.ts +2 -0
  54. package/dist/card-stack/index.js +2 -0
  55. package/dist/carousel/carousel.d.ts +77 -0
  56. package/dist/carousel/carousel.js +415 -0
  57. package/dist/carousel/carousel.js.map +1 -0
  58. package/dist/carousel/index.d.ts +2 -0
  59. package/dist/carousel/index.js +2 -0
  60. package/dist/checkbox/checkbox.js +4 -4
  61. package/dist/checkbox/checkbox.js.map +1 -1
  62. package/dist/chip-group/chip-group.d.ts +37 -0
  63. package/dist/chip-group/chip-group.js +148 -0
  64. package/dist/chip-group/chip-group.js.map +1 -0
  65. package/dist/chip-group/index.d.ts +2 -0
  66. package/dist/chip-group/index.js +2 -0
  67. package/dist/copy-button/copy-button.d.ts +18 -2
  68. package/dist/copy-button/copy-button.js +29 -8
  69. package/dist/copy-button/copy-button.js.map +1 -1
  70. package/dist/countdown/countdown.d.ts +45 -0
  71. package/dist/countdown/countdown.js +298 -0
  72. package/dist/countdown/countdown.js.map +1 -0
  73. package/dist/countdown/index.d.ts +2 -0
  74. package/dist/countdown/index.js +2 -0
  75. package/dist/date-picker/picker-parts.js +1 -1
  76. package/dist/date-picker/picker-parts.js.map +1 -1
  77. package/dist/dialog/dialog.js +1 -1
  78. package/dist/dialog/dialog.js.map +1 -1
  79. package/dist/donut-chart/donut-chart.d.ts +86 -0
  80. package/dist/donut-chart/donut-chart.js +593 -0
  81. package/dist/donut-chart/donut-chart.js.map +1 -0
  82. package/dist/donut-chart/index.d.ts +2 -0
  83. package/dist/donut-chart/index.js +2 -0
  84. package/dist/empty-state/empty-state.d.ts +42 -10
  85. package/dist/empty-state/empty-state.js +44 -26
  86. package/dist/empty-state/empty-state.js.map +1 -1
  87. package/dist/empty-state/index.d.ts +2 -2
  88. package/dist/empty-state/index.js +2 -2
  89. package/dist/file-drop-zone/file-drop-zone.js +1 -1
  90. package/dist/file-drop-zone/file-drop-zone.js.map +1 -1
  91. package/dist/filter-bar/filter-bar.d.ts +50 -26
  92. package/dist/filter-bar/filter-bar.js +317 -157
  93. package/dist/filter-bar/filter-bar.js.map +1 -1
  94. package/dist/gauge/gauge.d.ts +45 -0
  95. package/dist/gauge/gauge.js +219 -0
  96. package/dist/gauge/gauge.js.map +1 -0
  97. package/dist/gauge/index.d.ts +2 -0
  98. package/dist/gauge/index.js +2 -0
  99. package/dist/in-view-title/in-view-title.d.ts +33 -0
  100. package/dist/in-view-title/in-view-title.js +119 -0
  101. package/dist/in-view-title/in-view-title.js.map +1 -0
  102. package/dist/in-view-title/index.d.ts +2 -0
  103. package/dist/in-view-title/index.js +2 -0
  104. package/dist/index.d.ts +53 -10
  105. package/dist/index.js +52 -12
  106. package/dist/input/input.js +4 -4
  107. package/dist/input/input.js.map +1 -1
  108. package/dist/lib/cn.js +11 -1
  109. package/dist/lib/cn.js.map +1 -1
  110. package/dist/lib/field-message.js +28 -10
  111. package/dist/lib/field-message.js.map +1 -1
  112. package/dist/lib/field-shell.js +10 -3
  113. package/dist/lib/field-shell.js.map +1 -1
  114. package/dist/lib/field-surface.js +31 -9
  115. package/dist/lib/field-surface.js.map +1 -1
  116. package/dist/lib/flag-emoji.js +50 -0
  117. package/dist/lib/flag-emoji.js.map +1 -0
  118. package/dist/lib/flip.js +11 -3
  119. package/dist/lib/flip.js.map +1 -1
  120. package/dist/lib/menu-highlight.js +64 -0
  121. package/dist/lib/menu-highlight.js.map +1 -0
  122. package/dist/lib/motion.js +9 -2
  123. package/dist/lib/motion.js.map +1 -1
  124. package/dist/lib/rolling-value.js +1 -1
  125. package/dist/lib/rolling-value.js.map +1 -1
  126. package/dist/lib/spring-tween.js +500 -0
  127. package/dist/lib/spring-tween.js.map +1 -0
  128. package/dist/lib/swap-text.js +3 -1
  129. package/dist/lib/swap-text.js.map +1 -1
  130. package/dist/lib/use-copy-feedback.js +17 -3
  131. package/dist/lib/use-copy-feedback.js.map +1 -1
  132. package/dist/line-chart/index.d.ts +2 -0
  133. package/dist/line-chart/index.js +2 -0
  134. package/dist/line-chart/line-chart.d.ts +116 -0
  135. package/dist/line-chart/line-chart.js +740 -0
  136. package/dist/line-chart/line-chart.js.map +1 -0
  137. package/dist/mention-input/index.d.ts +2 -0
  138. package/dist/mention-input/index.js +2 -0
  139. package/dist/mention-input/mention-input.d.ts +98 -0
  140. package/dist/mention-input/mention-input.js +576 -0
  141. package/dist/mention-input/mention-input.js.map +1 -0
  142. package/dist/menu/menu.d.ts +2 -0
  143. package/dist/menu/menu.js +23 -12
  144. package/dist/menu/menu.js.map +1 -1
  145. package/dist/money-input/index.d.ts +2 -0
  146. package/dist/money-input/index.js +2 -0
  147. package/dist/money-input/money-input.d.ts +86 -0
  148. package/dist/money-input/money-input.js +613 -0
  149. package/dist/money-input/money-input.js.map +1 -0
  150. package/dist/morph-select/index.d.ts +2 -0
  151. package/dist/morph-select/index.js +2 -0
  152. package/dist/morph-select/morph-select.d.ts +89 -0
  153. package/dist/morph-select/morph-select.js +712 -0
  154. package/dist/morph-select/morph-select.js.map +1 -0
  155. package/dist/multi-select/index.d.ts +2 -0
  156. package/dist/multi-select/index.js +2 -0
  157. package/dist/multi-select/multi-select.d.ts +77 -0
  158. package/dist/multi-select/multi-select.js +209 -0
  159. package/dist/multi-select/multi-select.js.map +1 -0
  160. package/dist/number-field/index.d.ts +2 -0
  161. package/dist/number-field/index.js +2 -0
  162. package/dist/number-field/number-field.d.ts +42 -0
  163. package/dist/number-field/number-field.js +199 -0
  164. package/dist/number-field/number-field.js.map +1 -0
  165. package/dist/onboarding-checklist/index.d.ts +2 -0
  166. package/dist/onboarding-checklist/index.js +2 -0
  167. package/dist/onboarding-checklist/onboarding-checklist.d.ts +55 -0
  168. package/dist/onboarding-checklist/onboarding-checklist.js +577 -0
  169. package/dist/onboarding-checklist/onboarding-checklist.js.map +1 -0
  170. package/dist/password-field/password-field.d.ts +16 -0
  171. package/dist/password-field/password-field.js +39 -10
  172. package/dist/password-field/password-field.js.map +1 -1
  173. package/dist/phone-input/index.d.ts +3 -0
  174. package/dist/phone-input/index.js +3 -0
  175. package/dist/phone-input/phone-countries.d.ts +29 -0
  176. package/dist/phone-input/phone-countries.js +170 -0
  177. package/dist/phone-input/phone-countries.js.map +1 -0
  178. package/dist/phone-input/phone-input.d.ts +66 -0
  179. package/dist/phone-input/phone-input.js +440 -0
  180. package/dist/phone-input/phone-input.js.map +1 -0
  181. package/dist/popover/index.d.ts +2 -2
  182. package/dist/popover/index.js +2 -2
  183. package/dist/popover/popover.d.ts +51 -2
  184. package/dist/popover/popover.js +59 -8
  185. package/dist/popover/popover.js.map +1 -1
  186. package/dist/progress/progress.d.ts +16 -2
  187. package/dist/progress/progress.js +16 -9
  188. package/dist/progress/progress.js.map +1 -1
  189. package/dist/radio-group/index.d.ts +2 -2
  190. package/dist/radio-group/index.js +2 -2
  191. package/dist/radio-group/radio-group.d.ts +33 -3
  192. package/dist/radio-group/radio-group.js +110 -10
  193. package/dist/radio-group/radio-group.js.map +1 -1
  194. package/dist/reactions/index.d.ts +2 -0
  195. package/dist/reactions/index.js +2 -0
  196. package/dist/reactions/reactions.d.ts +50 -0
  197. package/dist/reactions/reactions.js +315 -0
  198. package/dist/reactions/reactions.js.map +1 -0
  199. package/dist/rich-text-editor/index.d.ts +3 -0
  200. package/dist/rich-text-editor/index.js +2 -0
  201. package/dist/rich-text-editor/rich-text-editor.d.ts +70 -0
  202. package/dist/rich-text-editor/rich-text-editor.js +909 -0
  203. package/dist/rich-text-editor/rich-text-editor.js.map +1 -0
  204. package/dist/rich-text-editor/rich-text-engine.d.ts +16 -0
  205. package/dist/rich-text-editor/rich-text-engine.js +366 -0
  206. package/dist/rich-text-editor/rich-text-engine.js.map +1 -0
  207. package/dist/rich-text-editor/rich-text-html.js +174 -0
  208. package/dist/rich-text-editor/rich-text-html.js.map +1 -0
  209. package/dist/rich-text-editor/rich-text-icons.js +43 -0
  210. package/dist/rich-text-editor/rich-text-icons.js.map +1 -0
  211. package/dist/ridgeline/index.d.ts +2 -0
  212. package/dist/ridgeline/index.js +2 -0
  213. package/dist/ridgeline/ridgeline.d.ts +55 -0
  214. package/dist/ridgeline/ridgeline.js +701 -0
  215. package/dist/ridgeline/ridgeline.js.map +1 -0
  216. package/dist/scroll-area/index.d.ts +2 -2
  217. package/dist/scroll-area/index.js +2 -2
  218. package/dist/scroll-area/scroll-area.d.ts +68 -31
  219. package/dist/scroll-area/scroll-area.js +212 -62
  220. package/dist/scroll-area/scroll-area.js.map +1 -1
  221. package/dist/scrub-input/evaluate.d.ts +14 -0
  222. package/dist/scrub-input/evaluate.js +72 -0
  223. package/dist/scrub-input/evaluate.js.map +1 -0
  224. package/dist/scrub-input/index.d.ts +3 -0
  225. package/dist/scrub-input/index.js +3 -0
  226. package/dist/scrub-input/scrub-input.d.ts +74 -0
  227. package/dist/scrub-input/scrub-input.js +486 -0
  228. package/dist/scrub-input/scrub-input.js.map +1 -0
  229. package/dist/search-field/search-field.d.ts +24 -2
  230. package/dist/search-field/search-field.js +24 -9
  231. package/dist/search-field/search-field.js.map +1 -1
  232. package/dist/select/select.d.ts +3 -1
  233. package/dist/select/select.js +28 -14
  234. package/dist/select/select.js.map +1 -1
  235. package/dist/separator/separator.d.ts +22 -10
  236. package/dist/separator/separator.js +33 -11
  237. package/dist/separator/separator.js.map +1 -1
  238. package/dist/sheet/sheet.js +2 -1
  239. package/dist/sheet/sheet.js.map +1 -1
  240. package/dist/shortcut-recorder/index.d.ts +3 -0
  241. package/dist/shortcut-recorder/index.js +3 -0
  242. package/dist/shortcut-recorder/shortcut-model.d.ts +27 -0
  243. package/dist/shortcut-recorder/shortcut-model.js +206 -0
  244. package/dist/shortcut-recorder/shortcut-model.js.map +1 -0
  245. package/dist/shortcut-recorder/shortcut-recorder.d.ts +93 -0
  246. package/dist/shortcut-recorder/shortcut-recorder.js +489 -0
  247. package/dist/shortcut-recorder/shortcut-recorder.js.map +1 -0
  248. package/dist/slider/index.d.ts +2 -2
  249. package/dist/slider/slider.d.ts +46 -14
  250. package/dist/slider/slider.js +151 -67
  251. package/dist/slider/slider.js.map +1 -1
  252. package/dist/slope-chart/index.d.ts +2 -0
  253. package/dist/slope-chart/index.js +2 -0
  254. package/dist/slope-chart/slope-chart.d.ts +54 -0
  255. package/dist/slope-chart/slope-chart.js +568 -0
  256. package/dist/slope-chart/slope-chart.js.map +1 -0
  257. package/dist/sparkline/index.d.ts +2 -0
  258. package/dist/sparkline/index.js +2 -0
  259. package/dist/sparkline/sparkline.d.ts +46 -0
  260. package/dist/sparkline/sparkline.js +390 -0
  261. package/dist/sparkline/sparkline.js.map +1 -0
  262. package/dist/split-bar/index.d.ts +2 -0
  263. package/dist/split-bar/index.js +2 -0
  264. package/dist/split-bar/split-bar.d.ts +62 -0
  265. package/dist/split-bar/split-bar.js +513 -0
  266. package/dist/split-bar/split-bar.js.map +1 -0
  267. package/dist/split-button/split-button.d.ts +1 -1
  268. package/dist/split-button/split-button.js +12 -11
  269. package/dist/split-button/split-button.js.map +1 -1
  270. package/dist/stat-card/index.d.ts +2 -2
  271. package/dist/stat-card/index.js +2 -2
  272. package/dist/stat-card/stat-card.d.ts +58 -5
  273. package/dist/stat-card/stat-card.js +144 -17
  274. package/dist/stat-card/stat-card.js.map +1 -1
  275. package/dist/stepper/index.d.ts +2 -0
  276. package/dist/stepper/index.js +2 -0
  277. package/dist/stepper/stepper.d.ts +51 -0
  278. package/dist/stepper/stepper.js +215 -0
  279. package/dist/stepper/stepper.js.map +1 -0
  280. package/dist/streamgraph/index.d.ts +2 -0
  281. package/dist/streamgraph/index.js +2 -0
  282. package/dist/streamgraph/streamgraph.d.ts +70 -0
  283. package/dist/streamgraph/streamgraph.js +613 -0
  284. package/dist/streamgraph/streamgraph.js.map +1 -0
  285. package/dist/styles.css +1 -1
  286. package/dist/swipe-actions/index.d.ts +2 -0
  287. package/dist/swipe-actions/index.js +2 -0
  288. package/dist/swipe-actions/swipe-actions.d.ts +67 -0
  289. package/dist/swipe-actions/swipe-actions.js +552 -0
  290. package/dist/swipe-actions/swipe-actions.js.map +1 -0
  291. package/dist/switch/switch.js +2 -2
  292. package/dist/switch/switch.js.map +1 -1
  293. package/dist/table/index.d.ts +2 -2
  294. package/dist/table/index.js +2 -2
  295. package/dist/table/table.d.ts +55 -3
  296. package/dist/table/table.js +340 -165
  297. package/dist/table/table.js.map +1 -1
  298. package/dist/tabs/tabs.js +2 -2
  299. package/dist/tabs/tabs.js.map +1 -1
  300. package/dist/tag-input/index.d.ts +2 -0
  301. package/dist/tag-input/index.js +2 -0
  302. package/dist/tag-input/tag-input.d.ts +40 -0
  303. package/dist/tag-input/tag-input.js +269 -0
  304. package/dist/tag-input/tag-input.js.map +1 -0
  305. package/dist/text-morph/index.d.ts +2 -0
  306. package/dist/text-morph/index.js +2 -0
  307. package/dist/text-morph/text-morph.d.ts +23 -0
  308. package/dist/text-morph/text-morph.js +21 -0
  309. package/dist/text-morph/text-morph.js.map +1 -0
  310. package/dist/text-reveal/index.d.ts +2 -0
  311. package/dist/text-reveal/index.js +2 -0
  312. package/dist/text-reveal/text-reveal.d.ts +27 -0
  313. package/dist/text-reveal/text-reveal.js +51 -0
  314. package/dist/text-reveal/text-reveal.js.map +1 -0
  315. package/dist/text-shimmer/index.d.ts +2 -0
  316. package/dist/text-shimmer/index.js +2 -0
  317. package/dist/text-shimmer/text-shimmer.d.ts +29 -0
  318. package/dist/text-shimmer/text-shimmer.js +29 -0
  319. package/dist/text-shimmer/text-shimmer.js.map +1 -0
  320. package/dist/time-picker/time-picker.d.ts +36 -1
  321. package/dist/time-picker/time-picker.js +35 -7
  322. package/dist/time-picker/time-picker.js.map +1 -1
  323. package/dist/toast/index.d.ts +2 -2
  324. package/dist/toast/index.js +2 -2
  325. package/dist/toast/toast.d.ts +7 -1
  326. package/dist/toast/toast.js +82 -40
  327. package/dist/toast/toast.js.map +1 -1
  328. package/dist/tooltip/tooltip.d.ts +9 -2
  329. package/dist/tooltip/tooltip.js +7 -7
  330. package/dist/tooltip/tooltip.js.map +1 -1
  331. package/dist/treemap/index.d.ts +2 -0
  332. package/dist/treemap/index.js +2 -0
  333. package/dist/treemap/treemap.d.ts +53 -0
  334. package/dist/treemap/treemap.js +699 -0
  335. package/dist/treemap/treemap.js.map +1 -0
  336. package/dist/usage-meter/index.d.ts +2 -0
  337. package/dist/usage-meter/index.js +2 -0
  338. package/dist/usage-meter/usage-meter.d.ts +80 -0
  339. package/dist/usage-meter/usage-meter.js +458 -0
  340. package/dist/usage-meter/usage-meter.js.map +1 -0
  341. package/dist/waffle-chart/index.d.ts +2 -0
  342. package/dist/waffle-chart/index.js +2 -0
  343. package/dist/waffle-chart/waffle-chart.d.ts +57 -0
  344. package/dist/waffle-chart/waffle-chart.js +426 -0
  345. package/dist/waffle-chart/waffle-chart.js.map +1 -0
  346. package/package.json +165 -3
@@ -0,0 +1,148 @@
1
+ "use client";
2
+ import { cn } from "../lib/cn.js";
3
+ import { mergeRefs } from "../lib/merge-refs.js";
4
+ import { SwapText } from "../lib/swap-text.js";
5
+ import { useHeightFollow } from "../lib/height-follow.js";
6
+ import { useFlip } from "../lib/flip.js";
7
+ import { forwardRef, useMemo, useRef, useState } from "react";
8
+ import { jsx, jsxs } from "react/jsx-runtime";
9
+ //#region src/chip-group/chip-group.tsx
10
+ /** Roving-focus key of the overflow chip; the NUL keeps it from colliding with any option value. */
11
+ const MORE = "\0more";
12
+ /** Stagger between chips revealed by the overflow (ms), at most 300 in all. */
13
+ const STAGGER_MS = 35;
14
+ const ChipGroup = forwardRef(function ChipGroup({ options, value, onValueChange, label, multiple = true, maxVisible = Infinity, moreLabel = (hidden) => `+${hidden} more`, lessLabel = "Show less", className }, ref) {
15
+ const [expanded, setExpanded] = useState(false);
16
+ const [pinned, setPinned] = useState(value);
17
+ const [active, setActive] = useState(null);
18
+ const foldable = options.length > maxVisible;
19
+ const visible = !foldable || expanded ? options : options.filter((option, index) => index < maxVisible || pinned.includes(option.value) || value.includes(option.value));
20
+ const hidden = options.length - visible.length;
21
+ const showMore = foldable && (expanded || hidden > 0);
22
+ const keys = [...visible.map((option) => option.value), ...showMore ? [MORE] : []];
23
+ const tabStop = active !== null && keys.includes(active) ? active : visible.find((option) => value.includes(option.value))?.value ?? keys[0];
24
+ const group = useRef(null);
25
+ const refs = useMemo(() => mergeRefs(ref, group), [ref]);
26
+ const frame = useRef(null);
27
+ const morphKey = `${expanded}|${value.join(",")}`;
28
+ useFlip(group, morphKey, "var(--duration-morph)", "var(--ease-morph)");
29
+ useHeightFollow(frame, group, morphKey);
30
+ const seen = useRef(new Set(options.map((option) => option.value)));
31
+ const revealedAt = /* @__PURE__ */ new Map();
32
+ let rank = 0;
33
+ for (const option of visible) if (!seen.current.has(option.value)) revealedAt.set(option.value, Math.min(rank++ * STAGGER_MS, 300));
34
+ function toggle(next) {
35
+ const on = value.includes(next);
36
+ if (!multiple) {
37
+ onValueChange(on ? [] : [next]);
38
+ return;
39
+ }
40
+ onValueChange(options.filter((option) => option.value === next ? !on : value.includes(option.value)).map((option) => option.value));
41
+ }
42
+ function toggleMore() {
43
+ if (expanded) setPinned(value);
44
+ else seen.current = new Set(visible.map((option) => option.value));
45
+ setExpanded(!expanded);
46
+ }
47
+ function onKeyDown(event) {
48
+ const buttons = Array.from(event.currentTarget.querySelectorAll("button[data-chip]"));
49
+ const index = buttons.indexOf(document.activeElement);
50
+ if (index < 0) return;
51
+ const last = buttons.length - 1;
52
+ const moves = {
53
+ ArrowRight: index === last ? 0 : index + 1,
54
+ ArrowDown: index === last ? 0 : index + 1,
55
+ ArrowLeft: index === 0 ? last : index - 1,
56
+ ArrowUp: index === 0 ? last : index - 1,
57
+ Home: 0,
58
+ End: last
59
+ };
60
+ if (!(event.key in moves)) return;
61
+ event.preventDefault();
62
+ buttons[moves[event.key]].focus();
63
+ }
64
+ const moreText = expanded ? lessLabel : moreLabel(hidden);
65
+ return /* @__PURE__ */ jsx("div", {
66
+ ref: frame,
67
+ "data-slot": "chip-group-frame",
68
+ className: "height-follow min-w-0",
69
+ children: /* @__PURE__ */ jsxs("div", {
70
+ ref: refs,
71
+ role: "group",
72
+ "aria-label": label,
73
+ "data-slot": "chip-group",
74
+ onKeyDown,
75
+ className: cn("relative isolate flex flex-wrap gap-2 box-border font-sans", className),
76
+ children: [visible.map((option) => {
77
+ const selected = value.includes(option.value);
78
+ const delay = revealedAt.get(option.value);
79
+ return /* @__PURE__ */ jsx("button", {
80
+ type: "button",
81
+ "data-flip": "",
82
+ "data-chip": option.value,
83
+ "data-slot": "chip",
84
+ "aria-pressed": selected,
85
+ tabIndex: tabStop === option.value ? 0 : -1,
86
+ onClick: () => toggle(option.value),
87
+ onFocus: () => setActive(option.value),
88
+ style: delay === void 0 ? void 0 : { animationDelay: `${delay}ms` },
89
+ className: cn("chip", delay === void 0 ? "" : "motion-safe:animate-chip-reveal"),
90
+ children: /* @__PURE__ */ jsxs("span", {
91
+ "data-selected": selected,
92
+ className: "chip-body",
93
+ children: [
94
+ /* @__PURE__ */ jsx("span", {
95
+ "aria-hidden": "true",
96
+ className: "chip-check",
97
+ children: /* @__PURE__ */ jsx("svg", {
98
+ width: 14,
99
+ height: 14,
100
+ viewBox: "0 0 24 24",
101
+ fill: "none",
102
+ stroke: "currentColor",
103
+ strokeWidth: 2.5,
104
+ strokeLinecap: "round",
105
+ strokeLinejoin: "round",
106
+ children: /* @__PURE__ */ jsx("path", {
107
+ d: "M4 12.5 9.5 18 20 6.5",
108
+ pathLength: 1
109
+ })
110
+ })
111
+ }),
112
+ /* @__PURE__ */ jsx("span", {
113
+ "aria-hidden": "true",
114
+ className: "chip-slot"
115
+ }),
116
+ /* @__PURE__ */ jsx("span", {
117
+ className: "truncate",
118
+ children: option.label
119
+ })
120
+ ]
121
+ })
122
+ }, option.value);
123
+ }), showMore ? /* @__PURE__ */ jsx("button", {
124
+ type: "button",
125
+ "data-flip": "",
126
+ "data-chip": MORE,
127
+ "data-more": "",
128
+ "aria-expanded": expanded,
129
+ tabIndex: tabStop === MORE ? 0 : -1,
130
+ onClick: toggleMore,
131
+ onFocus: () => setActive(MORE),
132
+ className: "chip chip-more",
133
+ children: /* @__PURE__ */ jsx("span", {
134
+ className: "chip-body tabular-nums",
135
+ children: /* @__PURE__ */ jsx(SwapText, {
136
+ swapKey: moreText,
137
+ motion: "rise",
138
+ children: moreText
139
+ })
140
+ })
141
+ }, MORE) : null]
142
+ })
143
+ });
144
+ });
145
+ //#endregion
146
+ export { ChipGroup };
147
+
148
+ //# sourceMappingURL=chip-group.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chip-group.js","names":[],"sources":["../../src/chip-group/chip-group.tsx"],"sourcesContent":["\"use client\";\n\nimport { forwardRef, useMemo, useRef, useState, type KeyboardEvent } from \"react\";\n\nimport { cn } from \"../lib/cn.js\";\nimport { useFlip } from \"../lib/flip.js\";\nimport { useHeightFollow } from \"../lib/height-follow.js\";\nimport { mergeRefs } from \"../lib/merge-refs.js\";\nimport { SwapText } from \"../lib/swap-text.js\";\n\n/**\n * ChipGroup: selectable filter chips for narrowing a list by facets people\n * toggle often, such as topics, statuses or tags.\n *\n * Selecting a chip grows a check in at its start (it draws itself as it\n * scales up from a soft blur), the label slides over, the chip widens on the\n * morph spring, and its neighbours glide to their new places, even across\n * lines. The row follows its own height. Long sets fold behind a \"+N more\"\n * chip; opening it grows the hidden chips in one after another. Arrow keys move\n * between chips, Space or Enter toggles, and each chip is a pressed or\n * unpressed button. Selected reads as a tint plus the check, never colour alone.\n */\nexport interface ChipOption {\n value: string;\n label: string;\n}\n\nexport interface ChipGroupProps {\n options: ChipOption[];\n value: string[];\n onValueChange: (value: string[]) => void;\n /** Accessible name of the group, such as \"Topics\". */\n label: string;\n /** Allow several chips at once. In single mode the selected chip can still be cleared. @default true */\n multiple?: boolean;\n /** Chips shown before the rest fold behind a \"+N more\" chip. Chips selected when it folds stay in view. */\n maxVisible?: number;\n /** Text of the overflow chip. @default (hidden) => `+${hidden} more` */\n moreLabel?: (hidden: number) => string;\n /** @default \"Show less\" */\n lessLabel?: string;\n /** Extra classes for the group. Merged last. */\n className?: string;\n}\n\n/** Roving-focus key of the overflow chip; the NUL keeps it from colliding with any option value. */\nconst MORE = \"\\u0000more\";\n/** Stagger between chips revealed by the overflow (ms), at most 300 in all. */\nconst STAGGER_MS = 35;\n\nexport const ChipGroup = forwardRef<HTMLDivElement, ChipGroupProps>(function ChipGroup(\n {\n options,\n value,\n onValueChange,\n label,\n multiple = true,\n maxVisible = Infinity,\n moreLabel = (hidden) => `+${hidden} more`,\n lessLabel = \"Show less\",\n className,\n },\n ref,\n) {\n const [expanded, setExpanded] = useState(false);\n // Chips selected when the overflow folds stay in view, so a selection is never hidden.\n const [pinned, setPinned] = useState<string[]>(value);\n const [active, setActive] = useState<string | null>(null);\n const foldable = options.length > maxVisible;\n const visible =\n !foldable || expanded\n ? options\n : options.filter(\n (option, index) => index < maxVisible || pinned.includes(option.value) || value.includes(option.value),\n );\n const hidden = options.length - visible.length;\n const showMore = foldable && (expanded || hidden > 0);\n const keys = [...visible.map((option) => option.value), ...(showMore ? [MORE] : [])];\n // One chip holds the tab stop: the last one focused, else the first selected, else the first.\n const tabStop =\n active !== null && keys.includes(active)\n ? active\n : (visible.find((option) => value.includes(option.value))?.value ?? keys[0]);\n\n const group = useRef<HTMLDivElement | null>(null);\n const refs = useMemo(() => mergeRefs(ref, group), [ref]);\n const frame = useRef<HTMLDivElement>(null);\n const morphKey = `${expanded}|${value.join(\",\")}`;\n // Neighbours glide to their new places when a chip widens, even across lines; the row follows its height.\n useFlip(group, morphKey, \"var(--duration-morph)\", \"var(--ease-morph)\");\n useHeightFollow(frame, group, morphKey);\n // Chips that appear after the first render grow in; the ones there from the start never animate.\n const seen = useRef(new Set(options.map((option) => option.value)));\n const revealedAt = new Map<string, number>();\n let rank = 0;\n for (const option of visible) {\n if (!seen.current.has(option.value)) revealedAt.set(option.value, Math.min(rank++ * STAGGER_MS, 300));\n }\n\n function toggle(next: string) {\n const on = value.includes(next);\n if (!multiple) {\n onValueChange(on ? [] : [next]);\n return;\n }\n onValueChange(\n options.filter((option) => (option.value === next ? !on : value.includes(option.value))).map((option) => option.value),\n );\n }\n\n function toggleMore() {\n if (expanded) setPinned(value);\n // Only the chips this opening reveals grow in; the ones already on screen stay put.\n else seen.current = new Set(visible.map((option) => option.value));\n setExpanded(!expanded);\n }\n\n function onKeyDown(event: KeyboardEvent<HTMLDivElement>) {\n const buttons = Array.from(event.currentTarget.querySelectorAll<HTMLButtonElement>(\"button[data-chip]\"));\n const index = buttons.indexOf(document.activeElement as HTMLButtonElement);\n if (index < 0) return;\n const last = buttons.length - 1;\n const moves: Record<string, number> = {\n ArrowRight: index === last ? 0 : index + 1,\n ArrowDown: index === last ? 0 : index + 1,\n ArrowLeft: index === 0 ? last : index - 1,\n ArrowUp: index === 0 ? last : index - 1,\n Home: 0,\n End: last,\n };\n if (!(event.key in moves)) return;\n event.preventDefault();\n buttons[moves[event.key]!]!.focus();\n }\n\n const moreText = expanded ? lessLabel : moreLabel(hidden);\n\n return (\n <div ref={frame} data-slot=\"chip-group-frame\" className=\"height-follow min-w-0\">\n <div\n ref={refs}\n role=\"group\"\n aria-label={label}\n data-slot=\"chip-group\"\n onKeyDown={onKeyDown}\n className={cn(\"relative isolate flex flex-wrap gap-2 box-border font-sans\", className)}\n >\n {visible.map((option) => {\n const selected = value.includes(option.value);\n const delay = revealedAt.get(option.value);\n return (\n <button\n key={option.value}\n type=\"button\"\n data-flip=\"\"\n data-chip={option.value}\n data-slot=\"chip\"\n aria-pressed={selected}\n tabIndex={tabStop === option.value ? 0 : -1}\n onClick={() => toggle(option.value)}\n onFocus={() => setActive(option.value)}\n style={delay === undefined ? undefined : ({ animationDelay: `${delay}ms` })}\n className={cn(\"chip\", delay === undefined ? \"\" : \"motion-safe:animate-chip-reveal\")}\n >\n <span data-selected={selected} className=\"chip-body\">\n <span aria-hidden=\"true\" className=\"chip-check\">\n <svg width={14} height={14} viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth={2.5} strokeLinecap=\"round\" strokeLinejoin=\"round\">\n <path d=\"M4 12.5 9.5 18 20 6.5\" pathLength={1} />\n </svg>\n </span>\n <span aria-hidden=\"true\" className=\"chip-slot\" />\n <span className=\"truncate\">{option.label}</span>\n </span>\n </button>\n );\n })}\n {showMore ? (\n <button\n key={MORE}\n type=\"button\"\n data-flip=\"\"\n data-chip={MORE}\n data-more=\"\"\n aria-expanded={expanded}\n tabIndex={tabStop === MORE ? 0 : -1}\n onClick={toggleMore}\n onFocus={() => setActive(MORE)}\n className=\"chip chip-more\"\n >\n <span className=\"chip-body tabular-nums\">\n <SwapText swapKey={moreText} motion=\"rise\">\n {moreText}\n </SwapText>\n </span>\n </button>\n ) : null}\n </div>\n </div>\n );\n});\n"],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ import { ChipGroup, ChipGroupProps, ChipOption } from "./chip-group.js";
2
+ export { ChipGroup, type ChipGroupProps, type ChipOption };
@@ -0,0 +1,2 @@
1
+ import { ChipGroup } from "./chip-group.js";
2
+ export { ChipGroup };
@@ -6,11 +6,24 @@ export interface CopyButtonProps {
6
6
  children?: string;
7
7
  /** Only the icon, a square button. The label is still its accessible name. */
8
8
  iconOnly?: boolean;
9
- /** `plain` has no border until hovered. @default "outline" */
10
- variant?: "outline" | "plain";
9
+ /**
10
+ * `plain` has no border until hovered. `primary` is the filled brand button, for a copy that is the
11
+ * main action of its row or dialog. @default "outline"
12
+ */
13
+ variant?: "outline" | "plain" | "primary";
14
+ /**
15
+ * Control height, from the density scale: `sm` sits in a row, `md` matches a default Button.
16
+ * @default "sm"
17
+ */
18
+ size?: "sm" | "md";
11
19
  isDisabled?: boolean;
12
20
  /** Called once the text has reached the clipboard. */
13
21
  onCopied?: () => void;
22
+ /**
23
+ * Called when the clipboard refused the text, with what it rejected with. The button already shows
24
+ * "Failed" and announces it; use this to keep a toast or log of your own.
25
+ */
26
+ onError?: (error: unknown) => void;
14
27
  /** Extra classes. Merged last, so they win conflicts with ours. */
15
28
  className?: string;
16
29
  }
@@ -26,6 +39,9 @@ export interface CopyButtonProps {
26
39
  * @example
27
40
  * <CopyButton value={token} />
28
41
  * <CopyButton value={url} iconOnly variant="plain" />
42
+ * <CopyButton value={ticket} variant="primary" size="md" onError={() => toast("Copy failed")}>
43
+ * Copy as ticket
44
+ * </CopyButton>
29
45
  */
30
46
  export declare const CopyButton: import("react").ForwardRefExoticComponent<CopyButtonProps & import("react").RefAttributes<HTMLButtonElement>>;
31
47
  //#endregion
@@ -3,12 +3,29 @@ import { cn } from "../lib/cn.js";
3
3
  import { buttonVariants } from "../button/button.js";
4
4
  import { MorphText } from "../lib/morph-text.js";
5
5
  import { IconSwap } from "../lib/icon-swap.js";
6
- import { useCopyFeedback } from "../lib/use-copy-feedback.js";
6
+ import { callSafely, useCopyFeedback } from "../lib/use-copy-feedback.js";
7
7
  import { forwardRef } from "react";
8
8
  import { Button } from "react-aria-components";
9
9
  import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
10
10
  //#region src/copy-button/copy-button.tsx
11
11
  const ICON = "size-4";
12
+ /** CopyButton's variant names, mapped onto Button's. */
13
+ const BUTTON_VARIANT = {
14
+ outline: "outlineNeutral",
15
+ plain: "ghost",
16
+ primary: "primary"
17
+ };
18
+ /** An icon-only button is square: as wide as its rung is tall. */
19
+ const ICON_ONLY_WIDTH = {
20
+ sm: " w-(--density-control-h-sm) px-0",
21
+ md: " w-(--density-control-h-md) px-0"
22
+ };
23
+ /** The result icon's tone on a page-surface button; at rest it takes the label's ink. */
24
+ const RESULT_TONE = {
25
+ idle: void 0,
26
+ copied: "text-success-strong",
27
+ error: "text-destructive-strong"
28
+ };
12
29
  /** The tick draws itself from its short stroke, the way a hand would write it: quick to start, then easing into place. */
13
30
  function DrawnCheck() {
14
31
  return /* @__PURE__ */ jsx("svg", {
@@ -70,17 +87,21 @@ function StateIcon({ state }) {
70
87
  * @example
71
88
  * <CopyButton value={token} />
72
89
  * <CopyButton value={url} iconOnly variant="plain" />
90
+ * <CopyButton value={ticket} variant="primary" size="md" onError={() => toast("Copy failed")}>
91
+ * Copy as ticket
92
+ * </CopyButton>
73
93
  */
74
- const CopyButton = forwardRef(function CopyButton({ value, children: label = "Copy", iconOnly = false, variant = "outline", isDisabled, onCopied, className }, ref) {
94
+ const CopyButton = forwardRef(function CopyButton({ value, children: label = "Copy", iconOnly = false, variant = "outline", size = "sm", isDisabled, onCopied, onError, className }, ref) {
75
95
  const { state, copy } = useCopyFeedback();
76
96
  const text = state === "copied" ? "Copied" : state === "error" ? "Failed" : label;
77
97
  const pressStart = (event) => {
78
98
  event.target.style.setProperty("--press-scale", iconOnly ? "0.96" : "0.97");
79
99
  };
80
100
  const classes = cn(buttonVariants({
81
- variant: variant === "plain" ? "ghost" : "outlineNeutral",
82
- size: "sm"
83
- }) + " [--release-duration:var(--duration-gentle)] [--release-ease:var(--ease-gentle)]" + (iconOnly ? " w-(--density-control-h-sm) px-0" : ""), className);
101
+ variant: BUTTON_VARIANT[variant],
102
+ size
103
+ }) + " [--release-duration:var(--duration-gentle)] [--release-ease:var(--ease-gentle)]" + (iconOnly ? ICON_ONLY_WIDTH[size] : ""), className);
104
+ const onBrandFill = variant === "primary";
84
105
  return /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsxs(Button, {
85
106
  ref,
86
107
  "data-slot": "button",
@@ -89,8 +110,8 @@ const CopyButton = forwardRef(function CopyButton({ value, children: label = "Co
89
110
  isDisabled,
90
111
  onPressStart: pressStart,
91
112
  onPress: () => {
92
- copy(value).then((ok) => {
93
- if (ok) onCopied?.();
113
+ copy(value, onError).then((ok) => {
114
+ if (ok) callSafely(() => onCopied?.());
94
115
  });
95
116
  },
96
117
  className: classes,
@@ -99,7 +120,7 @@ const CopyButton = forwardRef(function CopyButton({ value, children: label = "Co
99
120
  calm: true,
100
121
  className: cn(ICON, "relative"),
101
122
  children: /* @__PURE__ */ jsx("span", {
102
- className: state === "copied" ? "text-success-strong" : state === "error" ? "text-destructive-strong" : void 0,
123
+ className: onBrandFill ? void 0 : RESULT_TONE[state],
103
124
  children: /* @__PURE__ */ jsx(StateIcon, { state })
104
125
  })
105
126
  }), iconOnly ? null : /* @__PURE__ */ jsxs("span", {
@@ -1 +1 @@
1
- {"version":3,"file":"copy-button.js","names":["AriaButton"],"sources":["../../src/copy-button/copy-button.tsx"],"sourcesContent":["\"use client\";\n\nimport { forwardRef } from \"react\";\nimport { Button as AriaButton, type PressEvent } from \"react-aria-components\";\n\nimport { buttonVariants } from \"../button/button.js\";\nimport { cn } from \"../lib/cn.js\";\nimport { IconSwap } from \"../lib/icon-swap.js\";\nimport { MorphText } from \"../lib/morph-text.js\";\nimport { useCopyFeedback, type CopyFeedbackState } from \"../lib/use-copy-feedback.js\";\n\nexport interface CopyButtonProps {\n /** What goes on the clipboard. */\n value: string;\n /** The label at rest, and the button's accessible name. @default \"Copy\" */\n children?: string;\n /** Only the icon, a square button. The label is still its accessible name. */\n iconOnly?: boolean;\n /** `plain` has no border until hovered. @default \"outline\" */\n variant?: \"outline\" | \"plain\";\n isDisabled?: boolean;\n /** Called once the text has reached the clipboard. */\n onCopied?: () => void;\n /** Extra classes. Merged last, so they win conflicts with ours. */\n className?: string;\n}\n\nconst ICON = \"size-4\";\n\n/** The tick draws itself from its short stroke, the way a hand would write it: quick to start, then easing into place. */\nfunction DrawnCheck() {\n return (\n <svg\n className={ICON}\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth={1.75}\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n aria-hidden=\"true\"\n >\n <path\n d=\"M4 12l5 5L20 6\"\n pathLength={1}\n className=\"[stroke-dasharray:1] motion-safe:animate-check-draw-calm\"\n />\n </svg>\n );\n}\n\nfunction StateIcon({ state }: { state: CopyFeedbackState }) {\n if (state === \"copied\") return <DrawnCheck />;\n const common = {\n className: ICON,\n viewBox: \"0 0 24 24\",\n fill: \"none\",\n stroke: \"currentColor\",\n strokeWidth: 1.75,\n strokeLinecap: \"round\",\n strokeLinejoin: \"round\",\n \"aria-hidden\": true,\n } as const;\n if (state === \"error\") {\n return (\n <svg {...common}>\n <circle cx=\"12\" cy=\"12\" r=\"10\" />\n <path d=\"M12 8v4M12 16h.01\" />\n </svg>\n );\n }\n return (\n <svg {...common}>\n <rect x=\"8\" y=\"8\" width=\"14\" height=\"14\" rx=\"2\" />\n <path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\" />\n </svg>\n );\n}\n\n/**\n * Copies a value and says so, calmly. The icon trades places in one soft breath\n * (copy, then a tick that draws itself, or an alert if the clipboard refused)\n * while the label's changed letters rise in, and the button never changes width:\n * its label cell reserves the widest of the three texts.\n *\n * It goes back to rest by itself after a moment, and a live region announces the\n * result. The accessible name is always the resting label.\n *\n * @example\n * <CopyButton value={token} />\n * <CopyButton value={url} iconOnly variant=\"plain\" />\n */\nexport const CopyButton = forwardRef<HTMLButtonElement, CopyButtonProps>(function CopyButton(\n {\n value,\n children: label = \"Copy\",\n iconOnly = false,\n variant = \"outline\",\n isDisabled,\n onCopied,\n className,\n },\n ref,\n) {\n const { state, copy } = useCopyFeedback();\n const text = state === \"copied\" ? \"Copied\" : state === \"error\" ? \"Failed\" : label;\n\n // Not width-based like Button's press: an icon button presses a little deeper, every other one the same.\n const pressStart = (event: PressEvent) => {\n (event.target as HTMLElement).style.setProperty(\"--press-scale\", iconOnly ? \"0.96\" : \"0.97\");\n };\n\n const classes = cn(\n buttonVariants({ variant: variant === \"plain\" ? \"ghost\" : \"outlineNeutral\", size: \"sm\" }) +\n \" [--release-duration:var(--duration-gentle)] [--release-ease:var(--ease-gentle)]\" +\n (iconOnly ? \" w-(--density-control-h-sm) px-0\" : \"\"),\n className,\n );\n\n return (\n <>\n <AriaButton\n ref={ref}\n data-slot=\"button\"\n data-copy-state={state}\n aria-label={label}\n isDisabled={isDisabled}\n onPressStart={pressStart}\n onPress={() => {\n void copy(value).then((ok) => {\n if (ok) onCopied?.();\n });\n }}\n className={classes}\n >\n <IconSwap iconKey={state} calm className={cn(ICON, \"relative\")}>\n <span\n className={\n state === \"copied\"\n ? \"text-success-strong\"\n : state === \"error\"\n ? \"text-destructive-strong\"\n : undefined\n }\n >\n <StateIcon state={state} />\n </span>\n </IconSwap>\n {iconOnly ? null : (\n // The cell reserves the widest state, so nothing moves but the letters. The inline padding gives blurred\n // edge letters room before the horizontal clip; the negative margin keeps the box the same size.\n <span\n className=\"-mx-1 grid min-w-0 overflow-x-clip px-1 text-start whitespace-nowrap\"\n aria-hidden=\"true\"\n >\n {[label, \"Copied\", \"Failed\"].map((measure) => (\n <span key={measure} className=\"invisible col-start-1 row-start-1\">\n {measure}\n </span>\n ))}\n <span className=\"col-start-1 row-start-1 justify-self-start\">\n <MorphText text={text} morphWidth={false} calm />\n </span>\n </span>\n )}\n </AriaButton>\n <span className=\"sr-only\" role=\"status\" aria-live=\"polite\">\n {state === \"idle\"\n ? \"\"\n : state === \"error\"\n ? label + \": Could not copy\"\n : label + \": Copied\"}\n </span>\n </>\n );\n});\n"],"mappings":";;;;;;;;;;AA2BA,MAAM,OAAO;;AAGb,SAAS,aAAa;CACpB,OACE,oBAAC,OAAD;EACE,WAAW;EACX,SAAQ;EACR,MAAK;EACL,QAAO;EACP,aAAa;EACb,eAAc;EACd,gBAAe;EACf,eAAY;EAEZ,UAAA,oBAAC,QAAD;GACE,GAAE;GACF,YAAY;GACZ,WAAU;EACX,CAAA;CACE,CAAA;AAET;AAEA,SAAS,UAAU,EAAE,SAAuC;CAC1D,IAAI,UAAU,UAAU,OAAO,oBAAC,YAAD,CAAa,CAAA;CAC5C,MAAM,SAAS;EACb,WAAW;EACX,SAAS;EACT,MAAM;EACN,QAAQ;EACR,aAAa;EACb,eAAe;EACf,gBAAgB;EAChB,eAAe;CACjB;CACA,IAAI,UAAU,SACZ,OACE,qBAAC,OAAD;EAAK,GAAI;EAAT,UAAA,CACE,oBAAC,UAAD;GAAQ,IAAG;GAAK,IAAG;GAAK,GAAE;EAAM,CAAA,GAChC,oBAAC,QAAD,EAAM,GAAE,oBAAqB,CAAA,CAC1B;;CAGT,OACE,qBAAC,OAAD;EAAK,GAAI;EAAT,UAAA,CACE,oBAAC,QAAD;GAAM,GAAE;GAAI,GAAE;GAAI,OAAM;GAAK,QAAO;GAAK,IAAG;EAAK,CAAA,GACjD,oBAAC,QAAD,EAAM,GAAE,0DAA2D,CAAA,CAChE;;AAET;;;;;;;;;;;;;;AAeA,MAAa,aAAa,WAA+C,SAAS,WAChF,EACE,OACA,UAAU,QAAQ,QAClB,WAAW,OACX,UAAU,WACV,YACA,UACA,aAEF,KACA;CACA,MAAM,EAAE,OAAO,SAAS,gBAAgB;CACxC,MAAM,OAAO,UAAU,WAAW,WAAW,UAAU,UAAU,WAAW;CAG5E,MAAM,cAAc,UAAsB;EACxC,MAAO,OAAuB,MAAM,YAAY,iBAAiB,WAAW,SAAS,MAAM;CAC7F;CAEA,MAAM,UAAU,GACd,eAAe;EAAE,SAAS,YAAY,UAAU,UAAU;EAAkB,MAAM;CAAK,CAAC,IACtF,sFACC,WAAW,qCAAqC,KACnD,SACF;CAEA,OACE,qBAAA,YAAA,EAAA,UAAA,CACE,qBAACA,QAAD;EACO;EACL,aAAU;EACV,mBAAiB;EACjB,cAAY;EACA;EACZ,cAAc;EACd,eAAe;GACb,KAAU,KAAK,CAAC,CAAC,MAAM,OAAO;IAC5B,IAAI,IAAI,WAAW;GACrB,CAAC;EACH;EACA,WAAW;EAZb,UAAA,CAcE,oBAAC,UAAD;GAAU,SAAS;GAAO,MAAA;GAAK,WAAW,GAAG,MAAM,UAAU;GAC3D,UAAA,oBAAC,QAAD;IACE,WACE,UAAU,WACN,wBACA,UAAU,UACR,4BACA,KAAA;IAGR,UAAA,oBAAC,WAAD,EAAkB,MAAQ,CAAA;GACtB,CAAA;EACE,CAAA,GACT,WAAW,OAGV,qBAAC,QAAD;GACE,WAAU;GACV,eAAY;GAFd,UAAA,CAIG;IAAC;IAAO;IAAU;GAAQ,CAAC,CAAC,KAAK,YAChC,oBAAC,QAAD;IAAoB,WAAU;IAC3B,UAAA;GACG,GAFK,OAEL,CACP,GACD,oBAAC,QAAD;IAAM,WAAU;IACd,UAAA,oBAAC,WAAD;KAAiB;KAAM,YAAY;KAAO,MAAA;IAAM,CAAA;GAC5C,CAAA,CACF;EAEE,CAAA,CAAA;CACZ,CAAA,GAAA,oBAAC,QAAD;EAAM,WAAU;EAAU,MAAK;EAAS,aAAU;EAC/C,UAAA,UAAU,SACP,KACA,UAAU,UACR,QAAQ,qBACR,QAAQ;CACV,CAAA,CACN,EAAA,CAAA;AAEN,CAAC"}
1
+ {"version":3,"file":"copy-button.js","names":["AriaButton"],"sources":["../../src/copy-button/copy-button.tsx"],"sourcesContent":["\"use client\";\n\nimport { forwardRef } from \"react\";\nimport { Button as AriaButton, type PressEvent } from \"react-aria-components\";\n\nimport { buttonVariants } from \"../button/button.js\";\nimport { cn } from \"../lib/cn.js\";\nimport { IconSwap } from \"../lib/icon-swap.js\";\nimport { MorphText } from \"../lib/morph-text.js\";\nimport { callSafely, useCopyFeedback, type CopyFeedbackState } from \"../lib/use-copy-feedback.js\";\n\nexport interface CopyButtonProps {\n /** What goes on the clipboard. */\n value: string;\n /** The label at rest, and the button's accessible name. @default \"Copy\" */\n children?: string;\n /** Only the icon, a square button. The label is still its accessible name. */\n iconOnly?: boolean;\n /**\n * `plain` has no border until hovered. `primary` is the filled brand button, for a copy that is the\n * main action of its row or dialog. @default \"outline\"\n */\n variant?: \"outline\" | \"plain\" | \"primary\";\n /**\n * Control height, from the density scale: `sm` sits in a row, `md` matches a default Button.\n * @default \"sm\"\n */\n size?: \"sm\" | \"md\";\n isDisabled?: boolean;\n /** Called once the text has reached the clipboard. */\n onCopied?: () => void;\n /**\n * Called when the clipboard refused the text, with what it rejected with. The button already shows\n * \"Failed\" and announces it; use this to keep a toast or log of your own.\n */\n onError?: (error: unknown) => void;\n /** Extra classes. Merged last, so they win conflicts with ours. */\n className?: string;\n}\n\nconst ICON = \"size-4\";\n\n/** CopyButton's variant names, mapped onto Button's. */\nconst BUTTON_VARIANT = {\n outline: \"outlineNeutral\",\n plain: \"ghost\",\n primary: \"primary\",\n} as const;\n\n/** An icon-only button is square: as wide as its rung is tall. */\nconst ICON_ONLY_WIDTH = {\n sm: \" w-(--density-control-h-sm) px-0\",\n md: \" w-(--density-control-h-md) px-0\",\n} as const;\n\n/** The result icon's tone on a page-surface button; at rest it takes the label's ink. */\nconst RESULT_TONE: Record<CopyFeedbackState, string | undefined> = {\n idle: undefined,\n copied: \"text-success-strong\",\n error: \"text-destructive-strong\",\n};\n\n/** The tick draws itself from its short stroke, the way a hand would write it: quick to start, then easing into place. */\nfunction DrawnCheck() {\n return (\n <svg\n className={ICON}\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth={1.75}\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n aria-hidden=\"true\"\n >\n <path\n d=\"M4 12l5 5L20 6\"\n pathLength={1}\n className=\"[stroke-dasharray:1] motion-safe:animate-check-draw-calm\"\n />\n </svg>\n );\n}\n\nfunction StateIcon({ state }: { state: CopyFeedbackState }) {\n if (state === \"copied\") return <DrawnCheck />;\n const common = {\n className: ICON,\n viewBox: \"0 0 24 24\",\n fill: \"none\",\n stroke: \"currentColor\",\n strokeWidth: 1.75,\n strokeLinecap: \"round\",\n strokeLinejoin: \"round\",\n \"aria-hidden\": true,\n } as const;\n if (state === \"error\") {\n return (\n <svg {...common}>\n <circle cx=\"12\" cy=\"12\" r=\"10\" />\n <path d=\"M12 8v4M12 16h.01\" />\n </svg>\n );\n }\n return (\n <svg {...common}>\n <rect x=\"8\" y=\"8\" width=\"14\" height=\"14\" rx=\"2\" />\n <path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\" />\n </svg>\n );\n}\n\n/**\n * Copies a value and says so, calmly. The icon trades places in one soft breath\n * (copy, then a tick that draws itself, or an alert if the clipboard refused)\n * while the label's changed letters rise in, and the button never changes width:\n * its label cell reserves the widest of the three texts.\n *\n * It goes back to rest by itself after a moment, and a live region announces the\n * result. The accessible name is always the resting label.\n *\n * @example\n * <CopyButton value={token} />\n * <CopyButton value={url} iconOnly variant=\"plain\" />\n * <CopyButton value={ticket} variant=\"primary\" size=\"md\" onError={() => toast(\"Copy failed\")}>\n * Copy as ticket\n * </CopyButton>\n */\nexport const CopyButton = forwardRef<HTMLButtonElement, CopyButtonProps>(function CopyButton(\n {\n value,\n children: label = \"Copy\",\n iconOnly = false,\n variant = \"outline\",\n size = \"sm\",\n isDisabled,\n onCopied,\n onError,\n className,\n },\n ref,\n) {\n const { state, copy } = useCopyFeedback();\n const text = state === \"copied\" ? \"Copied\" : state === \"error\" ? \"Failed\" : label;\n\n // Not width-based like Button's press: an icon button presses a little deeper, every other one the same.\n const pressStart = (event: PressEvent) => {\n (event.target as HTMLElement).style.setProperty(\"--press-scale\", iconOnly ? \"0.96\" : \"0.97\");\n };\n\n const classes = cn(\n buttonVariants({ variant: BUTTON_VARIANT[variant], size }) +\n \" [--release-duration:var(--duration-gentle)] [--release-ease:var(--ease-gentle)]\" +\n (iconOnly ? ICON_ONLY_WIDTH[size] : \"\"),\n className,\n );\n // On the brand fill the result icon keeps the label's ink: the success and destructive tones are set\n // against the page surface, not the orange.\n const onBrandFill = variant === \"primary\";\n\n return (\n <>\n <AriaButton\n ref={ref}\n data-slot=\"button\"\n data-copy-state={state}\n aria-label={label}\n isDisabled={isDisabled}\n onPressStart={pressStart}\n onPress={() => {\n void copy(value, onError).then((ok) => {\n if (ok) callSafely(() => onCopied?.());\n });\n }}\n className={classes}\n >\n <IconSwap iconKey={state} calm className={cn(ICON, \"relative\")}>\n <span className={onBrandFill ? undefined : RESULT_TONE[state]}>\n <StateIcon state={state} />\n </span>\n </IconSwap>\n {iconOnly ? null : (\n // The cell reserves the widest state, so nothing moves but the letters. The inline padding gives blurred\n // edge letters room before the horizontal clip; the negative margin keeps the box the same size.\n <span\n className=\"-mx-1 grid min-w-0 overflow-x-clip px-1 text-start whitespace-nowrap\"\n aria-hidden=\"true\"\n >\n {[label, \"Copied\", \"Failed\"].map((measure) => (\n <span key={measure} className=\"invisible col-start-1 row-start-1\">\n {measure}\n </span>\n ))}\n <span className=\"col-start-1 row-start-1 justify-self-start\">\n <MorphText text={text} morphWidth={false} calm />\n </span>\n </span>\n )}\n </AriaButton>\n <span className=\"sr-only\" role=\"status\" aria-live=\"polite\">\n {state === \"idle\"\n ? \"\"\n : state === \"error\"\n ? label + \": Could not copy\"\n : label + \": Copied\"}\n </span>\n </>\n );\n});\n"],"mappings":";;;;;;;;;;AAwCA,MAAM,OAAO;;AAGb,MAAM,iBAAiB;CACrB,SAAS;CACT,OAAO;CACP,SAAS;AACX;;AAGA,MAAM,kBAAkB;CACtB,IAAI;CACJ,IAAI;AACN;;AAGA,MAAM,cAA6D;CACjE,MAAM,KAAA;CACN,QAAQ;CACR,OAAO;AACT;;AAGA,SAAS,aAAa;CACpB,OACE,oBAAC,OAAD;EACE,WAAW;EACX,SAAQ;EACR,MAAK;EACL,QAAO;EACP,aAAa;EACb,eAAc;EACd,gBAAe;EACf,eAAY;EAEZ,UAAA,oBAAC,QAAD;GACE,GAAE;GACF,YAAY;GACZ,WAAU;EACX,CAAA;CACE,CAAA;AAET;AAEA,SAAS,UAAU,EAAE,SAAuC;CAC1D,IAAI,UAAU,UAAU,OAAO,oBAAC,YAAD,CAAa,CAAA;CAC5C,MAAM,SAAS;EACb,WAAW;EACX,SAAS;EACT,MAAM;EACN,QAAQ;EACR,aAAa;EACb,eAAe;EACf,gBAAgB;EAChB,eAAe;CACjB;CACA,IAAI,UAAU,SACZ,OACE,qBAAC,OAAD;EAAK,GAAI;EAAT,UAAA,CACE,oBAAC,UAAD;GAAQ,IAAG;GAAK,IAAG;GAAK,GAAE;EAAM,CAAA,GAChC,oBAAC,QAAD,EAAM,GAAE,oBAAqB,CAAA,CAC1B;;CAGT,OACE,qBAAC,OAAD;EAAK,GAAI;EAAT,UAAA,CACE,oBAAC,QAAD;GAAM,GAAE;GAAI,GAAE;GAAI,OAAM;GAAK,QAAO;GAAK,IAAG;EAAK,CAAA,GACjD,oBAAC,QAAD,EAAM,GAAE,0DAA2D,CAAA,CAChE;;AAET;;;;;;;;;;;;;;;;;AAkBA,MAAa,aAAa,WAA+C,SAAS,WAChF,EACE,OACA,UAAU,QAAQ,QAClB,WAAW,OACX,UAAU,WACV,OAAO,MACP,YACA,UACA,SACA,aAEF,KACA;CACA,MAAM,EAAE,OAAO,SAAS,gBAAgB;CACxC,MAAM,OAAO,UAAU,WAAW,WAAW,UAAU,UAAU,WAAW;CAG5E,MAAM,cAAc,UAAsB;EACxC,MAAO,OAAuB,MAAM,YAAY,iBAAiB,WAAW,SAAS,MAAM;CAC7F;CAEA,MAAM,UAAU,GACd,eAAe;EAAE,SAAS,eAAe;EAAU;CAAK,CAAC,IACvD,sFACC,WAAW,gBAAgB,QAAQ,KACtC,SACF;CAGA,MAAM,cAAc,YAAY;CAEhC,OACE,qBAAA,YAAA,EAAA,UAAA,CACE,qBAACA,QAAD;EACO;EACL,aAAU;EACV,mBAAiB;EACjB,cAAY;EACA;EACZ,cAAc;EACd,eAAe;GACb,KAAU,OAAO,OAAO,CAAC,CAAC,MAAM,OAAO;IACrC,IAAI,IAAI,iBAAiB,WAAW,CAAC;GACvC,CAAC;EACH;EACA,WAAW;EAZb,UAAA,CAcE,oBAAC,UAAD;GAAU,SAAS;GAAO,MAAA;GAAK,WAAW,GAAG,MAAM,UAAU;GAC3D,UAAA,oBAAC,QAAD;IAAM,WAAW,cAAc,KAAA,IAAY,YAAY;IACrD,UAAA,oBAAC,WAAD,EAAkB,MAAQ,CAAA;GACtB,CAAA;EACE,CAAA,GACT,WAAW,OAGV,qBAAC,QAAD;GACE,WAAU;GACV,eAAY;GAFd,UAAA,CAIG;IAAC;IAAO;IAAU;GAAQ,CAAC,CAAC,KAAK,YAChC,oBAAC,QAAD;IAAoB,WAAU;IAC3B,UAAA;GACG,GAFK,OAEL,CACP,GACD,oBAAC,QAAD;IAAM,WAAU;IACd,UAAA,oBAAC,WAAD;KAAiB;KAAM,YAAY;KAAO,MAAA;IAAM,CAAA;GAC5C,CAAA,CACF;EAEE,CAAA,CAAA;CACZ,CAAA,GAAA,oBAAC,QAAD;EAAM,WAAU;EAAU,MAAK;EAAS,aAAU;EAC/C,UAAA,UAAU,SACP,KACA,UAAU,UACR,QAAQ,qBACR,QAAQ;CACV,CAAA,CACN,EAAA,CAAA;AAEN,CAAC"}
@@ -0,0 +1,45 @@
1
+ import { ReactNode } from "react";
2
+ //#region src/countdown/countdown.d.ts
3
+ export type CountdownUnit = "days" | "hours" | "minutes" | "seconds";
4
+ /**
5
+ * Countdown: the time left until a fixed moment people wait for, such as a
6
+ * launch, a live event or a deadline. Each digit is a wheel that takes the short
7
+ * way round, so a tick turns downward and 00 to 59 is a single step; the first
8
+ * reading rolls up from zero, one digit after another. When the moment passes
9
+ * the digits give way to `completeLabel` and the box follows the new size.
10
+ *
11
+ * The clock runs only while the countdown is on screen and the tab is visible,
12
+ * and reads the clock again on return, so it never drifts. Completion is armed
13
+ * separately, so `onComplete` fires on time even while the ticking is paused.
14
+ *
15
+ * The digits are a picture. The timer is read as plain text ("1 day, 2 hours,
16
+ * 5 minutes") that is not announced every second, and finishing is announced
17
+ * once, politely. Reduced motion jumps.
18
+ */
19
+ export interface CountdownProps {
20
+ /**
21
+ * The moment to count down to. A number is epoch milliseconds, a Date is used as is, and an ISO string with `Z` or an
22
+ * offset such as `2026-10-06T10:00:00-07:00` is exact. A string without an offset is wall time in `timeZone`, or UTC when
23
+ * no zone is given, so the result never depends on the visitor's own time zone.
24
+ */
25
+ target: Date | number | string;
26
+ /** IANA zone for a target string without an offset, such as "America/Los_Angeles". Daylight saving is resolved for the target date. */
27
+ timeZone?: string;
28
+ /** Units to show, largest first. The largest unit absorbs the rest, so hours can pass 24 when days are left out. @default ["days", "hours", "minutes", "seconds"] */
29
+ units?: CountdownUnit[];
30
+ /** Large digits with unit names, or one compact line for banners and table cells. @default "large" */
31
+ variant?: "large" | "compact";
32
+ /** Shown once the target passes. The digits swap into it. @default "Live now" */
33
+ completeLabel?: ReactNode;
34
+ /** Fires once, the moment the countdown reaches zero while mounted. It fires on time even when the tab is hidden. */
35
+ onComplete?: () => void;
36
+ /** Accessible name, such as "Keynote starts in". @default "Time remaining" */
37
+ label?: string;
38
+ /** Clock source in epoch milliseconds. Pass a server-synced clock when the visitor's device time cannot be trusted. @default Date.now */
39
+ clock?: () => number;
40
+ /** Extra classes for the countdown. Merged last. */
41
+ className?: string;
42
+ }
43
+ export declare const Countdown: import("react").ForwardRefExoticComponent<CountdownProps & import("react").RefAttributes<HTMLDivElement>>;
44
+ //#endregion
45
+ //# sourceMappingURL=countdown.d.ts.map