@seekalfred/primitives 0.6.0 → 0.7.1

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 (322) hide show
  1. package/README.md +19 -11
  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 +5 -1
  8. package/dist/alert/alert.js +68 -7
  9. package/dist/alert/alert.js.map +1 -1
  10. package/dist/alert-dialog/alert-dialog.d.ts +45 -18
  11. package/dist/alert-dialog/alert-dialog.js +128 -9
  12. package/dist/alert-dialog/alert-dialog.js.map +1 -1
  13. package/dist/alert-dialog/index.d.ts +2 -2
  14. package/dist/alert-dialog/index.js +2 -2
  15. package/dist/animated-counter/animated-counter.d.ts +38 -0
  16. package/dist/animated-counter/animated-counter.js +230 -0
  17. package/dist/animated-counter/animated-counter.js.map +1 -0
  18. package/dist/animated-counter/index.d.ts +2 -0
  19. package/dist/animated-counter/index.js +2 -0
  20. package/dist/announcement-bar/announcement-bar.d.ts +67 -0
  21. package/dist/announcement-bar/announcement-bar.js +364 -0
  22. package/dist/announcement-bar/announcement-bar.js.map +1 -0
  23. package/dist/announcement-bar/index.d.ts +2 -0
  24. package/dist/announcement-bar/index.js +2 -0
  25. package/dist/badge/badge.d.ts +1 -1
  26. package/dist/bar-chart/bar-chart.d.ts +51 -0
  27. package/dist/bar-chart/bar-chart.js +565 -0
  28. package/dist/bar-chart/bar-chart.js.map +1 -0
  29. package/dist/bar-chart/index.d.ts +2 -0
  30. package/dist/bar-chart/index.js +2 -0
  31. package/dist/breadcrumb/breadcrumb.d.ts +2 -2
  32. package/dist/breadcrumb/breadcrumb.js +12 -3
  33. package/dist/breadcrumb/breadcrumb.js.map +1 -1
  34. package/dist/brush-chart/brush-chart.d.ts +64 -0
  35. package/dist/brush-chart/brush-chart.js +1056 -0
  36. package/dist/brush-chart/brush-chart.js.map +1 -0
  37. package/dist/brush-chart/index.d.ts +2 -0
  38. package/dist/brush-chart/index.js +2 -0
  39. package/dist/button/button.d.ts +2 -2
  40. package/dist/button/button.js +31 -21
  41. package/dist/button/button.js.map +1 -1
  42. package/dist/button-group/button-group.js +3 -4
  43. package/dist/button-group/button-group.js.map +1 -1
  44. package/dist/card/card.js +1 -1
  45. package/dist/card/card.js.map +1 -1
  46. package/dist/card-stack/card-stack.d.ts +54 -0
  47. package/dist/card-stack/card-stack.js +492 -0
  48. package/dist/card-stack/card-stack.js.map +1 -0
  49. package/dist/card-stack/index.d.ts +2 -0
  50. package/dist/card-stack/index.js +2 -0
  51. package/dist/carousel/carousel.d.ts +56 -0
  52. package/dist/carousel/carousel.js +389 -0
  53. package/dist/carousel/carousel.js.map +1 -0
  54. package/dist/carousel/index.d.ts +2 -0
  55. package/dist/carousel/index.js +2 -0
  56. package/dist/checkbox/checkbox.js +4 -4
  57. package/dist/checkbox/checkbox.js.map +1 -1
  58. package/dist/chip-group/chip-group.d.ts +37 -0
  59. package/dist/chip-group/chip-group.js +148 -0
  60. package/dist/chip-group/chip-group.js.map +1 -0
  61. package/dist/chip-group/index.d.ts +2 -0
  62. package/dist/chip-group/index.js +2 -0
  63. package/dist/countdown/countdown.d.ts +45 -0
  64. package/dist/countdown/countdown.js +298 -0
  65. package/dist/countdown/countdown.js.map +1 -0
  66. package/dist/countdown/index.d.ts +2 -0
  67. package/dist/countdown/index.js +2 -0
  68. package/dist/date-picker/picker-parts.js +1 -1
  69. package/dist/date-picker/picker-parts.js.map +1 -1
  70. package/dist/dialog/dialog.js +1 -1
  71. package/dist/dialog/dialog.js.map +1 -1
  72. package/dist/donut-chart/donut-chart.d.ts +64 -0
  73. package/dist/donut-chart/donut-chart.js +576 -0
  74. package/dist/donut-chart/donut-chart.js.map +1 -0
  75. package/dist/donut-chart/index.d.ts +2 -0
  76. package/dist/donut-chart/index.js +2 -0
  77. package/dist/empty-state/empty-state.d.ts +21 -10
  78. package/dist/empty-state/empty-state.js +32 -26
  79. package/dist/empty-state/empty-state.js.map +1 -1
  80. package/dist/empty-state/index.d.ts +2 -2
  81. package/dist/empty-state/index.js +2 -2
  82. package/dist/file-drop-zone/file-drop-zone.js +1 -1
  83. package/dist/file-drop-zone/file-drop-zone.js.map +1 -1
  84. package/dist/filter-bar/filter-bar.d.ts +47 -26
  85. package/dist/filter-bar/filter-bar.js +272 -156
  86. package/dist/filter-bar/filter-bar.js.map +1 -1
  87. package/dist/gauge/gauge.d.ts +45 -0
  88. package/dist/gauge/gauge.js +219 -0
  89. package/dist/gauge/gauge.js.map +1 -0
  90. package/dist/gauge/index.d.ts +2 -0
  91. package/dist/gauge/index.js +2 -0
  92. package/dist/in-view-title/in-view-title.d.ts +33 -0
  93. package/dist/in-view-title/in-view-title.js +119 -0
  94. package/dist/in-view-title/in-view-title.js.map +1 -0
  95. package/dist/in-view-title/index.d.ts +2 -0
  96. package/dist/in-view-title/index.js +2 -0
  97. package/dist/index.d.ts +49 -6
  98. package/dist/index.js +48 -8
  99. package/dist/lib/cn.js +11 -1
  100. package/dist/lib/cn.js.map +1 -1
  101. package/dist/lib/field-message.js +28 -10
  102. package/dist/lib/field-message.js.map +1 -1
  103. package/dist/lib/field-surface.js +9 -8
  104. package/dist/lib/field-surface.js.map +1 -1
  105. package/dist/lib/flag-emoji.js +50 -0
  106. package/dist/lib/flag-emoji.js.map +1 -0
  107. package/dist/lib/flip.js +11 -3
  108. package/dist/lib/flip.js.map +1 -1
  109. package/dist/lib/menu-highlight.js +64 -0
  110. package/dist/lib/menu-highlight.js.map +1 -0
  111. package/dist/lib/motion.js +9 -2
  112. package/dist/lib/motion.js.map +1 -1
  113. package/dist/lib/rolling-value.js +1 -1
  114. package/dist/lib/rolling-value.js.map +1 -1
  115. package/dist/lib/spring-tween.js +500 -0
  116. package/dist/lib/spring-tween.js.map +1 -0
  117. package/dist/lib/swap-text.js +3 -1
  118. package/dist/lib/swap-text.js.map +1 -1
  119. package/dist/line-chart/index.d.ts +2 -0
  120. package/dist/line-chart/index.js +2 -0
  121. package/dist/line-chart/line-chart.d.ts +76 -0
  122. package/dist/line-chart/line-chart.js +611 -0
  123. package/dist/line-chart/line-chart.js.map +1 -0
  124. package/dist/mention-input/index.d.ts +2 -0
  125. package/dist/mention-input/index.js +2 -0
  126. package/dist/mention-input/mention-input.d.ts +98 -0
  127. package/dist/mention-input/mention-input.js +576 -0
  128. package/dist/mention-input/mention-input.js.map +1 -0
  129. package/dist/menu/menu.d.ts +2 -0
  130. package/dist/menu/menu.js +23 -12
  131. package/dist/menu/menu.js.map +1 -1
  132. package/dist/money-input/index.d.ts +2 -0
  133. package/dist/money-input/index.js +2 -0
  134. package/dist/money-input/money-input.d.ts +65 -0
  135. package/dist/money-input/money-input.js +607 -0
  136. package/dist/money-input/money-input.js.map +1 -0
  137. package/dist/morph-select/index.d.ts +2 -0
  138. package/dist/morph-select/index.js +2 -0
  139. package/dist/morph-select/morph-select.d.ts +71 -0
  140. package/dist/morph-select/morph-select.js +693 -0
  141. package/dist/morph-select/morph-select.js.map +1 -0
  142. package/dist/multi-select/index.d.ts +2 -0
  143. package/dist/multi-select/index.js +2 -0
  144. package/dist/multi-select/multi-select.d.ts +36 -0
  145. package/dist/multi-select/multi-select.js +152 -0
  146. package/dist/multi-select/multi-select.js.map +1 -0
  147. package/dist/number-field/index.d.ts +2 -0
  148. package/dist/number-field/index.js +2 -0
  149. package/dist/number-field/number-field.d.ts +42 -0
  150. package/dist/number-field/number-field.js +199 -0
  151. package/dist/number-field/number-field.js.map +1 -0
  152. package/dist/onboarding-checklist/index.d.ts +2 -0
  153. package/dist/onboarding-checklist/index.js +2 -0
  154. package/dist/onboarding-checklist/onboarding-checklist.d.ts +55 -0
  155. package/dist/onboarding-checklist/onboarding-checklist.js +577 -0
  156. package/dist/onboarding-checklist/onboarding-checklist.js.map +1 -0
  157. package/dist/password-field/password-field.js +2 -2
  158. package/dist/password-field/password-field.js.map +1 -1
  159. package/dist/phone-input/index.d.ts +3 -0
  160. package/dist/phone-input/index.js +3 -0
  161. package/dist/phone-input/phone-countries.d.ts +29 -0
  162. package/dist/phone-input/phone-countries.js +170 -0
  163. package/dist/phone-input/phone-countries.js.map +1 -0
  164. package/dist/phone-input/phone-input.d.ts +66 -0
  165. package/dist/phone-input/phone-input.js +440 -0
  166. package/dist/phone-input/phone-input.js.map +1 -0
  167. package/dist/popover/popover.d.ts +12 -0
  168. package/dist/popover/popover.js +31 -7
  169. package/dist/popover/popover.js.map +1 -1
  170. package/dist/progress/progress.js +1 -1
  171. package/dist/radio-group/radio-group.js +1 -1
  172. package/dist/radio-group/radio-group.js.map +1 -1
  173. package/dist/reactions/index.d.ts +2 -0
  174. package/dist/reactions/index.js +2 -0
  175. package/dist/reactions/reactions.d.ts +50 -0
  176. package/dist/reactions/reactions.js +315 -0
  177. package/dist/reactions/reactions.js.map +1 -0
  178. package/dist/rich-text-editor/index.d.ts +3 -0
  179. package/dist/rich-text-editor/index.js +2 -0
  180. package/dist/rich-text-editor/rich-text-editor.d.ts +70 -0
  181. package/dist/rich-text-editor/rich-text-editor.js +909 -0
  182. package/dist/rich-text-editor/rich-text-editor.js.map +1 -0
  183. package/dist/rich-text-editor/rich-text-engine.d.ts +16 -0
  184. package/dist/rich-text-editor/rich-text-engine.js +366 -0
  185. package/dist/rich-text-editor/rich-text-engine.js.map +1 -0
  186. package/dist/rich-text-editor/rich-text-html.js +174 -0
  187. package/dist/rich-text-editor/rich-text-html.js.map +1 -0
  188. package/dist/rich-text-editor/rich-text-icons.js +43 -0
  189. package/dist/rich-text-editor/rich-text-icons.js.map +1 -0
  190. package/dist/ridgeline/index.d.ts +2 -0
  191. package/dist/ridgeline/index.js +2 -0
  192. package/dist/ridgeline/ridgeline.d.ts +55 -0
  193. package/dist/ridgeline/ridgeline.js +701 -0
  194. package/dist/ridgeline/ridgeline.js.map +1 -0
  195. package/dist/scroll-area/index.d.ts +2 -2
  196. package/dist/scroll-area/index.js +2 -2
  197. package/dist/scroll-area/scroll-area.d.ts +68 -31
  198. package/dist/scroll-area/scroll-area.js +208 -59
  199. package/dist/scroll-area/scroll-area.js.map +1 -1
  200. package/dist/scrub-input/evaluate.d.ts +14 -0
  201. package/dist/scrub-input/evaluate.js +72 -0
  202. package/dist/scrub-input/evaluate.js.map +1 -0
  203. package/dist/scrub-input/index.d.ts +3 -0
  204. package/dist/scrub-input/index.js +3 -0
  205. package/dist/scrub-input/scrub-input.d.ts +74 -0
  206. package/dist/scrub-input/scrub-input.js +486 -0
  207. package/dist/scrub-input/scrub-input.js.map +1 -0
  208. package/dist/search-field/search-field.d.ts +2 -1
  209. package/dist/search-field/search-field.js +4 -4
  210. package/dist/search-field/search-field.js.map +1 -1
  211. package/dist/select/select.d.ts +3 -1
  212. package/dist/select/select.js +20 -8
  213. package/dist/select/select.js.map +1 -1
  214. package/dist/separator/separator.d.ts +1 -1
  215. package/dist/sheet/sheet.js +2 -1
  216. package/dist/sheet/sheet.js.map +1 -1
  217. package/dist/shortcut-recorder/index.d.ts +3 -0
  218. package/dist/shortcut-recorder/index.js +3 -0
  219. package/dist/shortcut-recorder/shortcut-model.d.ts +27 -0
  220. package/dist/shortcut-recorder/shortcut-model.js +206 -0
  221. package/dist/shortcut-recorder/shortcut-model.js.map +1 -0
  222. package/dist/shortcut-recorder/shortcut-recorder.d.ts +93 -0
  223. package/dist/shortcut-recorder/shortcut-recorder.js +489 -0
  224. package/dist/shortcut-recorder/shortcut-recorder.js.map +1 -0
  225. package/dist/slider/index.d.ts +2 -2
  226. package/dist/slider/slider.d.ts +46 -14
  227. package/dist/slider/slider.js +151 -67
  228. package/dist/slider/slider.js.map +1 -1
  229. package/dist/slope-chart/index.d.ts +2 -0
  230. package/dist/slope-chart/index.js +2 -0
  231. package/dist/slope-chart/slope-chart.d.ts +54 -0
  232. package/dist/slope-chart/slope-chart.js +568 -0
  233. package/dist/slope-chart/slope-chart.js.map +1 -0
  234. package/dist/sparkline/index.d.ts +2 -0
  235. package/dist/sparkline/index.js +2 -0
  236. package/dist/sparkline/sparkline.d.ts +46 -0
  237. package/dist/sparkline/sparkline.js +390 -0
  238. package/dist/sparkline/sparkline.js.map +1 -0
  239. package/dist/split-bar/index.d.ts +2 -0
  240. package/dist/split-bar/index.js +2 -0
  241. package/dist/split-bar/split-bar.d.ts +62 -0
  242. package/dist/split-bar/split-bar.js +513 -0
  243. package/dist/split-bar/split-bar.js.map +1 -0
  244. package/dist/split-button/split-button.d.ts +1 -1
  245. package/dist/split-button/split-button.js +12 -11
  246. package/dist/split-button/split-button.js.map +1 -1
  247. package/dist/stat-card/index.d.ts +2 -2
  248. package/dist/stat-card/index.js +2 -2
  249. package/dist/stat-card/stat-card.d.ts +24 -4
  250. package/dist/stat-card/stat-card.js +114 -17
  251. package/dist/stat-card/stat-card.js.map +1 -1
  252. package/dist/stepper/index.d.ts +2 -0
  253. package/dist/stepper/index.js +2 -0
  254. package/dist/stepper/stepper.d.ts +51 -0
  255. package/dist/stepper/stepper.js +215 -0
  256. package/dist/stepper/stepper.js.map +1 -0
  257. package/dist/streamgraph/index.d.ts +2 -0
  258. package/dist/streamgraph/index.js +2 -0
  259. package/dist/streamgraph/streamgraph.d.ts +70 -0
  260. package/dist/streamgraph/streamgraph.js +613 -0
  261. package/dist/streamgraph/streamgraph.js.map +1 -0
  262. package/dist/styles.css +1 -1
  263. package/dist/swipe-actions/index.d.ts +2 -0
  264. package/dist/swipe-actions/index.js +2 -0
  265. package/dist/swipe-actions/swipe-actions.d.ts +67 -0
  266. package/dist/swipe-actions/swipe-actions.js +552 -0
  267. package/dist/swipe-actions/swipe-actions.js.map +1 -0
  268. package/dist/switch/switch.js +2 -2
  269. package/dist/switch/switch.js.map +1 -1
  270. package/dist/table/index.d.ts +2 -2
  271. package/dist/table/index.js +2 -2
  272. package/dist/table/table.d.ts +56 -4
  273. package/dist/table/table.js +340 -165
  274. package/dist/table/table.js.map +1 -1
  275. package/dist/tabs/tabs.js +2 -2
  276. package/dist/tabs/tabs.js.map +1 -1
  277. package/dist/tag-input/index.d.ts +2 -0
  278. package/dist/tag-input/index.js +2 -0
  279. package/dist/tag-input/tag-input.d.ts +40 -0
  280. package/dist/tag-input/tag-input.js +269 -0
  281. package/dist/tag-input/tag-input.js.map +1 -0
  282. package/dist/text-morph/index.d.ts +2 -0
  283. package/dist/text-morph/index.js +2 -0
  284. package/dist/text-morph/text-morph.d.ts +23 -0
  285. package/dist/text-morph/text-morph.js +21 -0
  286. package/dist/text-morph/text-morph.js.map +1 -0
  287. package/dist/text-reveal/index.d.ts +2 -0
  288. package/dist/text-reveal/index.js +2 -0
  289. package/dist/text-reveal/text-reveal.d.ts +27 -0
  290. package/dist/text-reveal/text-reveal.js +51 -0
  291. package/dist/text-reveal/text-reveal.js.map +1 -0
  292. package/dist/text-shimmer/index.d.ts +2 -0
  293. package/dist/text-shimmer/index.js +2 -0
  294. package/dist/text-shimmer/text-shimmer.d.ts +29 -0
  295. package/dist/text-shimmer/text-shimmer.js +29 -0
  296. package/dist/text-shimmer/text-shimmer.js.map +1 -0
  297. package/dist/time-picker/time-picker.js +2 -2
  298. package/dist/time-picker/time-picker.js.map +1 -1
  299. package/dist/toast/index.d.ts +2 -2
  300. package/dist/toast/index.js +2 -2
  301. package/dist/toast/toast.d.ts +6 -0
  302. package/dist/toast/toast.js +82 -40
  303. package/dist/toast/toast.js.map +1 -1
  304. package/dist/tooltip/tooltip.d.ts +9 -2
  305. package/dist/tooltip/tooltip.js +7 -7
  306. package/dist/tooltip/tooltip.js.map +1 -1
  307. package/dist/treemap/index.d.ts +2 -0
  308. package/dist/treemap/index.js +2 -0
  309. package/dist/treemap/treemap.d.ts +53 -0
  310. package/dist/treemap/treemap.js +699 -0
  311. package/dist/treemap/treemap.js.map +1 -0
  312. package/dist/usage-meter/index.d.ts +2 -0
  313. package/dist/usage-meter/index.js +2 -0
  314. package/dist/usage-meter/usage-meter.d.ts +50 -0
  315. package/dist/usage-meter/usage-meter.js +440 -0
  316. package/dist/usage-meter/usage-meter.js.map +1 -0
  317. package/dist/waffle-chart/index.d.ts +2 -0
  318. package/dist/waffle-chart/index.js +2 -0
  319. package/dist/waffle-chart/waffle-chart.d.ts +57 -0
  320. package/dist/waffle-chart/waffle-chart.js +426 -0
  321. package/dist/waffle-chart/waffle-chart.js.map +1 -0
  322. package/package.json +165 -3
