@seekalfred/primitives 0.6.0 → 0.7.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (346) hide show
  1. package/README.md +214 -15
  2. package/dist/activity-heatmap/activity-heatmap.d.ts +92 -0
  3. package/dist/activity-heatmap/activity-heatmap.js +699 -0
  4. package/dist/activity-heatmap/activity-heatmap.js.map +1 -0
  5. package/dist/activity-heatmap/index.d.ts +2 -0
  6. package/dist/activity-heatmap/index.js +2 -0
  7. package/dist/alert/alert.d.ts +53 -4
  8. package/dist/alert/alert.js +100 -11
  9. package/dist/alert/alert.js.map +1 -1
  10. package/dist/alert/index.d.ts +2 -2
  11. package/dist/alert/index.js +2 -2
  12. package/dist/alert-dialog/alert-dialog.d.ts +58 -18
  13. package/dist/alert-dialog/alert-dialog.js +152 -9
  14. package/dist/alert-dialog/alert-dialog.js.map +1 -1
  15. package/dist/alert-dialog/index.d.ts +2 -2
  16. package/dist/alert-dialog/index.js +2 -2
  17. package/dist/animated-counter/animated-counter.d.ts +38 -0
  18. package/dist/animated-counter/animated-counter.js +230 -0
  19. package/dist/animated-counter/animated-counter.js.map +1 -0
  20. package/dist/animated-counter/index.d.ts +2 -0
  21. package/dist/animated-counter/index.js +2 -0
  22. package/dist/announcement-bar/announcement-bar.d.ts +67 -0
  23. package/dist/announcement-bar/announcement-bar.js +364 -0
  24. package/dist/announcement-bar/announcement-bar.js.map +1 -0
  25. package/dist/announcement-bar/index.d.ts +2 -0
  26. package/dist/announcement-bar/index.js +2 -0
  27. package/dist/badge/badge.d.ts +1 -1
  28. package/dist/bar-chart/bar-chart.d.ts +51 -0
  29. package/dist/bar-chart/bar-chart.js +565 -0
  30. package/dist/bar-chart/bar-chart.js.map +1 -0
  31. package/dist/bar-chart/index.d.ts +2 -0
  32. package/dist/bar-chart/index.js +2 -0
  33. package/dist/breadcrumb/breadcrumb.d.ts +2 -2
  34. package/dist/breadcrumb/breadcrumb.js +12 -3
  35. package/dist/breadcrumb/breadcrumb.js.map +1 -1
  36. package/dist/brush-chart/brush-chart.d.ts +64 -0
  37. package/dist/brush-chart/brush-chart.js +1056 -0
  38. package/dist/brush-chart/brush-chart.js.map +1 -0
  39. package/dist/brush-chart/index.d.ts +2 -0
  40. package/dist/brush-chart/index.js +2 -0
  41. package/dist/button/button.d.ts +1 -1
  42. package/dist/button/button.js +31 -21
  43. package/dist/button/button.js.map +1 -1
  44. package/dist/button-group/button-group.js +3 -4
  45. package/dist/button-group/button-group.js.map +1 -1
  46. package/dist/calendar/calendar.d.ts +1 -1
  47. package/dist/card/card.d.ts +22 -1
  48. package/dist/card/card.js +60 -7
  49. package/dist/card/card.js.map +1 -1
  50. package/dist/card-stack/card-stack.d.ts +54 -0
  51. package/dist/card-stack/card-stack.js +492 -0
  52. package/dist/card-stack/card-stack.js.map +1 -0
  53. package/dist/card-stack/index.d.ts +2 -0
  54. package/dist/card-stack/index.js +2 -0
  55. package/dist/carousel/carousel.d.ts +77 -0
  56. package/dist/carousel/carousel.js +415 -0
  57. package/dist/carousel/carousel.js.map +1 -0
  58. package/dist/carousel/index.d.ts +2 -0
  59. package/dist/carousel/index.js +2 -0
  60. package/dist/checkbox/checkbox.js +4 -4
  61. package/dist/checkbox/checkbox.js.map +1 -1
  62. package/dist/chip-group/chip-group.d.ts +37 -0
  63. package/dist/chip-group/chip-group.js +148 -0
  64. package/dist/chip-group/chip-group.js.map +1 -0
  65. package/dist/chip-group/index.d.ts +2 -0
  66. package/dist/chip-group/index.js +2 -0
  67. package/dist/copy-button/copy-button.d.ts +18 -2
  68. package/dist/copy-button/copy-button.js +29 -8
  69. package/dist/copy-button/copy-button.js.map +1 -1
  70. package/dist/countdown/countdown.d.ts +45 -0
  71. package/dist/countdown/countdown.js +298 -0
  72. package/dist/countdown/countdown.js.map +1 -0
  73. package/dist/countdown/index.d.ts +2 -0
  74. package/dist/countdown/index.js +2 -0
  75. package/dist/date-picker/picker-parts.js +1 -1
  76. package/dist/date-picker/picker-parts.js.map +1 -1
  77. package/dist/dialog/dialog.js +1 -1
  78. package/dist/dialog/dialog.js.map +1 -1
  79. package/dist/donut-chart/donut-chart.d.ts +86 -0
  80. package/dist/donut-chart/donut-chart.js +593 -0
  81. package/dist/donut-chart/donut-chart.js.map +1 -0
  82. package/dist/donut-chart/index.d.ts +2 -0
  83. package/dist/donut-chart/index.js +2 -0
  84. package/dist/empty-state/empty-state.d.ts +42 -10
  85. package/dist/empty-state/empty-state.js +44 -26
  86. package/dist/empty-state/empty-state.js.map +1 -1
  87. package/dist/empty-state/index.d.ts +2 -2
  88. package/dist/empty-state/index.js +2 -2
  89. package/dist/file-drop-zone/file-drop-zone.js +1 -1
  90. package/dist/file-drop-zone/file-drop-zone.js.map +1 -1
  91. package/dist/filter-bar/filter-bar.d.ts +50 -26
  92. package/dist/filter-bar/filter-bar.js +317 -157
  93. package/dist/filter-bar/filter-bar.js.map +1 -1
  94. package/dist/gauge/gauge.d.ts +45 -0
  95. package/dist/gauge/gauge.js +219 -0
  96. package/dist/gauge/gauge.js.map +1 -0
  97. package/dist/gauge/index.d.ts +2 -0
  98. package/dist/gauge/index.js +2 -0
  99. package/dist/in-view-title/in-view-title.d.ts +33 -0
  100. package/dist/in-view-title/in-view-title.js +119 -0
  101. package/dist/in-view-title/in-view-title.js.map +1 -0
  102. package/dist/in-view-title/index.d.ts +2 -0
  103. package/dist/in-view-title/index.js +2 -0
  104. package/dist/index.d.ts +53 -10
  105. package/dist/index.js +52 -12
  106. package/dist/input/input.js +4 -4
  107. package/dist/input/input.js.map +1 -1
  108. package/dist/lib/cn.js +11 -1
  109. package/dist/lib/cn.js.map +1 -1
  110. package/dist/lib/field-message.js +28 -10
  111. package/dist/lib/field-message.js.map +1 -1
  112. package/dist/lib/field-shell.js +10 -3
  113. package/dist/lib/field-shell.js.map +1 -1
  114. package/dist/lib/field-surface.js +31 -9
  115. package/dist/lib/field-surface.js.map +1 -1
  116. package/dist/lib/flag-emoji.js +50 -0
  117. package/dist/lib/flag-emoji.js.map +1 -0
  118. package/dist/lib/flip.js +11 -3
  119. package/dist/lib/flip.js.map +1 -1
  120. package/dist/lib/menu-highlight.js +64 -0
  121. package/dist/lib/menu-highlight.js.map +1 -0
  122. package/dist/lib/motion.js +9 -2
  123. package/dist/lib/motion.js.map +1 -1
  124. package/dist/lib/rolling-value.js +1 -1
  125. package/dist/lib/rolling-value.js.map +1 -1
  126. package/dist/lib/spring-tween.js +500 -0
  127. package/dist/lib/spring-tween.js.map +1 -0
  128. package/dist/lib/swap-text.js +3 -1
  129. package/dist/lib/swap-text.js.map +1 -1
  130. package/dist/lib/use-copy-feedback.js +17 -3
  131. package/dist/lib/use-copy-feedback.js.map +1 -1
  132. package/dist/line-chart/index.d.ts +2 -0
  133. package/dist/line-chart/index.js +2 -0
  134. package/dist/line-chart/line-chart.d.ts +116 -0
  135. package/dist/line-chart/line-chart.js +740 -0
  136. package/dist/line-chart/line-chart.js.map +1 -0
  137. package/dist/mention-input/index.d.ts +2 -0
  138. package/dist/mention-input/index.js +2 -0
  139. package/dist/mention-input/mention-input.d.ts +98 -0
  140. package/dist/mention-input/mention-input.js +576 -0
  141. package/dist/mention-input/mention-input.js.map +1 -0
  142. package/dist/menu/menu.d.ts +2 -0
  143. package/dist/menu/menu.js +23 -12
  144. package/dist/menu/menu.js.map +1 -1
  145. package/dist/money-input/index.d.ts +2 -0
  146. package/dist/money-input/index.js +2 -0
  147. package/dist/money-input/money-input.d.ts +86 -0
  148. package/dist/money-input/money-input.js +613 -0
  149. package/dist/money-input/money-input.js.map +1 -0
  150. package/dist/morph-select/index.d.ts +2 -0
  151. package/dist/morph-select/index.js +2 -0
  152. package/dist/morph-select/morph-select.d.ts +89 -0
  153. package/dist/morph-select/morph-select.js +712 -0
  154. package/dist/morph-select/morph-select.js.map +1 -0
  155. package/dist/multi-select/index.d.ts +2 -0
  156. package/dist/multi-select/index.js +2 -0
  157. package/dist/multi-select/multi-select.d.ts +77 -0
  158. package/dist/multi-select/multi-select.js +209 -0
  159. package/dist/multi-select/multi-select.js.map +1 -0
  160. package/dist/number-field/index.d.ts +2 -0
  161. package/dist/number-field/index.js +2 -0
  162. package/dist/number-field/number-field.d.ts +42 -0
  163. package/dist/number-field/number-field.js +199 -0
  164. package/dist/number-field/number-field.js.map +1 -0
  165. package/dist/onboarding-checklist/index.d.ts +2 -0
  166. package/dist/onboarding-checklist/index.js +2 -0
  167. package/dist/onboarding-checklist/onboarding-checklist.d.ts +55 -0
  168. package/dist/onboarding-checklist/onboarding-checklist.js +577 -0
  169. package/dist/onboarding-checklist/onboarding-checklist.js.map +1 -0
  170. package/dist/password-field/password-field.d.ts +16 -0
  171. package/dist/password-field/password-field.js +39 -10
  172. package/dist/password-field/password-field.js.map +1 -1
  173. package/dist/phone-input/index.d.ts +3 -0
  174. package/dist/phone-input/index.js +3 -0
  175. package/dist/phone-input/phone-countries.d.ts +29 -0
  176. package/dist/phone-input/phone-countries.js +170 -0
  177. package/dist/phone-input/phone-countries.js.map +1 -0
  178. package/dist/phone-input/phone-input.d.ts +66 -0
  179. package/dist/phone-input/phone-input.js +440 -0
  180. package/dist/phone-input/phone-input.js.map +1 -0
  181. package/dist/popover/index.d.ts +2 -2
  182. package/dist/popover/index.js +2 -2
  183. package/dist/popover/popover.d.ts +51 -2
  184. package/dist/popover/popover.js +59 -8
  185. package/dist/popover/popover.js.map +1 -1
  186. package/dist/progress/progress.d.ts +16 -2
  187. package/dist/progress/progress.js +16 -9
  188. package/dist/progress/progress.js.map +1 -1
  189. package/dist/radio-group/index.d.ts +2 -2
  190. package/dist/radio-group/index.js +2 -2
  191. package/dist/radio-group/radio-group.d.ts +33 -3
  192. package/dist/radio-group/radio-group.js +110 -10
  193. package/dist/radio-group/radio-group.js.map +1 -1
  194. package/dist/reactions/index.d.ts +2 -0
  195. package/dist/reactions/index.js +2 -0
  196. package/dist/reactions/reactions.d.ts +50 -0
  197. package/dist/reactions/reactions.js +315 -0
  198. package/dist/reactions/reactions.js.map +1 -0
  199. package/dist/rich-text-editor/index.d.ts +3 -0
  200. package/dist/rich-text-editor/index.js +2 -0
  201. package/dist/rich-text-editor/rich-text-editor.d.ts +70 -0
  202. package/dist/rich-text-editor/rich-text-editor.js +909 -0
  203. package/dist/rich-text-editor/rich-text-editor.js.map +1 -0
  204. package/dist/rich-text-editor/rich-text-engine.d.ts +16 -0
  205. package/dist/rich-text-editor/rich-text-engine.js +366 -0
  206. package/dist/rich-text-editor/rich-text-engine.js.map +1 -0
  207. package/dist/rich-text-editor/rich-text-html.js +174 -0
  208. package/dist/rich-text-editor/rich-text-html.js.map +1 -0
  209. package/dist/rich-text-editor/rich-text-icons.js +43 -0
  210. package/dist/rich-text-editor/rich-text-icons.js.map +1 -0
  211. package/dist/ridgeline/index.d.ts +2 -0
  212. package/dist/ridgeline/index.js +2 -0
  213. package/dist/ridgeline/ridgeline.d.ts +55 -0
  214. package/dist/ridgeline/ridgeline.js +701 -0
  215. package/dist/ridgeline/ridgeline.js.map +1 -0
  216. package/dist/scroll-area/index.d.ts +2 -2
  217. package/dist/scroll-area/index.js +2 -2
  218. package/dist/scroll-area/scroll-area.d.ts +68 -31
  219. package/dist/scroll-area/scroll-area.js +212 -62
  220. package/dist/scroll-area/scroll-area.js.map +1 -1
  221. package/dist/scrub-input/evaluate.d.ts +14 -0
  222. package/dist/scrub-input/evaluate.js +72 -0
  223. package/dist/scrub-input/evaluate.js.map +1 -0
  224. package/dist/scrub-input/index.d.ts +3 -0
  225. package/dist/scrub-input/index.js +3 -0
  226. package/dist/scrub-input/scrub-input.d.ts +74 -0
  227. package/dist/scrub-input/scrub-input.js +486 -0
  228. package/dist/scrub-input/scrub-input.js.map +1 -0
  229. package/dist/search-field/search-field.d.ts +24 -2
  230. package/dist/search-field/search-field.js +24 -9
  231. package/dist/search-field/search-field.js.map +1 -1
  232. package/dist/select/select.d.ts +3 -1
  233. package/dist/select/select.js +28 -14
  234. package/dist/select/select.js.map +1 -1
  235. package/dist/separator/separator.d.ts +22 -10
  236. package/dist/separator/separator.js +33 -11
  237. package/dist/separator/separator.js.map +1 -1
  238. package/dist/sheet/sheet.js +2 -1
  239. package/dist/sheet/sheet.js.map +1 -1
  240. package/dist/shortcut-recorder/index.d.ts +3 -0
  241. package/dist/shortcut-recorder/index.js +3 -0
  242. package/dist/shortcut-recorder/shortcut-model.d.ts +27 -0
  243. package/dist/shortcut-recorder/shortcut-model.js +206 -0
  244. package/dist/shortcut-recorder/shortcut-model.js.map +1 -0
  245. package/dist/shortcut-recorder/shortcut-recorder.d.ts +93 -0
  246. package/dist/shortcut-recorder/shortcut-recorder.js +489 -0
  247. package/dist/shortcut-recorder/shortcut-recorder.js.map +1 -0
  248. package/dist/slider/index.d.ts +2 -2
  249. package/dist/slider/slider.d.ts +46 -14
  250. package/dist/slider/slider.js +151 -67
  251. package/dist/slider/slider.js.map +1 -1
  252. package/dist/slope-chart/index.d.ts +2 -0
  253. package/dist/slope-chart/index.js +2 -0
  254. package/dist/slope-chart/slope-chart.d.ts +54 -0
  255. package/dist/slope-chart/slope-chart.js +568 -0
  256. package/dist/slope-chart/slope-chart.js.map +1 -0
  257. package/dist/sparkline/index.d.ts +2 -0
  258. package/dist/sparkline/index.js +2 -0
  259. package/dist/sparkline/sparkline.d.ts +46 -0
  260. package/dist/sparkline/sparkline.js +390 -0
  261. package/dist/sparkline/sparkline.js.map +1 -0
  262. package/dist/split-bar/index.d.ts +2 -0
  263. package/dist/split-bar/index.js +2 -0
  264. package/dist/split-bar/split-bar.d.ts +62 -0
  265. package/dist/split-bar/split-bar.js +513 -0
  266. package/dist/split-bar/split-bar.js.map +1 -0
  267. package/dist/split-button/split-button.d.ts +1 -1
  268. package/dist/split-button/split-button.js +12 -11
  269. package/dist/split-button/split-button.js.map +1 -1
  270. package/dist/stat-card/index.d.ts +2 -2
  271. package/dist/stat-card/index.js +2 -2
  272. package/dist/stat-card/stat-card.d.ts +58 -5
  273. package/dist/stat-card/stat-card.js +144 -17
  274. package/dist/stat-card/stat-card.js.map +1 -1
  275. package/dist/stepper/index.d.ts +2 -0
  276. package/dist/stepper/index.js +2 -0
  277. package/dist/stepper/stepper.d.ts +51 -0
  278. package/dist/stepper/stepper.js +215 -0
  279. package/dist/stepper/stepper.js.map +1 -0
  280. package/dist/streamgraph/index.d.ts +2 -0
  281. package/dist/streamgraph/index.js +2 -0
  282. package/dist/streamgraph/streamgraph.d.ts +70 -0
  283. package/dist/streamgraph/streamgraph.js +613 -0
  284. package/dist/streamgraph/streamgraph.js.map +1 -0
  285. package/dist/styles.css +1 -1
  286. package/dist/swipe-actions/index.d.ts +2 -0
  287. package/dist/swipe-actions/index.js +2 -0
  288. package/dist/swipe-actions/swipe-actions.d.ts +67 -0
  289. package/dist/swipe-actions/swipe-actions.js +552 -0
  290. package/dist/swipe-actions/swipe-actions.js.map +1 -0
  291. package/dist/switch/switch.js +2 -2
  292. package/dist/switch/switch.js.map +1 -1
  293. package/dist/table/index.d.ts +2 -2
  294. package/dist/table/index.js +2 -2
  295. package/dist/table/table.d.ts +55 -3
  296. package/dist/table/table.js +340 -165
  297. package/dist/table/table.js.map +1 -1
  298. package/dist/tabs/tabs.js +2 -2
  299. package/dist/tabs/tabs.js.map +1 -1
  300. package/dist/tag-input/index.d.ts +2 -0
  301. package/dist/tag-input/index.js +2 -0
  302. package/dist/tag-input/tag-input.d.ts +40 -0
  303. package/dist/tag-input/tag-input.js +269 -0
  304. package/dist/tag-input/tag-input.js.map +1 -0
  305. package/dist/text-morph/index.d.ts +2 -0
  306. package/dist/text-morph/index.js +2 -0
  307. package/dist/text-morph/text-morph.d.ts +23 -0
  308. package/dist/text-morph/text-morph.js +21 -0
  309. package/dist/text-morph/text-morph.js.map +1 -0
  310. package/dist/text-reveal/index.d.ts +2 -0
  311. package/dist/text-reveal/index.js +2 -0
  312. package/dist/text-reveal/text-reveal.d.ts +27 -0
  313. package/dist/text-reveal/text-reveal.js +51 -0
  314. package/dist/text-reveal/text-reveal.js.map +1 -0
  315. package/dist/text-shimmer/index.d.ts +2 -0
  316. package/dist/text-shimmer/index.js +2 -0
  317. package/dist/text-shimmer/text-shimmer.d.ts +29 -0
  318. package/dist/text-shimmer/text-shimmer.js +29 -0
  319. package/dist/text-shimmer/text-shimmer.js.map +1 -0
  320. package/dist/time-picker/time-picker.d.ts +36 -1
  321. package/dist/time-picker/time-picker.js +35 -7
  322. package/dist/time-picker/time-picker.js.map +1 -1
  323. package/dist/toast/index.d.ts +2 -2
  324. package/dist/toast/index.js +2 -2
  325. package/dist/toast/toast.d.ts +7 -1
  326. package/dist/toast/toast.js +82 -40
  327. package/dist/toast/toast.js.map +1 -1
  328. package/dist/tooltip/tooltip.d.ts +9 -2
  329. package/dist/tooltip/tooltip.js +7 -7
  330. package/dist/tooltip/tooltip.js.map +1 -1
  331. package/dist/treemap/index.d.ts +2 -0
  332. package/dist/treemap/index.js +2 -0
  333. package/dist/treemap/treemap.d.ts +53 -0
  334. package/dist/treemap/treemap.js +699 -0
  335. package/dist/treemap/treemap.js.map +1 -0
  336. package/dist/usage-meter/index.d.ts +2 -0
  337. package/dist/usage-meter/index.js +2 -0
  338. package/dist/usage-meter/usage-meter.d.ts +80 -0
  339. package/dist/usage-meter/usage-meter.js +458 -0
  340. package/dist/usage-meter/usage-meter.js.map +1 -0
  341. package/dist/waffle-chart/index.d.ts +2 -0
  342. package/dist/waffle-chart/index.js +2 -0
  343. package/dist/waffle-chart/waffle-chart.d.ts +57 -0
  344. package/dist/waffle-chart/waffle-chart.js +426 -0
  345. package/dist/waffle-chart/waffle-chart.js.map +1 -0
  346. package/package.json +165 -3
