@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
@@ -0,0 +1 @@
1
+ {"version":3,"file":"donut-chart.js","names":[],"sources":["../../src/donut-chart/donut-chart.tsx"],"sourcesContent":["\"use client\";\n\nimport {\n forwardRef,\n useCallback,\n useEffect,\n useId,\n useMemo,\n useRef,\n useState,\n type CSSProperties,\n type FocusEvent,\n type KeyboardEvent,\n type MouseEvent,\n type PointerEvent,\n} from \"react\";\n\nimport { cn } from \"../lib/cn.js\";\nimport { useCountTo } from \"../lib/count-to.js\";\nimport { mergeRefs } from \"../lib/merge-refs.js\";\nimport { prefersReducedMotion, useIsomorphicLayoutEffect } from \"../lib/motion.js\";\nimport { live, retarget, REVEAL, SETTLE, SNAPPY, type Live } from \"../lib/spring-tween.js\";\n\n/**\n * DonutChart: a few parts of one whole, when the share of each part matters\n * more than a precise comparison.\n *\n * The ring sweeps open the first time it is seen: every segment leaves the top\n * together and each trailing edge follows a beat behind the one before. New\n * data, or a segment shown or hidden, moves every arc from where it is on\n * screen; a leaving segment closes where it sits. The active segment (hover,\n * focus, selection) slides out along its middle and thickens; the rest dim.\n * The centre rolls like a drum between the total and each segment, and every\n * number counts to its new value. Small parts fold into one \"Other\" segment.\n *\n * Plain SVG and CSS; one small spring tween moves the arcs. The legend is the\n * keyboard path (or the ring itself, without a legend), and a visually hidden\n * table carries every value.\n */\nexport interface DonutChartDatum {\n /** Stable identity. A segment whose key survives a data change keeps its place and colour. */\n key: string;\n label: string;\n value: number;\n /** Any CSS colour. Segments default to chart-1 to chart-4 in data order, then neutral steps. */\n color?: string;\n}\n\nexport interface DonutChartProps {\n data: DonutChartDatum[];\n /** What the whole is, such as \"Visits by source\". Names the chart for assistive technology. */\n label: string;\n /** Unit after values, such as \"visits\". */\n unit?: string;\n formatValue?: (value: number) => string;\n /** Centre label at rest, above the total. @default \"Total\" */\n totalLabel?: string;\n /** Diameter in pixels. The chart scales down to fit narrower containers. @default 208 */\n size?: number;\n /** Ring thickness in pixels. @default 24 */\n thickness?: number;\n /** Segments below this share of the total join \"Other\". @default 0.04 */\n groupBelow?: number;\n /** The most segments drawn, counting \"Other\". @default 6 */\n maxSegments?: number;\n /** @default \"Other\" */\n otherLabel?: string;\n /** Controlled selected segment key. Hover and focus preview other segments without changing it. */\n activeKey?: string | null;\n defaultActiveKey?: string | null;\n onActiveChange?: (key: string | null) => void;\n /** Controlled hidden segment keys. A hidden segment closes and the rest of the ring redistributes. */\n hiddenKeys?: string[];\n defaultHiddenKeys?: string[];\n onHiddenKeysChange?: (keys: string[]) => void;\n /** What clicking a legend row does: show or hide its segment, or pin it as selected. @default \"toggle\" */\n legendAction?: \"toggle\" | \"select\";\n /** The synced legend beside or below the ring. @default true */\n legend?: boolean;\n /** @default \"No data yet\" */\n emptyLabel?: string;\n /** Extra classes for the root. Merged last. */\n className?: string;\n}\n\ntype Slice = { key: string; label: string; value: number; color?: string; members?: DonutChartDatum[] };\ntype Arc = { start: number; end: number };\ntype Segment = { start: Live; end: Live; lift: Live };\n\nconst OTHER = \"__other\";\nconst TAU = Math.PI * 2;\n/** Neutral steps after the four series colours, and the quietest step for Other. */\nconst NEUTRAL = [56, 42, 32];\nconst seriesColor = (index: number) =>\n index < 4\n ? `var(--color-chart-${index + 1})`\n : `color-mix(in oklab, var(--color-foreground) ${NEUTRAL[(index - 4) % NEUTRAL.length]}%, var(--color-surface))`;\nconst OTHER_COLOR = \"color-mix(in oklab, var(--color-foreground) 26%, var(--color-surface))\";\n/** Room outside the ring for the lift; growth, margin, the gap between segments and their corner radius, in pixels. */\nconst LIFT = 4;\nconst GROW = 2;\nconst MARGIN = 7;\nconst GAP = 3;\nconst CORNER = 4;\n/** Stagger of the trailing edges on the first sweep. */\nconst STAGGER_MS = 35;\nconst GROUPED = new Intl.NumberFormat(\"en-US\", { maximumFractionDigits: 1 });\nconst PERCENT = new Intl.NumberFormat(\"en-US\", { style: \"percent\", maximumFractionDigits: 0 });\nconst shareText = (share: number) => (share > 0 && share < 0.01 ? \"<1%\" : PERCENT.format(share));\nconst n2 = (value: number) => (Math.round(value * 100) / 100).toString();\n\n/** Small parts join one \"Other\" segment, but only when at least two would; grouping a single part hides it for nothing. */\nfunction sliceData(data: DonutChartDatum[], groupBelow: number, maxSegments: number, otherLabel: string): Slice[] {\n const parts = data.filter((item) => item.value > 0);\n const total = parts.reduce((sum, item) => sum + item.value, 0);\n if (!total) return [];\n const ranked = [...parts].sort((a, b) => b.value - a.value);\n const keep = new Set(\n ranked.filter((item, rank) => item.value / total >= groupBelow && rank < maxSegments - 1).map((item) => item.key),\n );\n const rest = parts.filter((item) => !keep.has(item.key));\n if (rest.length < 2) return parts.map((item) => ({ ...item }));\n return [\n ...parts.filter((item) => keep.has(item.key)).map((item) => ({ ...item })),\n { key: OTHER, label: otherLabel, value: rest.reduce((sum, item) => sum + item.value, 0), members: rest },\n ];\n}\n\n/** Turns of the ring for each slice in data order; a hidden slice closes to a point where it sits. */\nfunction layout(slices: Slice[], hidden: Set<string>) {\n const total = slices.reduce((sum, item) => sum + (hidden.has(item.key) ? 0 : item.value), 0) || 1;\n let at = 0;\n return new Map(\n slices.map((item) => {\n const arc = { start: at, end: at + (hidden.has(item.key) ? 0 : item.value / total) };\n at = arc.end;\n return [item.key, arc] as const;\n }),\n );\n}\n\n/** Half the width, in pixels, of a sector's outer edge once the gap is cut; below a couple of pixels a segment fades rather than drawing a hairline. */\nfunction breadth(R: number, t0: number, t1: number, gap: number) {\n const turns = t1 - t0;\n if (turns >= 1 - 1e-6) return Infinity;\n return Math.sin(Math.min(turns * Math.PI, Math.PI / 2)) * R - Math.min(gap / 2, ((1 - turns) * TAU * R) / 2);\n}\n\n/**\n * An annular sector between two turns of the ring, clockwise from the top, with\n * rounded corners. Its sides run parallel to the radius at a fixed distance, so\n * the gap between neighbours is the same width from the inner edge to the outer.\n * A thin sector narrows to a wedge and then to nothing, so a closing one never pops.\n */\nfunction sector(c: number, R: number, r: number, t0: number, t1: number, gap: number, corner: number) {\n const turns = t1 - t0;\n if (turns <= 1e-6) return \"\";\n const at = (radius: number, angle: number) => `${n2(c + radius * Math.cos(angle))} ${n2(c + radius * Math.sin(angle))}`;\n if (turns >= 1 - 1e-6) {\n return `M${at(R, -Math.PI / 2)}A${n2(R)} ${n2(R)} 0 1 1 ${at(R, Math.PI / 2)}A${n2(R)} ${n2(R)} 0 1 1 ${at(R, -Math.PI / 2)}ZM${at(r, -Math.PI / 2)}A${n2(r)} ${n2(r)} 0 1 0 ${at(r, Math.PI / 2)}A${n2(r)} ${n2(r)} 0 1 0 ${at(r, -Math.PI / 2)}Z`;\n }\n // Half the gap on each side; a lone segment closes its gap as the rest of the ring empties, so it becomes a whole ring without a jump.\n const p = Math.min(gap / 2, ((1 - turns) * TAU * R) / 2);\n const a0 = t0 * TAU - Math.PI / 2;\n const a1 = t1 * TAU - Math.PI / 2;\n const half = (a1 - a0) / 2;\n const s = Math.sin(Math.min(half, Math.PI / 2));\n if (s * R <= p + 1e-6) return \"\";\n const side = (t: number, angle: number, sign: number) =>\n `${n2(c + t * Math.cos(angle) - sign * p * Math.sin(angle))} ${n2(c + t * Math.sin(angle) + sign * p * Math.cos(angle))}`;\n const band = (R - r) / 2;\n const ro = Math.max(0, Math.min(corner, band, s >= 0.9999 ? Infinity : (s * R - p) / (1 + s)));\n const dO = Math.asin(Math.min(1, (p + ro) / (R - ro)));\n const tO = Math.sqrt(Math.max(0, (R - ro) ** 2 - (p + ro) ** 2));\n const bigO = a1 - a0 - 2 * dO > Math.PI ? 1 : 0;\n let d = `M${side(tO, a0, 1)}`;\n if (ro > 0.01) d += `A${n2(ro)} ${n2(ro)} 0 0 1 ${at(R, a0 + dO)}`;\n d += `A${n2(R)} ${n2(R)} 0 ${bigO} 1 ${at(R, a1 - dO)}`;\n if (ro > 0.01) d += `A${n2(ro)} ${n2(ro)} 0 0 1 ${side(tO, a1, -1)}`;\n if (s * r > p + 1e-6 || s >= 0.9999) {\n const ri = Math.max(0, Math.min(corner, band, s >= 0.9999 ? Infinity : (s * r - p) / (1 - s)));\n const dI = Math.asin(Math.min(1, (p + ri) / (r + ri)));\n const tI = Math.sqrt(Math.max(0, (r + ri) ** 2 - (p + ri) ** 2));\n const bigI = a1 - a0 - 2 * dI > Math.PI ? 1 : 0;\n d += `L${side(tI, a1, -1)}`;\n if (ri > 0.01) d += `A${n2(ri)} ${n2(ri)} 0 0 1 ${at(r, a1 - dI)}`;\n d += `A${n2(r)} ${n2(r)} 0 ${bigI} 0 ${at(r, a0 + dI)}`;\n if (ri > 0.01) d += `A${n2(ri)} ${n2(ri)} 0 0 1 ${side(tI, a0, 1)}`;\n } else d += `L${at(p / s, (a0 + a1) / 2)}`;\n return `${d}Z`;\n}\n\n/**\n * A number that counts to each new value on the smooth spring. useCountTo counts\n * in whole steps, so a fraction (a share) is counted in ten-thousandths.\n */\nfunction Count({ value, format }: { value: number; format: (value: number) => string }) {\n const fine = !Number.isInteger(value);\n const shown = useCountTo(fine ? value * 10000 : value);\n return <>{format(fine ? shown / 10000 : shown)}</>;\n}\n\nexport const DonutChart = forwardRef<HTMLElement, DonutChartProps>(function DonutChart(\n {\n data,\n label,\n unit = \"\",\n formatValue = (value) => GROUPED.format(value),\n totalLabel = \"Total\",\n size = 208,\n thickness = 24,\n groupBelow = 0.04,\n maxSegments = 6,\n otherLabel = \"Other\",\n activeKey,\n defaultActiveKey = null,\n onActiveChange,\n hiddenKeys,\n defaultHiddenKeys,\n onHiddenKeysChange,\n legendAction = \"toggle\",\n legend = true,\n emptyLabel = \"No data yet\",\n className,\n },\n ref,\n) {\n const figure = useRef<HTMLElement | null>(null);\n const refs = useMemo(() => mergeRefs(ref, figure), [ref]);\n const legendId = useId();\n const slices = sliceData(data, groupBelow, maxSegments, otherLabel);\n\n const [ownHidden, setOwnHidden] = useState<string[]>(defaultHiddenKeys ?? []);\n const hiddenList = hiddenKeys ?? ownHidden;\n const hidden = new Set(hiddenList.filter((key) => slices.some((item) => item.key === key)));\n const visible = slices.filter((item) => !hidden.has(item.key));\n const total = visible.reduce((sum, item) => sum + item.value, 0);\n const signature = slices.map((item) => `${item.key}:${item.value}${hidden.has(item.key) ? \"h\" : \"\"}`).join(\"|\");\n const suffix = unit ? ` ${unit}` : \"\";\n\n // Colours follow a key from the first time it is seen, so a segment keeps its colour through every dataset.\n const [palette, setPalette] = useState(() => new Map(data.map((item, index) => [item.key, index])));\n if (data.some((item) => !palette.has(item.key))) {\n const next = new Map(palette);\n for (const item of data) if (!next.has(item.key)) next.set(item.key, next.size);\n setPalette(next);\n }\n const colorFor = (item: Slice) =>\n item.color ?? (item.key === OTHER ? OTHER_COLOR : seriesColor(palette.get(item.key) ?? 0));\n\n // Leaving segments stay drawn in their old place while they close, then drop out.\n const [drawn, setDrawn] = useState<Slice[]>(slices);\n const [seen, setSeen] = useState(signature);\n if (seen !== signature) {\n setSeen(signature);\n const next = [...slices];\n drawn.forEach((item, index) => {\n if (next.some((slice) => slice.key === item.key)) return;\n const before = drawn\n .slice(0, index)\n .reverse()\n .find((prior) => next.some((slice) => slice.key === prior.key));\n next.splice(before ? next.findIndex((slice) => slice.key === before.key) + 1 : 0, 0, { ...item, value: 0 });\n });\n setDrawn(next);\n }\n\n // Selection is controlled or not; hover and focus preview on top of it.\n const [ownActive, setOwnActive] = useState<string | null>(defaultActiveKey);\n const selected = activeKey !== undefined ? activeKey : ownActive;\n const [preview, setPreview] = useState<string | null>(null);\n const select = (key: string | null) => {\n if (activeKey === undefined) setOwnActive(key);\n onActiveChange?.(key);\n };\n const current = [preview, selected].find((key) => key && visible.some((item) => item.key === key)) ?? null;\n const activeSlice = visible.find((item) => item.key === current) ?? null;\n\n const [announcement, setAnnouncement] = useState(\"\");\n const setHidden = (key: string) => {\n const isHidden = hidden.has(key);\n if (!isHidden && visible.length <= 1) return;\n const next = isHidden ? hiddenList.filter((item) => item !== key) : [...hiddenList, key];\n if (hiddenKeys === undefined) setOwnHidden(next);\n onHiddenKeysChange?.(next);\n const item = slices.find((slice) => slice.key === key);\n const nextTotal = slices.reduce((sum, slice) => sum + (next.includes(slice.key) ? 0 : slice.value), 0);\n if (item) setAnnouncement(`${item.label} ${isHidden ? \"shown\" : \"hidden\"}. ${totalLabel} ${formatValue(nextTotal)}${suffix}.`);\n };\n\n // Geometry in the chart's own units; the SVG scales with its container.\n const c = size / 2;\n const outer = c - MARGIN;\n const inner = Math.max(8, outer - thickness);\n const corner = Math.min(CORNER, thickness / 4);\n const geometry = useRef({ c, outer, inner, corner });\n const segments = useRef(new Map<string, Segment>());\n const svg = useRef<SVGSVGElement>(null);\n\n /** One writer: every path's shape, opacity and lift, straight onto the elements. */\n const paint = useCallback(() => {\n const root = svg.current;\n if (!root) return;\n const { c, outer, inner, corner } = geometry.current;\n for (const node of root.querySelectorAll<SVGPathElement>(\"[data-key]\")) {\n const segment = segments.current.get(node.dataset.key!);\n if (!segment) {\n node.setAttribute(\"d\", \"\");\n continue;\n }\n const start = segment.start.value;\n const end = segment.end.value;\n const lift = Math.max(0, segment.lift.value);\n const R = outer + lift * GROW;\n node.setAttribute(\"d\", sector(c, R, inner - lift * GROW * 0.5, start, end, GAP, corner));\n node.setAttribute(\"fill-opacity\", n2(Math.min(1, Math.max(0, (breadth(R, start, end, GAP) - 1) / 3))));\n const middle = ((start + end) / 2) * TAU - Math.PI / 2;\n node.setAttribute(\"transform\", `translate(${n2(Math.cos(middle) * lift * LIFT)} ${n2(Math.sin(middle) * lift * LIFT)})`);\n }\n }, []);\n useIsomorphicLayoutEffect(() => {\n geometry.current = { c, outer, inner, corner };\n paint();\n });\n useEffect(\n () => () => {\n for (const segment of segments.current.values()) {\n segment.start.stop();\n segment.end.stop();\n segment.lift.stop();\n }\n },\n [],\n );\n\n // The ring sweeps once a third of it is in view.\n const [shown, setShown] = useState(false);\n useEffect(() => {\n const node = figure.current;\n if (!node || prefersReducedMotion() || typeof IntersectionObserver === \"undefined\") {\n setShown(true);\n return;\n }\n const observer = new IntersectionObserver(\n ([entry]) => {\n if (!entry?.isIntersecting) return;\n observer.disconnect();\n setShown(true);\n },\n { threshold: 0.35 },\n );\n observer.observe(node);\n return () => observer.disconnect();\n }, []);\n\n const targets = useRef(new Map<string, Arc>());\n const latest = useRef({ drawn, slices });\n useIsomorphicLayoutEffect(() => {\n latest.current = { drawn, slices };\n });\n\n // New data, or a segment shown or hidden, moves every arc from wherever it is on screen.\n useEffect(() => {\n if (!shown) return;\n const { drawn: order, slices: now } = latest.current;\n const goal = layout(now, hidden);\n targets.current = goal;\n const first = segments.current.size === 0;\n const place = (index: number) => {\n const before = order\n .slice(0, index)\n .reverse()\n .find((item) => goal.has(item.key));\n return before ? goal.get(before.key)!.end : 0;\n };\n const drop = (key: string) => {\n if (latest.current.slices.some((item) => item.key === key)) return;\n segments.current.delete(key);\n setDrawn((list) => list.filter((item) => item.key !== key));\n };\n order.forEach((item, index) => {\n const leaving = !goal.has(item.key);\n const to = goal.get(item.key) ?? { start: place(index), end: place(index) };\n let segment = segments.current.get(item.key);\n if (!segment) {\n // A new segment opens from the edge of the one before it, as it is right now.\n const prior = order\n .slice(0, index)\n .reverse()\n .map((entry) => segments.current.get(entry.key))\n .find(Boolean);\n const from = first ? 0 : prior ? prior.end.value : 0;\n segment = { start: live(from), end: live(from), lift: live(0) };\n segments.current.set(item.key, segment);\n }\n if (first) {\n // One sweep: every segment leaves the top together, and each trailing edge follows a beat behind the one before.\n retarget(segment.start, to.start, REVEAL, paint);\n retarget(segment.end, to.end, REVEAL, paint, 40 + index * STAGGER_MS);\n return;\n }\n retarget(segment.start, to.start, SETTLE, paint);\n retarget(segment.end, to.end, SETTLE, paint, 0, leaving ? () => drop(item.key) : undefined);\n });\n paint();\n // The signature stands for the slices and the hidden set.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [shown, signature, paint]);\n\n // The active segment slides out along its middle and thickens; the one it replaces settles back.\n useEffect(() => {\n for (const [key, segment] of segments.current) retarget(segment.lift, key === current ? 1 : 0, SNAPPY, paint);\n }, [current, drawn, shown, paint]);\n\n // One pointer handler for the whole ring: the angle picks the segment, so crossing a gap never drops the hover.\n const hit = (event: { clientX: number; clientY: number }) => {\n const box = svg.current?.getBoundingClientRect();\n if (!box?.width) return undefined;\n const scale = size / box.width;\n const x = (event.clientX - box.left) * scale - c;\n const y = (event.clientY - box.top) * scale - c;\n const radius = Math.hypot(x, y);\n if (radius < inner - 6 || radius > c) return null;\n let turn = (Math.atan2(y, x) + Math.PI / 2) / TAU;\n if (turn < 0) turn += 1;\n for (const [key, arc] of targets.current) {\n if (arc.end > arc.start && turn >= arc.start && turn < arc.end && visible.some((item) => item.key === key)) return key;\n }\n return undefined;\n };\n const onRingMove = (event: PointerEvent<SVGSVGElement>) => {\n if (event.pointerType !== \"mouse\") return;\n const key = hit(event);\n if (key !== undefined && key !== preview) setPreview(key);\n };\n const onRingClick = (event: MouseEvent<SVGSVGElement>) => {\n const key = hit(event);\n if (key) select(selected === key ? null : key);\n };\n\n const format = (value: number) => `${formatValue(value)}${suffix}`;\n const describe = (item: Slice) =>\n `${item.label}, ${format(item.value)}, ${shareText(item.value / (total || 1))}${\n item.members ? `. Includes ${item.members.map((member) => member.label).join(\", \")}` : \"\"\n }`;\n\n // Arrow keys walk the segments, in the legend or on the ring itself when there is no legend.\n const rows = useRef<(HTMLButtonElement | null)[]>([]);\n const step = (key: string, index: number, count: number) => {\n const moves: Record<string, number> = {\n ArrowDown: index + 1,\n ArrowRight: index + 1,\n ArrowUp: index - 1,\n ArrowLeft: index - 1,\n Home: 0,\n End: count - 1,\n };\n return moves[key];\n };\n const onLegendKey = (event: KeyboardEvent<HTMLButtonElement>, index: number) => {\n if (event.key === \"Escape\" && selected) {\n event.preventDefault();\n select(null);\n return;\n }\n const next = step(event.key, index, slices.length);\n if (next === undefined) return;\n event.preventDefault();\n rows.current[(next + slices.length) % slices.length]?.focus();\n };\n const onRingKey = (event: KeyboardEvent<HTMLDivElement>) => {\n if (!visible.length) return;\n if (event.key === \"Escape\") {\n event.preventDefault();\n setPreview(null);\n if (selected) select(null);\n return;\n }\n if ((event.key === \"Enter\" || event.key === \" \") && current) {\n event.preventDefault();\n select(selected === current ? null : current);\n return;\n }\n const index = visible.findIndex((item) => item.key === current);\n const next = step(\n event.key,\n index < 0 ? (event.key.endsWith(\"Up\") || event.key.endsWith(\"Left\") ? 0 : -1) : index,\n visible.length,\n );\n if (next === undefined) return;\n event.preventDefault();\n const item = visible[(next + visible.length) % visible.length]!;\n setPreview(item.key);\n setAnnouncement(describe(item));\n };\n const onLegendBlur = (event: FocusEvent<HTMLButtonElement>) => {\n if (!(event.relatedTarget instanceof Node && event.currentTarget.closest(\"ul\")?.contains(event.relatedTarget))) {\n setPreview(null);\n }\n };\n\n // One readout per segment plus the total; the active one rolls in from the side it sits on.\n const identity = activeSlice ? activeSlice.key : \"__total\";\n const readouts = [\n { key: \"__total\", label: total ? totalLabel : emptyLabel, value: total, meta: null as Slice | null },\n ...slices.map((item) => ({ key: item.key, label: item.label, value: item.value, meta: item })),\n ];\n const activeIndex = Math.max(0, readouts.findIndex((item) => item.key === identity));\n const summary = total ? `${label}. ${totalLabel} ${format(total)}. ${visible.map(describe).join(\". \")}.` : `${label}. ${emptyLabel}.`;\n const members = (item: Slice) =>\n item.members\n ? item.members.length > 2\n ? `${item.members\n .slice(0, 2)\n .map((member) => member.label)\n .join(\", \")} and ${item.members.length - 2} more`\n : item.members.map((member) => member.label).join(\" and \")\n : null;\n\n return (\n <figure\n ref={refs}\n data-slot=\"donut-chart\"\n aria-label={label}\n data-legend={legend || undefined}\n className={cn(\"donut-chart box-border font-sans text-foreground\", className)}\n >\n <div\n data-slot=\"donut-chart-ring\"\n className=\"donut-chart-ring\"\n style={{ width: size, \"--inner\": `${((inner * 2) / size) * 100}%` } as CSSProperties}\n tabIndex={legend ? undefined : 0}\n role={legend ? undefined : \"application\"}\n aria-label={legend ? undefined : `${label}. Use the arrow keys to read each segment.`}\n onKeyDown={legend ? undefined : onRingKey}\n onBlur={legend ? undefined : () => setPreview(null)}\n >\n <svg\n ref={svg}\n viewBox={`0 0 ${size} ${size}`}\n aria-hidden=\"true\"\n focusable=\"false\"\n onPointerMove={onRingMove}\n onPointerLeave={() => setPreview(null)}\n onClick={onRingClick}\n data-hovering={current !== null || undefined}\n className=\"block size-full overflow-visible data-[hovering]:cursor-pointer\"\n >\n <circle\n className=\"donut-chart-track\"\n cx={c}\n cy={c}\n r={(outer + inner) / 2}\n strokeWidth={outer - inner}\n data-idle={!shown || !total || undefined}\n />\n {drawn.map((item) => (\n <path\n key={item.key}\n data-key={item.key}\n data-active={item.key === current || undefined}\n data-dim={(current !== null && item.key !== current) || undefined}\n className=\"donut-chart-segment\"\n style={{ \"--slice\": colorFor(item) } as CSSProperties}\n />\n ))}\n </svg>\n <div aria-hidden=\"true\" className=\"donut-chart-center\">\n {readouts.map((item, index) => (\n <span\n key={item.key}\n data-place={Math.sign(index - activeIndex)}\n className=\"donut-chart-readout\"\n >\n <span className=\"block w-full min-h-5 truncate text-xs text-text-secondary\">{item.label}</span>\n <span className=\"block max-w-full min-h-8 truncate text-h3 font-medium tabular-nums\">\n {item.value || item.meta ? <Count value={item.value} format={formatValue} /> : \" \"}\n </span>\n <span className=\"block w-full min-h-5 truncate text-xs tabular-nums text-muted-foreground\">\n {item.meta ? (\n <>\n <Count value={total && !hidden.has(item.key) ? item.value / total : 0} format={shareText} /> of{\" \"}\n {totalLabel.toLowerCase()}\n </>\n ) : (\n unit || \" \"\n )}\n </span>\n </span>\n ))}\n </div>\n </div>\n {legend && slices.length > 0 ? (\n <ul\n id={legendId}\n aria-label={`${label}, segments${legendAction === \"toggle\" ? \". Press a segment to show or hide it\" : \"\"}`}\n onPointerLeave={() => setPreview(null)}\n className=\"m-0 -mb-0.5 grid list-none p-0\"\n >\n {slices.map((item, index) => {\n const off = hidden.has(item.key);\n return (\n <li key={item.key} className=\"overflow-hidden motion-safe:animate-fade-in\">\n <button\n ref={(node) => {\n rows.current[index] = node;\n }}\n type=\"button\"\n data-slot=\"donut-chart-row\"\n aria-pressed={legendAction === \"toggle\" ? !off : selected === item.key}\n aria-label={off ? `${item.label}, hidden` : describe(item)}\n data-active={item.key === current || undefined}\n data-pinned={selected === item.key || undefined}\n data-hidden={off || undefined}\n style={{ \"--slice\": colorFor(item) } as CSSProperties}\n onClick={() => (legendAction === \"toggle\" ? setHidden(item.key) : select(selected === item.key ? null : item.key))}\n onPointerEnter={(event) => {\n if (event.pointerType === \"mouse\") setPreview(off ? null : item.key);\n }}\n onFocus={(event) => {\n if (event.currentTarget.matches(\":focus-visible\")) setPreview(off ? null : item.key);\n }}\n onBlur={onLegendBlur}\n onKeyDown={(event) => onLegendKey(event, index)}\n className=\"donut-chart-row\"\n >\n <span aria-hidden=\"true\" className=\"donut-chart-dot\" />\n <span className=\"grid min-w-0\">\n <span className=\"donut-chart-row-label truncate\">{item.label}</span>\n {item.members ? <span className=\"truncate text-xs text-muted-foreground\">{members(item)}</span> : null}\n </span>\n <span aria-hidden=\"true\" className=\"donut-chart-row-value justify-self-end tabular-nums text-text-secondary\">\n <Count value={item.value} format={formatValue} />\n </span>\n <span aria-hidden=\"true\" className=\"donut-chart-row-value justify-self-end font-medium tabular-nums\">\n <Count value={off || !total ? 0 : item.value / total} format={shareText} />\n </span>\n </button>\n </li>\n );\n })}\n </ul>\n ) : null}\n <p className=\"sr-only\">{summary}</p>\n <p className=\"sr-only\" aria-live=\"polite\">\n {announcement}\n </p>\n {total > 0 ? (\n <div className=\"sr-only\">\n <table>\n <caption>{label}</caption>\n <thead>\n <tr>\n <th scope=\"col\">Segment</th>\n <th scope=\"col\">Value</th>\n <th scope=\"col\">Share</th>\n </tr>\n </thead>\n <tbody>\n {visible\n .flatMap((item) =>\n item.members\n ? item.members.map((member) => ({ key: member.key, label: `${member.label} (${otherLabel})`, value: member.value }))\n : [item],\n )\n .map((item) => (\n <tr key={item.key}>\n <th scope=\"row\">{item.label}</th>\n <td>{format(item.value)}</td>\n <td>{shareText(item.value / total)}</td>\n </tr>\n ))}\n </tbody>\n </table>\n </div>\n ) : null}\n </figure>\n );\n});\n"],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ import { DonutChart, DonutChartDatum, DonutChartProps } from "./donut-chart.js";
2
+ export { DonutChart, type DonutChartDatum, type DonutChartProps };
@@ -0,0 +1,2 @@
1
+ import { DonutChart } from "./donut-chart.js";
2
+ export { DonutChart };
@@ -1,9 +1,9 @@
1
1
  import { ComponentPropsWithoutRef, ReactNode } from "react";
2
2
  //#region src/empty-state/empty-state.d.ts
3
3
  /**
4
- * EmptyState: what a list shows when it has nothing to show. One line of
5
- * muted text, centred in a bordered box the width of the list, with an
6
- * optional next step under it.
4
+ * EmptyState: what a list shows when it has nothing to show. An icon in a
5
+ * circle, a short title, a one- or two-line description and an optional next
6
+ * step, centred on the page surface with room to breathe.
7
7
  *
8
8
  * Presentational, so there is no React Aria base. It exists because every
9
9
  * list has an empty case, and without a shared component each screen picks its
@@ -15,21 +15,32 @@ import { ComponentPropsWithoutRef, ReactNode } from "react";
15
15
  * simply never be emitted.
16
16
  */
17
17
  export declare const emptyStateVariants: (props?: import("class-variance-authority/types").ClassProp | undefined) => string;
18
+ export declare const emptyStateTitleVariants: (props?: import("class-variance-authority/types").ClassProp | undefined) => string;
18
19
  export declare const emptyStateTextVariants: (props?: import("class-variance-authority/types").ClassProp | undefined) => string;
19
20
  export declare const emptyStateActionVariants: (props?: import("class-variance-authority/types").ClassProp | undefined) => string;
20
21
  /**
21
22
  * An empty list's message.
22
23
  *
23
24
  * @example
24
- * <EmptyState action={<Button size="sm">Clear filters</Button>}>
25
- * Nothing matches these filters.
26
- * </EmptyState>
25
+ * <EmptyState
26
+ * icon={<Folder aria-hidden="true" />}
27
+ * title="No saved views"
28
+ * description="Save a set of filters as a view to come back to it later."
29
+ * action={<Button>Create view</Button>}
30
+ * />
27
31
  */
28
- export interface EmptyStateProps extends ComponentPropsWithoutRef<"div"> {
29
- /** Why the list is empty, in one short statement. */
30
- children: ReactNode;
32
+ export interface EmptyStateProps extends Omit<ComponentPropsWithoutRef<"div">, "title"> {
31
33
  /**
32
- * A glyph above the message. It settles in once when the state first appears
34
+ * A short heading above the description. When it is set, `description` (or
35
+ * `children`) is the supporting text under it.
36
+ */
37
+ title?: ReactNode;
38
+ /** One or two lines of supporting text. `children` is the same slot and works the same way. */
39
+ description?: ReactNode;
40
+ /** Why the list is empty, in one short statement. The message when there is no `description`. */
41
+ children?: ReactNode;
42
+ /**
43
+ * A glyph above the message, inside a circle. It settles in once when the state first appears
33
44
  * (small and transparent to rest over 480ms); a different icon component later
34
45
  * swaps in place, and the same one re-rendering stays still. Mark it `aria-hidden`.
35
46
  */
@@ -1,5 +1,5 @@
1
1
  "use client";
2
- import { cn } from "../lib/cn.js";
2
+ import { cn, cnAll } from "../lib/cn.js";
3
3
  import { SwapText } from "../lib/swap-text.js";
4
4
  import { useHeightFollow } from "../lib/height-follow.js";
5
5
  import { cva } from "class-variance-authority";
@@ -7,9 +7,9 @@ import { forwardRef, isValidElement, useRef } from "react";
7
7
  import { jsx, jsxs } from "react/jsx-runtime";
8
8
  //#region src/empty-state/empty-state.tsx
9
9
  /**
10
- * EmptyState: what a list shows when it has nothing to show. One line of
11
- * muted text, centred in a bordered box the width of the list, with an
12
- * optional next step under it.
10
+ * EmptyState: what a list shows when it has nothing to show. An icon in a
11
+ * circle, a short title, a one- or two-line description and an optional next
12
+ * step, centred on the page surface with room to breathe.
13
13
  *
14
14
  * Presentational, so there is no React Aria base. It exists because every
15
15
  * list has an empty case, and without a shared component each screen picks its
@@ -20,23 +20,26 @@ import { jsx, jsxs } from "react/jsx-runtime";
20
20
  * source text, so a class assembled at runtime is invisible to it and would
21
21
  * simply never be emitted.
22
22
  */
23
- const emptyStateVariants = cva([
24
- "block px-4 py-8 text-center",
25
- "rounded-md border-(length:--border-width) border-solid border-border",
26
- "box-border font-sans"
27
- ]);
28
- const emptyStateTextVariants = cva("m-0 text-sm leading-(--leading-normal) text-muted-foreground");
29
- const emptyStateActionVariants = cva("mt-3 flex justify-center gap-2");
23
+ const emptyStateVariants = cva(["flex flex-col items-center px-5 py-12 text-center", "box-border font-sans text-foreground"]);
24
+ const emptyStateTitleVariants = cva("m-0 mt-5 text-base leading-(--leading-normal) font-medium text-foreground");
25
+ const emptyStateTextVariants = cva("mx-auto mt-0 mb-0 max-w-72 text-sm leading-(--leading-normal) text-pretty text-text-secondary");
26
+ const emptyStateActionVariants = cva("mt-5 flex flex-wrap justify-center gap-3");
30
27
  /** The name an icon swaps under: a different component swaps; the same one re-rendering does not. */
31
28
  function iconKey(icon) {
32
29
  if (!isValidElement(icon)) return "icon";
33
30
  const type = icon.type;
34
31
  return typeof type === "string" ? type : type.displayName ?? type.name ?? "icon";
35
32
  }
36
- const EmptyState = forwardRef(function EmptyState({ children, icon, action, className, ...props }, ref) {
33
+ const EmptyState = forwardRef(function EmptyState({ title, description, children, icon, action, className, ...props }, ref) {
37
34
  const frame = useRef(null);
38
- const text = useRef(null);
39
- useHeightFollow(frame, text, typeof children === "string" ? children : "");
35
+ const copy = useRef(null);
36
+ const message = description ?? children;
37
+ const text = (value) => typeof value === "string" ? /* @__PURE__ */ jsx(SwapText, {
38
+ swapKey: value,
39
+ className: "block",
40
+ children: value
41
+ }) : value;
42
+ useHeightFollow(frame, copy, `${typeof title === "string" ? title : ""}\n${typeof message === "string" ? message : ""}`);
40
43
  return /* @__PURE__ */ jsxs("div", {
41
44
  ref,
42
45
  ...props,
@@ -46,7 +49,7 @@ const EmptyState = forwardRef(function EmptyState({ children, icon, action, clas
46
49
  icon === void 0 || icon === null ? null : /* @__PURE__ */ jsx("div", {
47
50
  "data-slot": "empty-state-icon",
48
51
  "aria-hidden": "true",
49
- className: "mx-auto mb-3 grid size-10 place-items-center rounded-md border-(length:--border-width) border-border bg-muted text-muted-foreground motion-safe:animate-empty-state-icon [&_svg]:size-5",
52
+ className: "grid size-12 place-items-center rounded-circle border-(length:--border-width) border-border bg-muted text-text-secondary motion-safe:animate-empty-state-icon [&_svg]:size-6",
50
53
  children: /* @__PURE__ */ jsx(SwapText, {
51
54
  swapKey: iconKey(icon),
52
55
  motion: "glyph",
@@ -57,16 +60,19 @@ const EmptyState = forwardRef(function EmptyState({ children, icon, action, clas
57
60
  /* @__PURE__ */ jsx("div", {
58
61
  ref: frame,
59
62
  "data-slot": "empty-state-copy",
60
- className: "height-follow",
61
- children: /* @__PURE__ */ jsx("p", {
62
- ref: text,
63
- "data-slot": "empty-state-text",
64
- className: emptyStateTextVariants(),
65
- children: typeof children === "string" ? /* @__PURE__ */ jsx(SwapText, {
66
- swapKey: children,
67
- className: "block",
68
- children
69
- }) : children
63
+ className: "height-follow w-full",
64
+ children: /* @__PURE__ */ jsxs("div", {
65
+ ref: copy,
66
+ className: "flow-root",
67
+ children: [title == null ? null : /* @__PURE__ */ jsx("h3", {
68
+ "data-slot": "empty-state-title",
69
+ className: emptyStateTitleVariants(),
70
+ children: text(title)
71
+ }), message == null ? null : /* @__PURE__ */ jsx("p", {
72
+ "data-slot": "empty-state-text",
73
+ className: cnAll(emptyStateTextVariants(), title != null ? "mt-2" : icon != null && "mt-5"),
74
+ children: text(message)
75
+ })]
70
76
  })
71
77
  }),
72
78
  action == null ? null : /* @__PURE__ */ jsx("div", {
@@ -78,6 +84,6 @@ const EmptyState = forwardRef(function EmptyState({ children, icon, action, clas
78
84
  });
79
85
  });
80
86
  //#endregion
81
- export { EmptyState, emptyStateActionVariants, emptyStateTextVariants, emptyStateVariants };
87
+ export { EmptyState, emptyStateActionVariants, emptyStateTextVariants, emptyStateTitleVariants, emptyStateVariants };
82
88
 
83
89
  //# sourceMappingURL=empty-state.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"empty-state.js","names":[],"sources":["../../src/empty-state/empty-state.tsx"],"sourcesContent":["\"use client\";\n\nimport { cva } from \"class-variance-authority\";\nimport {\n forwardRef,\n isValidElement,\n useRef,\n type ComponentPropsWithoutRef,\n type ReactNode,\n} from \"react\";\n\nimport { cn } from \"../lib/cn.js\";\nimport { useHeightFollow } from \"../lib/height-follow.js\";\nimport { SwapText } from \"../lib/swap-text.js\";\n\n/**\n * EmptyState: what a list shows when it has nothing to show. One line of\n * muted text, centred in a bordered box the width of the list, with an\n * optional next step under it.\n *\n * Presentational, so there is no React Aria base. It exists because every\n * list has an empty case, and without a shared component each screen picks its\n * own padding, border and alignment, so \"nothing here\" looks different on\n * every page.\n *\n * Every string in the cva blocks below is a STATIC literal. Tailwind scans\n * source text, so a class assembled at runtime is invisible to it and would\n * simply never be emitted.\n */\nexport const emptyStateVariants = cva([\n \"block px-4 py-8 text-center\",\n \"rounded-md border-(length:--border-width) border-solid border-border\",\n // The reset in @seekalfred/styles is opt-in, so a consumer may load none.\n \"box-border font-sans\",\n]);\n\nexport const emptyStateTextVariants = cva(\n \"m-0 text-sm leading-(--leading-normal) text-muted-foreground\",\n);\n\nexport const emptyStateActionVariants = cva(\"mt-3 flex justify-center gap-2\");\n\n/**\n * An empty list's message.\n *\n * @example\n * <EmptyState action={<Button size=\"sm\">Clear filters</Button>}>\n * Nothing matches these filters.\n * </EmptyState>\n */\nexport interface EmptyStateProps extends ComponentPropsWithoutRef<\"div\"> {\n /** Why the list is empty, in one short statement. */\n children: ReactNode;\n /**\n * A glyph above the message. It settles in once when the state first appears\n * (small and transparent to rest over 480ms); a different icon component later\n * swaps in place, and the same one re-rendering stays still. Mark it `aria-hidden`.\n */\n icon?: ReactNode;\n /** A next step under the text: a button or a link. Omitted from the DOM when absent. */\n action?: ReactNode;\n /** Extra classes for the box. Merged last, so they win conflicts with ours. */\n className?: string;\n}\n\n/** The name an icon swaps under: a different component swaps; the same one re-rendering does not. */\nfunction iconKey(icon: ReactNode) {\n if (!isValidElement(icon)) return \"icon\";\n const type = icon.type as string | { displayName?: string; name?: string };\n return typeof type === \"string\" ? type : (type.displayName ?? type.name ?? \"icon\");\n}\n\nexport const EmptyState = forwardRef<HTMLDivElement, EmptyStateProps>(function EmptyState(\n { children, icon, action, className, ...props },\n ref,\n) {\n const frame = useRef<HTMLDivElement>(null);\n const text = useRef<HTMLParagraphElement>(null);\n // The message column follows its height when the message changes.\n useHeightFollow(frame, text, typeof children === \"string\" ? children : \"\");\n return (\n <div\n ref={ref}\n {...props}\n data-slot=\"empty-state\"\n className={cn(emptyStateVariants(), className)}\n >\n {icon === undefined || icon === null ? null : (\n <div\n data-slot=\"empty-state-icon\"\n aria-hidden=\"true\"\n className=\"mx-auto mb-3 grid size-10 place-items-center rounded-md border-(length:--border-width) border-border bg-muted text-muted-foreground motion-safe:animate-empty-state-icon [&_svg]:size-5\"\n >\n <SwapText swapKey={iconKey(icon)} motion=\"glyph\" className=\"grid place-items-center\">\n {icon}\n </SwapText>\n </div>\n )}\n <div ref={frame} data-slot=\"empty-state-copy\" className=\"height-follow\">\n <p ref={text} data-slot=\"empty-state-text\" className={emptyStateTextVariants()}>\n {typeof children === \"string\" ? (\n <SwapText swapKey={children} className=\"block\">\n {children}\n </SwapText>\n ) : (\n children\n )}\n </p>\n </div>\n {action == null ? null : (\n <div data-slot=\"empty-state-action\" className={emptyStateActionVariants()}>\n {action}\n </div>\n )}\n </div>\n );\n});\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;AA6BA,MAAa,qBAAqB,IAAI;CACpC;CACA;CAEA;AACF,CAAC;AAED,MAAa,yBAAyB,IACpC,8DACF;AAEA,MAAa,2BAA2B,IAAI,gCAAgC;;AA0B5E,SAAS,QAAQ,MAAiB;CAChC,IAAI,CAAC,eAAe,IAAI,GAAG,OAAO;CAClC,MAAM,OAAO,KAAK;CAClB,OAAO,OAAO,SAAS,WAAW,OAAQ,KAAK,eAAe,KAAK,QAAQ;AAC7E;AAEA,MAAa,aAAa,WAA4C,SAAS,WAC7E,EAAE,UAAU,MAAM,QAAQ,WAAW,GAAG,SACxC,KACA;CACA,MAAM,QAAQ,OAAuB,IAAI;CACzC,MAAM,OAAO,OAA6B,IAAI;CAE9C,gBAAgB,OAAO,MAAM,OAAO,aAAa,WAAW,WAAW,EAAE;CACzE,OACE,qBAAC,OAAD;EACO;EACL,GAAI;EACJ,aAAU;EACV,WAAW,GAAG,mBAAmB,GAAG,SAAS;EAJ/C,UAAA;GAMG,SAAS,KAAA,KAAa,SAAS,OAAO,OACrC,oBAAC,OAAD;IACE,aAAU;IACV,eAAY;IACZ,WAAU;IAEV,UAAA,oBAAC,UAAD;KAAU,SAAS,QAAQ,IAAI;KAAG,QAAO;KAAQ,WAAU;KACxD,UAAA;IACO,CAAA;GACP,CAAA;GAEP,oBAAC,OAAD;IAAK,KAAK;IAAO,aAAU;IAAmB,WAAU;IACtD,UAAA,oBAAC,KAAD;KAAG,KAAK;KAAM,aAAU;KAAmB,WAAW,uBAAuB;KAC1E,UAAA,OAAO,aAAa,WACnB,oBAAC,UAAD;MAAU,SAAS;MAAU,WAAU;MACpC;KACO,CAAA,IAEV;IAED,CAAA;GACA,CAAA;GACJ,UAAU,OAAO,OAChB,oBAAC,OAAD;IAAK,aAAU;IAAqB,WAAW,yBAAyB;IACrE,UAAA;GACE,CAAA;EAEJ;;AAET,CAAC"}
1
+ {"version":3,"file":"empty-state.js","names":[],"sources":["../../src/empty-state/empty-state.tsx"],"sourcesContent":["\"use client\";\n\nimport { cva } from \"class-variance-authority\";\nimport {\n forwardRef,\n isValidElement,\n useRef,\n type ComponentPropsWithoutRef,\n type ReactNode,\n} from \"react\";\n\nimport { cn, cnAll } from \"../lib/cn.js\";\nimport { useHeightFollow } from \"../lib/height-follow.js\";\nimport { SwapText } from \"../lib/swap-text.js\";\n\n/**\n * EmptyState: what a list shows when it has nothing to show. An icon in a\n * circle, a short title, a one- or two-line description and an optional next\n * step, centred on the page surface with room to breathe.\n *\n * Presentational, so there is no React Aria base. It exists because every\n * list has an empty case, and without a shared component each screen picks its\n * own padding, border and alignment, so \"nothing here\" looks different on\n * every page.\n *\n * Every string in the cva blocks below is a STATIC literal. Tailwind scans\n * source text, so a class assembled at runtime is invisible to it and would\n * simply never be emitted.\n */\nexport const emptyStateVariants = cva([\n \"flex flex-col items-center px-5 py-12 text-center\",\n // The reset in @seekalfred/styles is opt-in, so a consumer may load none.\n // Its own text colour too, so it reads the same on any page background.\n \"box-border font-sans text-foreground\",\n]);\n\nexport const emptyStateTitleVariants = cva(\n \"m-0 mt-5 text-base leading-(--leading-normal) font-medium text-foreground\",\n);\n\nexport const emptyStateTextVariants = cva(\n \"mx-auto mt-0 mb-0 max-w-72 text-sm leading-(--leading-normal) text-pretty text-text-secondary\",\n);\n\nexport const emptyStateActionVariants = cva(\"mt-5 flex flex-wrap justify-center gap-3\");\n\n/**\n * An empty list's message.\n *\n * @example\n * <EmptyState\n * icon={<Folder aria-hidden=\"true\" />}\n * title=\"No saved views\"\n * description=\"Save a set of filters as a view to come back to it later.\"\n * action={<Button>Create view</Button>}\n * />\n */\nexport interface EmptyStateProps extends Omit<ComponentPropsWithoutRef<\"div\">, \"title\"> {\n /**\n * A short heading above the description. When it is set, `description` (or\n * `children`) is the supporting text under it.\n */\n title?: ReactNode;\n /** One or two lines of supporting text. `children` is the same slot and works the same way. */\n description?: ReactNode;\n /** Why the list is empty, in one short statement. The message when there is no `description`. */\n children?: ReactNode;\n /**\n * A glyph above the message, inside a circle. It settles in once when the state first appears\n * (small and transparent to rest over 480ms); a different icon component later\n * swaps in place, and the same one re-rendering stays still. Mark it `aria-hidden`.\n */\n icon?: ReactNode;\n /** A next step under the text: a button or a link. Omitted from the DOM when absent. */\n action?: ReactNode;\n /** Extra classes for the box. Merged last, so they win conflicts with ours. */\n className?: string;\n}\n\n/** The name an icon swaps under: a different component swaps; the same one re-rendering does not. */\nfunction iconKey(icon: ReactNode) {\n if (!isValidElement(icon)) return \"icon\";\n const type = icon.type as string | { displayName?: string; name?: string };\n return typeof type === \"string\" ? type : (type.displayName ?? type.name ?? \"icon\");\n}\n\nexport const EmptyState = forwardRef<HTMLDivElement, EmptyStateProps>(function EmptyState(\n { title, description, children, icon, action, className, ...props },\n ref,\n) {\n const frame = useRef<HTMLDivElement>(null);\n const copy = useRef<HTMLDivElement>(null);\n const message = description ?? children;\n const text = (value: ReactNode) =>\n typeof value === \"string\" ? (\n <SwapText swapKey={value} className=\"block\">\n {value}\n </SwapText>\n ) : (\n value\n );\n // The copy column follows its height when the title or the message changes.\n useHeightFollow(\n frame,\n copy,\n `${typeof title === \"string\" ? title : \"\"}\\n${typeof message === \"string\" ? message : \"\"}`,\n );\n return (\n <div\n ref={ref}\n {...props}\n data-slot=\"empty-state\"\n className={cn(emptyStateVariants(), className)}\n >\n {icon === undefined || icon === null ? null : (\n <div\n data-slot=\"empty-state-icon\"\n aria-hidden=\"true\"\n className=\"grid size-12 place-items-center rounded-circle border-(length:--border-width) border-border bg-muted text-text-secondary motion-safe:animate-empty-state-icon [&_svg]:size-6\"\n >\n <SwapText swapKey={iconKey(icon)} motion=\"glyph\" className=\"grid place-items-center\">\n {icon}\n </SwapText>\n </div>\n )}\n <div ref={frame} data-slot=\"empty-state-copy\" className=\"height-follow w-full\">\n <div ref={copy} className=\"flow-root\">\n {title == null ? null : (\n <h3 data-slot=\"empty-state-title\" className={emptyStateTitleVariants()}>\n {text(title)}\n </h3>\n )}\n {message == null ? null : (\n <p\n data-slot=\"empty-state-text\"\n className={cnAll(\n emptyStateTextVariants(),\n // Under the title it sits close; straight under the icon it clears it.\n title != null ? \"mt-2\" : icon != null && \"mt-5\",\n )}\n >\n {text(message)}\n </p>\n )}\n </div>\n </div>\n {action == null ? null : (\n <div data-slot=\"empty-state-action\" className={emptyStateActionVariants()}>\n {action}\n </div>\n )}\n </div>\n );\n});\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;AA6BA,MAAa,qBAAqB,IAAI,CACpC,qDAGA,sCACF,CAAC;AAED,MAAa,0BAA0B,IACrC,2EACF;AAEA,MAAa,yBAAyB,IACpC,+FACF;AAEA,MAAa,2BAA2B,IAAI,0CAA0C;;AAoCtF,SAAS,QAAQ,MAAiB;CAChC,IAAI,CAAC,eAAe,IAAI,GAAG,OAAO;CAClC,MAAM,OAAO,KAAK;CAClB,OAAO,OAAO,SAAS,WAAW,OAAQ,KAAK,eAAe,KAAK,QAAQ;AAC7E;AAEA,MAAa,aAAa,WAA4C,SAAS,WAC7E,EAAE,OAAO,aAAa,UAAU,MAAM,QAAQ,WAAW,GAAG,SAC5D,KACA;CACA,MAAM,QAAQ,OAAuB,IAAI;CACzC,MAAM,OAAO,OAAuB,IAAI;CACxC,MAAM,UAAU,eAAe;CAC/B,MAAM,QAAQ,UACZ,OAAO,UAAU,WACf,oBAAC,UAAD;EAAU,SAAS;EAAO,WAAU;EACjC,UAAA;CACO,CAAA,IAEV;CAGJ,gBACE,OACA,MACA,GAAG,OAAO,UAAU,WAAW,QAAQ,GAAG,IAAI,OAAO,YAAY,WAAW,UAAU,IACxF;CACA,OACE,qBAAC,OAAD;EACO;EACL,GAAI;EACJ,aAAU;EACV,WAAW,GAAG,mBAAmB,GAAG,SAAS;EAJ/C,UAAA;GAMG,SAAS,KAAA,KAAa,SAAS,OAAO,OACrC,oBAAC,OAAD;IACE,aAAU;IACV,eAAY;IACZ,WAAU;IAEV,UAAA,oBAAC,UAAD;KAAU,SAAS,QAAQ,IAAI;KAAG,QAAO;KAAQ,WAAU;KACxD,UAAA;IACO,CAAA;GACP,CAAA;GAEP,oBAAC,OAAD;IAAK,KAAK;IAAO,aAAU;IAAmB,WAAU;IACtD,UAAA,qBAAC,OAAD;KAAK,KAAK;KAAM,WAAU;KAA1B,UAAA,CACG,SAAS,OAAO,OACf,oBAAC,MAAD;MAAI,aAAU;MAAoB,WAAW,wBAAwB;MAClE,UAAA,KAAK,KAAK;KACT,CAAA,GAEL,WAAW,OAAO,OACjB,oBAAC,KAAD;MACE,aAAU;MACV,WAAW,MACT,uBAAuB,GAEvB,SAAS,OAAO,SAAS,QAAQ,QAAQ,MAC3C;MAEC,UAAA,KAAK,OAAO;KACZ,CAAA,CAEF;;GACF,CAAA;GACJ,UAAU,OAAO,OAChB,oBAAC,OAAD;IAAK,aAAU;IAAqB,WAAW,yBAAyB;IACrE,UAAA;GACE,CAAA;EAEJ;;AAET,CAAC"}
@@ -1,2 +1,2 @@
1
- import { EmptyState, EmptyStateProps, emptyStateActionVariants, emptyStateTextVariants, emptyStateVariants } from "./empty-state.js";
2
- export { EmptyState, type EmptyStateProps, emptyStateActionVariants, emptyStateTextVariants, emptyStateVariants };
1
+ import { EmptyState, EmptyStateProps, emptyStateActionVariants, emptyStateTextVariants, emptyStateTitleVariants, emptyStateVariants } from "./empty-state.js";
2
+ export { EmptyState, type EmptyStateProps, emptyStateActionVariants, emptyStateTextVariants, emptyStateTitleVariants, emptyStateVariants };
@@ -1,2 +1,2 @@
1
- import { EmptyState, emptyStateActionVariants, emptyStateTextVariants, emptyStateVariants } from "./empty-state.js";
2
- export { EmptyState, emptyStateActionVariants, emptyStateTextVariants, emptyStateVariants };
1
+ import { EmptyState, emptyStateActionVariants, emptyStateTextVariants, emptyStateTitleVariants, emptyStateVariants } from "./empty-state.js";
2
+ export { EmptyState, emptyStateActionVariants, emptyStateTextVariants, emptyStateTitleVariants, emptyStateVariants };
@@ -28,7 +28,7 @@ import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
28
28
  */
29
29
  const fileDropZoneVariants = cva([
30
30
  "flex flex-col items-center gap-2 px-4 py-8 text-center",
31
- "rounded-md border-(length:--border-width) border-dashed border-border",
31
+ "rounded-panel border-(length:--border-width) border-dashed border-border",
32
32
  "box-border font-sans text-foreground",
33
33
  "outline-none",
34
34
  "drop-zone-motion",
@@ -1 +1 @@
1
- {"version":3,"file":"file-drop-zone.js","names":["AriaDropZone","AriaFileTrigger","Button"],"sources":["../../src/file-drop-zone/file-drop-zone.tsx"],"sourcesContent":["\"use client\";\n\nimport { cva } from \"class-variance-authority\";\nimport { forwardRef, useMemo, useRef, type ReactNode } from \"react\";\nimport {\n DropZone as AriaDropZone,\n FileTrigger as AriaFileTrigger,\n type DropZoneProps as AriaDropZoneProps,\n type FileDropItem,\n} from \"react-aria-components\";\n\nimport { Button } from \"../button/button.js\";\nimport { cn } from \"../lib/cn.js\";\nimport { mergeRefs } from \"../lib/merge-refs.js\";\nimport { SwapText } from \"../lib/swap-text.js\";\n\n/**\n * FileDropZone: a dashed area a file can be dropped on, with a \"Choose file\"\n * button that opens the system's file dialog.\n *\n * Built on React Aria's `DropZone` and `FileTrigger`. The browser's own\n * `<input type=\"file\">` (\"Choose Files / No file chosen\") is never shown: its\n * look differs on every browser and it cannot be styled. FileTrigger keeps a\n * real file input, visually hidden and out of the Tab order, and opens it when\n * the button is pressed, so the keyboard path is Tab to the button, then Enter\n * or Space.\n *\n * The zone states what it takes before a file is chosen: `hint` is where the\n * accepted types and the size limit go.\n *\n * Every string in the cva blocks below is a STATIC literal. Tailwind scans\n * source text, so a class assembled at runtime is invisible to it and would\n * simply never be emitted.\n */\nexport const fileDropZoneVariants = cva([\n \"flex flex-col items-center gap-2 px-4 py-8 text-center\",\n \"rounded-md border-(length:--border-width) border-dashed border-border\",\n // The reset in @seekalfred/styles is opt-in, so a consumer may load none.\n \"box-border font-sans text-foreground\",\n \"outline-none\",\n // Fill and edge ease, a file over it breathes and light follows the pointer (see drop-zone-motion).\n \"drop-zone-motion\",\n \"data-[hovered]:bg-muted\",\n \"data-[drop-target]:border-primary data-[drop-target]:bg-muted\",\n \"data-[focus-visible]:outline-solid\",\n \"data-[focus-visible]:outline-(length:--focus-ring-width)\",\n \"data-[focus-visible]:outline-offset-(--focus-ring-offset)\",\n \"data-[focus-visible]:outline-(--color-ring)\",\n // Disabled dims only the button (its own disabled look). Dimming the whole\n // zone took the prompt and hint below the 4.5:1 contrast floor.\n \"data-[disabled]:cursor-not-allowed\",\n]);\n\nexport const fileDropZonePromptVariants = cva(\"m-0 text-sm leading-(--leading-normal)\");\n\nexport const fileDropZoneHintVariants = cva(\n \"m-0 text-xs leading-(--leading-normal) text-muted-foreground\",\n);\n\n/**\n * A place to drop or choose files.\n *\n * @example\n * <FileDropZone\n * label=\"Contract\"\n * prompt=\"Drop a contract here\"\n * hint=\"PDF only, up to 20 MB.\"\n * acceptedFileTypes={[\"application/pdf\"]}\n * onFiles={(files) => upload(files[0])}\n * />\n */\nexport interface FileDropZoneProps {\n /** What the file is. The drop zone's accessible name. */\n label: string;\n /** The zone's main line: what to drop here, or the chosen file's name. */\n prompt: ReactNode;\n /**\n * What the prompt says while a file is over the zone. When it is given the\n * prompt swaps to it (the new text rises in, the old lifts away) and back again.\n */\n dropPrompt?: ReactNode;\n /** The types and size accepted, and anything else to know before choosing. */\n hint?: ReactNode;\n /**\n * MIME types or extensions the file dialog offers, as for a native\n * `accept`. A drop is not filtered: what was dropped is handed over and the\n * consumer decides what it can read.\n */\n acceptedFileTypes?: string[];\n /** Take several files at once. Otherwise only the first is handed over. */\n allowsMultiple?: boolean;\n /** Called with the chosen or dropped files. */\n onFiles: (files: File[]) => void;\n /**\n * The button's text.\n *\n * @default \"Choose file\", or \"Choose files\" with `allowsMultiple`\n */\n buttonLabel?: string;\n /** Take no file: the button is disabled and a drop is refused. */\n isDisabled?: boolean;\n /** Extra classes for the zone. Merged last, so they win conflicts with ours. */\n className?: string;\n}\n\ntype DropEvent = Parameters<NonNullable<AriaDropZoneProps[\"onDrop\"]>>[0];\n\nasync function filesOf(event: DropEvent): Promise<File[]> {\n const items = event.items.filter((item): item is FileDropItem => item.kind === \"file\");\n return Promise.all(items.map((item) => item.getFile()));\n}\n\nexport const FileDropZone = forwardRef<HTMLDivElement, FileDropZoneProps>(function FileDropZone(\n {\n label,\n prompt,\n dropPrompt,\n hint,\n acceptedFileTypes,\n allowsMultiple = false,\n onFiles,\n buttonLabel,\n isDisabled = false,\n className,\n },\n ref,\n) {\n const hand = (files: File[]) => {\n if (isDisabled || files.length === 0) return;\n onFiles(allowsMultiple ? files : files.slice(0, 1));\n };\n const zone = useRef<HTMLDivElement | null>(null);\n const refs = useMemo(() => mergeRefs(ref, zone), [ref]);\n // Where the file is over the zone, as percentages, for the light that follows it.\n const lean = (x: number, y: number) => {\n const el = zone.current;\n if (!el) return;\n el.style.setProperty(\"--drop-x\", `${(x / Math.max(1, el.offsetWidth)) * 100}%`);\n el.style.setProperty(\"--drop-y\", `${(y / Math.max(1, el.offsetHeight)) * 100}%`);\n };\n return (\n <AriaDropZone\n ref={refs}\n aria-label={label}\n data-slot=\"file-drop-zone\"\n isDisabled={isDisabled}\n className={cn(fileDropZoneVariants(), className)}\n onDropEnter={(event) => lean(event.x, event.y)}\n onDropMove={(event) => lean(event.x, event.y)}\n onDrop={(event) => {\n void filesOf(event).then(hand);\n }}\n >\n {({ isDropTarget }) => (\n <>\n {/* The wash and the edge light sit under the content and follow the file. */}\n <span aria-hidden=\"true\" data-slot=\"file-drop-zone-wash\" className=\"drop-wash\" />\n <span aria-hidden=\"true\" data-slot=\"file-drop-zone-glow\" className=\"drop-glow\" />\n <p data-slot=\"file-drop-zone-prompt\" className={cn(fileDropZonePromptVariants(), \"relative\")}>\n {dropPrompt == null ? (\n prompt\n ) : (\n <SwapText swapKey={isDropTarget ? \"drop\" : \"prompt\"} className=\"block\">\n {isDropTarget ? dropPrompt : prompt}\n </SwapText>\n )}\n </p>\n <AriaFileTrigger\n acceptedFileTypes={acceptedFileTypes}\n allowsMultiple={allowsMultiple}\n onSelect={(list) => hand(Array.from(list ?? []))}\n >\n <Button variant=\"outlineNeutral\" size=\"sm\" isDisabled={isDisabled} className=\"relative\">\n {buttonLabel ?? (allowsMultiple ? \"Choose files\" : \"Choose file\")}\n </Button>\n </AriaFileTrigger>\n {hint == null ? null : (\n <p\n data-slot=\"file-drop-zone-hint\"\n className={cn(fileDropZoneHintVariants(), \"relative\")}\n >\n {hint}\n </p>\n )}\n </>\n )}\n </AriaDropZone>\n );\n});\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAkCA,MAAa,uBAAuB,IAAI;CACtC;CACA;CAEA;CACA;CAEA;CACA;CACA;CACA;CACA;CACA;CACA;CAGA;AACF,CAAC;AAED,MAAa,6BAA6B,IAAI,wCAAwC;AAEtF,MAAa,2BAA2B,IACtC,8DACF;AAkDA,eAAe,QAAQ,OAAmC;CACxD,MAAM,QAAQ,MAAM,MAAM,QAAQ,SAA+B,KAAK,SAAS,MAAM;CACrF,OAAO,QAAQ,IAAI,MAAM,KAAK,SAAS,KAAK,QAAQ,CAAC,CAAC;AACxD;AAEA,MAAa,eAAe,WAA8C,SAAS,aACjF,EACE,OACA,QACA,YACA,MACA,mBACA,iBAAiB,OACjB,SACA,aACA,aAAa,OACb,aAEF,KACA;CACA,MAAM,QAAQ,UAAkB;EAC9B,IAAI,cAAc,MAAM,WAAW,GAAG;EACtC,QAAQ,iBAAiB,QAAQ,MAAM,MAAM,GAAG,CAAC,CAAC;CACpD;CACA,MAAM,OAAO,OAA8B,IAAI;CAC/C,MAAM,OAAO,cAAc,UAAU,KAAK,IAAI,GAAG,CAAC,GAAG,CAAC;CAEtD,MAAM,QAAQ,GAAW,MAAc;EACrC,MAAM,KAAK,KAAK;EAChB,IAAI,CAAC,IAAI;EACT,GAAG,MAAM,YAAY,YAAY,GAAI,IAAI,KAAK,IAAI,GAAG,GAAG,WAAW,IAAK,IAAI,EAAE;EAC9E,GAAG,MAAM,YAAY,YAAY,GAAI,IAAI,KAAK,IAAI,GAAG,GAAG,YAAY,IAAK,IAAI,EAAE;CACjF;CACA,OACE,oBAACA,UAAD;EACE,KAAK;EACL,cAAY;EACZ,aAAU;EACE;EACZ,WAAW,GAAG,qBAAqB,GAAG,SAAS;EAC/C,cAAc,UAAU,KAAK,MAAM,GAAG,MAAM,CAAC;EAC7C,aAAa,UAAU,KAAK,MAAM,GAAG,MAAM,CAAC;EAC5C,SAAS,UAAU;GACjB,QAAa,KAAK,CAAC,CAAC,KAAK,IAAI;EAC/B;EAEE,WAAA,EAAE,mBACF,qBAAA,YAAA,EAAA,UAAA;GAEE,oBAAC,QAAD;IAAM,eAAY;IAAO,aAAU;IAAsB,WAAU;GAAa,CAAA;GAChF,oBAAC,QAAD;IAAM,eAAY;IAAO,aAAU;IAAsB,WAAU;GAAa,CAAA;GAChF,oBAAC,KAAD;IAAG,aAAU;IAAwB,WAAW,GAAG,2BAA2B,GAAG,UAAU;IACxF,UAAA,cAAc,OACb,SAEA,oBAAC,UAAD;KAAU,SAAS,eAAe,SAAS;KAAU,WAAU;KAC5D,UAAA,eAAe,aAAa;IACrB,CAAA;GAEX,CAAA;GACH,oBAACC,aAAD;IACqB;IACH;IAChB,WAAW,SAAS,KAAK,MAAM,KAAK,QAAQ,CAAC,CAAC,CAAC;IAE/C,UAAA,oBAACC,UAAD;KAAQ,SAAQ;KAAiB,MAAK;KAAiB;KAAY,WAAU;KAC1E,UAAA,gBAAgB,iBAAiB,iBAAiB;IAC7C,CAAA;GACO,CAAA;GAChB,QAAQ,OAAO,OACd,oBAAC,KAAD;IACE,aAAU;IACV,WAAW,GAAG,yBAAyB,GAAG,UAAU;IAEnD,UAAA;GACA,CAAA;EAEL,EAAA,CAAA;CAEQ,CAAA;AAElB,CAAC"}
1
+ {"version":3,"file":"file-drop-zone.js","names":["AriaDropZone","AriaFileTrigger","Button"],"sources":["../../src/file-drop-zone/file-drop-zone.tsx"],"sourcesContent":["\"use client\";\n\nimport { cva } from \"class-variance-authority\";\nimport { forwardRef, useMemo, useRef, type ReactNode } from \"react\";\nimport {\n DropZone as AriaDropZone,\n FileTrigger as AriaFileTrigger,\n type DropZoneProps as AriaDropZoneProps,\n type FileDropItem,\n} from \"react-aria-components\";\n\nimport { Button } from \"../button/button.js\";\nimport { cn } from \"../lib/cn.js\";\nimport { mergeRefs } from \"../lib/merge-refs.js\";\nimport { SwapText } from \"../lib/swap-text.js\";\n\n/**\n * FileDropZone: a dashed area a file can be dropped on, with a \"Choose file\"\n * button that opens the system's file dialog.\n *\n * Built on React Aria's `DropZone` and `FileTrigger`. The browser's own\n * `<input type=\"file\">` (\"Choose Files / No file chosen\") is never shown: its\n * look differs on every browser and it cannot be styled. FileTrigger keeps a\n * real file input, visually hidden and out of the Tab order, and opens it when\n * the button is pressed, so the keyboard path is Tab to the button, then Enter\n * or Space.\n *\n * The zone states what it takes before a file is chosen: `hint` is where the\n * accepted types and the size limit go.\n *\n * Every string in the cva blocks below is a STATIC literal. Tailwind scans\n * source text, so a class assembled at runtime is invisible to it and would\n * simply never be emitted.\n */\nexport const fileDropZoneVariants = cva([\n \"flex flex-col items-center gap-2 px-4 py-8 text-center\",\n \"rounded-panel border-(length:--border-width) border-dashed border-border\",\n // The reset in @seekalfred/styles is opt-in, so a consumer may load none.\n \"box-border font-sans text-foreground\",\n \"outline-none\",\n // Fill and edge ease, a file over it breathes and light follows the pointer (see drop-zone-motion).\n \"drop-zone-motion\",\n \"data-[hovered]:bg-muted\",\n \"data-[drop-target]:border-primary data-[drop-target]:bg-muted\",\n \"data-[focus-visible]:outline-solid\",\n \"data-[focus-visible]:outline-(length:--focus-ring-width)\",\n \"data-[focus-visible]:outline-offset-(--focus-ring-offset)\",\n \"data-[focus-visible]:outline-(--color-ring)\",\n // Disabled dims only the button (its own disabled look). Dimming the whole\n // zone took the prompt and hint below the 4.5:1 contrast floor.\n \"data-[disabled]:cursor-not-allowed\",\n]);\n\nexport const fileDropZonePromptVariants = cva(\"m-0 text-sm leading-(--leading-normal)\");\n\nexport const fileDropZoneHintVariants = cva(\n \"m-0 text-xs leading-(--leading-normal) text-muted-foreground\",\n);\n\n/**\n * A place to drop or choose files.\n *\n * @example\n * <FileDropZone\n * label=\"Contract\"\n * prompt=\"Drop a contract here\"\n * hint=\"PDF only, up to 20 MB.\"\n * acceptedFileTypes={[\"application/pdf\"]}\n * onFiles={(files) => upload(files[0])}\n * />\n */\nexport interface FileDropZoneProps {\n /** What the file is. The drop zone's accessible name. */\n label: string;\n /** The zone's main line: what to drop here, or the chosen file's name. */\n prompt: ReactNode;\n /**\n * What the prompt says while a file is over the zone. When it is given the\n * prompt swaps to it (the new text rises in, the old lifts away) and back again.\n */\n dropPrompt?: ReactNode;\n /** The types and size accepted, and anything else to know before choosing. */\n hint?: ReactNode;\n /**\n * MIME types or extensions the file dialog offers, as for a native\n * `accept`. A drop is not filtered: what was dropped is handed over and the\n * consumer decides what it can read.\n */\n acceptedFileTypes?: string[];\n /** Take several files at once. Otherwise only the first is handed over. */\n allowsMultiple?: boolean;\n /** Called with the chosen or dropped files. */\n onFiles: (files: File[]) => void;\n /**\n * The button's text.\n *\n * @default \"Choose file\", or \"Choose files\" with `allowsMultiple`\n */\n buttonLabel?: string;\n /** Take no file: the button is disabled and a drop is refused. */\n isDisabled?: boolean;\n /** Extra classes for the zone. Merged last, so they win conflicts with ours. */\n className?: string;\n}\n\ntype DropEvent = Parameters<NonNullable<AriaDropZoneProps[\"onDrop\"]>>[0];\n\nasync function filesOf(event: DropEvent): Promise<File[]> {\n const items = event.items.filter((item): item is FileDropItem => item.kind === \"file\");\n return Promise.all(items.map((item) => item.getFile()));\n}\n\nexport const FileDropZone = forwardRef<HTMLDivElement, FileDropZoneProps>(function FileDropZone(\n {\n label,\n prompt,\n dropPrompt,\n hint,\n acceptedFileTypes,\n allowsMultiple = false,\n onFiles,\n buttonLabel,\n isDisabled = false,\n className,\n },\n ref,\n) {\n const hand = (files: File[]) => {\n if (isDisabled || files.length === 0) return;\n onFiles(allowsMultiple ? files : files.slice(0, 1));\n };\n const zone = useRef<HTMLDivElement | null>(null);\n const refs = useMemo(() => mergeRefs(ref, zone), [ref]);\n // Where the file is over the zone, as percentages, for the light that follows it.\n const lean = (x: number, y: number) => {\n const el = zone.current;\n if (!el) return;\n el.style.setProperty(\"--drop-x\", `${(x / Math.max(1, el.offsetWidth)) * 100}%`);\n el.style.setProperty(\"--drop-y\", `${(y / Math.max(1, el.offsetHeight)) * 100}%`);\n };\n return (\n <AriaDropZone\n ref={refs}\n aria-label={label}\n data-slot=\"file-drop-zone\"\n isDisabled={isDisabled}\n className={cn(fileDropZoneVariants(), className)}\n onDropEnter={(event) => lean(event.x, event.y)}\n onDropMove={(event) => lean(event.x, event.y)}\n onDrop={(event) => {\n void filesOf(event).then(hand);\n }}\n >\n {({ isDropTarget }) => (\n <>\n {/* The wash and the edge light sit under the content and follow the file. */}\n <span aria-hidden=\"true\" data-slot=\"file-drop-zone-wash\" className=\"drop-wash\" />\n <span aria-hidden=\"true\" data-slot=\"file-drop-zone-glow\" className=\"drop-glow\" />\n <p data-slot=\"file-drop-zone-prompt\" className={cn(fileDropZonePromptVariants(), \"relative\")}>\n {dropPrompt == null ? (\n prompt\n ) : (\n <SwapText swapKey={isDropTarget ? \"drop\" : \"prompt\"} className=\"block\">\n {isDropTarget ? dropPrompt : prompt}\n </SwapText>\n )}\n </p>\n <AriaFileTrigger\n acceptedFileTypes={acceptedFileTypes}\n allowsMultiple={allowsMultiple}\n onSelect={(list) => hand(Array.from(list ?? []))}\n >\n <Button variant=\"outlineNeutral\" size=\"sm\" isDisabled={isDisabled} className=\"relative\">\n {buttonLabel ?? (allowsMultiple ? \"Choose files\" : \"Choose file\")}\n </Button>\n </AriaFileTrigger>\n {hint == null ? null : (\n <p\n data-slot=\"file-drop-zone-hint\"\n className={cn(fileDropZoneHintVariants(), \"relative\")}\n >\n {hint}\n </p>\n )}\n </>\n )}\n </AriaDropZone>\n );\n});\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAkCA,MAAa,uBAAuB,IAAI;CACtC;CACA;CAEA;CACA;CAEA;CACA;CACA;CACA;CACA;CACA;CACA;CAGA;AACF,CAAC;AAED,MAAa,6BAA6B,IAAI,wCAAwC;AAEtF,MAAa,2BAA2B,IACtC,8DACF;AAkDA,eAAe,QAAQ,OAAmC;CACxD,MAAM,QAAQ,MAAM,MAAM,QAAQ,SAA+B,KAAK,SAAS,MAAM;CACrF,OAAO,QAAQ,IAAI,MAAM,KAAK,SAAS,KAAK,QAAQ,CAAC,CAAC;AACxD;AAEA,MAAa,eAAe,WAA8C,SAAS,aACjF,EACE,OACA,QACA,YACA,MACA,mBACA,iBAAiB,OACjB,SACA,aACA,aAAa,OACb,aAEF,KACA;CACA,MAAM,QAAQ,UAAkB;EAC9B,IAAI,cAAc,MAAM,WAAW,GAAG;EACtC,QAAQ,iBAAiB,QAAQ,MAAM,MAAM,GAAG,CAAC,CAAC;CACpD;CACA,MAAM,OAAO,OAA8B,IAAI;CAC/C,MAAM,OAAO,cAAc,UAAU,KAAK,IAAI,GAAG,CAAC,GAAG,CAAC;CAEtD,MAAM,QAAQ,GAAW,MAAc;EACrC,MAAM,KAAK,KAAK;EAChB,IAAI,CAAC,IAAI;EACT,GAAG,MAAM,YAAY,YAAY,GAAI,IAAI,KAAK,IAAI,GAAG,GAAG,WAAW,IAAK,IAAI,EAAE;EAC9E,GAAG,MAAM,YAAY,YAAY,GAAI,IAAI,KAAK,IAAI,GAAG,GAAG,YAAY,IAAK,IAAI,EAAE;CACjF;CACA,OACE,oBAACA,UAAD;EACE,KAAK;EACL,cAAY;EACZ,aAAU;EACE;EACZ,WAAW,GAAG,qBAAqB,GAAG,SAAS;EAC/C,cAAc,UAAU,KAAK,MAAM,GAAG,MAAM,CAAC;EAC7C,aAAa,UAAU,KAAK,MAAM,GAAG,MAAM,CAAC;EAC5C,SAAS,UAAU;GACjB,QAAa,KAAK,CAAC,CAAC,KAAK,IAAI;EAC/B;EAEE,WAAA,EAAE,mBACF,qBAAA,YAAA,EAAA,UAAA;GAEE,oBAAC,QAAD;IAAM,eAAY;IAAO,aAAU;IAAsB,WAAU;GAAa,CAAA;GAChF,oBAAC,QAAD;IAAM,eAAY;IAAO,aAAU;IAAsB,WAAU;GAAa,CAAA;GAChF,oBAAC,KAAD;IAAG,aAAU;IAAwB,WAAW,GAAG,2BAA2B,GAAG,UAAU;IACxF,UAAA,cAAc,OACb,SAEA,oBAAC,UAAD;KAAU,SAAS,eAAe,SAAS;KAAU,WAAU;KAC5D,UAAA,eAAe,aAAa;IACrB,CAAA;GAEX,CAAA;GACH,oBAACC,aAAD;IACqB;IACH;IAChB,WAAW,SAAS,KAAK,MAAM,KAAK,QAAQ,CAAC,CAAC,CAAC;IAE/C,UAAA,oBAACC,UAAD;KAAQ,SAAQ;KAAiB,MAAK;KAAiB;KAAY,WAAU;KAC1E,UAAA,gBAAgB,iBAAiB,iBAAiB;IAC7C,CAAA;GACO,CAAA;GAChB,QAAQ,OAAO,OACd,oBAAC,KAAD;IACE,aAAU;IACV,WAAW,GAAG,yBAAyB,GAAG,UAAU;IAEnD,UAAA;GACA,CAAA;EAEL,EAAA,CAAA;CAEQ,CAAA;AAElB,CAAC"}
@@ -2,9 +2,9 @@ import { ReactNode } from "react";
2
2
  import { Key } from "react-aria-components";
3
3
  //#region src/filter-bar/filter-bar.d.ts
4
4
  /**
5
- * FilterBar -- a list toolbar that keeps the controls people use every time
6
- * in the row and puts every other dropdown filter behind ONE "Filters"
7
- * button.
5
+ * FilterBar -- a filter toolbar: one pill that shows what is filtered as
6
+ * removable chips ("No filters applied" while nothing is), with an "Add filter"
7
+ * button that opens a menu of the filters.
8
8
  *
9
9
  * Why it exists: list screens grew a row of four to seven dropdowns beside
10
10
  * the search box and the status tabs. At desktop width that row wraps onto a
@@ -12,27 +12,32 @@ import { Key } from "react-aria-components";
12
12
  * line and makes what IS filtered visible as chips instead of as the
13
13
  * resting text of a dropdown.
14
14
  *
15
- * Three regions:
15
+ * Three regions, all inside the one pill:
16
16
  *
17
- * - `inline`: always in the row, at its start (status tabs, the search box).
18
- * - the Filters button: a popover with a one-column form, one full-width
19
- * Select per filter with its label above. The button carries a count badge
20
- * of the filters that are set.
21
- * - `end`: always in the row, at its end (a count, Export, a view toggle).
17
+ * - `inline`: always in the pill, at its start (status tabs, a search box).
18
+ * - the chips, then "Clear all" once any filter is set; or the empty note.
19
+ * - the Add filter button, then `end`: always in the pill, at its end (a
20
+ * count, Export, a view toggle).
22
21
  *
23
- * While any filter is set, a list of chips sits under the row, each reading
24
- * "Label: option label" with its own remove button, plus "Clear all".
22
+ * The button opens a menu of the filters: each row is the filter's icon and
23
+ * name, the value it holds now, and a chevron. A row opens that filter's own
24
+ * options in the same panel (a back button returns to the list); choosing an
25
+ * option sets the filter and closes the menu. A custom filter shows its own
26
+ * control in that step.
25
27
  *
26
28
  * The bar holds NO filter state. Every value and every change belongs to the
27
29
  * caller: removing a chip hands the filter's `emptyValue` back to its
28
30
  * `onChange`; Clear all does that for each set filter, or calls
29
31
  * `onClearAll` when the caller resets its filters in one step.
30
32
  *
31
- * Accessibility comes from the parts: the popover is a dialog named by its
32
- * trigger, the trigger carries `aria-expanded`, focus moves into the popover
33
- * and is contained there, Escape closes it and focus returns to the trigger.
33
+ * Accessibility comes from the parts: the panel is a dialog named by its
34
+ * trigger, the trigger carries `aria-expanded`, focus moves into the panel and
35
+ * is contained there, Escape closes it and focus returns to the trigger, and
36
+ * each list is a real menu with arrow keys and type-ahead.
34
37
  */
38
+ /** The toolbar: one pill with a light border on the surface. */
35
39
  export declare const filterBarVariants: (props?: import("class-variance-authority/types").ClassProp | undefined) => string;
40
+ /** The group at the pill's end: the Add filter button and the caller's `end`. */
36
41
  export declare const filterBarRowVariants: (props?: import("class-variance-authority/types").ClassProp | undefined) => string;
37
42
  export declare const filterBarChipVariants: (props?: import("class-variance-authority/types").ClassProp | undefined) => string;
38
43
  /** One option of a dropdown filter. */
@@ -42,12 +47,14 @@ export interface FilterBarOption {
42
47
  /** The option's text: in the list, on the trigger and on the chip. */
43
48
  label: string;
44
49
  }
45
- /** A dropdown filter, rendered as a Select in the popover. */
50
+ /** A dropdown filter: a row in the Add filter menu that opens its options. */
46
51
  export interface FilterBarSelectField {
47
52
  /** Stable identity, used as the React key. */
48
53
  id: string;
49
- /** The field's name: the label above the dropdown and the start of its chip. */
54
+ /** The field's name: its row in the menu and the start of its chip. */
50
55
  label: string;
56
+ /** An icon before the name in the menu row. Mark it `aria-hidden`. */
57
+ icon?: ReactNode;
51
58
  /** The current value. `null` reads as not set. */
52
59
  value: Key | null;
53
60
  /** The value that means "not filtered" (the "All" option's id). */
@@ -64,9 +71,11 @@ export interface FilterBarSelectField {
64
71
  export interface FilterBarCustomField {
65
72
  /** Stable identity, used as the React key. */
66
73
  id: string;
67
- /** The start of its chip. The control rendered by `render` names itself. */
74
+ /** The start of its chip, and its row in the menu. The control rendered by `render` names itself. */
68
75
  label: string;
69
- /** The control, rendered in the popover. */
76
+ /** An icon before the name in the menu row. Mark it `aria-hidden`. */
77
+ icon?: ReactNode;
78
+ /** The control, rendered in the menu's step for this filter. */
70
79
  render: () => ReactNode;
71
80
  /** The chip's value text while the filter is set, or `null` while it is not. */
72
81
  activeLabel: string | null;
@@ -75,32 +84,38 @@ export interface FilterBarCustomField {
75
84
  }
76
85
  export type FilterBarField = FilterBarSelectField | FilterBarCustomField;
77
86
  export interface FilterBarProps {
78
- /** Always in the row, at its start: status tabs and the search box. */
87
+ /** Always in the pill, at its start: status tabs and the search box. */
79
88
  inline?: ReactNode;
80
- /** Always in the row, at its end: a count, Export, a view toggle. */
89
+ /** Always in the pill, at its end: a count, Export, a view toggle. */
81
90
  end?: ReactNode;
82
- /** The filters behind the Filters button. With none, no button renders. */
91
+ /** The filters in the Add filter menu. With none, no button renders. */
83
92
  filters: readonly FilterBarField[];
84
93
  /** Resets every filter in one step. Without it, each set filter is handed its `emptyValue`. */
85
94
  onClearAll?: () => void;
86
95
  /**
87
- * The button's text, and the popover's name.
96
+ * The button's text, and the menu's name.
88
97
  *
89
- * @default "Filters"
98
+ * @default "Add filter"
90
99
  */
91
100
  buttonLabel?: string;
92
101
  /**
93
- * The text of both Clear all buttons.
102
+ * The text of Clear all.
94
103
  *
95
104
  * @default "Clear all"
96
105
  */
97
106
  clearAllLabel?: string;
98
107
  /**
99
- * The text of the button that closes the popover.
108
+ * Ignored: the menu has no Done button, because choosing an option closes it.
100
109
  *
101
- * @default "Done"
110
+ * @deprecated Kept so existing callers still compile.
102
111
  */
103
112
  doneLabel?: string;
113
+ /**
114
+ * The note shown while no filter is set.
115
+ *
116
+ * @default "No filters applied"
117
+ */
118
+ emptyLabel?: string;
104
119
  /**
105
120
  * The accessible name of the list of chips.
106
121
  *
@@ -113,6 +128,12 @@ export interface FilterBarProps {
113
128
  * @default (text) => `Remove filter ${text}`
114
129
  */
115
130
  removeLabel?: (chipText: string) => string;
131
+ /**
132
+ * The accessible name of the menu's back button.
133
+ *
134
+ * @default "Back to filters"
135
+ */
136
+ backLabel?: string;
116
137
  /** Extra classes on the root. Merged last, so they win conflicts with ours. */
117
138
  className?: string;
118
139
  }