@@ -28,7 +28,7 @@ const radioGroupVariants = cva(["flex flex-col gap-2", "box-border font-sans"]);
28
28
  const radioVariants = cva([
29
29
  "inline-flex size-4 shrink-0 items-center justify-center",
30
30
  "rounded-full border-(length:--border-width) box-border",
31
- "bg-background border-border",
31
+ "bg-surface border-control-edge",
32
32
  "radio-mark",
33
33
  "group-data-[selected]:bg-primary group-data-[selected]:border-primary-strong",
34
34
  "group-data-[selected]:text-primary-foreground",
@@ -1 +1 @@
1
- {"version":3,"file":"radio-group.js","names":["RadioGroup","AriaRadioGroup","Radio","AriaRadio"],"sources":["../../src/radio-group/radio-group.tsx"],"sourcesContent":["\"use client\";\n\nimport { cva } from \"class-variance-authority\";\nimport { forwardRef, type ReactNode } from \"react\";\nimport {\n Radio as AriaRadio,\n RadioGroup as AriaRadioGroup,\n type RadioGroupProps as AriaRadioGroupProps,\n type RadioProps as AriaRadioProps,\n} from \"react-aria-components\";\n\nimport { cn, cnAll } from \"../lib/cn.js\";\nimport { selectionLabelVariants, selectionRingVariants } from \"../lib/selection-control.js\";\n\n/**\n * RadioGroup, ported from the legacy shadcn/Radix component onto React Aria.\n *\n * Census: 1 file, 3 uses -- `RadioGroup` 1, `RadioGroupItem` 2. The\n * smallest real surface in the migration, so the port is deliberately plain.\n *\n * THE LABEL MOVES INSIDE THE ITEM. Radix's `RadioGroupItem` is a bare\n * button and the caller pairs it with a separate `<Label htmlFor>`; React\n * Aria's `Radio` takes its label as `children` and wires the association\n * itself. That is the one rename a call site has to make, and it removes\n * the id bookkeeping.\n *\n * `border-primary` becomes `border-border` for the same reason it did on\n * Checkbox: brand orange is 2.44:1 on white and cannot legally be the only\n * thing describing a control's edge (WCAG 1.4.11). Selected reuses\n * Checkbox's exact pair -- `bg-primary` with a `primary-strong` boundary --\n * so a radio and a checkbox in the same form agree.\n */\nexport const radioGroupVariants = cva([\n \"flex flex-col gap-2\",\n \"box-border font-sans\",\n]);\n\nexport const radioVariants = cva([\n \"inline-flex size-4 shrink-0 items-center justify-center\",\n \"rounded-full border-(length:--border-width) box-border\",\n \"bg-background border-border\",\n // Colours ease, the mark dips under a press and springs back (see radio-mark).\n \"radio-mark\",\n \"group-data-[selected]:bg-primary group-data-[selected]:border-primary-strong\",\n \"group-data-[selected]:text-primary-foreground\",\n selectionRingVariants(),\n]);\n\n/**\n * A set of mutually exclusive choices.\n *\n * @example\n * <RadioGroup value={plan} onChange={setPlan} aria-label=\"Plan\">\n * <Radio value=\"free\">Free</Radio>\n * <Radio value=\"pro\">Pro</Radio>\n * </RadioGroup>\n */\nexport interface RadioGroupProps extends Omit<AriaRadioGroupProps, \"className\" | \"style\"> {\n /** Extra classes. Merged last, so they win conflicts with ours. */\n className?: string;\n}\n\nexport const RadioGroup = forwardRef<HTMLDivElement, RadioGroupProps>(function RadioGroup(\n { className, ...props },\n ref,\n) {\n return (\n <AriaRadioGroup\n ref={ref}\n {...props}\n data-slot=\"radio-group\"\n className={cn(radioGroupVariants(), className)}\n />\n );\n});\n\n/**\n * One choice.\n *\n * @example\n * <Radio value=\"pro\">Pro</Radio>\n */\nexport interface RadioProps extends Omit<AriaRadioProps, \"className\" | \"style\" | \"children\"> {\n /** The label, and this option's accessible name. */\n children?: ReactNode;\n /** Extra classes for the label. Merged last, so they win conflicts. */\n className?: string;\n}\n\nexport const Radio = forwardRef<HTMLInputElement, RadioProps>(function Radio(\n { className, children, ...props },\n ref,\n) {\n return (\n <AriaRadio\n {...props}\n // inputRef, not ref: AriaRadio's own ref targets the label it renders\n // around a visually-hidden native input, and a label cannot be\n // focused -- ref.current.focus() would silently no-op. inputRef is\n // React Aria's own escape hatch to the real control.\n inputRef={ref}\n data-slot=\"radio\"\n // justify-start, overriding the shared label's centring: a radio sits\n // in a vertical list where every label must start on the same line,\n // not centre itself in a 24px box.\n className={cn(cnAll(selectionLabelVariants(), \"justify-start\"), className)}\n >\n {({ isSelected }) => (\n <>\n <span aria-hidden=\"true\" data-slot=\"radio-dot\" className={radioVariants()}>\n {/*\n Always there: the state decides whether it shows, so the dot can\n spring in and shrink away. data-selected carries the state to a\n test, which can no longer read it from the dot's presence.\n */}\n <span\n data-slot=\"radio-indicator\"\n data-selected={isSelected || undefined}\n className=\"radio-dot size-1.5 rounded-full bg-current\"\n />\n </span>\n {children == null ? null : <span data-slot=\"radio-label\">{children}</span>}\n </>\n )}\n </AriaRadio>\n );\n});\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;AAgCA,MAAa,qBAAqB,IAAI,CACpC,uBACA,sBACF,CAAC;AAED,MAAa,gBAAgB,IAAI;CAC/B;CACA;CACA;CAEA;CACA;CACA;CACA,sBAAsB;AACxB,CAAC;AAgBD,MAAaA,eAAa,WAA4C,SAASA,aAC7E,EAAE,WAAW,GAAG,SAChB,KACA;CACA,OACE,oBAACC,YAAD;EACO;EACL,GAAI;EACJ,aAAU;EACV,WAAW,GAAG,mBAAmB,GAAG,SAAS;CAC9C,CAAA;AAEL,CAAC;AAeD,MAAaC,UAAQ,WAAyC,SAASA,QACrE,EAAE,WAAW,UAAU,GAAG,SAC1B,KACA;CACA,OACE,oBAACC,OAAD;EACE,GAAI;EAKJ,UAAU;EACV,aAAU;EAIV,WAAW,GAAG,MAAM,uBAAuB,GAAG,eAAe,GAAG,SAAS;EAEvE,WAAA,EAAE,iBACF,qBAAA,YAAA,EAAA,UAAA,CACE,oBAAC,QAAD;GAAM,eAAY;GAAO,aAAU;GAAY,WAAW,cAAc;GAMtE,UAAA,oBAAC,QAAD;IACE,aAAU;IACV,iBAAe,cAAc,KAAA;IAC7B,WAAU;GACX,CAAA;EACG,CAAA,GACL,YAAY,OAAO,OAAO,oBAAC,QAAD;GAAM,aAAU;GAAe;EAAe,CAAA,CACzE,EAAA,CAAA;CAEK,CAAA;AAEf,CAAC"}
1
+ {"version":3,"file":"radio-group.js","names":["RadioGroup","AriaRadioGroup","Radio","AriaRadio"],"sources":["../../src/radio-group/radio-group.tsx"],"sourcesContent":["\"use client\";\n\nimport { cva } from \"class-variance-authority\";\nimport { forwardRef, type ReactNode } from \"react\";\nimport {\n Radio as AriaRadio,\n RadioGroup as AriaRadioGroup,\n type RadioGroupProps as AriaRadioGroupProps,\n type RadioProps as AriaRadioProps,\n} from \"react-aria-components\";\n\nimport { cn, cnAll } from \"../lib/cn.js\";\nimport { selectionLabelVariants, selectionRingVariants } from \"../lib/selection-control.js\";\n\n/**\n * RadioGroup, ported from the legacy shadcn/Radix component onto React Aria.\n *\n * Census: 1 file, 3 uses -- `RadioGroup` 1, `RadioGroupItem` 2. The\n * smallest real surface in the migration, so the port is deliberately plain.\n *\n * THE LABEL MOVES INSIDE THE ITEM. Radix's `RadioGroupItem` is a bare\n * button and the caller pairs it with a separate `<Label htmlFor>`; React\n * Aria's `Radio` takes its label as `children` and wires the association\n * itself. That is the one rename a call site has to make, and it removes\n * the id bookkeeping.\n *\n * `border-primary` becomes `border-border` for the same reason it did on\n * Checkbox: brand orange is 2.44:1 on white and cannot legally be the only\n * thing describing a control's edge (WCAG 1.4.11). Selected reuses\n * Checkbox's exact pair -- `bg-primary` with a `primary-strong` boundary --\n * so a radio and a checkbox in the same form agree.\n */\nexport const radioGroupVariants = cva([\n \"flex flex-col gap-2\",\n \"box-border font-sans\",\n]);\n\nexport const radioVariants = cva([\n \"inline-flex size-4 shrink-0 items-center justify-center\",\n \"rounded-full border-(length:--border-width) box-border\",\n \"bg-surface border-control-edge\",\n // Colours ease, the mark dips under a press and springs back (see radio-mark).\n \"radio-mark\",\n \"group-data-[selected]:bg-primary group-data-[selected]:border-primary-strong\",\n \"group-data-[selected]:text-primary-foreground\",\n selectionRingVariants(),\n]);\n\n/**\n * A set of mutually exclusive choices.\n *\n * @example\n * <RadioGroup value={plan} onChange={setPlan} aria-label=\"Plan\">\n * <Radio value=\"free\">Free</Radio>\n * <Radio value=\"pro\">Pro</Radio>\n * </RadioGroup>\n */\nexport interface RadioGroupProps extends Omit<AriaRadioGroupProps, \"className\" | \"style\"> {\n /** Extra classes. Merged last, so they win conflicts with ours. */\n className?: string;\n}\n\nexport const RadioGroup = forwardRef<HTMLDivElement, RadioGroupProps>(function RadioGroup(\n { className, ...props },\n ref,\n) {\n return (\n <AriaRadioGroup\n ref={ref}\n {...props}\n data-slot=\"radio-group\"\n className={cn(radioGroupVariants(), className)}\n />\n );\n});\n\n/**\n * One choice.\n *\n * @example\n * <Radio value=\"pro\">Pro</Radio>\n */\nexport interface RadioProps extends Omit<AriaRadioProps, \"className\" | \"style\" | \"children\"> {\n /** The label, and this option's accessible name. */\n children?: ReactNode;\n /** Extra classes for the label. Merged last, so they win conflicts. */\n className?: string;\n}\n\nexport const Radio = forwardRef<HTMLInputElement, RadioProps>(function Radio(\n { className, children, ...props },\n ref,\n) {\n return (\n <AriaRadio\n {...props}\n // inputRef, not ref: AriaRadio's own ref targets the label it renders\n // around a visually-hidden native input, and a label cannot be\n // focused -- ref.current.focus() would silently no-op. inputRef is\n // React Aria's own escape hatch to the real control.\n inputRef={ref}\n data-slot=\"radio\"\n // justify-start, overriding the shared label's centring: a radio sits\n // in a vertical list where every label must start on the same line,\n // not centre itself in a 24px box.\n className={cn(cnAll(selectionLabelVariants(), \"justify-start\"), className)}\n >\n {({ isSelected }) => (\n <>\n <span aria-hidden=\"true\" data-slot=\"radio-dot\" className={radioVariants()}>\n {/*\n Always there: the state decides whether it shows, so the dot can\n spring in and shrink away. data-selected carries the state to a\n test, which can no longer read it from the dot's presence.\n */}\n <span\n data-slot=\"radio-indicator\"\n data-selected={isSelected || undefined}\n className=\"radio-dot size-1.5 rounded-full bg-current\"\n />\n </span>\n {children == null ? null : <span data-slot=\"radio-label\">{children}</span>}\n </>\n )}\n </AriaRadio>\n );\n});\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;AAgCA,MAAa,qBAAqB,IAAI,CACpC,uBACA,sBACF,CAAC;AAED,MAAa,gBAAgB,IAAI;CAC/B;CACA;CACA;CAEA;CACA;CACA;CACA,sBAAsB;AACxB,CAAC;AAgBD,MAAaA,eAAa,WAA4C,SAASA,aAC7E,EAAE,WAAW,GAAG,SAChB,KACA;CACA,OACE,oBAACC,YAAD;EACO;EACL,GAAI;EACJ,aAAU;EACV,WAAW,GAAG,mBAAmB,GAAG,SAAS;CAC9C,CAAA;AAEL,CAAC;AAeD,MAAaC,UAAQ,WAAyC,SAASA,QACrE,EAAE,WAAW,UAAU,GAAG,SAC1B,KACA;CACA,OACE,oBAACC,OAAD;EACE,GAAI;EAKJ,UAAU;EACV,aAAU;EAIV,WAAW,GAAG,MAAM,uBAAuB,GAAG,eAAe,GAAG,SAAS;EAEvE,WAAA,EAAE,iBACF,qBAAA,YAAA,EAAA,UAAA,CACE,oBAAC,QAAD;GAAM,eAAY;GAAO,aAAU;GAAY,WAAW,cAAc;GAMtE,UAAA,oBAAC,QAAD;IACE,aAAU;IACV,iBAAe,cAAc,KAAA;IAC7B,WAAU;GACX,CAAA;EACG,CAAA,GACL,YAAY,OAAO,OAAO,oBAAC,QAAD;GAAM,aAAU;GAAe;EAAe,CAAA,CACzE,EAAA,CAAA;CAEK,CAAA;AAEf,CAAC"}
@@ -0,0 +1,2 @@
1
+ import { Reaction, ReactionOption, Reactions, ReactionsProps } from "./reactions.js";
2
+ export { type Reaction, type ReactionOption, Reactions, type ReactionsProps };
@@ -0,0 +1,2 @@
1
+ import { Reactions } from "./reactions.js";
2
+ export { Reactions };
@@ -0,0 +1,50 @@
1
+ import { ReactNode } from "react";
2
+ //#region src/reactions/reactions.d.ts
3
+ /**
4
+ * Reactions: a reaction bar for comments, posts and release notes. Each pill shows
5
+ * one reaction and how many people chose it; the viewer's own carry the primary
6
+ * tint and a filled glyph (never colour alone). Pressing a pill adds or removes
7
+ * the viewer's reaction, and the add button opens a compact picker that grows out
8
+ * of it. Use it for quick acknowledgement; when people need to say something
9
+ * specific, give them a reply field instead.
10
+ *
11
+ * Adding gives the icon a kick (it overshoots and settles), the count swaps to its
12
+ * new number, a pill that arrives or leaves opens or closes its slot so the others
13
+ * glide, and the bar follows its own height when it wraps. In the picker a shared
14
+ * highlight glides to the hovered or focused option and a caption names it.
15
+ * Arrow keys, Home and End move through the options. A change is announced
16
+ * politely ("Added Like. 3 reactions."). Reduced motion fades.
17
+ */
18
+ export interface ReactionOption {
19
+ id: string;
20
+ /** The reaction's name, used on the pill, in the picker and in announcements. */
21
+ label: string;
22
+ /** The glyph: an svg that takes `currentColor`, 16px on a pill and 20px in the picker. */
23
+ icon: ReactNode;
24
+ }
25
+ export interface Reaction {
26
+ id: string;
27
+ count: number;
28
+ /** Whether the viewer chose it. */
29
+ reacted: boolean;
30
+ }
31
+ export interface ReactionsProps {
32
+ /** Every reaction the picker offers, in picker order. */
33
+ options: ReactionOption[];
34
+ /** Controlled reactions. Pills keep this order, and a reaction whose count reaches zero leaves the bar. */
35
+ value?: Reaction[];
36
+ /** The reactions when uncontrolled. */
37
+ defaultValue?: Reaction[];
38
+ /** Called with the next reactions and the change that produced them. */
39
+ onValueChange?: (next: Reaction[], change: {
40
+ id: string;
41
+ reacted: boolean;
42
+ }) => void;
43
+ /** Accessible name for the bar. @default "Reactions" */
44
+ label?: string;
45
+ /** Extra classes for the bar. Merged last. */
46
+ className?: string;
47
+ }
48
+ export declare function Reactions({ options, value, defaultValue, onValueChange, label, className }: ReactionsProps): import("react").JSX.Element;
49
+ //#endregion
50
+ //# sourceMappingURL=reactions.d.ts.map
@@ -0,0 +1,315 @@
1
+ "use client";
2
+ import { cn, cnAll } from "../lib/cn.js";
3
+ import { prefersReducedMotion, useExitBackstop, useIsomorphicLayoutEffect, useMorphWidth } from "../lib/motion.js";
4
+ import { SwapText } from "../lib/swap-text.js";
5
+ import { useHeightFollow } from "../lib/height-follow.js";
6
+ import { IconSwap } from "../lib/icon-swap.js";
7
+ import { Popover as Popover$1, PopoverTrigger } from "../popover/popover.js";
8
+ import { useLeavingList } from "../lib/leaving-list.js";
9
+ import { useCallback, useEffect, useRef, useState } from "react";
10
+ import { Button } from "react-aria-components";
11
+ import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
12
+ //#region src/reactions/reactions.tsx
13
+ const iconProps = {
14
+ width: 16,
15
+ height: 16,
16
+ viewBox: "0 0 24 24",
17
+ fill: "none",
18
+ stroke: "currentColor",
19
+ strokeWidth: 1.75,
20
+ strokeLinecap: "round",
21
+ strokeLinejoin: "round",
22
+ "aria-hidden": true
23
+ };
24
+ const AddIcon = () => /* @__PURE__ */ jsxs("svg", {
25
+ ...iconProps,
26
+ children: [/* @__PURE__ */ jsx("circle", {
27
+ cx: 11,
28
+ cy: 13,
29
+ r: 8
30
+ }), /* @__PURE__ */ jsx("path", { d: "M7.5 15.5s1.3 1.8 3.5 1.8 3.5-1.8 3.5-1.8M8.5 11h.01M13.5 11h.01M19 2v6M16 5h6" })]
31
+ });
32
+ const CloseIcon = () => /* @__PURE__ */ jsx("svg", {
33
+ ...iconProps,
34
+ children: /* @__PURE__ */ jsx("path", { d: "M6 6l12 12M18 6 6 18" })
35
+ });
36
+ const CheckIcon = () => /* @__PURE__ */ jsx("svg", {
37
+ ...iconProps,
38
+ width: 12,
39
+ height: 12,
40
+ strokeWidth: 2,
41
+ children: /* @__PURE__ */ jsx("path", { d: "M5 12.5 10 17.5 19 7" })
42
+ });
43
+ /** The count: a changed number swaps (rising in when it grows, dropping in when it shrinks) and the pill's width springs to it. */
44
+ function Count({ value }) {
45
+ const [roll, setRoll] = useState({
46
+ value,
47
+ dir: 1
48
+ });
49
+ if (roll.value !== value) setRoll({
50
+ value,
51
+ dir: value > roll.value ? 1 : -1
52
+ });
53
+ const content = useRef(null);
54
+ const text = String(value);
55
+ useMorphWidth(content, text);
56
+ return /* @__PURE__ */ jsx("span", {
57
+ className: "button-slot",
58
+ "aria-hidden": "true",
59
+ children: /* @__PURE__ */ jsx("span", {
60
+ ref: content,
61
+ className: "inline-flex flex-none items-center tabular-nums",
62
+ children: /* @__PURE__ */ jsx(SwapText, {
63
+ swapKey: text,
64
+ dir: roll.dir,
65
+ motion: "roll",
66
+ className: "inline-block",
67
+ children: text
68
+ })
69
+ })
70
+ });
71
+ }
72
+ function Pill({ reaction, option, pop, onToggle }) {
73
+ const noun = reaction.count === 1 ? "reaction" : "reactions";
74
+ return /* @__PURE__ */ jsxs("button", {
75
+ type: "button",
76
+ "data-reaction": reaction.id,
77
+ "aria-pressed": reaction.reacted,
78
+ "aria-label": `${option.label}, ${reaction.count} ${noun}${reaction.reacted ? ", including yours" : ""}`,
79
+ onClick: () => onToggle(reaction.id),
80
+ className: "reactions-pill",
81
+ children: [
82
+ /* @__PURE__ */ jsx("span", {
83
+ "data-pop": pop > 0 ? "" : void 0,
84
+ className: "reactions-icon",
85
+ "aria-hidden": "true",
86
+ children: option.icon
87
+ }, pop),
88
+ /* @__PURE__ */ jsx(Count, { value: reaction.count }),
89
+ /* @__PURE__ */ jsx("span", {
90
+ className: "reactions-tip",
91
+ "aria-hidden": "true",
92
+ children: option.label
93
+ })
94
+ ]
95
+ });
96
+ }
97
+ /**
98
+ * One place in the bar. A slot added after the first render opens from nothing
99
+ * while its pill fades up and scales in, so the others glide over; one that is
100
+ * removed stays, shrinking and collapsing, until its exit ends. Nothing animates
101
+ * at first render.
102
+ */
103
+ function Slot({ leaving, ready, onDone, id, children }) {
104
+ const [fresh] = useState(ready);
105
+ const remove = useCallback(() => onDone(id), [onDone, id]);
106
+ useExitBackstop(leaving, remove, 900);
107
+ useIsomorphicLayoutEffect(() => {
108
+ if (leaving && prefersReducedMotion()) remove();
109
+ }, [leaving, remove]);
110
+ return /* @__PURE__ */ jsx("div", {
111
+ "data-slot": leaving ? "reactions-leaving" : void 0,
112
+ "aria-hidden": leaving || void 0,
113
+ ref: (node) => {
114
+ if (!node) return;
115
+ if (leaving) node.setAttribute("inert", "");
116
+ else node.removeAttribute("inert");
117
+ },
118
+ onAnimationEnd: (event) => {
119
+ if (leaving && event.target === event.currentTarget && event.animationName === "chip-slot-out") remove();
120
+ },
121
+ className: cnAll("min-w-0 flex-none [interpolate-size:allow-keywords]", leaving ? "pointer-events-none motion-safe:animate-chip-out" : fresh && "motion-safe:animate-chip-in motion-reduce:animate-fade-in"),
122
+ children
123
+ });
124
+ }
125
+ /** The tray of every option. A shared highlight glides to the hovered or focused option, and a caption names it. */
126
+ function Picker({ options, reacted, onPick }) {
127
+ const [focusIndex, setFocusIndex] = useState(0);
128
+ const [hovered, setHovered] = useState(null);
129
+ const active = Math.min(hovered ?? focusIndex, options.length - 1);
130
+ const buttons = useRef([]);
131
+ useEffect(() => {
132
+ buttons.current[0]?.focus();
133
+ }, []);
134
+ function onKeyDown(event) {
135
+ const steps = {
136
+ ArrowRight: 1,
137
+ ArrowDown: 1,
138
+ ArrowLeft: -1,
139
+ ArrowUp: -1
140
+ };
141
+ const count = options.length;
142
+ const next = event.key in steps ? (focusIndex + steps[event.key] + count) % count : event.key === "Home" ? 0 : event.key === "End" ? count - 1 : -1;
143
+ if (next < 0) return;
144
+ event.preventDefault();
145
+ setHovered(null);
146
+ setFocusIndex(next);
147
+ buttons.current[next]?.focus();
148
+ }
149
+ const option = options[active];
150
+ const chosen = option ? reacted.has(option.id) : false;
151
+ return /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsxs("div", {
152
+ role: "toolbar",
153
+ "aria-label": "Reactions",
154
+ "aria-orientation": "horizontal",
155
+ className: "reactions-options",
156
+ style: { "--active": active },
157
+ onKeyDown,
158
+ onPointerLeave: () => setHovered(null),
159
+ children: [/* @__PURE__ */ jsx("span", {
160
+ className: "reactions-highlight",
161
+ "aria-hidden": "true"
162
+ }), options.map((item, index) => /* @__PURE__ */ jsx("button", {
163
+ ref: (node) => {
164
+ buttons.current[index] = node;
165
+ },
166
+ type: "button",
167
+ className: "reactions-option",
168
+ style: { "--i": index },
169
+ "data-active": index === active ? "" : void 0,
170
+ "aria-label": item.label,
171
+ "aria-pressed": reacted.has(item.id),
172
+ tabIndex: index === focusIndex ? 0 : -1,
173
+ onFocus: () => setFocusIndex(index),
174
+ onPointerMove: (event) => {
175
+ if (event.pointerType === "mouse" && hovered !== index) setHovered(index);
176
+ },
177
+ onClick: () => onPick(item.id),
178
+ children: /* @__PURE__ */ jsx("span", {
179
+ className: "reactions-option-icon",
180
+ "aria-hidden": "true",
181
+ children: item.icon
182
+ })
183
+ }, item.id))]
184
+ }), /* @__PURE__ */ jsx("div", {
185
+ className: "reactions-caption",
186
+ "aria-hidden": "true",
187
+ children: option ? /* @__PURE__ */ jsx(SwapText, {
188
+ swapKey: `${option.id}:${chosen}`,
189
+ motion: "rise",
190
+ className: "inline-flex",
191
+ children: /* @__PURE__ */ jsxs("span", {
192
+ className: "inline-flex items-center gap-1",
193
+ children: [chosen ? /* @__PURE__ */ jsx(CheckIcon, {}) : null, option.label]
194
+ })
195
+ }) : null
196
+ })] });
197
+ }
198
+ function Reactions({ options, value, defaultValue = [], onValueChange, label = "Reactions", className }) {
199
+ const [own, setOwn] = useState(defaultValue);
200
+ const list = value ?? own;
201
+ const [pops, setPops] = useState({});
202
+ const [announcement, setAnnouncement] = useState("");
203
+ const [open, setOpen] = useState(false);
204
+ const frame = useRef(null);
205
+ const bar = useRef(null);
206
+ const refocus = useRef(null);
207
+ const byId = new Map(options.map((option) => [option.id, option]));
208
+ const shown = list.filter((reaction) => reaction.count > 0 && byId.has(reaction.id));
209
+ const reacted = new Set(shown.filter((reaction) => reaction.reacted).map((reaction) => reaction.id));
210
+ const places = useLeavingList(shown);
211
+ const [ready, setReady] = useState(false);
212
+ useIsomorphicLayoutEffect(() => setReady(true), []);
213
+ useHeightFollow(frame, bar, shown.map((reaction) => `${reaction.id}${reaction.count}`).join());
214
+ useIsomorphicLayoutEffect(() => {
215
+ const target = refocus.current;
216
+ if (!target) return;
217
+ refocus.current = null;
218
+ (target === "add" ? bar.current?.querySelector("[data-add]") : bar.current?.querySelector(`[data-reaction="${CSS.escape(target)}"]`))?.focus();
219
+ });
220
+ function toggle(id) {
221
+ const option = byId.get(id);
222
+ if (!option) return;
223
+ const current = shown.find((reaction) => reaction.id === id);
224
+ const add = !current?.reacted;
225
+ const count = Math.max(0, (current?.count ?? 0) + (add ? 1 : -1));
226
+ const next = current ? list.map((reaction) => reaction.id === id ? {
227
+ ...reaction,
228
+ count,
229
+ reacted: add
230
+ } : reaction).filter((reaction) => reaction.count > 0) : [...list.filter((reaction) => reaction.id !== id), {
231
+ id,
232
+ count: 1,
233
+ reacted: true
234
+ }];
235
+ if (count === 0) {
236
+ const pill = bar.current?.querySelector(`[data-reaction="${CSS.escape(id)}"]`);
237
+ if (pill && pill === document.activeElement) {
238
+ const index = shown.findIndex((reaction) => reaction.id === id);
239
+ refocus.current = shown[index + 1]?.id ?? shown[index - 1]?.id ?? "add";
240
+ }
241
+ }
242
+ if (add) setPops((previous) => ({
243
+ ...previous,
244
+ [id]: (previous[id] ?? 0) + 1
245
+ }));
246
+ if (value === void 0) setOwn(next);
247
+ setAnnouncement(add ? `Added ${option.label}. ${count} ${count === 1 ? "reaction" : "reactions"}.` : count ? `Removed ${option.label}. ${count} ${count === 1 ? "reaction" : "reactions"}.` : `Removed ${option.label}.`);
248
+ onValueChange?.(next, {
249
+ id,
250
+ reacted: add
251
+ });
252
+ }
253
+ function pick(id) {
254
+ setOpen(false);
255
+ toggle(id);
256
+ }
257
+ return /* @__PURE__ */ jsxs("div", {
258
+ ref: frame,
259
+ "data-slot": "reactions",
260
+ className: cn("reactions height-follow", className),
261
+ children: [/* @__PURE__ */ jsxs("div", {
262
+ ref: bar,
263
+ role: "group",
264
+ "aria-label": label,
265
+ className: "reactions-bar",
266
+ children: [places.list.map(({ item, leaving }) => /* @__PURE__ */ jsx(Slot, {
267
+ id: item.id,
268
+ leaving,
269
+ ready,
270
+ onDone: places.done,
271
+ children: /* @__PURE__ */ jsx(Pill, {
272
+ reaction: item,
273
+ option: byId.get(item.id) ?? options[0],
274
+ pop: pops[item.id] ?? 0,
275
+ onToggle: toggle
276
+ })
277
+ }, item.id)), /* @__PURE__ */ jsxs(PopoverTrigger, {
278
+ isOpen: open,
279
+ onOpenChange: setOpen,
280
+ children: [/* @__PURE__ */ jsxs(Button, {
281
+ "data-add": "",
282
+ "aria-label": "Add reaction",
283
+ className: "reactions-add",
284
+ children: [/* @__PURE__ */ jsx(IconSwap, {
285
+ iconKey: open ? "close" : "add",
286
+ tight: true,
287
+ children: open ? /* @__PURE__ */ jsx(CloseIcon, {}) : /* @__PURE__ */ jsx(AddIcon, {})
288
+ }), open ? null : /* @__PURE__ */ jsx("span", {
289
+ className: "reactions-tip",
290
+ "aria-hidden": "true",
291
+ children: "Add reaction"
292
+ })]
293
+ }), /* @__PURE__ */ jsx(Popover$1, {
294
+ placement: "top",
295
+ offset: 8,
296
+ entrance: "none",
297
+ className: "reactions-picker w-auto p-1.5",
298
+ children: /* @__PURE__ */ jsx(Picker, {
299
+ options,
300
+ reacted,
301
+ onPick: pick
302
+ })
303
+ })]
304
+ })]
305
+ }), /* @__PURE__ */ jsx("span", {
306
+ className: "sr-only",
307
+ role: "status",
308
+ children: announcement
309
+ })]
310
+ });
311
+ }
312
+ //#endregion
313
+ export { Reactions };
314
+
315
+ //# sourceMappingURL=reactions.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reactions.js","names":["AriaButton","Popover"],"sources":["../../src/reactions/reactions.tsx"],"sourcesContent":["\"use client\";\n\nimport {\n useCallback,\n useEffect,\n useRef,\n useState,\n type CSSProperties,\n type KeyboardEvent,\n type ReactNode,\n type SVGProps,\n} from \"react\";\nimport { Button as AriaButton } from \"react-aria-components\";\n\nimport { cn, cnAll } from \"../lib/cn.js\";\nimport { useHeightFollow } from \"../lib/height-follow.js\";\nimport { IconSwap } from \"../lib/icon-swap.js\";\nimport { useLeavingList } from \"../lib/leaving-list.js\";\nimport {\n prefersReducedMotion,\n useExitBackstop,\n useIsomorphicLayoutEffect,\n useMorphWidth,\n} from \"../lib/motion.js\";\nimport { SwapText } from \"../lib/swap-text.js\";\nimport { Popover, PopoverTrigger } from \"../popover/popover.js\";\n\n/**\n * Reactions: a reaction bar for comments, posts and release notes. Each pill shows\n * one reaction and how many people chose it; the viewer's own carry the primary\n * tint and a filled glyph (never colour alone). Pressing a pill adds or removes\n * the viewer's reaction, and the add button opens a compact picker that grows out\n * of it. Use it for quick acknowledgement; when people need to say something\n * specific, give them a reply field instead.\n *\n * Adding gives the icon a kick (it overshoots and settles), the count swaps to its\n * new number, a pill that arrives or leaves opens or closes its slot so the others\n * glide, and the bar follows its own height when it wraps. In the picker a shared\n * highlight glides to the hovered or focused option and a caption names it.\n * Arrow keys, Home and End move through the options. A change is announced\n * politely (\"Added Like. 3 reactions.\"). Reduced motion fades.\n */\nexport interface ReactionOption {\n id: string;\n /** The reaction's name, used on the pill, in the picker and in announcements. */\n label: string;\n /** The glyph: an svg that takes `currentColor`, 16px on a pill and 20px in the picker. */\n icon: ReactNode;\n}\n\nexport interface Reaction {\n id: string;\n count: number;\n /** Whether the viewer chose it. */\n reacted: boolean;\n}\n\nexport interface ReactionsProps {\n /** Every reaction the picker offers, in picker order. */\n options: ReactionOption[];\n /** Controlled reactions. Pills keep this order, and a reaction whose count reaches zero leaves the bar. */\n value?: Reaction[];\n /** The reactions when uncontrolled. */\n defaultValue?: Reaction[];\n /** Called with the next reactions and the change that produced them. */\n onValueChange?: (next: Reaction[], change: { id: string; reacted: boolean }) => void;\n /** Accessible name for the bar. @default \"Reactions\" */\n label?: string;\n /** Extra classes for the bar. Merged last. */\n className?: string;\n}\n\nconst iconProps: SVGProps<SVGSVGElement> = {\n width: 16,\n height: 16,\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\nconst AddIcon = () => (\n <svg {...iconProps}>\n <circle cx={11} cy={13} r={8} />\n <path d=\"M7.5 15.5s1.3 1.8 3.5 1.8 3.5-1.8 3.5-1.8M8.5 11h.01M13.5 11h.01M19 2v6M16 5h6\" />\n </svg>\n);\nconst CloseIcon = () => (\n <svg {...iconProps}>\n <path d=\"M6 6l12 12M18 6 6 18\" />\n </svg>\n);\nconst CheckIcon = () => (\n <svg {...iconProps} width={12} height={12} strokeWidth={2}>\n <path d=\"M5 12.5 10 17.5 19 7\" />\n </svg>\n);\n\n/** The count: a changed number swaps (rising in when it grows, dropping in when it shrinks) and the pill's width springs to it. */\nfunction Count({ value }: { value: number }) {\n const [roll, setRoll] = useState({ value, dir: 1 });\n if (roll.value !== value) setRoll({ value, dir: value > roll.value ? 1 : -1 });\n const content = useRef<HTMLSpanElement>(null);\n const text = String(value);\n useMorphWidth(content, text);\n return (\n <span className=\"button-slot\" aria-hidden=\"true\">\n <span ref={content} className=\"inline-flex flex-none items-center tabular-nums\">\n <SwapText swapKey={text} dir={roll.dir} motion=\"roll\" className=\"inline-block\">\n {text}\n </SwapText>\n </span>\n </span>\n );\n}\n\nfunction Pill({\n reaction,\n option,\n pop,\n onToggle,\n}: {\n reaction: Reaction;\n option: ReactionOption;\n pop: number;\n onToggle: (id: string) => void;\n}) {\n const noun = reaction.count === 1 ? \"reaction\" : \"reactions\";\n return (\n <button\n type=\"button\"\n data-reaction={reaction.id}\n aria-pressed={reaction.reacted}\n aria-label={`${option.label}, ${reaction.count} ${noun}${reaction.reacted ? \", including yours\" : \"\"}`}\n onClick={() => onToggle(reaction.id)}\n className=\"reactions-pill\"\n >\n {/* A new key each time it is added, so the kick plays again even on a quick second press. */}\n <span\n key={pop}\n data-pop={pop > 0 ? \"\" : undefined}\n className=\"reactions-icon\"\n aria-hidden=\"true\"\n >\n {option.icon}\n </span>\n <Count value={reaction.count} />\n <span className=\"reactions-tip\" aria-hidden=\"true\">\n {option.label}\n </span>\n </button>\n );\n}\n\n/**\n * One place in the bar. A slot added after the first render opens from nothing\n * while its pill fades up and scales in, so the others glide over; one that is\n * removed stays, shrinking and collapsing, until its exit ends. Nothing animates\n * at first render.\n */\nfunction Slot({\n leaving,\n ready,\n onDone,\n id,\n children,\n}: {\n leaving: boolean;\n ready: boolean;\n onDone: (id: string) => void;\n id: string;\n children: ReactNode;\n}) {\n // Decided once, at this slot's own mount.\n const [fresh] = useState(ready);\n const remove = useCallback(() => onDone(id), [onDone, id]);\n useExitBackstop(leaving, remove, 900);\n // Reduced motion removes it at once: an animation that is switched off never ends.\n useIsomorphicLayoutEffect(() => {\n if (leaving && prefersReducedMotion()) remove();\n }, [leaving, remove]);\n return (\n <div\n data-slot={leaving ? \"reactions-leaving\" : undefined}\n aria-hidden={leaving || undefined}\n ref={(node) => {\n if (!node) return;\n if (leaving) node.setAttribute(\"inert\", \"\");\n else node.removeAttribute(\"inert\");\n }}\n onAnimationEnd={(event) => {\n if (\n leaving &&\n event.target === event.currentTarget &&\n event.animationName === \"chip-slot-out\"\n )\n remove();\n }}\n className={cnAll(\n \"min-w-0 flex-none [interpolate-size:allow-keywords]\",\n leaving\n ? \"pointer-events-none motion-safe:animate-chip-out\"\n : fresh && \"motion-safe:animate-chip-in motion-reduce:animate-fade-in\",\n )}\n >\n {children}\n </div>\n );\n}\n\n/** The tray of every option. A shared highlight glides to the hovered or focused option, and a caption names it. */\nfunction Picker({\n options,\n reacted,\n onPick,\n}: {\n options: ReactionOption[];\n reacted: Set<string>;\n onPick: (id: string) => void;\n}) {\n const [focusIndex, setFocusIndex] = useState(0);\n const [hovered, setHovered] = useState<number | null>(null);\n const active = Math.min(hovered ?? focusIndex, options.length - 1);\n const buttons = useRef<(HTMLButtonElement | null)[]>([]);\n // The first option takes focus once the popover has recorded where focus was, so closing hands it back to the add button.\n useEffect(() => {\n buttons.current[0]?.focus();\n }, []);\n\n function onKeyDown(event: KeyboardEvent<HTMLDivElement>) {\n const steps: Record<string, number> = {\n ArrowRight: 1,\n ArrowDown: 1,\n ArrowLeft: -1,\n ArrowUp: -1,\n };\n const count = options.length;\n const next =\n event.key in steps\n ? (focusIndex + steps[event.key]! + count) % count\n : event.key === \"Home\"\n ? 0\n : event.key === \"End\"\n ? count - 1\n : -1;\n if (next < 0) return;\n event.preventDefault();\n setHovered(null);\n setFocusIndex(next);\n buttons.current[next]?.focus();\n }\n\n const option = options[active];\n const chosen = option ? reacted.has(option.id) : false;\n return (\n <>\n <div\n role=\"toolbar\"\n aria-label=\"Reactions\"\n aria-orientation=\"horizontal\"\n className=\"reactions-options\"\n style={{ \"--active\": active } as CSSProperties}\n onKeyDown={onKeyDown}\n onPointerLeave={() => setHovered(null)}\n >\n <span className=\"reactions-highlight\" aria-hidden=\"true\" />\n {options.map((item, index) => (\n <button\n key={item.id}\n ref={(node) => {\n buttons.current[index] = node;\n }}\n type=\"button\"\n className=\"reactions-option\"\n style={{ \"--i\": index } as CSSProperties}\n data-active={index === active ? \"\" : undefined}\n aria-label={item.label}\n aria-pressed={reacted.has(item.id)}\n tabIndex={index === focusIndex ? 0 : -1}\n onFocus={() => setFocusIndex(index)}\n onPointerMove={(event) => {\n if (event.pointerType === \"mouse\" && hovered !== index) setHovered(index);\n }}\n onClick={() => onPick(item.id)}\n >\n <span className=\"reactions-option-icon\" aria-hidden=\"true\">\n {item.icon}\n </span>\n </button>\n ))}\n </div>\n <div className=\"reactions-caption\" aria-hidden=\"true\">\n {option ? (\n <SwapText swapKey={`${option.id}:${chosen}`} motion=\"rise\" className=\"inline-flex\">\n <span className=\"inline-flex items-center gap-1\">\n {chosen ? <CheckIcon /> : null}\n {option.label}\n </span>\n </SwapText>\n ) : null}\n </div>\n </>\n );\n}\n\nexport function Reactions({\n options,\n value,\n defaultValue = [],\n onValueChange,\n label = \"Reactions\",\n className,\n}: ReactionsProps) {\n const [own, setOwn] = useState(defaultValue);\n const list = value ?? own;\n const [pops, setPops] = useState<Record<string, number>>({});\n const [announcement, setAnnouncement] = useState(\"\");\n const [open, setOpen] = useState(false);\n const frame = useRef<HTMLDivElement>(null);\n const bar = useRef<HTMLDivElement>(null);\n const refocus = useRef<string | null>(null);\n const byId = new Map(options.map((option) => [option.id, option]));\n const shown = list.filter((reaction) => reaction.count > 0 && byId.has(reaction.id));\n const reacted = new Set(\n shown.filter((reaction) => reaction.reacted).map((reaction) => reaction.id),\n );\n const places = useLeavingList(shown);\n // Pills that arrive after the first render open and fade in; the ones there from the start never animate.\n const [ready, setReady] = useState(false);\n useIsomorphicLayoutEffect(() => setReady(true), []);\n // Wrapping onto a new row changes the bar's height; the frame follows it instead of shoving the content below.\n useHeightFollow(frame, bar, shown.map((reaction) => `${reaction.id}${reaction.count}`).join());\n\n // A pill that leaves while focused hands focus to its neighbour, or to the add button, so keyboard users never fall back to the page.\n useIsomorphicLayoutEffect(() => {\n const target = refocus.current;\n if (!target) return;\n refocus.current = null;\n const next =\n target === \"add\"\n ? bar.current?.querySelector<HTMLElement>(\"[data-add]\")\n : bar.current?.querySelector<HTMLElement>(`[data-reaction=\"${CSS.escape(target)}\"]`);\n next?.focus();\n });\n\n function toggle(id: string) {\n const option = byId.get(id);\n if (!option) return;\n const current = shown.find((reaction) => reaction.id === id);\n const add = !current?.reacted;\n const count = Math.max(0, (current?.count ?? 0) + (add ? 1 : -1));\n const next = current\n ? list\n .map((reaction) => (reaction.id === id ? { ...reaction, count, reacted: add } : reaction))\n .filter((reaction) => reaction.count > 0)\n : [...list.filter((reaction) => reaction.id !== id), { id, count: 1, reacted: true }];\n if (count === 0) {\n const pill = bar.current?.querySelector(`[data-reaction=\"${CSS.escape(id)}\"]`);\n if (pill && pill === document.activeElement) {\n const index = shown.findIndex((reaction) => reaction.id === id);\n refocus.current = shown[index + 1]?.id ?? shown[index - 1]?.id ?? \"add\";\n }\n }\n if (add) setPops((previous) => ({ ...previous, [id]: (previous[id] ?? 0) + 1 }));\n if (value === undefined) setOwn(next);\n setAnnouncement(\n add\n ? `Added ${option.label}. ${count} ${count === 1 ? \"reaction\" : \"reactions\"}.`\n : count\n ? `Removed ${option.label}. ${count} ${count === 1 ? \"reaction\" : \"reactions\"}.`\n : `Removed ${option.label}.`,\n );\n onValueChange?.(next, { id, reacted: add });\n }\n\n function pick(id: string) {\n setOpen(false);\n toggle(id);\n }\n\n return (\n <div ref={frame} data-slot=\"reactions\" className={cn(\"reactions height-follow\", className)}>\n <div ref={bar} role=\"group\" aria-label={label} className=\"reactions-bar\">\n {places.list.map(({ item, leaving }) => (\n <Slot key={item.id} id={item.id} leaving={leaving} ready={ready} onDone={places.done}>\n <Pill\n reaction={item}\n option={byId.get(item.id) ?? options[0]!}\n pop={pops[item.id] ?? 0}\n onToggle={toggle}\n />\n </Slot>\n ))}\n <PopoverTrigger isOpen={open} onOpenChange={setOpen}>\n {/* The add button anchors the picker, so it answers a press with colour and never scales. */}\n <AriaButton data-add=\"\" aria-label=\"Add reaction\" className=\"reactions-add\">\n <IconSwap iconKey={open ? \"close\" : \"add\"} tight>\n {open ? <CloseIcon /> : <AddIcon />}\n </IconSwap>\n {open ? null : (\n <span className=\"reactions-tip\" aria-hidden=\"true\">\n Add reaction\n </span>\n )}\n </AriaButton>\n <Popover\n placement=\"top\"\n offset={8}\n entrance=\"none\"\n className=\"reactions-picker w-auto p-1.5\"\n >\n <Picker options={options} reacted={reacted} onPick={pick} />\n </Popover>\n </PopoverTrigger>\n </div>\n <span className=\"sr-only\" role=\"status\">\n {announcement}\n </span>\n </div>\n );\n}\n"],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ import { BlockType, RichTextValue } from "./rich-text-engine.js";
2
+ import { RichTextEditor, RichTextEditorHandle, RichTextEditorProps } from "./rich-text-editor.js";
3
+ export { type BlockType, RichTextEditor, type RichTextEditorHandle, type RichTextEditorProps, type RichTextValue };
@@ -0,0 +1,2 @@
1
+ import { RichTextEditor } from "./rich-text-editor.js";
2
+ export { RichTextEditor };
@@ -0,0 +1,70 @@
1
+ import { BlockType, RichTextValue } from "./rich-text-engine.js";
2
+ import { ReactNode } from "react";
3
+ //#region src/rich-text-editor/rich-text-editor.d.ts
4
+ /** What the editor can be told to do from outside. */
5
+ export interface RichTextEditorHandle {
6
+ focus: () => void;
7
+ getHTML: () => string;
8
+ getMarkdown: () => string;
9
+ getText: () => string;
10
+ /** Replaces the document. Reports a change, like an edit. */
11
+ setHTML: (html: string) => void;
12
+ /** Replaces the document with Markdown. Reports a change, like an edit. */
13
+ setMarkdown: (markdown: string) => void;
14
+ /** Empties the document. Reports a change, like an edit. */
15
+ clear: () => void;
16
+ undo: () => void;
17
+ redo: () => void;
18
+ /** The editable element. Null before mount. */
19
+ element: HTMLDivElement | null;
20
+ }
21
+ /**
22
+ * A rich text field: headings, lists, quotes, code, dividers, links and marks, with a floating
23
+ * selection toolbar, a slash menu for blocks and Markdown shortcuts as you type.
24
+ *
25
+ * The value is canonical HTML in a fixed vocabulary (p, h1-h3, ul, ol, li, blockquote, pre, hr,
26
+ * strong, em, u, s, code, a[href], br), cleaned on the way in and on the way out. It is not the
27
+ * engine's own format, so a stored value does not depend on the engine.
28
+ *
29
+ * @example
30
+ * <RichTextEditor label="Notes" value={html} onChange={(v) => setHtml(v.html)} />
31
+ */
32
+ export interface RichTextEditorProps {
33
+ /** Controlled HTML. The editor rewrites its content only when this differs from what it last reported, so typing never fights the caret. */
34
+ value?: string;
35
+ /** Initial HTML for an uncontrolled editor. */
36
+ defaultValue?: string;
37
+ /** Initial content as Markdown, used when no HTML is given. */
38
+ defaultMarkdown?: string;
39
+ /** Called with the document in every format after each edit (not after a controlled `value` arrives). */
40
+ onChange?: (value: RichTextValue) => void;
41
+ /** Reports whether undo and redo are available, for your own buttons. */
42
+ onHistoryChange?: (state: {
43
+ canUndo: boolean;
44
+ canRedo: boolean;
45
+ }) => void;
46
+ /** Shown while the document is empty. @default "Start writing" */
47
+ placeholder?: string;
48
+ /** The hint on an empty line while typing. @default "Type / for blocks" */
49
+ blockHint?: string;
50
+ /** Not focusable and not editable, muted. */
51
+ isDisabled?: boolean;
52
+ /** Focusable and selectable, but not editable. The toolbar and slash menu do not open. */
53
+ isReadOnly?: boolean;
54
+ autoFocus?: boolean;
55
+ /** A visible label above the field, which names it. */
56
+ label?: ReactNode;
57
+ /** Helper text under the field, named by `aria-describedby`. */
58
+ description?: string;
59
+ /** Error text under the field. Sets `aria-invalid` and is announced when it appears. */
60
+ error?: string;
61
+ /** The accessible name when there is no `label`. @default "Editor" */
62
+ "aria-label"?: string;
63
+ id?: string;
64
+ /** Extra classes on the outer element. */
65
+ className?: string;
66
+ }
67
+ export declare const RichTextEditor: import("react").ForwardRefExoticComponent<RichTextEditorProps & import("react").RefAttributes<RichTextEditorHandle>>;
68
+ //#endregion
69
+ export type { BlockType, RichTextValue };
70
+ //# sourceMappingURL=rich-text-editor.d.ts.map