@seekalfred/primitives 0.5.1 → 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 (446) hide show
  1. package/README.md +682 -19
  2. package/dist/accordion/accordion.js +10 -4
  3. package/dist/accordion/accordion.js.map +1 -1
  4. package/dist/action-button/action-button.d.ts +50 -0
  5. package/dist/action-button/action-button.js +159 -0
  6. package/dist/action-button/action-button.js.map +1 -0
  7. package/dist/action-button/index.d.ts +2 -0
  8. package/dist/action-button/index.js +2 -0
  9. package/dist/activity-heatmap/activity-heatmap.d.ts +92 -0
  10. package/dist/activity-heatmap/activity-heatmap.js +699 -0
  11. package/dist/activity-heatmap/activity-heatmap.js.map +1 -0
  12. package/dist/activity-heatmap/index.d.ts +2 -0
  13. package/dist/activity-heatmap/index.js +2 -0
  14. package/dist/alert/alert.d.ts +19 -1
  15. package/dist/alert/alert.js +180 -23
  16. package/dist/alert/alert.js.map +1 -1
  17. package/dist/alert-dialog/alert-dialog.d.ts +45 -18
  18. package/dist/alert-dialog/alert-dialog.js +128 -9
  19. package/dist/alert-dialog/alert-dialog.js.map +1 -1
  20. package/dist/alert-dialog/index.d.ts +2 -2
  21. package/dist/alert-dialog/index.js +2 -2
  22. package/dist/animated-counter/animated-counter.d.ts +38 -0
  23. package/dist/animated-counter/animated-counter.js +230 -0
  24. package/dist/animated-counter/animated-counter.js.map +1 -0
  25. package/dist/animated-counter/index.d.ts +2 -0
  26. package/dist/animated-counter/index.js +2 -0
  27. package/dist/announcement-bar/announcement-bar.d.ts +67 -0
  28. package/dist/announcement-bar/announcement-bar.js +364 -0
  29. package/dist/announcement-bar/announcement-bar.js.map +1 -0
  30. package/dist/announcement-bar/index.d.ts +2 -0
  31. package/dist/announcement-bar/index.js +2 -0
  32. package/dist/avatar/avatar.d.ts +6 -0
  33. package/dist/avatar/avatar.js +47 -16
  34. package/dist/avatar/avatar.js.map +1 -1
  35. package/dist/badge/badge.d.ts +10 -3
  36. package/dist/badge/badge.js +35 -5
  37. package/dist/badge/badge.js.map +1 -1
  38. package/dist/bar-chart/bar-chart.d.ts +51 -0
  39. package/dist/bar-chart/bar-chart.js +565 -0
  40. package/dist/bar-chart/bar-chart.js.map +1 -0
  41. package/dist/bar-chart/index.d.ts +2 -0
  42. package/dist/bar-chart/index.js +2 -0
  43. package/dist/breadcrumb/breadcrumb.d.ts +2 -29
  44. package/dist/breadcrumb/breadcrumb.js +32 -9
  45. package/dist/breadcrumb/breadcrumb.js.map +1 -1
  46. package/dist/brush-chart/brush-chart.d.ts +64 -0
  47. package/dist/brush-chart/brush-chart.js +1056 -0
  48. package/dist/brush-chart/brush-chart.js.map +1 -0
  49. package/dist/brush-chart/index.d.ts +2 -0
  50. package/dist/brush-chart/index.js +2 -0
  51. package/dist/button/button.d.ts +36 -23
  52. package/dist/button/button.js +198 -45
  53. package/dist/button/button.js.map +1 -1
  54. package/dist/button-group/button-group.d.ts +76 -0
  55. package/dist/button-group/button-group.js +402 -0
  56. package/dist/button-group/button-group.js.map +1 -0
  57. package/dist/button-group/index.d.ts +2 -0
  58. package/dist/button-group/index.js +2 -0
  59. package/dist/calendar/calendar.d.ts +1 -1
  60. package/dist/calendar/calendar.js +257 -30
  61. package/dist/calendar/calendar.js.map +1 -1
  62. package/dist/card/card.d.ts +9 -2
  63. package/dist/card/card.js +16 -5
  64. package/dist/card/card.js.map +1 -1
  65. package/dist/card-stack/card-stack.d.ts +54 -0
  66. package/dist/card-stack/card-stack.js +492 -0
  67. package/dist/card-stack/card-stack.js.map +1 -0
  68. package/dist/card-stack/index.d.ts +2 -0
  69. package/dist/card-stack/index.js +2 -0
  70. package/dist/carousel/carousel.d.ts +56 -0
  71. package/dist/carousel/carousel.js +389 -0
  72. package/dist/carousel/carousel.js.map +1 -0
  73. package/dist/carousel/index.d.ts +2 -0
  74. package/dist/carousel/index.js +2 -0
  75. package/dist/checkbox/checkbox.d.ts +0 -7
  76. package/dist/checkbox/checkbox.js +26 -29
  77. package/dist/checkbox/checkbox.js.map +1 -1
  78. package/dist/chip-group/chip-group.d.ts +37 -0
  79. package/dist/chip-group/chip-group.js +148 -0
  80. package/dist/chip-group/chip-group.js.map +1 -0
  81. package/dist/chip-group/index.d.ts +2 -0
  82. package/dist/chip-group/index.js +2 -0
  83. package/dist/confirm-morph/confirm-morph.d.ts +85 -0
  84. package/dist/confirm-morph/confirm-morph.js +652 -0
  85. package/dist/confirm-morph/confirm-morph.js.map +1 -0
  86. package/dist/confirm-morph/index.d.ts +2 -0
  87. package/dist/confirm-morph/index.js +2 -0
  88. package/dist/copy-button/copy-button.d.ts +32 -0
  89. package/dist/copy-button/copy-button.js +134 -0
  90. package/dist/copy-button/copy-button.js.map +1 -0
  91. package/dist/copy-button/index.d.ts +2 -0
  92. package/dist/copy-button/index.js +2 -0
  93. package/dist/countdown/countdown.d.ts +45 -0
  94. package/dist/countdown/countdown.js +298 -0
  95. package/dist/countdown/countdown.js.map +1 -0
  96. package/dist/countdown/index.d.ts +2 -0
  97. package/dist/countdown/index.js +2 -0
  98. package/dist/date-picker/date-picker.d.ts +51 -0
  99. package/dist/date-picker/date-picker.js +241 -0
  100. package/dist/date-picker/date-picker.js.map +1 -0
  101. package/dist/date-picker/date-range-picker.d.ts +78 -0
  102. package/dist/date-picker/date-range-picker.js +1015 -0
  103. package/dist/date-picker/date-range-picker.js.map +1 -0
  104. package/dist/date-picker/index.d.ts +4 -0
  105. package/dist/date-picker/index.js +4 -0
  106. package/dist/date-picker/picker-parts.d.ts +46 -0
  107. package/dist/date-picker/picker-parts.js +135 -0
  108. package/dist/date-picker/picker-parts.js.map +1 -0
  109. package/dist/dialog/dialog.d.ts +10 -14
  110. package/dist/dialog/dialog.js +40 -12
  111. package/dist/dialog/dialog.js.map +1 -1
  112. package/dist/donut-chart/donut-chart.d.ts +64 -0
  113. package/dist/donut-chart/donut-chart.js +576 -0
  114. package/dist/donut-chart/donut-chart.js.map +1 -0
  115. package/dist/donut-chart/index.d.ts +2 -0
  116. package/dist/donut-chart/index.js +2 -0
  117. package/dist/empty-state/empty-state.d.ts +55 -0
  118. package/dist/empty-state/empty-state.js +89 -0
  119. package/dist/empty-state/empty-state.js.map +1 -0
  120. package/dist/empty-state/index.d.ts +2 -0
  121. package/dist/empty-state/index.js +2 -0
  122. package/dist/expanding-button-group/expanding-button-group.d.ts +54 -0
  123. package/dist/expanding-button-group/expanding-button-group.js +350 -0
  124. package/dist/expanding-button-group/expanding-button-group.js.map +1 -0
  125. package/dist/expanding-button-group/index.d.ts +2 -0
  126. package/dist/expanding-button-group/index.js +2 -0
  127. package/dist/file-drop-zone/file-drop-zone.d.ts +71 -0
  128. package/dist/file-drop-zone/file-drop-zone.js +116 -0
  129. package/dist/file-drop-zone/file-drop-zone.js.map +1 -0
  130. package/dist/file-drop-zone/index.d.ts +2 -0
  131. package/dist/file-drop-zone/index.js +2 -0
  132. package/dist/filter-bar/filter-bar.d.ts +142 -0
  133. package/dist/filter-bar/filter-bar.js +416 -0
  134. package/dist/filter-bar/filter-bar.js.map +1 -0
  135. package/dist/filter-bar/index.d.ts +2 -0
  136. package/dist/filter-bar/index.js +2 -0
  137. package/dist/floating-button-group/floating-button-group.d.ts +61 -0
  138. package/dist/floating-button-group/floating-button-group.js +340 -0
  139. package/dist/floating-button-group/floating-button-group.js.map +1 -0
  140. package/dist/floating-button-group/index.d.ts +2 -0
  141. package/dist/floating-button-group/index.js +2 -0
  142. package/dist/gauge/gauge.d.ts +45 -0
  143. package/dist/gauge/gauge.js +219 -0
  144. package/dist/gauge/gauge.js.map +1 -0
  145. package/dist/gauge/index.d.ts +2 -0
  146. package/dist/gauge/index.js +2 -0
  147. package/dist/icon-morph/icon-morph.d.ts +55 -0
  148. package/dist/icon-morph/icon-morph.js +331 -0
  149. package/dist/icon-morph/icon-morph.js.map +1 -0
  150. package/dist/icon-morph/index.d.ts +2 -0
  151. package/dist/icon-morph/index.js +2 -0
  152. package/dist/in-view-title/in-view-title.d.ts +33 -0
  153. package/dist/in-view-title/in-view-title.js +119 -0
  154. package/dist/in-view-title/in-view-title.js.map +1 -0
  155. package/dist/in-view-title/index.d.ts +2 -0
  156. package/dist/in-view-title/index.js +2 -0
  157. package/dist/index.d.ts +71 -6
  158. package/dist/index.js +73 -11
  159. package/dist/input/input.d.ts +22 -0
  160. package/dist/input/input.js +36 -12
  161. package/dist/input/input.js.map +1 -1
  162. package/dist/lib/cn.js +11 -1
  163. package/dist/lib/cn.js.map +1 -1
  164. package/dist/lib/count-to.js +113 -0
  165. package/dist/lib/count-to.js.map +1 -0
  166. package/dist/lib/eye-morph.js +62 -0
  167. package/dist/lib/eye-morph.js.map +1 -0
  168. package/dist/lib/field-message.js +354 -0
  169. package/dist/lib/field-message.js.map +1 -0
  170. package/dist/lib/field-shell.js +81 -0
  171. package/dist/lib/field-shell.js.map +1 -0
  172. package/dist/lib/field-surface.js +11 -8
  173. package/dist/lib/field-surface.js.map +1 -1
  174. package/dist/lib/flag-emoji.js +50 -0
  175. package/dist/lib/flag-emoji.js.map +1 -0
  176. package/dist/lib/flip.js +88 -0
  177. package/dist/lib/flip.js.map +1 -0
  178. package/dist/lib/height-follow.js +59 -0
  179. package/dist/lib/height-follow.js.map +1 -0
  180. package/dist/lib/icon-swap.js +47 -0
  181. package/dist/lib/icon-swap.js.map +1 -0
  182. package/dist/lib/leaving-list.js +57 -0
  183. package/dist/lib/leaving-list.js.map +1 -0
  184. package/dist/lib/menu-highlight.js +64 -0
  185. package/dist/lib/menu-highlight.js.map +1 -0
  186. package/dist/lib/merge-refs.js +3 -3
  187. package/dist/lib/merge-refs.js.map +1 -1
  188. package/dist/lib/morph-text.js +128 -0
  189. package/dist/lib/morph-text.js.map +1 -0
  190. package/dist/lib/motion.js +169 -0
  191. package/dist/lib/motion.js.map +1 -0
  192. package/dist/lib/pausable-timeout.js +67 -0
  193. package/dist/lib/pausable-timeout.js.map +1 -0
  194. package/dist/lib/rolling-value.js +31 -0
  195. package/dist/lib/rolling-value.js.map +1 -0
  196. package/dist/lib/spring-progress.js +114 -0
  197. package/dist/lib/spring-progress.js.map +1 -0
  198. package/dist/lib/spring-tween.js +500 -0
  199. package/dist/lib/spring-tween.js.map +1 -0
  200. package/dist/lib/svg-id.js +10 -0
  201. package/dist/lib/svg-id.js.map +1 -0
  202. package/dist/lib/swap-text.js +79 -0
  203. package/dist/lib/swap-text.js.map +1 -0
  204. package/dist/lib/use-copy-feedback.js +36 -0
  205. package/dist/lib/use-copy-feedback.js.map +1 -0
  206. package/dist/line-chart/index.d.ts +2 -0
  207. package/dist/line-chart/index.js +2 -0
  208. package/dist/line-chart/line-chart.d.ts +76 -0
  209. package/dist/line-chart/line-chart.js +611 -0
  210. package/dist/line-chart/line-chart.js.map +1 -0
  211. package/dist/mention-input/index.d.ts +2 -0
  212. package/dist/mention-input/index.js +2 -0
  213. package/dist/mention-input/mention-input.d.ts +98 -0
  214. package/dist/mention-input/mention-input.js +576 -0
  215. package/dist/mention-input/mention-input.js.map +1 -0
  216. package/dist/menu/menu.d.ts +2 -0
  217. package/dist/menu/menu.js +23 -12
  218. package/dist/menu/menu.js.map +1 -1
  219. package/dist/money-input/index.d.ts +2 -0
  220. package/dist/money-input/index.js +2 -0
  221. package/dist/money-input/money-input.d.ts +65 -0
  222. package/dist/money-input/money-input.js +607 -0
  223. package/dist/money-input/money-input.js.map +1 -0
  224. package/dist/morph-select/index.d.ts +2 -0
  225. package/dist/morph-select/index.js +2 -0
  226. package/dist/morph-select/morph-select.d.ts +71 -0
  227. package/dist/morph-select/morph-select.js +693 -0
  228. package/dist/morph-select/morph-select.js.map +1 -0
  229. package/dist/multi-select/index.d.ts +2 -0
  230. package/dist/multi-select/index.js +2 -0
  231. package/dist/multi-select/multi-select.d.ts +36 -0
  232. package/dist/multi-select/multi-select.js +152 -0
  233. package/dist/multi-select/multi-select.js.map +1 -0
  234. package/dist/number-field/index.d.ts +2 -0
  235. package/dist/number-field/index.js +2 -0
  236. package/dist/number-field/number-field.d.ts +42 -0
  237. package/dist/number-field/number-field.js +199 -0
  238. package/dist/number-field/number-field.js.map +1 -0
  239. package/dist/onboarding-checklist/index.d.ts +2 -0
  240. package/dist/onboarding-checklist/index.js +2 -0
  241. package/dist/onboarding-checklist/onboarding-checklist.d.ts +55 -0
  242. package/dist/onboarding-checklist/onboarding-checklist.js +577 -0
  243. package/dist/onboarding-checklist/onboarding-checklist.js.map +1 -0
  244. package/dist/page-header/index.d.ts +2 -0
  245. package/dist/page-header/index.js +2 -0
  246. package/dist/page-header/page-header.d.ts +75 -0
  247. package/dist/page-header/page-header.js +83 -0
  248. package/dist/page-header/page-header.js.map +1 -0
  249. package/dist/password-field/index.d.ts +2 -0
  250. package/dist/password-field/index.js +2 -0
  251. package/dist/password-field/password-field.d.ts +37 -0
  252. package/dist/password-field/password-field.js +101 -0
  253. package/dist/password-field/password-field.js.map +1 -0
  254. package/dist/password-strength/index.d.ts +2 -0
  255. package/dist/password-strength/index.js +2 -0
  256. package/dist/password-strength/password-strength.d.ts +127 -0
  257. package/dist/password-strength/password-strength.js +455 -0
  258. package/dist/password-strength/password-strength.js.map +1 -0
  259. package/dist/phone-input/index.d.ts +3 -0
  260. package/dist/phone-input/index.js +3 -0
  261. package/dist/phone-input/phone-countries.d.ts +29 -0
  262. package/dist/phone-input/phone-countries.js +170 -0
  263. package/dist/phone-input/phone-countries.js.map +1 -0
  264. package/dist/phone-input/phone-input.d.ts +66 -0
  265. package/dist/phone-input/phone-input.js +440 -0
  266. package/dist/phone-input/phone-input.js.map +1 -0
  267. package/dist/popover/popover.d.ts +20 -0
  268. package/dist/popover/popover.js +33 -9
  269. package/dist/popover/popover.js.map +1 -1
  270. package/dist/progress/progress.js +46 -5
  271. package/dist/progress/progress.js.map +1 -1
  272. package/dist/radio-group/radio-group.js +8 -7
  273. package/dist/radio-group/radio-group.js.map +1 -1
  274. package/dist/reactions/index.d.ts +2 -0
  275. package/dist/reactions/index.js +2 -0
  276. package/dist/reactions/reactions.d.ts +50 -0
  277. package/dist/reactions/reactions.js +315 -0
  278. package/dist/reactions/reactions.js.map +1 -0
  279. package/dist/rich-text-editor/index.d.ts +3 -0
  280. package/dist/rich-text-editor/index.js +2 -0
  281. package/dist/rich-text-editor/rich-text-editor.d.ts +70 -0
  282. package/dist/rich-text-editor/rich-text-editor.js +909 -0
  283. package/dist/rich-text-editor/rich-text-editor.js.map +1 -0
  284. package/dist/rich-text-editor/rich-text-engine.d.ts +16 -0
  285. package/dist/rich-text-editor/rich-text-engine.js +366 -0
  286. package/dist/rich-text-editor/rich-text-engine.js.map +1 -0
  287. package/dist/rich-text-editor/rich-text-html.js +174 -0
  288. package/dist/rich-text-editor/rich-text-html.js.map +1 -0
  289. package/dist/rich-text-editor/rich-text-icons.js +43 -0
  290. package/dist/rich-text-editor/rich-text-icons.js.map +1 -0
  291. package/dist/ridgeline/index.d.ts +2 -0
  292. package/dist/ridgeline/index.js +2 -0
  293. package/dist/ridgeline/ridgeline.d.ts +55 -0
  294. package/dist/ridgeline/ridgeline.js +701 -0
  295. package/dist/ridgeline/ridgeline.js.map +1 -0
  296. package/dist/scroll-area/index.d.ts +2 -2
  297. package/dist/scroll-area/index.js +2 -2
  298. package/dist/scroll-area/scroll-area.d.ts +75 -28
  299. package/dist/scroll-area/scroll-area.js +212 -41
  300. package/dist/scroll-area/scroll-area.js.map +1 -1
  301. package/dist/scrub-input/evaluate.d.ts +14 -0
  302. package/dist/scrub-input/evaluate.js +72 -0
  303. package/dist/scrub-input/evaluate.js.map +1 -0
  304. package/dist/scrub-input/index.d.ts +3 -0
  305. package/dist/scrub-input/index.js +3 -0
  306. package/dist/scrub-input/scrub-input.d.ts +74 -0
  307. package/dist/scrub-input/scrub-input.js +486 -0
  308. package/dist/scrub-input/scrub-input.js.map +1 -0
  309. package/dist/search-field/index.d.ts +2 -0
  310. package/dist/search-field/index.js +2 -0
  311. package/dist/search-field/search-field.d.ts +27 -0
  312. package/dist/search-field/search-field.js +155 -0
  313. package/dist/search-field/search-field.js.map +1 -0
  314. package/dist/select/select.d.ts +3 -1
  315. package/dist/select/select.js +95 -25
  316. package/dist/select/select.js.map +1 -1
  317. package/dist/separator/separator.d.ts +1 -1
  318. package/dist/sheet/sheet.js +4 -2
  319. package/dist/sheet/sheet.js.map +1 -1
  320. package/dist/shortcut-recorder/index.d.ts +3 -0
  321. package/dist/shortcut-recorder/index.js +3 -0
  322. package/dist/shortcut-recorder/shortcut-model.d.ts +27 -0
  323. package/dist/shortcut-recorder/shortcut-model.js +206 -0
  324. package/dist/shortcut-recorder/shortcut-model.js.map +1 -0
  325. package/dist/shortcut-recorder/shortcut-recorder.d.ts +93 -0
  326. package/dist/shortcut-recorder/shortcut-recorder.js +489 -0
  327. package/dist/shortcut-recorder/shortcut-recorder.js.map +1 -0
  328. package/dist/side-nav/index.d.ts +2 -0
  329. package/dist/side-nav/index.js +2 -0
  330. package/dist/side-nav/side-nav.d.ts +72 -0
  331. package/dist/side-nav/side-nav.js +142 -0
  332. package/dist/side-nav/side-nav.js.map +1 -0
  333. package/dist/skeleton/skeleton.d.ts +8 -2
  334. package/dist/skeleton/skeleton.js +6 -2
  335. package/dist/skeleton/skeleton.js.map +1 -1
  336. package/dist/slider/index.d.ts +2 -2
  337. package/dist/slider/slider.d.ts +46 -14
  338. package/dist/slider/slider.js +188 -50
  339. package/dist/slider/slider.js.map +1 -1
  340. package/dist/slope-chart/index.d.ts +2 -0
  341. package/dist/slope-chart/index.js +2 -0
  342. package/dist/slope-chart/slope-chart.d.ts +54 -0
  343. package/dist/slope-chart/slope-chart.js +568 -0
  344. package/dist/slope-chart/slope-chart.js.map +1 -0
  345. package/dist/sparkline/index.d.ts +2 -0
  346. package/dist/sparkline/index.js +2 -0
  347. package/dist/sparkline/sparkline.d.ts +46 -0
  348. package/dist/sparkline/sparkline.js +390 -0
  349. package/dist/sparkline/sparkline.js.map +1 -0
  350. package/dist/split-bar/index.d.ts +2 -0
  351. package/dist/split-bar/index.js +2 -0
  352. package/dist/split-bar/split-bar.d.ts +62 -0
  353. package/dist/split-bar/split-bar.js +513 -0
  354. package/dist/split-bar/split-bar.js.map +1 -0
  355. package/dist/split-button/index.d.ts +2 -0
  356. package/dist/split-button/index.js +2 -0
  357. package/dist/split-button/split-button.d.ts +46 -0
  358. package/dist/split-button/split-button.js +123 -0
  359. package/dist/split-button/split-button.js.map +1 -0
  360. package/dist/stat-card/index.d.ts +2 -0
  361. package/dist/stat-card/index.js +2 -0
  362. package/dist/stat-card/stat-card.d.ts +101 -0
  363. package/dist/stat-card/stat-card.js +249 -0
  364. package/dist/stat-card/stat-card.js.map +1 -0
  365. package/dist/stepper/index.d.ts +2 -0
  366. package/dist/stepper/index.js +2 -0
  367. package/dist/stepper/stepper.d.ts +51 -0
  368. package/dist/stepper/stepper.js +215 -0
  369. package/dist/stepper/stepper.js.map +1 -0
  370. package/dist/streamgraph/index.d.ts +2 -0
  371. package/dist/streamgraph/index.js +2 -0
  372. package/dist/streamgraph/streamgraph.d.ts +70 -0
  373. package/dist/streamgraph/streamgraph.js +613 -0
  374. package/dist/streamgraph/streamgraph.js.map +1 -0
  375. package/dist/styles.css +1 -1
  376. package/dist/swipe-actions/index.d.ts +2 -0
  377. package/dist/swipe-actions/index.js +2 -0
  378. package/dist/swipe-actions/swipe-actions.d.ts +67 -0
  379. package/dist/swipe-actions/swipe-actions.js +552 -0
  380. package/dist/swipe-actions/swipe-actions.js.map +1 -0
  381. package/dist/switch/switch.js +10 -9
  382. package/dist/switch/switch.js.map +1 -1
  383. package/dist/table/index.d.ts +2 -2
  384. package/dist/table/index.js +2 -2
  385. package/dist/table/table.d.ts +87 -4
  386. package/dist/table/table.js +441 -165
  387. package/dist/table/table.js.map +1 -1
  388. package/dist/tabs/tabs.js +143 -7
  389. package/dist/tabs/tabs.js.map +1 -1
  390. package/dist/tag-input/index.d.ts +2 -0
  391. package/dist/tag-input/index.js +2 -0
  392. package/dist/tag-input/tag-input.d.ts +40 -0
  393. package/dist/tag-input/tag-input.js +269 -0
  394. package/dist/tag-input/tag-input.js.map +1 -0
  395. package/dist/text-morph/index.d.ts +2 -0
  396. package/dist/text-morph/index.js +2 -0
  397. package/dist/text-morph/text-morph.d.ts +23 -0
  398. package/dist/text-morph/text-morph.js +21 -0
  399. package/dist/text-morph/text-morph.js.map +1 -0
  400. package/dist/text-reveal/index.d.ts +2 -0
  401. package/dist/text-reveal/index.js +2 -0
  402. package/dist/text-reveal/text-reveal.d.ts +27 -0
  403. package/dist/text-reveal/text-reveal.js +51 -0
  404. package/dist/text-reveal/text-reveal.js.map +1 -0
  405. package/dist/text-shimmer/index.d.ts +2 -0
  406. package/dist/text-shimmer/index.js +2 -0
  407. package/dist/text-shimmer/text-shimmer.d.ts +29 -0
  408. package/dist/text-shimmer/text-shimmer.js +29 -0
  409. package/dist/text-shimmer/text-shimmer.js.map +1 -0
  410. package/dist/textarea/textarea.d.ts +23 -0
  411. package/dist/textarea/textarea.js +24 -2
  412. package/dist/textarea/textarea.js.map +1 -1
  413. package/dist/theme-switch/index.d.ts +2 -0
  414. package/dist/theme-switch/index.js +2 -0
  415. package/dist/theme-switch/theme-switch.d.ts +37 -0
  416. package/dist/theme-switch/theme-switch.js +123 -0
  417. package/dist/theme-switch/theme-switch.js.map +1 -0
  418. package/dist/time-picker/index.d.ts +2 -0
  419. package/dist/time-picker/index.js +2 -0
  420. package/dist/time-picker/time-picker.d.ts +54 -0
  421. package/dist/time-picker/time-picker.js +201 -0
  422. package/dist/time-picker/time-picker.js.map +1 -0
  423. package/dist/toast/index.d.ts +2 -2
  424. package/dist/toast/index.js +2 -2
  425. package/dist/toast/toast.d.ts +6 -0
  426. package/dist/toast/toast.js +98 -36
  427. package/dist/toast/toast.js.map +1 -1
  428. package/dist/tooltip/tooltip.d.ts +10 -3
  429. package/dist/tooltip/tooltip.js +67 -18
  430. package/dist/tooltip/tooltip.js.map +1 -1
  431. package/dist/treemap/index.d.ts +2 -0
  432. package/dist/treemap/index.js +2 -0
  433. package/dist/treemap/treemap.d.ts +53 -0
  434. package/dist/treemap/treemap.js +699 -0
  435. package/dist/treemap/treemap.js.map +1 -0
  436. package/dist/usage-meter/index.d.ts +2 -0
  437. package/dist/usage-meter/index.js +2 -0
  438. package/dist/usage-meter/usage-meter.d.ts +50 -0
  439. package/dist/usage-meter/usage-meter.js +440 -0
  440. package/dist/usage-meter/usage-meter.js.map +1 -0
  441. package/dist/waffle-chart/index.d.ts +2 -0
  442. package/dist/waffle-chart/index.js +2 -0
  443. package/dist/waffle-chart/waffle-chart.d.ts +57 -0
  444. package/dist/waffle-chart/waffle-chart.js +426 -0
  445. package/dist/waffle-chart/waffle-chart.js.map +1 -0
  446. package/package.json +245 -3