@@ -1,21 +1,22 @@
1
1
  "use client";
2
2
  import { cn, cnAll } from "../lib/cn.js";
3
3
  import { prefersReducedMotion, useExitBackstop, useIsomorphicLayoutEffect, useMorphWidth } from "../lib/motion.js";
4
- import { Button } from "../button/button.js";
4
+ import { Button as Button$1 } from "../button/button.js";
5
5
  import { SwapText } from "../lib/swap-text.js";
6
6
  import { useHeightFollow } from "../lib/height-follow.js";
7
- import { Badge } from "../badge/badge.js";
8
- import { Popover, PopoverTrigger } from "../popover/popover.js";
7
+ import { useMenuHighlight } from "../lib/menu-highlight.js";
8
+ import { MenuItem as MenuItem$1, menuVariants } from "../menu/menu.js";
9
+ import { Popover as Popover$1, PopoverTrigger } from "../popover/popover.js";
9
10
  import { useLeavingList } from "../lib/leaving-list.js";
10
- import { Select, SelectItem } from "../select/select.js";
11
11
  import { cva } from "class-variance-authority";
12
12
  import { forwardRef, useCallback, useEffect, useRef, useState } from "react";
13
+ import { Menu } from "react-aria-components";
13
14
  import { jsx, jsxs } from "react/jsx-runtime";