@@ -1,9 +1,11 @@
1
1
  "use client";
2
2
  import { cn, cnAll } from "../lib/cn.js";
3
+ import { useAfter, useCountTo } from "../lib/count-to.js";
4
+ import { SwapText } from "../lib/swap-text.js";
3
5
  import { cva } from "class-variance-authority";
4
6
  import { forwardRef } from "react";
5
7
  import { Label, ProgressBar } from "react-aria-components";
6
- import { Fragment, jsx, jsxs } from "react/jsx-runtime";
8
+ import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
7
9
  //#region src/progress/progress.tsx
8
10
  /**
9
11
  * Progress, ported from the legacy shadcn/Radix component.
@@ -37,7 +39,7 @@ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
37
39
  */
38
40
  const progressVariants = cva(["flex w-full flex-col gap-1.5 box-border font-sans"]);
39
41
  const progressTrackVariants = cva(["relative h-2 w-full overflow-hidden rounded-pill box-border", "bg-muted border-(length:--border-width) border-border"]);
40
- const progressFillVariants = cva(["h-full rounded-pill", "motion-safe:transition-[width] motion-safe:duration-(--duration-base) motion-safe:ease-standard"], {
42
+ const progressFillVariants = cva(["h-full rounded-pill", "progress-fill"], {
41
43
  variants: { tone: {
42
44
  neutral: "bg-primary-strong",
43
45
  success: "bg-success-strong",
@@ -47,24 +49,62 @@ const progressFillVariants = cva(["h-full rounded-pill", "motion-safe:transition
47
49
  } },
48
50
  defaultVariants: { tone: "neutral" }
49
51
  });
52
+ /**
53
+ * The printed value. A percentage counts to its new number on the same spring the
54
+ * fill follows, and a check lands beside the final count once the bar is full.
55
+ * Any other text (a price, a step count) is shown as it is.
56
+ */
57
+ function ProgressValue({ percentage, valueText }) {
58
+ const percent = Math.round(percentage);
59
+ const counted = useCountTo(percent);
60
+ const done = useAfter(percent >= 100, 240);
61
+ const isPercent = valueText === `${percent}%`;
62
+ return /* @__PURE__ */ jsxs(Fragment$1, { children: [isPercent ? `${counted}%` : valueText, isPercent ? /* @__PURE__ */ jsx("span", {
63
+ "data-slot": "progress-done",
64
+ className: "ms-1 inline-flex align-middle text-success-strong",
65
+ children: /* @__PURE__ */ jsx(SwapText, {
66
+ swapKey: done ? "done" : "",
67
+ motion: "glyph",
68
+ className: "inline-flex",
69
+ children: /* @__PURE__ */ jsx("svg", {
70
+ viewBox: "0 0 16 16",
71
+ fill: "none",
72
+ stroke: "currentColor",
73
+ strokeWidth: "2",
74
+ strokeLinecap: "round",
75
+ strokeLinejoin: "round",
76
+ className: "size-3.5",
77
+ "aria-hidden": "true",
78
+ children: /* @__PURE__ */ jsx("path", { d: "m3.5 8.5 3 3 6-6" })
79
+ })
80
+ })
81
+ }) : null] });
82
+ }
50
83
  const Progress = forwardRef(function Progress({ label, labelHidden, showValue, tone, trackClassName, fillClassName, className, ...props }, ref) {
51
84
  return /* @__PURE__ */ jsx(ProgressBar, {
52
85
  ref,
53
86
  ...props,
54
87
  "data-slot": "progress",
55
88
  className: cn(progressVariants(), className),
56
- children: ({ percentage, isIndeterminate, valueText }) => /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsxs("div", {
89
+ children: ({ percentage, isIndeterminate, valueText }) => /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsxs("div", {
57
90
  "data-slot": "progress-header",
58
91
  className: cnAll(showValue && "flex items-baseline justify-between gap-2"),
59
92
  children: [/* @__PURE__ */ jsx(Label, {
60
93
  "data-slot": "progress-label",
61
94
  className: cnAll("text-sm leading-(--leading-normal) text-foreground", labelHidden && "sr-only"),
62
- children: label
95
+ children: typeof label === "string" ? /* @__PURE__ */ jsx(SwapText, {
96
+ swapKey: label,
97
+ className: "block",
98
+ children: label
99
+ }) : label
63
100
  }), showValue ? /* @__PURE__ */ jsx("span", {
64
101
  "data-slot": "progress-value",
65
102
  "aria-hidden": "true",
66
103
  className: "text-sm leading-(--leading-normal) text-muted-foreground tabular-nums",
67
- children: valueText
104
+ children: /* @__PURE__ */ jsx(ProgressValue, {
105
+ percentage: percentage ?? 0,
106
+ valueText
107
+ })
68
108
  }) : null]
69
109
  }), /* @__PURE__ */ jsx("div", {
70
110
  "data-slot": "progress-track",
@@ -72,6 +112,7 @@ const Progress = forwardRef(function Progress({ label, labelHidden, showValue, t
72
112
  children: /* @__PURE__ */ jsx("div", {
73
113
  "data-slot": "progress-fill",
74
114
  "data-indeterminate": isIndeterminate || void 0,
115
+ "data-complete": (!tone || tone === "neutral") && !isIndeterminate && (percentage ?? 0) >= 100 ? "" : void 0,
75
116
  className: cn(progressFillVariants({ tone }), fillClassName),
76
117
  style: { width: isIndeterminate ? "0%" : `${percentage ?? 0}%` }
77
118
  })
@@ -1 +1 @@
1
- {"version":3,"file":"progress.js","names":["AriaProgressBar","AriaLabel"],"sources":["../../src/progress/progress.tsx"],"sourcesContent":["\"use client\";\n\nimport { cva, type VariantProps } from \"class-variance-authority\";\nimport { forwardRef, type ReactNode } from \"react\";\nimport {\n Label as AriaLabel,\n ProgressBar as AriaProgressBar,\n type ProgressBarProps as AriaProgressBarProps,\n} from \"react-aria-components\";\n\nimport { cn, cnAll } from \"../lib/cn.js\";\n\n/**\n * Progress, ported from the legacy shadcn/Radix component.\n *\n * Census: seven files import it and only TWO render anything --\n * `ResponseOption.tsx` and `LiveQA.tsx`. Both pass `value` and\n * `className` and nothing else.\n *\n * `label` IS REQUIRED, and that is the point. Neither live call site\n * passes `aria-label`, `aria-labelledby` or an `id` a label targets, and\n * `role=\"progressbar\"` does not take its name from content -- so both\n * rendered progress bars are announced as an unnamed progressbar. React\n * Aria supplies the plumbing for three naming routes but no name of its\n * own: `<ProgressBar value={60}/>` is just as nameless there. Making the\n * prop required is what turns it into something you cannot forget.\n *\n * The migration plan said this component \"adds the missing\n * aria-valuenow\". That is wrong -- Radix already supplies `aria-valuenow`,\n * `aria-valuemin`, `aria-valuemax` and `aria-valuetext`. The missing thing\n * was always the NAME.\n *\n * THE COLOURS ARE NOT THE LEGACY'S. Its track is `bg-primary/20`, an alpha\n * fill that composites against unknown page content and therefore has no\n * fixed contrast pair at all -- over white it is roughly 1.05:1, an\n * invisible boundary. Its indicator is `bg-primary`, 2.44:1 on the page.\n * Here the track is `muted` with a `border` boundary and the fill is\n * `primary-strong`, which puts the fill against the track at 4.61:1 and\n * the track against the page at 5.25:1.\n *\n * Both are in the build-failing contrast contract.\n */\nexport const progressVariants = cva([\"flex w-full flex-col gap-1.5 box-border font-sans\"]);\n\nexport const progressTrackVariants = cva([\n \"relative h-2 w-full overflow-hidden rounded-pill box-border\",\n // muted, NOT `border` as the Switch uses. A switch has no fill; a\n // progress bar does, and primary-strong on a `border` track is 1.01:1 --\n // the two halves are the same grey and the value disappears.\n \"bg-muted border-(length:--border-width) border-border\",\n]);\n\nexport const progressFillVariants = cva(\n [\n \"h-full rounded-pill\",\n \"motion-safe:transition-[width] motion-safe:duration-(--duration-base) motion-safe:ease-standard\",\n ],\n {\n variants: {\n /*\n * What the value MEANS, which for a progress bar is usually a score\n * rather than a completion. The product's bar carried exactly this axis\n * (`default`/`success`/`warning`/`error`/`info`) and reached for it to\n * colour performance metrics.\n *\n * `-strong` throughout: the fill sits on the `muted` track and has to\n * clear the 3:1 non-text floor of WCAG 1.4.11 against it, which is the\n * same reason the track is muted rather than `border` -- primary-strong\n * on a `border` track measures 1.01:1 and the value disappears.\n */\n tone: {\n neutral: \"bg-primary-strong\",\n success: \"bg-success-strong\",\n warning: \"bg-warning-strong\",\n danger: \"bg-destructive-strong\",\n info: \"bg-info-strong\",\n },\n },\n defaultVariants: { tone: \"neutral\" },\n },\n);\n\n/**\n * A determinate or indeterminate progress bar.\n *\n * @example\n * <Progress label=\"Upload\" value={62} />\n * <Progress label=\"Loading results\" />\n */\nexport interface ProgressProps\n extends Omit<AriaProgressBarProps, \"className\" | \"style\" | \"children\">,\n VariantProps<typeof progressFillVariants> {\n /**\n * What is progressing, and the bar's accessible name.\n *\n * **Required.** Both legacy call sites render an unnamed progressbar.\n * Pass `labelHidden` when the surrounding UI already says it.\n */\n label: ReactNode;\n /**\n * Render the label for screen readers only.\n *\n * Both legacy call sites sit beside visible text that was never\n * associated with the bar -- this is how that text becomes the name\n * without printing it twice.\n */\n labelHidden?: boolean;\n /**\n * Print the value beside the label.\n *\n * The text itself comes from React Aria, which already formats it from\n * `value`/`minValue`/`maxValue` and honours `formatOptions` and\n * `valueLabel` -- so a percentage, a currency or \"3 of 7\" are all reachable\n * without a second prop here. It was never RENDERED before: the bar\n * announced its value to assistive tech and showed a sighted reader nothing.\n */\n showValue?: boolean;\n /** Extra classes for the track. */\n trackClassName?: string;\n /** Extra classes for the fill -- a per-call-site colour the tone axis cannot express. */\n fillClassName?: string;\n /** Extra classes. Merged last, so they win conflicts with ours. */\n className?: string;\n}\n\nexport const Progress = forwardRef<HTMLDivElement, ProgressProps>(function Progress(\n { label, labelHidden, showValue, tone, trackClassName, fillClassName, className, ...props },\n ref,\n) {\n return (\n <AriaProgressBar\n ref={ref}\n {...props}\n data-slot=\"progress\"\n className={cn(progressVariants(), className)}\n >\n {({ percentage, isIndeterminate, valueText }) => (\n <>\n {/*\n The row exists only when the value is printed. Wrapping the label\n in a flex row unconditionally would change the label's box for\n every existing consumer to no purpose.\n */}\n <div\n data-slot=\"progress-header\"\n className={cnAll(showValue && \"flex items-baseline justify-between gap-2\")}\n >\n <AriaLabel\n data-slot=\"progress-label\"\n className={cnAll(\n \"text-sm leading-(--leading-normal) text-foreground\",\n labelHidden && \"sr-only\",\n )}\n >\n {label}\n </AriaLabel>\n {showValue ? (\n <span\n data-slot=\"progress-value\"\n // aria-hidden: React Aria already announces the value on the\n // progressbar itself. Without this a screen reader reads it\n // twice, once as the widget's value and once as loose text.\n aria-hidden=\"true\"\n className=\"text-sm leading-(--leading-normal) text-muted-foreground tabular-nums\"\n >\n {valueText}\n </span>\n ) : null}\n </div>\n <div\n data-slot=\"progress-track\"\n className={cn(progressTrackVariants(), trackClassName)}\n >\n {/*\n width, not the legacy translateX(-(100 - value)%). That\n overflowed for value > 100 -- a positive translate slid the\n indicator off to the right, while the announced value stayed\n whatever was passed. React Aria clamps the value, so the two\n can no longer disagree.\n\n An indeterminate bar renders no fill rather than the\n legacy's silent 0%: `value={undefined}` became `(value || 0)`\n there, so a sighted user saw \"0% done\" while a screen reader\n said \"indeterminate\".\n */}\n <div\n data-slot=\"progress-fill\"\n data-indeterminate={isIndeterminate || undefined}\n className={cn(progressFillVariants({ tone }), fillClassName)}\n style={{ width: isIndeterminate ? \"0%\" : `${percentage ?? 0}%` }}\n />\n </div>\n </>\n )}\n </AriaProgressBar>\n );\n});\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA0CA,MAAa,mBAAmB,IAAI,CAAC,mDAAmD,CAAC;AAEzF,MAAa,wBAAwB,IAAI,CACvC,+DAIA,uDACF,CAAC;AAED,MAAa,uBAAuB,IAClC,CACE,uBACA,iGACF,GACA;CACE,UAAU,EAYR,MAAM;EACJ,SAAS;EACT,SAAS;EACT,SAAS;EACT,QAAQ;EACR,MAAM;CACR,EACF;CACA,iBAAiB,EAAE,MAAM,UAAU;AACrC,CACF;AA6CA,MAAa,WAAW,WAA0C,SAAS,SACzE,EAAE,OAAO,aAAa,WAAW,MAAM,gBAAgB,eAAe,WAAW,GAAG,SACpF,KACA;CACA,OACE,oBAACA,aAAD;EACO;EACL,GAAI;EACJ,aAAU;EACV,WAAW,GAAG,iBAAiB,GAAG,SAAS;EAEzC,WAAA,EAAE,YAAY,iBAAiB,gBAC/B,qBAAA,UAAA,EAAA,UAAA,CAME,qBAAC,OAAD;GACE,aAAU;GACV,WAAW,MAAM,aAAa,2CAA2C;GAF3E,UAAA,CAIE,oBAACC,OAAD;IACE,aAAU;IACV,WAAW,MACT,sDACA,eAAe,SACjB;IAEC,UAAA;GACQ,CAAA,GACV,YACC,oBAAC,QAAD;IACE,aAAU;IAIV,eAAY;IACZ,WAAU;IAET,UAAA;GACG,CAAA,IACJ,IACD;EACL,CAAA,GAAA,oBAAC,OAAD;GACE,aAAU;GACV,WAAW,GAAG,sBAAsB,GAAG,cAAc;GAcrD,UAAA,oBAAC,OAAD;IACE,aAAU;IACV,sBAAoB,mBAAmB,KAAA;IACvC,WAAW,GAAG,qBAAqB,EAAE,KAAK,CAAC,GAAG,aAAa;IAC3D,OAAO,EAAE,OAAO,kBAAkB,OAAO,GAAG,cAAc,EAAE,GAAG;GAChE,CAAA;EACE,CAAA,CACL,EAAA,CAAA;CAEW,CAAA;AAErB,CAAC"}
1
+ {"version":3,"file":"progress.js","names":["AriaProgressBar","AriaLabel"],"sources":["../../src/progress/progress.tsx"],"sourcesContent":["\"use client\";\n\nimport { cva, type VariantProps } from \"class-variance-authority\";\nimport { forwardRef, type ReactNode } from \"react\";\nimport {\n Label as AriaLabel,\n ProgressBar as AriaProgressBar,\n type ProgressBarProps as AriaProgressBarProps,\n} from \"react-aria-components\";\n\nimport { cn, cnAll } from \"../lib/cn.js\";\nimport { useAfter, useCountTo } from \"../lib/count-to.js\";\nimport { SwapText } from \"../lib/swap-text.js\";\n\n/**\n * Progress, ported from the legacy shadcn/Radix component.\n *\n * Census: seven files import it and only TWO render anything --\n * `ResponseOption.tsx` and `LiveQA.tsx`. Both pass `value` and\n * `className` and nothing else.\n *\n * `label` IS REQUIRED, and that is the point. Neither live call site\n * passes `aria-label`, `aria-labelledby` or an `id` a label targets, and\n * `role=\"progressbar\"` does not take its name from content -- so both\n * rendered progress bars are announced as an unnamed progressbar. React\n * Aria supplies the plumbing for three naming routes but no name of its\n * own: `<ProgressBar value={60}/>` is just as nameless there. Making the\n * prop required is what turns it into something you cannot forget.\n *\n * The migration plan said this component \"adds the missing\n * aria-valuenow\". That is wrong -- Radix already supplies `aria-valuenow`,\n * `aria-valuemin`, `aria-valuemax` and `aria-valuetext`. The missing thing\n * was always the NAME.\n *\n * THE COLOURS ARE NOT THE LEGACY'S. Its track is `bg-primary/20`, an alpha\n * fill that composites against unknown page content and therefore has no\n * fixed contrast pair at all -- over white it is roughly 1.05:1, an\n * invisible boundary. Its indicator is `bg-primary`, 2.44:1 on the page.\n * Here the track is `muted` with a `border` boundary and the fill is\n * `primary-strong`, which puts the fill against the track at 4.61:1 and\n * the track against the page at 5.25:1.\n *\n * Both are in the build-failing contrast contract.\n */\nexport const progressVariants = cva([\"flex w-full flex-col gap-1.5 box-border font-sans\"]);\n\nexport const progressTrackVariants = cva([\n \"relative h-2 w-full overflow-hidden rounded-pill box-border\",\n // muted, NOT `border` as the Switch uses. A switch has no fill; a\n // progress bar does, and primary-strong on a `border` track is 1.01:1 --\n // the two halves are the same grey and the value disappears.\n \"bg-muted border-(length:--border-width) border-border\",\n]);\n\nexport const progressFillVariants = cva(\n [\n \"h-full rounded-pill\",\n // The fill follows the value on the smooth spring and turns success-coloured when it completes (see progress-fill).\n \"progress-fill\",\n ],\n {\n variants: {\n /*\n * What the value MEANS, which for a progress bar is usually a score\n * rather than a completion. The product's bar carried exactly this axis\n * (`default`/`success`/`warning`/`error`/`info`) and reached for it to\n * colour performance metrics.\n *\n * `-strong` throughout: the fill sits on the `muted` track and has to\n * clear the 3:1 non-text floor of WCAG 1.4.11 against it, which is the\n * same reason the track is muted rather than `border` -- primary-strong\n * on a `border` track measures 1.01:1 and the value disappears.\n */\n tone: {\n neutral: \"bg-primary-strong\",\n success: \"bg-success-strong\",\n warning: \"bg-warning-strong\",\n danger: \"bg-destructive-strong\",\n info: \"bg-info-strong\",\n },\n },\n defaultVariants: { tone: \"neutral\" },\n },\n);\n\n/**\n * A determinate or indeterminate progress bar.\n *\n * @example\n * <Progress label=\"Upload\" value={62} />\n * <Progress label=\"Loading results\" />\n */\nexport interface ProgressProps\n extends Omit<AriaProgressBarProps, \"className\" | \"style\" | \"children\">,\n VariantProps<typeof progressFillVariants> {\n /**\n * What is progressing, and the bar's accessible name.\n *\n * **Required.** Both legacy call sites render an unnamed progressbar.\n * Pass `labelHidden` when the surrounding UI already says it.\n */\n label: ReactNode;\n /**\n * Render the label for screen readers only.\n *\n * Both legacy call sites sit beside visible text that was never\n * associated with the bar -- this is how that text becomes the name\n * without printing it twice.\n */\n labelHidden?: boolean;\n /**\n * Print the value beside the label.\n *\n * The text itself comes from React Aria, which already formats it from\n * `value`/`minValue`/`maxValue` and honours `formatOptions` and\n * `valueLabel` -- so a percentage, a currency or \"3 of 7\" are all reachable\n * without a second prop here. It was never RENDERED before: the bar\n * announced its value to assistive tech and showed a sighted reader nothing.\n */\n showValue?: boolean;\n /** Extra classes for the track. */\n trackClassName?: string;\n /** Extra classes for the fill -- a per-call-site colour the tone axis cannot express. */\n fillClassName?: string;\n /** Extra classes. Merged last, so they win conflicts with ours. */\n className?: string;\n}\n\n/**\n * The printed value. A percentage counts to its new number on the same spring the\n * fill follows, and a check lands beside the final count once the bar is full.\n * Any other text (a price, a step count) is shown as it is.\n */\nfunction ProgressValue({ percentage, valueText }: { percentage: number; valueText: string | undefined }) {\n const percent = Math.round(percentage);\n const counted = useCountTo(percent);\n const done = useAfter(percent >= 100, 240);\n const isPercent = valueText === `${percent}%`;\n return (\n <>\n {isPercent ? `${counted}%` : valueText}\n {isPercent ? (\n <span data-slot=\"progress-done\" className=\"ms-1 inline-flex align-middle text-success-strong\">\n <SwapText swapKey={done ? \"done\" : \"\"} motion=\"glyph\" className=\"inline-flex\">\n <svg\n viewBox=\"0 0 16 16\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n className=\"size-3.5\"\n aria-hidden=\"true\"\n >\n <path d=\"m3.5 8.5 3 3 6-6\" />\n </svg>\n </SwapText>\n </span>\n ) : null}\n </>\n );\n}\n\nexport const Progress = forwardRef<HTMLDivElement, ProgressProps>(function Progress(\n { label, labelHidden, showValue, tone, trackClassName, fillClassName, className, ...props },\n ref,\n) {\n return (\n <AriaProgressBar\n ref={ref}\n {...props}\n data-slot=\"progress\"\n className={cn(progressVariants(), className)}\n >\n {({ percentage, isIndeterminate, valueText }) => (\n <>\n {/*\n The row exists only when the value is printed. Wrapping the label\n in a flex row unconditionally would change the label's box for\n every existing consumer to no purpose.\n */}\n <div\n data-slot=\"progress-header\"\n className={cnAll(showValue && \"flex items-baseline justify-between gap-2\")}\n >\n <AriaLabel\n data-slot=\"progress-label\"\n className={cnAll(\n \"text-sm leading-(--leading-normal) text-foreground\",\n labelHidden && \"sr-only\",\n )}\n >\n {typeof label === \"string\" ? (\n <SwapText swapKey={label} className=\"block\">\n {label}\n </SwapText>\n ) : (\n label\n )}\n </AriaLabel>\n {showValue ? (\n <span\n data-slot=\"progress-value\"\n // aria-hidden: React Aria already announces the value on the\n // progressbar itself. Without this a screen reader reads it\n // twice, once as the widget's value and once as loose text.\n aria-hidden=\"true\"\n className=\"text-sm leading-(--leading-normal) text-muted-foreground tabular-nums\"\n >\n <ProgressValue percentage={percentage ?? 0} valueText={valueText} />\n </span>\n ) : null}\n </div>\n <div\n data-slot=\"progress-track\"\n className={cn(progressTrackVariants(), trackClassName)}\n >\n {/*\n width, not the legacy translateX(-(100 - value)%). That\n overflowed for value > 100 -- a positive translate slid the\n indicator off to the right, while the announced value stayed\n whatever was passed. React Aria clamps the value, so the two\n can no longer disagree.\n\n An indeterminate bar renders no fill rather than the\n legacy's silent 0%: `value={undefined}` became `(value || 0)`\n there, so a sighted user saw \"0% done\" while a screen reader\n said \"indeterminate\".\n */}\n <div\n data-slot=\"progress-fill\"\n data-indeterminate={isIndeterminate || undefined}\n // A default-toned bar turns success-coloured when it completes; an explicit tone is never overridden.\n data-complete={(!tone || tone === \"neutral\") && !isIndeterminate && (percentage ?? 0) >= 100 ? \"\" : undefined}\n className={cn(progressFillVariants({ tone }), fillClassName)}\n style={{ width: isIndeterminate ? \"0%\" : `${percentage ?? 0}%` }}\n />\n </div>\n </>\n )}\n </AriaProgressBar>\n );\n});\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA4CA,MAAa,mBAAmB,IAAI,CAAC,mDAAmD,CAAC;AAEzF,MAAa,wBAAwB,IAAI,CACvC,+DAIA,uDACF,CAAC;AAED,MAAa,uBAAuB,IAClC,CACE,uBAEA,eACF,GACA;CACE,UAAU,EAYR,MAAM;EACJ,SAAS;EACT,SAAS;EACT,SAAS;EACT,QAAQ;EACR,MAAM;CACR,EACF;CACA,iBAAiB,EAAE,MAAM,UAAU;AACrC,CACF;;;;;;AAkDA,SAAS,cAAc,EAAE,YAAY,aAAoE;CACvG,MAAM,UAAU,KAAK,MAAM,UAAU;CACrC,MAAM,UAAU,WAAW,OAAO;CAClC,MAAM,OAAO,SAAS,WAAW,KAAK,GAAG;CACzC,MAAM,YAAY,cAAc,GAAG,QAAQ;CAC3C,OACE,qBAAA,YAAA,EAAA,UAAA,CACG,YAAY,GAAG,QAAQ,KAAK,WAC5B,YACC,oBAAC,QAAD;EAAM,aAAU;EAAgB,WAAU;EACxC,UAAA,oBAAC,UAAD;GAAU,SAAS,OAAO,SAAS;GAAI,QAAO;GAAQ,WAAU;GAC9D,UAAA,oBAAC,OAAD;IACE,SAAQ;IACR,MAAK;IACL,QAAO;IACP,aAAY;IACZ,eAAc;IACd,gBAAe;IACf,WAAU;IACV,eAAY;IAEZ,UAAA,oBAAC,QAAD,EAAM,GAAE,mBAAoB,CAAA;GACzB,CAAA;EACG,CAAA;CACN,CAAA,IACJ,IACJ,EAAA,CAAA;AAEN;AAEA,MAAa,WAAW,WAA0C,SAAS,SACzE,EAAE,OAAO,aAAa,WAAW,MAAM,gBAAgB,eAAe,WAAW,GAAG,SACpF,KACA;CACA,OACE,oBAACA,aAAD;EACO;EACL,GAAI;EACJ,aAAU;EACV,WAAW,GAAG,iBAAiB,GAAG,SAAS;EAEzC,WAAA,EAAE,YAAY,iBAAiB,gBAC/B,qBAAA,YAAA,EAAA,UAAA,CAME,qBAAC,OAAD;GACE,aAAU;GACV,WAAW,MAAM,aAAa,2CAA2C;GAF3E,UAAA,CAIE,oBAACC,OAAD;IACE,aAAU;IACV,WAAW,MACT,sDACA,eAAe,SACjB;IAEC,UAAA,OAAO,UAAU,WAChB,oBAAC,UAAD;KAAU,SAAS;KAAO,WAAU;KACjC,UAAA;IACO,CAAA,IAEV;GAEO,CAAA,GACV,YACC,oBAAC,QAAD;IACE,aAAU;IAIV,eAAY;IACZ,WAAU;IAEV,UAAA,oBAAC,eAAD;KAAe,YAAY,cAAc;KAAc;IAAY,CAAA;GAC/D,CAAA,IACJ,IACD;EACL,CAAA,GAAA,oBAAC,OAAD;GACE,aAAU;GACV,WAAW,GAAG,sBAAsB,GAAG,cAAc;GAcrD,UAAA,oBAAC,OAAD;IACE,aAAU;IACV,sBAAoB,mBAAmB,KAAA;IAEvC,kBAAgB,CAAC,QAAQ,SAAS,cAAc,CAAC,oBAAoB,cAAc,MAAM,MAAM,KAAK,KAAA;IACpG,WAAW,GAAG,qBAAqB,EAAE,KAAK,CAAC,GAAG,aAAa;IAC3D,OAAO,EAAE,OAAO,kBAAkB,OAAO,GAAG,cAAc,EAAE,GAAG;GAChE,CAAA;EACE,CAAA,CACL,EAAA,CAAA;CAEW,CAAA;AAErB,CAAC"}
@@ -4,7 +4,7 @@ import { selectionLabelVariants, selectionRingVariants } from "../lib/selection-
4
4
  import { cva } from "class-variance-authority";
5
5
  import { forwardRef } from "react";
6
6
  import { Radio, RadioGroup } from "react-aria-components";
7
- import { Fragment, jsx, jsxs } from "react/jsx-runtime";
7
+ import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
8
8
  //#region src/radio-group/radio-group.tsx
9
9
  /**
10
10
  * RadioGroup, ported from the legacy shadcn/Radix component onto React Aria.
@@ -28,8 +28,8 @@ 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",
32
- "motion-safe:transition-colors motion-safe:duration-(--duration-fast) motion-safe:ease-standard",
31
+ "bg-surface border-control-edge",
32
+ "radio-mark",
33
33
  "group-data-[selected]:bg-primary group-data-[selected]:border-primary-strong",
34
34
  "group-data-[selected]:text-primary-foreground",
35
35
  selectionRingVariants()
@@ -48,14 +48,15 @@ const Radio$1 = forwardRef(function Radio$2({ className, children, ...props }, r
48
48
  inputRef: ref,
49
49
  "data-slot": "radio",
50
50
  className: cn(cnAll(selectionLabelVariants(), "justify-start"), className),
51
- children: ({ isSelected }) => /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("span", {
51
+ children: ({ isSelected }) => /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx("span", {
52
52
  "aria-hidden": "true",
53
53
  "data-slot": "radio-dot",
54
54
  className: radioVariants(),
55
- children: isSelected ? /* @__PURE__ */ jsx("span", {
55
+ children: /* @__PURE__ */ jsx("span", {
56
56
  "data-slot": "radio-indicator",
57
- className: "size-1.5 rounded-full bg-current"
58
- }) : null
57
+ "data-selected": isSelected || void 0,
58
+ className: "radio-dot size-1.5 rounded-full bg-current"
59
+ })
59
60
  }), children == null ? null : /* @__PURE__ */ jsx("span", {
60
61
  "data-slot": "radio-label",
61
62
  children
@@ -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 \"motion-safe:transition-colors motion-safe:duration-(--duration-fast) motion-safe:ease-standard\",\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 {isSelected ? (\n // Rendered in JS rather than toggled with CSS, so the\n // precedence is explicit and a test can read it from the DOM.\n <span data-slot=\"radio-indicator\" className=\"size-1.5 rounded-full bg-current\" />\n ) : null}\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;CACA;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,UAAA,EAAA,UAAA,CACE,oBAAC,QAAD;GAAM,eAAY;GAAO,aAAU;GAAY,WAAW,cAAc;GACrE,UAAA,aAGC,oBAAC,QAAD;IAAM,aAAU;IAAkB,WAAU;GAAoC,CAAA,IAC9E;EACA,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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"}