14
15
  //#region src/filter-bar/filter-bar.tsx
15
16
  /**
16
- * FilterBar -- a list toolbar that keeps the controls people use every time
17
- * in the row and puts every other dropdown filter behind ONE "Filters"
18
- * button.
17
+ * FilterBar -- a filter toolbar: one pill that shows what is filtered as
18
+ * removable chips ("No filters applied" while nothing is), with an "Add filter"
19
+ * button that opens a menu of the filters.
19
20
  *
20
21
  * Why it exists: list screens grew a row of four to seven dropdowns beside
21
22
  * the search box and the status tabs. At desktop width that row wraps onto a
@@ -23,28 +24,40 @@ import { jsx, jsxs } from "react/jsx-runtime";
23
24
  * line and makes what IS filtered visible as chips instead of as the
24
25
  * resting text of a dropdown.
25
26
  *
26
- * Three regions:
27
+ * Three regions, all inside the one pill:
27
28
  *
28
- * - `inline`: always in the row, at its start (status tabs, the search box).
29
- * - the Filters button: a popover with a one-column form, one full-width
30
- * Select per filter with its label above. The button carries a count badge
31
- * of the filters that are set.
32
- * - `end`: always in the row, at its end (a count, Export, a view toggle).
29
+ * - `inline`: always in the pill, at its start (status tabs, a search box).
30
+ * - the chips, then "Clear all" once any filter is set; or the empty note.
31
+ * - the Add filter button, then `end`: always in the pill, at its end (a
32
+ * count, Export, a view toggle).
33
33
  *
34
- * While any filter is set, a list of chips sits under the row, each reading
35
- * "Label: option label" with its own remove button, plus "Clear all".
34
+ * The button opens a menu of the filters: each row is the filter's icon and
35
+ * name, the value it holds now, and a chevron. A row opens that filter's own
36
+ * options in the same panel (a back button returns to the list); choosing an
37
+ * option sets the filter and closes the menu. A custom filter shows its own
38
+ * control in that step.
36
39
  *
37
40
  * The bar holds NO filter state. Every value and every change belongs to the
38
41
  * caller: removing a chip hands the filter's `emptyValue` back to its
39
42
  * `onChange`; Clear all does that for each set filter, or calls
40
43
  * `onClearAll` when the caller resets its filters in one step.
41
44
  *
42
- * Accessibility comes from the parts: the popover is a dialog named by its
43
- * trigger, the trigger carries `aria-expanded`, focus moves into the popover
44
- * and is contained there, Escape closes it and focus returns to the trigger.
45
+ * Accessibility comes from the parts: the panel is a dialog named by its
46
+ * trigger, the trigger carries `aria-expanded`, focus moves into the panel and
47
+ * is contained there, Escape closes it and focus returns to the trigger, and
48
+ * each list is a real menu with arrow keys and type-ahead. Removing a chip, by
49
+ * keyboard or pointer, moves focus to the chip that takes its place (the one
50
+ * before it when it was last); removing the last chip, or Clear all, moves it to
51
+ * the Add filter button.
45
52
  */
46
- const filterBarVariants = cva(["flex flex-col gap-2 box-border font-sans"]);
47
- const filterBarRowVariants = cva(["flex flex-wrap items-center gap-2"]);
53
+ /** The toolbar: one pill with a light border on the surface. */
54
+ const filterBarVariants = cva([
55
+ "flex flex-wrap items-center gap-x-3 gap-y-2 rounded-full",
56
+ "border-(length:--border-width) border-border bg-surface",
57
+ "px-4 py-2 box-border font-sans"
58
+ ]);
59
+ /** The group at the pill's end: the Add filter button and the caller's `end`. */
60
+ const filterBarRowVariants = cva(["ms-auto flex flex-wrap items-center gap-2"]);
48
61
  const filterBarChipVariants = cva([
49
62
  "inline-flex items-center gap-1 rounded-full",
50
63
  "border-(length:--border-width) border-border bg-muted",
@@ -52,43 +65,28 @@ const filterBarChipVariants = cva([
52
65
  "box-border font-sans"
53
66
  ]);
54
67
  const isCustom = (f) => "render" in f;
68
+ /** What a filter holds now, for its menu row and its chip: `null` while it is not set. */
69
+ function valueOf(f) {
70
+ if (isCustom(f)) return f.activeLabel;
71
+ if (f.value == null || f.value === f.emptyValue) return null;
72
+ const option = f.options.find((o) => o.id === f.value);
73
+ return option ? option.label : String(f.value);
74
+ }
55
75
  /** The set filters, each with its chip text and how to unset it. */
56
76
  function chipsFor(filters) {
57
77
  const chips = [];
58
78
  for (const f of filters) {
59
- if (isCustom(f)) {
60
- if (f.activeLabel != null) chips.push({
61
- id: f.id,
62
- text: `${f.label}: ${f.activeLabel}`,
63
- clear: f.onClear
64
- });
65
- continue;
66
- }
67
- if (f.value == null || f.value === f.emptyValue) continue;
68
- const option = f.options.find((o) => o.id === f.value);
79
+ const value = valueOf(f);
80
+ if (value == null) continue;
69
81
  chips.push({
70
82
  id: f.id,
71
- text: `${f.label}: ${option ? option.label : String(f.value)}`,
72
- clear: () => f.onChange(f.emptyValue)
83
+ text: `${f.label}: ${value}`,
84
+ clear: isCustom(f) ? f.onClear : () => f.onChange(f.emptyValue)
73
85
  });
74
86
  }
75
87
  return chips;
76
88
  }
77
- /** A funnel, drawn inline: this package ships no icon dependency. */
78
- function FunnelGlyph() {
79
- return /* @__PURE__ */ jsx("svg", {
80
- viewBox: "0 0 16 16",
81
- fill: "none",
82
- stroke: "currentColor",
83
- strokeWidth: "1.5",
84
- "aria-hidden": "true",
85
- children: /* @__PURE__ */ jsx("path", {
86
- d: "M2 3h12M4.5 8h7M7 13h2",
87
- strokeLinecap: "round"
88
- })
89
- });
90
- }
91
- function CrossGlyph() {
89
+ function Glyph({ path }) {
92
90
  return /* @__PURE__ */ jsx("svg", {
93
91
  viewBox: "0 0 16 16",
94
92
  fill: "none",
@@ -96,20 +94,25 @@ function CrossGlyph() {
96
94
  strokeWidth: "1.5",
97
95
  "aria-hidden": "true",
98
96
  children: /* @__PURE__ */ jsx("path", {
99
- d: "M4 4l8 8M12 4l-8 8",
100
- strokeLinecap: "round"
97
+ d: path,
98
+ strokeLinecap: "round",
99
+ strokeLinejoin: "round"
101
100
  })
102
101
  });
103
102
  }
103
+ const PlusGlyph = () => /* @__PURE__ */ jsx(Glyph, { path: "M8 3v10M3 8h10" });
104
+ const CrossGlyph = () => /* @__PURE__ */ jsx(Glyph, { path: "M4 4l8 8M12 4l-8 8" });
105
+ const ChevronRightGlyph = () => /* @__PURE__ */ jsx(Glyph, { path: "M6 3l5 5-5 5" });
106
+ const ChevronLeftGlyph = () => /* @__PURE__ */ jsx(Glyph, { path: "M10 3 5 8l5 5" });
107
+ const CheckGlyph = () => /* @__PURE__ */ jsx(Glyph, { path: "M3 8.5l3.5 3.5L13 4.5" });
104
108
  /**
105
- * A list toolbar with its dropdown filters behind one Filters button.
109
+ * A list toolbar with its filters behind one Add filter button.
106
110
  *
107
111
  * @example
108
112
  * <FilterBar
109
- * inline={<SearchField aria-label="Search" />}
110
113
  * end={<Button>Export</Button>}
111
- * filters={[{ id: "region", label: "Region", value, emptyValue: "all", onChange: setValue,
112
- * options: [{ id: "all", label: "All regions" }, { id: "nsw", label: "NSW" }] }]}
114
+ * filters={[{ id: "status", label: "Status", icon: <StatusIcon aria-hidden />, value, emptyValue: "all",
115
+ * onChange: setValue, options: [{ id: "all", label: "Any status" }, { id: "open", label: "Open" }] }]}
113
116
  * />
114
117
  */
115
118
  /**
@@ -140,6 +143,23 @@ function Slot({ leaving, ready, onDone, id, children }) {
140
143
  children
141
144
  });
142
145
  }
146
+ /**
147
+ * A chip's remove button. It registers itself with the bar under its chip's id
148
+ * through a ref callback that stays the same across renders, so React does not
149
+ * detach and re-attach it on every commit.
150
+ */
151
+ function ChipRemove({ id, label, register, onPress }) {
152
+ const ref = useCallback((node) => register(id, node), [register, id]);
153
+ return /* @__PURE__ */ jsx(Button$1, {
154
+ ref,
155
+ variant: "ghost",
156
+ size: "sm",
157
+ "aria-label": label,
158
+ onPress,
159
+ className: "h-6 rounded-full px-1 data-[focus-visible]:-outline-offset-(--focus-ring-width)",
160
+ children: /* @__PURE__ */ jsx(CrossGlyph, {})
161
+ });
162
+ }
143
163
  /** A chip's text: a changed value swaps (the new text rises in) and the chip's width springs to it. */
144
164
  function ChipText({ text }) {
145
165
  const content = useRef(null);
@@ -157,7 +177,154 @@ function ChipText({ text }) {
157
177
  })
158
178
  });
159
179
  }
160
- const FilterBar = forwardRef(function FilterBar({ inline, end, filters, onClearAll, buttonLabel = "Filters", clearAllLabel = "Clear all", doneLabel = "Done", chipsLabel = "Filters set", removeLabel = (text) => `Remove filter ${text}`, className }, ref) {
180
+ /** A filter's icon, drawn at the control icon size in the secondary ink. */
181
+ function RowIcon({ icon }) {
182
+ return icon ? /* @__PURE__ */ jsx("span", {
183
+ "aria-hidden": "true",
184
+ className: "inline-flex text-text-secondary",
185
+ children: icon
186
+ }) : null;
187
+ }
188
+ /** The note for no filters. Arriving after the first render (the last chip has gone) it fades in; at first render it does not. */
189
+ function EmptyNote({ ready, children }) {
190
+ const [fresh] = useState(ready);
191
+ return /* @__PURE__ */ jsx("span", {
192
+ "data-slot": "filter-bar-empty",
193
+ className: cn("text-sm text-text-secondary" + (fresh ? " motion-safe:animate-fade-in" : "")),
194
+ children
195
+ });
196
+ }
197
+ const STEP = "motion-safe:animate-filter-step-in motion-reduce:animate-fade-in";
198
+ /**
199
+ * The Add filter menu's panel, in two steps: the list of filters, then the
200
+ * chosen filter's options (or its own control). A step arrives from the side it
201
+ * was reached from; the first one, on open, does not animate by itself (the
202
+ * popover grows from the button instead).
203
+ */
204
+ function FilterPanel({ filters, label, backLabel, onClose }) {
205
+ const [fieldId, setFieldId] = useState(null);
206
+ const [dir, setDir] = useState(null);
207
+ const [returnTo, setReturnTo] = useState(null);
208
+ const listRef = useRef(null);
209
+ const bindFields = useMenuHighlight();
210
+ const bindOptions = useMenuHighlight();
211
+ const field = filters.find((f) => f.id === fieldId) ?? null;
212
+ useEffect(() => {
213
+ if (field || returnTo === null) return;
214
+ const id = returnTo;
215
+ let frame = 0;
216
+ let tries = 0;
217
+ const focusRow = () => {
218
+ const row = Array.from(listRef.current?.querySelectorAll("[data-slot='menu-item']") ?? []).find((node) => node.dataset.key === id);
219
+ if (row) row.focus();
220
+ else if (tries++ < 20) frame = requestAnimationFrame(focusRow);
221
+ };
222
+ focusRow();
223
+ return () => cancelAnimationFrame(frame);
224
+ }, [field, returnTo]);
225
+ const open = (id) => {
226
+ setDir(1);
227
+ setFieldId(String(id));
228
+ };
229
+ const back = () => {
230
+ if (!field) return;
231
+ setReturnTo(field.id);
232
+ setDir(-1);
233
+ setFieldId(null);
234
+ };
235
+ const stepClass = dir === null ? "" : cn(STEP + (dir === 1 ? " [--step-dir:1]" : " [--step-dir:-1]"));
236
+ return /* @__PURE__ */ jsx("div", {
237
+ "data-slot": "filter-bar-panel",
238
+ className: "overflow-x-clip",
239
+ children: field ? /* @__PURE__ */ jsxs("div", {
240
+ className: stepClass,
241
+ onKeyDown: (event) => {
242
+ if (event.key === "ArrowLeft" && !isCustom(field)) {
243
+ event.preventDefault();
244
+ back();
245
+ }
246
+ },
247
+ children: [/* @__PURE__ */ jsxs("div", {
248
+ className: "flex items-center gap-1 px-1 pb-1",
249
+ children: [/* @__PURE__ */ jsx(Button$1, {
250
+ variant: "ghost",
251
+ size: "icon",
252
+ "aria-label": backLabel,
253
+ onPress: back,
254
+ className: "size-8 flex-none rounded-full",
255
+ children: /* @__PURE__ */ jsx(ChevronLeftGlyph, {})
256
+ }), /* @__PURE__ */ jsx("span", {
257
+ className: "min-w-0 truncate text-sm font-medium text-foreground",
258
+ children: field.label
259
+ })]
260
+ }), isCustom(field) ? /* @__PURE__ */ jsx("div", {
261
+ className: "p-1.5",
262
+ children: field.render()
263
+ }) : /* @__PURE__ */ jsx(Menu, {
264
+ ref: bindOptions,
265
+ "aria-label": field.label,
266
+ "data-slot": "filter-bar-options",
267
+ autoFocus: "first",
268
+ selectionMode: "single",
269
+ disallowEmptySelection: true,
270
+ selectedKeys: field.value == null ? [] : [field.value],
271
+ onAction: (key) => {
272
+ field.onChange(key);
273
+ onClose();
274
+ },
275
+ className: menuVariants(),
276
+ children: field.options.map((o) => /* @__PURE__ */ jsxs(MenuItem$1, {
277
+ id: o.id,
278
+ textValue: o.label,
279
+ children: [/* @__PURE__ */ jsx("span", {
280
+ className: "min-w-0 flex-1 truncate",
281
+ children: o.label
282
+ }), /* @__PURE__ */ jsx("span", {
283
+ "aria-hidden": "true",
284
+ className: o.id === field.value ? "inline-flex text-foreground" : "inline-flex text-foreground opacity-0",
285
+ children: /* @__PURE__ */ jsx(CheckGlyph, {})
286
+ })]
287
+ }, o.id))
288
+ })]
289
+ }, field.id) : /* @__PURE__ */ jsx("div", {
290
+ ref: listRef,
291
+ className: stepClass,
292
+ children: /* @__PURE__ */ jsx(Menu, {
293
+ ref: bindFields,
294
+ "aria-label": label,
295
+ "data-slot": "filter-bar-fields",
296
+ autoFocus: returnTo === null ? "first" : false,
297
+ onAction: open,
298
+ onClose: () => {},
299
+ className: menuVariants(),
300
+ children: filters.map((f) => {
301
+ const value = valueOf(f);
302
+ return /* @__PURE__ */ jsxs(MenuItem$1, {
303
+ id: f.id,
304
+ textValue: f.label,
305
+ children: [
306
+ /* @__PURE__ */ jsx(RowIcon, { icon: f.icon }),
307
+ /* @__PURE__ */ jsx("span", {
308
+ className: "min-w-0 flex-1 truncate",
309
+ children: f.label
310
+ }),
311
+ value ? /* @__PURE__ */ jsx("span", {
312
+ className: "max-w-24 truncate text-xs text-text-secondary",
313
+ children: value
314
+ }) : null,
315
+ /* @__PURE__ */ jsx("span", {
316
+ "aria-hidden": "true",
317
+ className: "inline-flex text-text-secondary",
318
+ children: /* @__PURE__ */ jsx(ChevronRightGlyph, {})
319
+ })
320
+ ]
321
+ }, f.id);
322
+ })
323
+ })
324
+ }, "fields")
325
+ });
326
+ }
327
+ const FilterBar = forwardRef(function FilterBar({ inline, end, filters, onClearAll, buttonLabel = "Add filter", clearAllLabel = "Clear all", emptyLabel = "No filters applied", chipsLabel = "Filters set", removeLabel = (text) => `Remove filter ${text}`, backLabel = "Back to filters", className }, ref) {
161
328
  const [open, setOpen] = useState(false);
162
329
  const chips = chipsFor(filters);
163
330
  const [ready, setReady] = useState(false);
@@ -174,8 +341,30 @@ const FilterBar = forwardRef(function FilterBar({ inline, end, filters, onClearA
174
341
  }] : []]);
175
342
  const frame = useRef(null);
176
343
  const row = useRef(null);
177
- useHeightFollow(frame, row, chips.map((chip) => chip.id).join());
344
+ const chipKey = chips.map((chip) => chip.id).join();
345
+ useHeightFollow(frame, row, chipKey);
346
+ const removeButtons = useRef(/* @__PURE__ */ new Map());
347
+ const registerRemove = useCallback((id, node) => {
348
+ if (node) removeButtons.current.set(id, node);
349
+ else removeButtons.current.delete(id);
350
+ }, []);
351
+ const addButton = useRef(null);
352
+ const focusAfter = useRef(null);
353
+ const noteFocusAfter = (after) => {
354
+ focusAfter.current = after;
355
+ requestAnimationFrame(() => {
356
+ if (focusAfter.current === after) focusAfter.current = null;
357
+ });
358
+ };
359
+ useIsomorphicLayoutEffect(() => {
360
+ const after = focusAfter.current;
361
+ if (after === null) return;
362
+ focusAfter.current = null;
363
+ const next = after === "add" || chips.length === 0 ? null : chips[Math.min(after, chips.length - 1)];
364
+ (next ? removeButtons.current.get(next.id) : addButton.current)?.focus();
365
+ }, [chipKey]);
178
366
  const clearAll = () => {
367
+ noteFocusAfter("add");
179
368
  if (onClearAll) onClearAll();
180
369
  else for (const c of chips) c.clear();
181
370
  };
@@ -183,115 +372,86 @@ const FilterBar = forwardRef(function FilterBar({ inline, end, filters, onClearA
183
372
  ref,
184
373
  "data-slot": "filter-bar",
185
374
  className: cn(filterBarVariants(), className),
186
- children: [/* @__PURE__ */ jsxs("div", {
187
- "data-slot": "filter-bar-row",
188
- className: filterBarRowVariants(),
189
- children: [
190
- inline,
191
- filters.length > 0 ? /* @__PURE__ */ jsxs(PopoverTrigger, {
375
+ children: [
376
+ inline ? /* @__PURE__ */ jsx("div", {
377
+ className: "flex flex-wrap items-center gap-2",
378
+ children: inline
379
+ }) : null,
380
+ /* @__PURE__ */ jsx("div", {
381
+ "data-slot": "filter-bar-chips-area",
382
+ className: "flex min-h-(--density-control-h-sm) min-w-0 flex-[1_1_8rem] items-center",
383
+ children: places.list.length > 0 ? /* @__PURE__ */ jsx("div", {
384
+ ref: frame,
385
+ "data-slot": "filter-bar-chips-frame",
386
+ className: "height-follow min-w-0",
387
+ children: /* @__PURE__ */ jsx("ul", {
388
+ ref: row,
389
+ "aria-label": chipsLabel,
390
+ "data-slot": "filter-bar-chips",
391
+ className: "m-0 flex list-none flex-wrap items-center gap-2 p-0",
392
+ children: places.list.map(({ item, leaving }) => item.chip ? /* @__PURE__ */ jsx(Slot, {
393
+ id: item.id,
394
+ leaving,
395
+ ready,
396
+ onDone: places.done,
397
+ children: /* @__PURE__ */ jsxs("span", {
398
+ "data-slot": "filter-bar-chip",
399
+ className: filterBarChipVariants(),
400
+ children: [/* @__PURE__ */ jsx(ChipText, { text: item.chip.text }), /* @__PURE__ */ jsx(ChipRemove, {
401
+ id: item.id,
402
+ label: removeLabel(item.chip.text),
403
+ register: registerRemove,
404
+ onPress: () => {
405
+ noteFocusAfter(chips.findIndex((chip) => chip.id === item.id));
406
+ item.chip.clear();
407
+ }
408
+ })]
409
+ })
410
+ }, item.id) : /* @__PURE__ */ jsx(Slot, {
411
+ id: item.id,
412
+ leaving,
413
+ ready,
414
+ onDone: places.done,
415
+ children: /* @__PURE__ */ jsx(Button$1, {
416
+ variant: "link",
417
+ size: "sm",
418
+ onPress: clearAll,
419
+ children: clearAllLabel
420
+ })
421
+ }, item.id))
422
+ })
423
+ }) : /* @__PURE__ */ jsx(EmptyNote, {
424
+ ready,
425
+ children: emptyLabel
426
+ })
427
+ }),
428
+ filters.length > 0 || end ? /* @__PURE__ */ jsxs("div", {
429
+ "data-slot": "filter-bar-actions",
430
+ className: filterBarRowVariants(),
431
+ children: [filters.length > 0 ? /* @__PURE__ */ jsxs(PopoverTrigger, {
192
432
  isOpen: open,
193
433
  onOpenChange: setOpen,
194
- children: [/* @__PURE__ */ jsxs(Button, {
434
+ children: [/* @__PURE__ */ jsx(Button$1, {
435
+ ref: addButton,
195
436
  variant: "outlineNeutral",
196
437
  size: "sm",
197
- startIcon: /* @__PURE__ */ jsx(FunnelGlyph, {}),
438
+ startIcon: /* @__PURE__ */ jsx(PlusGlyph, {}),
198
439
  "data-slot": "filter-bar-button",
199
- children: [buttonLabel, chips.length > 0 ? /* @__PURE__ */ jsx(Badge, {
200
- tone: "primary",
201
- appearance: "solid",
202
- size: "sm",
203
- shape: "pill",
204
- "data-slot": "filter-bar-count",
205
- children: chips.length
206
- }) : null]
207
- }), /* @__PURE__ */ jsx(Popover, {
208
- placement: "bottom start",
209
- className: "w-80 max-w-[calc(100vw-2rem)]",
210
- children: /* @__PURE__ */ jsxs("div", {
211
- "data-slot": "filter-bar-panel",
212
- className: "flex flex-col gap-3",
213
- children: [
214
- /* @__PURE__ */ jsx("p", {
215
- className: "m-0 text-sm font-semibold",
216
- children: buttonLabel
217
- }),
218
- filters.map((f) => isCustom(f) ? /* @__PURE__ */ jsx("div", { children: f.render() }, f.id) : /* @__PURE__ */ jsx(Select, {
219
- label: f.label,
220
- size: "sm",
221
- selectedKey: f.value,
222
- onSelectionChange: (key) => {
223
- if (key != null) f.onChange(key);
224
- },
225
- children: f.options.map((o) => /* @__PURE__ */ jsx(SelectItem, {
226
- id: o.id,
227
- textValue: o.label,
228
- children: o.label
229
- }, o.id))
230
- }, f.id)),
231
- /* @__PURE__ */ jsxs("div", {
232
- className: "flex items-center justify-between gap-2 pt-1",
233
- children: [/* @__PURE__ */ jsx(Button, {
234
- variant: "ghost",
235
- size: "sm",
236
- onPress: clearAll,
237
- isDisabled: chips.length === 0,
238
- children: clearAllLabel
239
- }), /* @__PURE__ */ jsx(Button, {
240
- variant: "primary",
241
- size: "sm",
242
- onPress: () => setOpen(false),
243
- children: doneLabel
244
- })]
245
- })
246
- ]
440
+ children: buttonLabel
441
+ }), /* @__PURE__ */ jsx(Popover$1, {
442
+ placement: "bottom end",
443
+ offset: 8,
444
+ className: "w-64 max-w-[calc(100vw-2rem)] p-1.5",
445
+ children: /* @__PURE__ */ jsx(FilterPanel, {
446
+ filters,
447
+ label: buttonLabel,
448
+ backLabel,
449
+ onClose: () => setOpen(false)
247
450
  })
248
451
  })]
249
- }) : null,
250
- end ? /* @__PURE__ */ jsx("div", {
251
- className: "ms-auto flex flex-wrap items-center gap-2",
252
- children: end
253
- }) : null
254
- ]
255
- }), places.list.length > 0 ? /* @__PURE__ */ jsx("div", {
256
- ref: frame,
257
- "data-slot": "filter-bar-chips-frame",
258
- className: "height-follow",
259
- children: /* @__PURE__ */ jsx("ul", {
260
- ref: row,
261
- "aria-label": chipsLabel,
262
- "data-slot": "filter-bar-chips",
263
- className: "m-0 flex list-none flex-wrap items-center gap-2 p-0",
264
- children: places.list.map(({ item, leaving }) => item.chip ? /* @__PURE__ */ jsx(Slot, {
265
- id: item.id,
266
- leaving,
267
- ready,
268
- onDone: places.done,
269
- children: /* @__PURE__ */ jsxs("span", {
270
- "data-slot": "filter-bar-chip",
271
- className: filterBarChipVariants(),
272
- children: [/* @__PURE__ */ jsx(ChipText, { text: item.chip.text }), /* @__PURE__ */ jsx(Button, {
273
- variant: "ghost",
274
- size: "sm",
275
- "aria-label": removeLabel(item.chip.text),
276
- onPress: item.chip.clear,
277
- className: "h-6 px-1",
278
- children: /* @__PURE__ */ jsx(CrossGlyph, {})
279
- })]
280
- })
281
- }, item.id) : /* @__PURE__ */ jsx(Slot, {
282
- id: item.id,
283
- leaving,
284
- ready,
285
- onDone: places.done,
286
- children: /* @__PURE__ */ jsx(Button, {
287
- variant: "link",
288
- size: "sm",
289
- onPress: clearAll,
290
- children: clearAllLabel
291
- })
292
- }, item.id))
293
- })
294
- }) : null]
452
+ }) : null, end]
453
+ }) : null
454
+ ]
295
455
  });
296
456
  });
297
457
  //#endregion