@eifi1/ui-kit 0.4.1 → 0.5.0

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 (410) hide show
  1. package/LICENSE +32 -0
  2. package/README.md +498 -57
  3. package/dist/chart.d.ts +10 -0
  4. package/dist/chart.js +10 -0
  5. package/dist/chart.js.map +1 -0
  6. package/dist/components/account-settings.d.ts +11 -0
  7. package/dist/components/account-settings.js +19 -12
  8. package/dist/components/account-settings.js.map +1 -1
  9. package/dist/components/alert-banner.d.ts +10 -6
  10. package/dist/components/alert-banner.js +20 -16
  11. package/dist/components/alert-banner.js.map +1 -1
  12. package/dist/components/amount-input.d.ts +30 -4
  13. package/dist/components/amount-input.js +36 -14
  14. package/dist/components/amount-input.js.map +1 -1
  15. package/dist/components/calculator.d.ts +24 -7
  16. package/dist/components/calculator.js +37 -20
  17. package/dist/components/calculator.js.map +1 -1
  18. package/dist/components/chart-zoom.d.ts +156 -0
  19. package/dist/components/chart-zoom.js +262 -0
  20. package/dist/components/chart-zoom.js.map +1 -0
  21. package/dist/components/chart.d.ts +28 -3
  22. package/dist/components/chart.js +71 -32
  23. package/dist/components/chart.js.map +1 -1
  24. package/dist/components/checkbox.d.ts +51 -0
  25. package/dist/components/checkbox.js +123 -0
  26. package/dist/components/checkbox.js.map +1 -0
  27. package/dist/components/chip.d.ts +128 -0
  28. package/dist/components/chip.js +357 -0
  29. package/dist/components/chip.js.map +1 -0
  30. package/dist/components/choice-parts.d.ts +24 -0
  31. package/dist/components/choice-parts.js +18 -0
  32. package/dist/components/choice-parts.js.map +1 -0
  33. package/dist/components/combobox-core.d.ts +52 -9
  34. package/dist/components/combobox-core.js +89 -19
  35. package/dist/components/combobox-core.js.map +1 -1
  36. package/dist/components/combobox.d.ts +43 -33
  37. package/dist/components/combobox.js +409 -340
  38. package/dist/components/combobox.js.map +1 -1
  39. package/dist/components/currency-select.d.ts +48 -8
  40. package/dist/components/currency-select.js +107 -65
  41. package/dist/components/currency-select.js.map +1 -1
  42. package/dist/components/data-table-filter-popover.d.ts +15 -3
  43. package/dist/components/data-table-filter-popover.js +23 -17
  44. package/dist/components/data-table-filter-popover.js.map +1 -1
  45. package/dist/components/data-table-filters.d.ts +1 -1
  46. package/dist/components/data-table-filters.js +3 -2
  47. package/dist/components/data-table-filters.js.map +1 -1
  48. package/dist/components/data-table-labels.d.ts +53 -2
  49. package/dist/components/data-table-labels.js +31 -2
  50. package/dist/components/data-table-labels.js.map +1 -1
  51. package/dist/components/data-table-pagination.d.ts +3 -1
  52. package/dist/components/data-table-pagination.js +16 -10
  53. package/dist/components/data-table-pagination.js.map +1 -1
  54. package/dist/components/data-table-sort.js +1 -0
  55. package/dist/components/data-table-sort.js.map +1 -1
  56. package/dist/components/data-table.d.ts +1 -1
  57. package/dist/components/data-table.js +201 -85
  58. package/dist/components/data-table.js.map +1 -1
  59. package/dist/components/date-picker.d.ts +25 -9
  60. package/dist/components/date-picker.js +232 -94
  61. package/dist/components/date-picker.js.map +1 -1
  62. package/dist/components/dropdown.d.ts +74 -19
  63. package/dist/components/dropdown.js +72 -22
  64. package/dist/components/dropdown.js.map +1 -1
  65. package/dist/components/entity-combobox.d.ts +5 -4
  66. package/dist/components/entity-combobox.js +107 -76
  67. package/dist/components/entity-combobox.js.map +1 -1
  68. package/dist/components/facing-pair.d.ts +36 -0
  69. package/dist/components/facing-pair.js +40 -0
  70. package/dist/components/facing-pair.js.map +1 -0
  71. package/dist/components/field-sync.d.ts +164 -0
  72. package/dist/components/field-sync.js +264 -0
  73. package/dist/components/field-sync.js.map +1 -0
  74. package/dist/components/file-dropzone.d.ts +21 -7
  75. package/dist/components/file-dropzone.js +28 -7
  76. package/dist/components/file-dropzone.js.map +1 -1
  77. package/dist/components/full-bleed-dialog.d.ts +37 -26
  78. package/dist/components/full-bleed-dialog.js +20 -5
  79. package/dist/components/full-bleed-dialog.js.map +1 -1
  80. package/dist/components/grouped-picker.d.ts +21 -10
  81. package/dist/components/grouped-picker.js +66 -52
  82. package/dist/components/grouped-picker.js.map +1 -1
  83. package/dist/components/hover-menu.d.ts +20 -4
  84. package/dist/components/hover-menu.js +15 -4
  85. package/dist/components/hover-menu.js.map +1 -1
  86. package/dist/components/mini-calendar.d.ts +102 -14
  87. package/dist/components/mini-calendar.js +256 -70
  88. package/dist/components/mini-calendar.js.map +1 -1
  89. package/dist/components/modal.d.ts +9 -3
  90. package/dist/components/modal.js +34 -45
  91. package/dist/components/modal.js.map +1 -1
  92. package/dist/components/month-picker.d.ts +79 -0
  93. package/dist/components/month-picker.js +342 -0
  94. package/dist/components/month-picker.js.map +1 -0
  95. package/dist/components/multi-entity-combobox.d.ts +7 -6
  96. package/dist/components/multi-entity-combobox.js +112 -72
  97. package/dist/components/multi-entity-combobox.js.map +1 -1
  98. package/dist/components/multi-select.d.ts +13 -11
  99. package/dist/components/multi-select.js +161 -84
  100. package/dist/components/multi-select.js.map +1 -1
  101. package/dist/components/number-field.d.ts +92 -0
  102. package/dist/components/number-field.js +111 -0
  103. package/dist/components/number-field.js.map +1 -0
  104. package/dist/components/number-input.d.ts +21 -2
  105. package/dist/components/number-input.js +2 -1
  106. package/dist/components/number-input.js.map +1 -1
  107. package/dist/components/numpad-sheet.d.ts +25 -4
  108. package/dist/components/numpad-sheet.js +30 -13
  109. package/dist/components/numpad-sheet.js.map +1 -1
  110. package/dist/components/page-contents.d.ts +90 -0
  111. package/dist/components/page-contents.js +170 -0
  112. package/dist/components/page-contents.js.map +1 -0
  113. package/dist/components/password-strength.d.ts +106 -0
  114. package/dist/components/password-strength.js +132 -0
  115. package/dist/components/password-strength.js.map +1 -0
  116. package/dist/components/picker-sheet.d.ts +23 -15
  117. package/dist/components/picker-sheet.js +34 -14
  118. package/dist/components/picker-sheet.js.map +1 -1
  119. package/dist/components/popover.d.ts +62 -4
  120. package/dist/components/popover.js +52 -2
  121. package/dist/components/popover.js.map +1 -1
  122. package/dist/components/search-field.d.ts +25 -7
  123. package/dist/components/search-field.js +7 -5
  124. package/dist/components/search-field.js.map +1 -1
  125. package/dist/components/series-chart-labels.d.ts +14 -0
  126. package/dist/components/series-chart-labels.js +11 -0
  127. package/dist/components/series-chart-labels.js.map +1 -0
  128. package/dist/components/series-chart.d.ts +207 -0
  129. package/dist/components/series-chart.js +223 -0
  130. package/dist/components/series-chart.js.map +1 -0
  131. package/dist/components/settings-fields.d.ts +14 -4
  132. package/dist/components/settings-fields.js +19 -8
  133. package/dist/components/settings-fields.js.map +1 -1
  134. package/dist/components/signature-pad.d.ts +119 -0
  135. package/dist/components/signature-pad.js +405 -0
  136. package/dist/components/signature-pad.js.map +1 -0
  137. package/dist/components/slider.d.ts +80 -0
  138. package/dist/components/slider.js +155 -0
  139. package/dist/components/slider.js.map +1 -0
  140. package/dist/components/sparkline.d.ts +93 -0
  141. package/dist/components/sparkline.js +205 -0
  142. package/dist/components/sparkline.js.map +1 -0
  143. package/dist/components/stat-tile.d.ts +151 -0
  144. package/dist/components/stat-tile.js +236 -0
  145. package/dist/components/stat-tile.js.map +1 -0
  146. package/dist/components/swipeable-row.d.ts +25 -2
  147. package/dist/components/swipeable-row.js +31 -0
  148. package/dist/components/swipeable-row.js.map +1 -1
  149. package/dist/components/switch.d.ts +60 -0
  150. package/dist/components/switch.js +81 -0
  151. package/dist/components/switch.js.map +1 -0
  152. package/dist/components/time-input.d.ts +58 -0
  153. package/dist/components/time-input.js +50 -0
  154. package/dist/components/time-input.js.map +1 -0
  155. package/dist/components/toggle-group.d.ts +18 -3
  156. package/dist/components/toggle-group.js +10 -6
  157. package/dist/components/toggle-group.js.map +1 -1
  158. package/dist/components/toggle-legend.d.ts +115 -0
  159. package/dist/components/toggle-legend.js +114 -0
  160. package/dist/components/toggle-legend.js.map +1 -0
  161. package/dist/components/tooltip.d.ts +40 -11
  162. package/dist/components/tooltip.js +77 -20
  163. package/dist/components/tooltip.js.map +1 -1
  164. package/dist/components/treemap.d.ts +132 -0
  165. package/dist/components/treemap.js +179 -0
  166. package/dist/components/treemap.js.map +1 -0
  167. package/dist/components/ui.d.ts +159 -46
  168. package/dist/components/ui.js +220 -115
  169. package/dist/components/ui.js.map +1 -1
  170. package/dist/components/use-mobile-reveal.js +1 -0
  171. package/dist/components/use-mobile-reveal.js.map +1 -1
  172. package/dist/components/use-table-state.d.ts +1 -1
  173. package/dist/components/use-table-state.js +38 -17
  174. package/dist/components/use-table-state.js.map +1 -1
  175. package/dist/components/user-avatar.d.ts +5 -3
  176. package/dist/components/user-avatar.js +4 -2
  177. package/dist/components/user-avatar.js.map +1 -1
  178. package/dist/components/wizard-stepper.js +3 -2
  179. package/dist/components/wizard-stepper.js.map +1 -1
  180. package/dist/{data-table-filters-aXyf0Xub.d.ts → data-table-filters-DM1auUfH.d.ts} +27 -5
  181. package/dist/data-table.d.ts +7 -0
  182. package/dist/data-table.js +12 -0
  183. package/dist/data-table.js.map +1 -0
  184. package/dist/feedback/feedback-attachment.js +12 -11
  185. package/dist/feedback/feedback-attachment.js.map +1 -1
  186. package/dist/feedback/feedback-dialog.js +2 -1
  187. package/dist/feedback/feedback-dialog.js.map +1 -1
  188. package/dist/feedback/feedback-inbox.d.ts +33 -7
  189. package/dist/feedback/feedback-inbox.js +49 -37
  190. package/dist/feedback/feedback-inbox.js.map +1 -1
  191. package/dist/feedback.d.ts +5 -0
  192. package/dist/feedback.js +5 -0
  193. package/dist/feedback.js.map +1 -0
  194. package/dist/hooks/use-anchored-panel.js +1 -0
  195. package/dist/hooks/use-anchored-panel.js.map +1 -1
  196. package/dist/hooks/use-anchored-rect.js +1 -0
  197. package/dist/hooks/use-anchored-rect.js.map +1 -1
  198. package/dist/hooks/use-announce.d.ts +46 -0
  199. package/dist/hooks/use-announce.js +37 -0
  200. package/dist/hooks/use-announce.js.map +1 -0
  201. package/dist/hooks/use-body-scroll-lock.js +1 -0
  202. package/dist/hooks/use-body-scroll-lock.js.map +1 -1
  203. package/dist/hooks/use-close-transition.js +1 -0
  204. package/dist/hooks/use-close-transition.js.map +1 -1
  205. package/dist/hooks/use-dismiss.js +1 -0
  206. package/dist/hooks/use-dismiss.js.map +1 -1
  207. package/dist/hooks/use-focus-trap.d.ts +16 -0
  208. package/dist/hooks/use-focus-trap.js +67 -0
  209. package/dist/hooks/use-focus-trap.js.map +1 -0
  210. package/dist/hooks/use-media-query.js +1 -0
  211. package/dist/hooks/use-media-query.js.map +1 -1
  212. package/dist/hooks/use-overlay-history.js +27 -1
  213. package/dist/hooks/use-overlay-history.js.map +1 -1
  214. package/dist/hooks/use-row-swipe.js +1 -0
  215. package/dist/hooks/use-row-swipe.js.map +1 -1
  216. package/dist/i18n/defaults.d.ts +31 -0
  217. package/dist/i18n/defaults.js +63 -0
  218. package/dist/i18n/defaults.js.map +1 -0
  219. package/dist/i18n/kit-labels.d.ts +250 -0
  220. package/dist/i18n/kit-labels.js +184 -0
  221. package/dist/i18n/kit-labels.js.map +1 -0
  222. package/dist/index.d.ts +47 -22
  223. package/dist/index.js +28 -0
  224. package/dist/index.js.map +1 -1
  225. package/dist/lib/calc.js +1 -0
  226. package/dist/lib/calc.js.map +1 -1
  227. package/dist/lib/cn.js +1 -0
  228. package/dist/lib/cn.js.map +1 -1
  229. package/dist/lib/dates.js +1 -0
  230. package/dist/lib/dates.js.map +1 -1
  231. package/dist/lib/logger.js +1 -0
  232. package/dist/lib/logger.js.map +1 -1
  233. package/dist/lib/safe-storage.js +1 -0
  234. package/dist/lib/safe-storage.js.map +1 -1
  235. package/dist/search/command-palette.d.ts +11 -1
  236. package/dist/search/command-palette.js +21 -15
  237. package/dist/search/command-palette.js.map +1 -1
  238. package/dist/search.d.ts +2 -0
  239. package/dist/search.js +3 -0
  240. package/dist/search.js.map +1 -0
  241. package/dist/shell/app-shell.d.ts +33 -4
  242. package/dist/shell/app-shell.js +275 -78
  243. package/dist/shell/app-shell.js.map +1 -1
  244. package/dist/shell/option-switcher-menu.js +3 -2
  245. package/dist/shell/option-switcher-menu.js.map +1 -1
  246. package/dist/shell/role-switcher.d.ts +6 -0
  247. package/dist/shell/role-switcher.js +7 -4
  248. package/dist/shell/role-switcher.js.map +1 -1
  249. package/dist/shell/top-bar.d.ts +15 -7
  250. package/dist/shell/top-bar.js +3 -5
  251. package/dist/shell/top-bar.js.map +1 -1
  252. package/dist/shell/topbar-action-menu.js +3 -2
  253. package/dist/shell/topbar-action-menu.js.map +1 -1
  254. package/dist/shell/topbar-controls.d.ts +5 -2
  255. package/dist/shell/topbar-controls.js +20 -15
  256. package/dist/shell/topbar-controls.js.map +1 -1
  257. package/dist/shell.d.ts +13 -0
  258. package/dist/shell.js +8 -0
  259. package/dist/shell.js.map +1 -0
  260. package/dist/theme/chart-palette.d.ts +19 -12
  261. package/dist/theme/chart-palette.js +24 -30
  262. package/dist/theme/chart-palette.js.map +1 -1
  263. package/dist/theme/color.d.ts +78 -0
  264. package/dist/theme/color.js +148 -0
  265. package/dist/theme/color.js.map +1 -0
  266. package/dist/theme/palette-derive.d.ts +140 -0
  267. package/dist/theme/palette-derive.js +376 -0
  268. package/dist/theme/palette-derive.js.map +1 -0
  269. package/dist/theme/palette-presets.d.ts +14 -1
  270. package/dist/theme/palette-presets.js +231 -3
  271. package/dist/theme/palette-presets.js.map +1 -1
  272. package/dist/theme/palette-store.js +1 -0
  273. package/dist/theme/palette-store.js.map +1 -1
  274. package/dist/theme/theme-store.js +1 -0
  275. package/dist/theme/theme-store.js.map +1 -1
  276. package/dist/tour/tour.d.ts +2 -1
  277. package/dist/tour/tour.js +15 -9
  278. package/dist/tour/tour.js.map +1 -1
  279. package/dist/tour.d.ts +2 -0
  280. package/dist/tour.js +3 -0
  281. package/dist/tour.js.map +1 -0
  282. package/dist/wizard/stepper-nav.js +10 -8
  283. package/dist/wizard/stepper-nav.js.map +1 -1
  284. package/dist/wizard/types.d.ts +18 -2
  285. package/dist/wizard/types.js +4 -1
  286. package/dist/wizard/types.js.map +1 -1
  287. package/dist/wizard/use-wizard.js +12 -4
  288. package/dist/wizard/use-wizard.js.map +1 -1
  289. package/dist/wizard/validation.js +1 -0
  290. package/dist/wizard/validation.js.map +1 -1
  291. package/dist/wizard/wizard-context.js +1 -0
  292. package/dist/wizard/wizard-context.js.map +1 -1
  293. package/dist/wizard/wizard-step.js +1 -0
  294. package/dist/wizard/wizard-step.js.map +1 -1
  295. package/dist/wizard/wizard-summary.js +5 -3
  296. package/dist/wizard/wizard-summary.js.map +1 -1
  297. package/dist/wizard.d.ts +8 -0
  298. package/dist/wizard.js +9 -0
  299. package/dist/wizard.js.map +1 -0
  300. package/package.json +66 -13
  301. package/src/chart.ts +22 -0
  302. package/src/components/account-settings.tsx +275 -0
  303. package/src/components/alert-banner.tsx +75 -0
  304. package/src/components/amount-input.tsx +471 -0
  305. package/src/components/calculator.tsx +252 -0
  306. package/src/components/chart-zoom.tsx +499 -0
  307. package/src/components/chart.tsx +363 -0
  308. package/src/components/checkbox.tsx +222 -0
  309. package/src/components/chip.tsx +570 -0
  310. package/src/components/choice-parts.ts +33 -0
  311. package/src/components/combobox-core.tsx +540 -0
  312. package/src/components/combobox.tsx +1026 -0
  313. package/src/components/currency-select.tsx +269 -0
  314. package/src/components/data-table-filter-popover.tsx +252 -0
  315. package/src/components/data-table-filters.ts +176 -0
  316. package/src/components/data-table-labels.ts +190 -0
  317. package/src/components/data-table-pagination.tsx +126 -0
  318. package/src/components/data-table-sort.ts +70 -0
  319. package/src/components/data-table.tsx +1740 -0
  320. package/src/components/date-picker.tsx +631 -0
  321. package/src/components/dropdown.tsx +341 -0
  322. package/src/components/entity-combobox.tsx +215 -0
  323. package/src/components/facing-pair.ts +86 -0
  324. package/src/components/field-sync.tsx +472 -0
  325. package/src/components/file-dropzone.tsx +181 -0
  326. package/src/components/full-bleed-dialog.tsx +179 -0
  327. package/src/components/grouped-picker.tsx +157 -0
  328. package/src/components/hover-menu.tsx +183 -0
  329. package/src/components/mini-calendar.tsx +554 -0
  330. package/src/components/modal.tsx +287 -0
  331. package/src/components/month-picker.tsx +549 -0
  332. package/src/components/multi-entity-combobox.tsx +226 -0
  333. package/src/components/multi-select.tsx +303 -0
  334. package/src/components/number-field.tsx +225 -0
  335. package/src/components/number-input.tsx +211 -0
  336. package/src/components/numpad-sheet.tsx +264 -0
  337. package/src/components/page-contents.tsx +289 -0
  338. package/src/components/password-strength.tsx +258 -0
  339. package/src/components/picker-sheet.tsx +251 -0
  340. package/src/components/popover.tsx +195 -0
  341. package/src/components/search-field.tsx +146 -0
  342. package/src/components/series-chart-labels.ts +26 -0
  343. package/src/components/series-chart.tsx +481 -0
  344. package/src/components/settings-fields.tsx +68 -0
  345. package/src/components/signature-pad.tsx +630 -0
  346. package/src/components/slider.tsx +313 -0
  347. package/src/components/sparkline.tsx +329 -0
  348. package/src/components/stat-tile.tsx +448 -0
  349. package/src/components/swipeable-row.tsx +271 -0
  350. package/src/components/switch.tsx +170 -0
  351. package/src/components/time-input.tsx +107 -0
  352. package/src/components/toggle-group.tsx +153 -0
  353. package/src/components/toggle-legend.tsx +229 -0
  354. package/src/components/tooltip.tsx +477 -0
  355. package/src/components/treemap.tsx +390 -0
  356. package/src/components/ui.tsx +1284 -0
  357. package/src/components/use-mobile-reveal.ts +54 -0
  358. package/src/components/use-table-state.ts +295 -0
  359. package/src/components/user-avatar.tsx +61 -0
  360. package/src/components/wizard-stepper.tsx +37 -0
  361. package/src/data-table.ts +19 -0
  362. package/src/feedback/feedback-attachment.tsx +246 -0
  363. package/src/feedback/feedback-dialog.tsx +209 -0
  364. package/src/feedback/feedback-inbox.tsx +615 -0
  365. package/src/feedback.ts +8 -0
  366. package/src/hooks/use-anchored-panel.ts +169 -0
  367. package/src/hooks/use-anchored-rect.ts +78 -0
  368. package/src/hooks/use-announce.ts +85 -0
  369. package/src/hooks/use-body-scroll-lock.ts +94 -0
  370. package/src/hooks/use-close-transition.ts +99 -0
  371. package/src/hooks/use-dismiss.ts +77 -0
  372. package/src/hooks/use-focus-trap.ts +160 -0
  373. package/src/hooks/use-media-query.ts +23 -0
  374. package/src/hooks/use-overlay-history.ts +345 -0
  375. package/src/hooks/use-row-swipe.ts +212 -0
  376. package/src/i18n/defaults.ts +68 -0
  377. package/src/i18n/kit-labels.tsx +447 -0
  378. package/src/index.ts +169 -0
  379. package/src/lib/calc.ts +309 -0
  380. package/src/lib/cn.ts +5 -0
  381. package/src/lib/dates.ts +187 -0
  382. package/src/lib/logger.ts +73 -0
  383. package/src/lib/safe-storage.ts +37 -0
  384. package/src/search/command-palette.tsx +342 -0
  385. package/src/search.ts +3 -0
  386. package/src/shell/app-shell.tsx +772 -0
  387. package/src/shell/option-switcher-menu.tsx +72 -0
  388. package/src/shell/role-switcher.tsx +46 -0
  389. package/src/shell/top-bar.tsx +39 -0
  390. package/src/shell/topbar-action-menu.tsx +91 -0
  391. package/src/shell/topbar-controls.tsx +229 -0
  392. package/src/shell.ts +10 -0
  393. package/src/theme/chart-palette.ts +145 -0
  394. package/src/theme/color.ts +266 -0
  395. package/src/theme/palette-derive.ts +681 -0
  396. package/src/theme/palette-presets.ts +760 -0
  397. package/src/theme/palette-store.ts +94 -0
  398. package/src/theme/theme-store.ts +145 -0
  399. package/src/tour/tour.tsx +586 -0
  400. package/src/tour.ts +3 -0
  401. package/src/vite-env.d.ts +26 -0
  402. package/src/wizard/stepper-nav.tsx +221 -0
  403. package/src/wizard/types.ts +178 -0
  404. package/src/wizard/use-wizard.ts +345 -0
  405. package/src/wizard/validation.ts +37 -0
  406. package/src/wizard/wizard-context.tsx +29 -0
  407. package/src/wizard/wizard-step.tsx +14 -0
  408. package/src/wizard/wizard-summary.tsx +69 -0
  409. package/src/wizard.ts +14 -0
  410. package/tokens.css +222 -20
@@ -1,9 +1,16 @@
1
+ "use client";
1
2
  import { jsx, jsxs } from "react/jsx-runtime";
2
- import { useMemo } from "react";
3
+ import { useId, useMemo } from "react";
3
4
  import { X } from "lucide-react";
4
5
  import { cn } from "../lib/cn.js";
5
6
  import { FieldChevron, FieldLabel, FIELD_TRIGGER, FIELD_FLOATING_PAD, FIELD_INVALID } from "./ui.js";
6
7
  import { ComboboxPanel, useComboboxCore } from "./combobox-core.js";
8
+ import {
9
+ DEFAULT_COMBOBOX_LABELS,
10
+ DEFAULT_COMMON_LABELS,
11
+ useKitLabels,
12
+ useKitLocale
13
+ } from "../i18n/kit-labels.js";
7
14
  function MultiEntityCombobox({
8
15
  value,
9
16
  onChange,
@@ -12,8 +19,8 @@ function MultiEntityCombobox({
12
19
  loading,
13
20
  label,
14
21
  placeholder,
15
- searchPlaceholder = "Search",
16
- emptyLabel = "No results",
22
+ searchPlaceholder,
23
+ emptyLabel,
17
24
  itemLabel,
18
25
  clearable,
19
26
  clearLabel,
@@ -22,16 +29,31 @@ function MultiEntityCombobox({
22
29
  onCreate,
23
30
  createLabel,
24
31
  className,
25
- invalid
32
+ invalid,
33
+ "aria-label": ariaLabel,
34
+ ...rest
26
35
  }) {
27
36
  const core = useComboboxCore({ options, loadOptions, loading });
28
- const { results, resolve, setOpen } = core;
37
+ const labels = useKitLabels("combobox", DEFAULT_COMBOBOX_LABELS, {
38
+ search: searchPlaceholder,
39
+ noResults: emptyLabel,
40
+ clear: clearLabel,
41
+ create: createLabel
42
+ });
43
+ const common = useKitLabels("common", DEFAULT_COMMON_LABELS);
44
+ const { open, results, resolve, setOpen } = core;
45
+ const listboxId = useId();
29
46
  const valueSet = useMemo(() => new Set(value), [value]);
47
+ const locale = useKitLocale();
48
+ const listFormat = useMemo(
49
+ () => new Intl.ListFormat(locale, { type: "conjunction", style: "narrow" }),
50
+ [locale]
51
+ );
30
52
  const summary = (() => {
31
53
  if (value.length === 0) return placeholder ?? "";
32
54
  if (itemLabel) return itemLabel(value.length);
33
55
  const chosen = value.map(resolve);
34
- return chosen.every(Boolean) ? chosen.map((o) => o.label).join(", ") : `${value.length} selected`;
56
+ return chosen.every(Boolean) ? listFormat.format(chosen.map((o) => o.label)) : labels.selectedCount(value.length);
35
57
  })();
36
58
  const q = core.query.trim();
37
59
  const showCreate = Boolean(onCreate) && q.length > 0 && !results.some((o) => o.label.toLowerCase() === q.toLowerCase());
@@ -41,74 +63,92 @@ function MultiEntityCombobox({
41
63
  if (valueSet.has(o.value)) onChange(value.filter((v) => v !== o.value));
42
64
  else onChange([...value, o.value]);
43
65
  };
44
- return /* @__PURE__ */ jsxs("div", { className: cn("relative", className), children: [
45
- label !== void 0 && /* @__PURE__ */ jsx(FieldLabel, { children: label }),
46
- /* @__PURE__ */ jsxs(
47
- "button",
48
- {
49
- ref: core.triggerRef,
50
- type: "button",
51
- "aria-label": typeof label === "string" ? `${label}: ${summary}` : void 0,
52
- disabled,
53
- "aria-invalid": invalid || void 0,
54
- onClick: () => !disabled && setOpen((o) => !o),
55
- className: cn(
56
- FIELD_TRIGGER,
57
- "pr-9",
58
- label !== void 0 && FIELD_FLOATING_PAD,
59
- disabled && "cursor-not-allowed opacity-50",
60
- invalid && FIELD_INVALID
61
- ),
62
- children: [
63
- /* @__PURE__ */ jsx(
64
- "span",
65
- {
66
- className: cn(
67
- "min-w-0 truncate",
68
- // Inherit FIELD_BASE's ink for a value, keep the placeholder grey — the
69
- // same rule every field on a form row follows (Keksdose dev#477).
70
- !value.length && "text-slate-400 dark:text-slate-500"
71
- ),
72
- children: summary
66
+ return (
67
+ // `rest` dresses the wrapper, which has no role; the accessible NAME goes on
68
+ // the trigger, which has one. Spread FIRST so the trigger's ARIA and the
69
+ // handlers that open the panel cannot be clobbered from outside.
70
+ /* @__PURE__ */ jsxs("div", { ...rest, className: cn("relative", className), children: [
71
+ label !== void 0 && /* @__PURE__ */ jsx(FieldLabel, { children: label }),
72
+ /* @__PURE__ */ jsxs(
73
+ "button",
74
+ {
75
+ ref: core.triggerRef,
76
+ type: "button",
77
+ role: "combobox",
78
+ "aria-expanded": open,
79
+ "aria-controls": listboxId,
80
+ "aria-haspopup": "listbox",
81
+ "aria-label": ariaLabel ?? (typeof label === "string" ? common.fieldValue(label, summary) : void 0),
82
+ disabled,
83
+ "aria-invalid": invalid || void 0,
84
+ onClick: () => !disabled && setOpen((o) => !o),
85
+ onKeyDown: (e) => {
86
+ if (disabled) return;
87
+ if (e.key === "ArrowDown" || e.key === "ArrowUp") {
88
+ e.preventDefault();
89
+ setOpen(true);
73
90
  }
91
+ },
92
+ className: cn(
93
+ FIELD_TRIGGER,
94
+ "pr-9",
95
+ label !== void 0 && FIELD_FLOATING_PAD,
96
+ disabled && "cursor-not-allowed opacity-50",
97
+ invalid && FIELD_INVALID
74
98
  ),
75
- showClear ? /* @__PURE__ */ jsx(
76
- "span",
77
- {
78
- role: "button",
79
- tabIndex: -1,
80
- "aria-label": clearLabel ?? "Clear",
81
- onClick: (e) => {
82
- e.stopPropagation();
83
- onChange([]);
84
- },
85
- className: "absolute right-2 top-1/2 -translate-y-1/2 rounded p-0.5 text-slate-400 hover:text-slate-600 dark:text-slate-500 dark:hover:text-slate-300",
86
- children: /* @__PURE__ */ jsx(X, { className: "size-4" })
87
- }
88
- ) : /* @__PURE__ */ jsx(FieldChevron, {})
89
- ]
90
- }
91
- ),
92
- /* @__PURE__ */ jsx(
93
- ComboboxPanel,
94
- {
95
- core,
96
- multi: true,
97
- sheetTitle: label ?? placeholder,
98
- searchPlaceholder,
99
- emptyLabel,
100
- closeLabel,
101
- isSelected: (v) => valueSet.has(v),
102
- onChoose: toggle,
103
- showCreate,
104
- onCreate: () => {
105
- onCreate?.(q);
106
- core.setQuery("");
107
- },
108
- createContent: createLabel ? createLabel(q) : `Create \u201C${q}\u201D`
109
- }
110
- )
111
- ] });
99
+ children: [
100
+ /* @__PURE__ */ jsx(
101
+ "span",
102
+ {
103
+ className: cn(
104
+ "min-w-0 truncate",
105
+ // Inherit FIELD_BASE's ink for a value, keep the placeholder tone
106
+ // (`--text-placeholder`) — the same rule every field on a form row
107
+ // follows (Keksdose dev#477).
108
+ !value.length && "text-[var(--text-placeholder)]"
109
+ ),
110
+ children: summary
111
+ }
112
+ ),
113
+ showClear ? /* @__PURE__ */ jsx(
114
+ "span",
115
+ {
116
+ role: "button",
117
+ tabIndex: -1,
118
+ "aria-label": labels.clear,
119
+ onClick: (e) => {
120
+ e.stopPropagation();
121
+ onChange([]);
122
+ },
123
+ className: "absolute right-2 top-1/2 -translate-y-1/2 rounded p-0.5 text-[var(--text-placeholder)] hover:text-[var(--text-secondary)]",
124
+ children: /* @__PURE__ */ jsx(X, { className: "size-4" })
125
+ }
126
+ ) : /* @__PURE__ */ jsx(FieldChevron, {})
127
+ ]
128
+ }
129
+ ),
130
+ /* @__PURE__ */ jsx(
131
+ ComboboxPanel,
132
+ {
133
+ core,
134
+ listboxId,
135
+ multi: true,
136
+ sheetTitle: label ?? placeholder,
137
+ searchPlaceholder: labels.search,
138
+ emptyLabel: labels.noResults,
139
+ closeLabel,
140
+ isSelected: (v) => valueSet.has(v),
141
+ onChoose: toggle,
142
+ showCreate,
143
+ onCreate: () => {
144
+ onCreate?.(q);
145
+ core.setQuery("");
146
+ },
147
+ createContent: labels.create(q)
148
+ }
149
+ )
150
+ ] })
151
+ );
112
152
  }
113
153
  export {
114
154
  MultiEntityCombobox
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/components/multi-entity-combobox.tsx"],"sourcesContent":["import { useMemo, type ReactNode } from \"react\";\nimport { X } from \"lucide-react\";\nimport { cn } from \"../lib/cn\";\nimport { FieldChevron, FieldLabel, FIELD_TRIGGER, FIELD_FLOATING_PAD, FIELD_INVALID } from \"./ui\";\nimport { ComboboxPanel, useComboboxCore, type ComboOption } from \"./combobox-core\";\n\nexport interface MultiEntityComboboxProps<V extends string | number> {\n /** Selected ids. */\n value: V[];\n onChange: (value: V[]) => void;\n /** Already-loaded options (client-side filtered); also resolves selected labels. */\n options?: ComboOption<V>[];\n /** Async option source, debounced + race-safe. When set, `options` resolves\n * labels only. */\n loadOptions?: (query: string) => Promise<ComboOption<V>[]>;\n loading?: boolean;\n label?: ReactNode;\n placeholder?: string;\n searchPlaceholder?: string;\n emptyLabel?: string;\n /** Trigger summary when 1+ selected (default: the labels joined by \", \", or\n * \"N selected\" when a label can't be resolved). */\n itemLabel?: (count: number) => string;\n clearable?: boolean;\n clearLabel?: string;\n /** The phone sheet's close button. Its own prop rather than a reuse of\n * `clearLabel`: \"clear the selection\" and \"close this screen\" are different\n * actions, and on a full-screen sheet the close button is the only way out —\n * so it is the one control here that MUST be in the reader's language. */\n closeLabel?: string;\n disabled?: boolean;\n /** When set, a \"create\" row appears for a non-empty query with no exact match.\n * The panel stays open (adding the new entity to `value` is the caller's job). */\n onCreate?: (query: string) => void;\n createLabel?: (query: string) => string;\n className?: string;\n /** Required and unanswered — {@link FIELD_INVALID}. See {@link Input}'s `invalid`. */\n invalid?: boolean;\n}\n\n/**\n * Multi-value sibling of {@link EntityCombobox}: pick several entities by search,\n * with the same async `loadOptions`/`onCreate` support. Rows show a checkbox and\n * toggle without closing the panel; the trigger summarises the selection.\n */\nexport function MultiEntityCombobox<V extends string | number>({\n value,\n onChange,\n options,\n loadOptions,\n loading,\n label,\n placeholder,\n searchPlaceholder = \"Search\",\n emptyLabel = \"No results\",\n itemLabel,\n clearable,\n clearLabel,\n closeLabel,\n disabled,\n onCreate,\n createLabel,\n className,\n invalid,\n}: MultiEntityComboboxProps<V>) {\n const core = useComboboxCore<V>({ options, loadOptions, loading });\n const { results, resolve, setOpen } = core;\n\n const valueSet = useMemo(() => new Set(value), [value]);\n const summary = (() => {\n if (value.length === 0) return placeholder ?? \"\";\n if (itemLabel) return itemLabel(value.length);\n const chosen = value.map(resolve);\n return chosen.every(Boolean)\n ? chosen.map((o) => o!.label).join(\", \")\n : `${value.length} selected`;\n })();\n\n const q = core.query.trim();\n const showCreate =\n Boolean(onCreate) && q.length > 0 && !results.some((o) => o.label.toLowerCase() === q.toLowerCase());\n const showClear = Boolean(clearable && value.length > 0 && !disabled);\n\n const toggle = (o: ComboOption<V>) => {\n core.cacheRef.current.set(o.value, o);\n if (valueSet.has(o.value)) onChange(value.filter((v) => v !== o.value));\n else onChange([...value, o.value]);\n // Stay open — multi-select keeps adding.\n };\n\n return (\n <div className={cn(\"relative\", className)}>\n {label !== undefined && <FieldLabel>{label}</FieldLabel>}\n <button\n ref={core.triggerRef}\n type=\"button\"\n // The label is a floating <span>, not a <label for>, so without this the\n // trigger's accessible name is whatever value happens to be selected —\n // \"Checking\" with nothing saying it is the account. The label AND the\n // value, because `aria-label` replaces the content rather than adding to\n // it, and a control that announces only its name has lost the answer.\n aria-label={\n typeof label === \"string\" ? `${label}: ${summary}` : undefined\n }\n disabled={disabled}\n aria-invalid={invalid || undefined}\n onClick={() => !disabled && setOpen((o) => !o)}\n className={cn(\n FIELD_TRIGGER,\n \"pr-9\",\n label !== undefined && FIELD_FLOATING_PAD,\n disabled && \"cursor-not-allowed opacity-50\",\n invalid && FIELD_INVALID,\n )}\n >\n <span\n className={cn(\n \"min-w-0 truncate\",\n // Inherit FIELD_BASE's ink for a value, keep the placeholder grey — the\n // same rule every field on a form row follows (Keksdose dev#477).\n !value.length && \"text-slate-400 dark:text-slate-500\",\n )}\n >\n {summary}\n </span>\n {showClear ? (\n <span\n role=\"button\"\n tabIndex={-1}\n aria-label={clearLabel ?? \"Clear\"}\n onClick={(e) => {\n e.stopPropagation();\n onChange([]);\n }}\n className=\"absolute right-2 top-1/2 -translate-y-1/2 rounded p-0.5 text-slate-400 hover:text-slate-600 dark:text-slate-500 dark:hover:text-slate-300\"\n >\n <X className=\"size-4\" />\n </span>\n ) : (\n <FieldChevron />\n )}\n </button>\n <ComboboxPanel\n core={core}\n multi\n // On a phone the panel becomes a full-screen sheet, which needs the field's\n // own label to say what it is asking for (live #200).\n sheetTitle={label ?? placeholder}\n searchPlaceholder={searchPlaceholder}\n emptyLabel={emptyLabel}\n closeLabel={closeLabel}\n isSelected={(v) => valueSet.has(v)}\n onChoose={toggle}\n showCreate={showCreate}\n onCreate={() => {\n onCreate?.(q);\n core.setQuery(\"\");\n }}\n createContent={createLabel ? createLabel(q) : `Create “${q}”`}\n />\n </div>\n );\n}\n"],"mappings":"AA4F8B,cACxB,YADwB;AA5F9B,SAAS,eAA+B;AACxC,SAAS,SAAS;AAClB,SAAS,UAAU;AACnB,SAAS,cAAc,YAAY,eAAe,oBAAoB,qBAAqB;AAC3F,SAAS,eAAe,uBAAyC;AAyC1D,SAAS,oBAA+C;AAAA,EAC7D;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,oBAAoB;AAAA,EACpB,aAAa;AAAA,EACb;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAAgC;AAC9B,QAAM,OAAO,gBAAmB,EAAE,SAAS,aAAa,QAAQ,CAAC;AACjE,QAAM,EAAE,SAAS,SAAS,QAAQ,IAAI;AAEtC,QAAM,WAAW,QAAQ,MAAM,IAAI,IAAI,KAAK,GAAG,CAAC,KAAK,CAAC;AACtD,QAAM,WAAW,MAAM;AACrB,QAAI,MAAM,WAAW,EAAG,QAAO,eAAe;AAC9C,QAAI,UAAW,QAAO,UAAU,MAAM,MAAM;AAC5C,UAAM,SAAS,MAAM,IAAI,OAAO;AAChC,WAAO,OAAO,MAAM,OAAO,IACvB,OAAO,IAAI,CAAC,MAAM,EAAG,KAAK,EAAE,KAAK,IAAI,IACrC,GAAG,MAAM,MAAM;AAAA,EACrB,GAAG;AAEH,QAAM,IAAI,KAAK,MAAM,KAAK;AAC1B,QAAM,aACJ,QAAQ,QAAQ,KAAK,EAAE,SAAS,KAAK,CAAC,QAAQ,KAAK,CAAC,MAAM,EAAE,MAAM,YAAY,MAAM,EAAE,YAAY,CAAC;AACrG,QAAM,YAAY,QAAQ,aAAa,MAAM,SAAS,KAAK,CAAC,QAAQ;AAEpE,QAAM,SAAS,CAAC,MAAsB;AACpC,SAAK,SAAS,QAAQ,IAAI,EAAE,OAAO,CAAC;AACpC,QAAI,SAAS,IAAI,EAAE,KAAK,EAAG,UAAS,MAAM,OAAO,CAAC,MAAM,MAAM,EAAE,KAAK,CAAC;AAAA,QACjE,UAAS,CAAC,GAAG,OAAO,EAAE,KAAK,CAAC;AAAA,EAEnC;AAEA,SACE,qBAAC,SAAI,WAAW,GAAG,YAAY,SAAS,GACrC;AAAA,cAAU,UAAa,oBAAC,cAAY,iBAAM;AAAA,IAC3C;AAAA,MAAC;AAAA;AAAA,QACC,KAAK,KAAK;AAAA,QACV,MAAK;AAAA,QAML,cACE,OAAO,UAAU,WAAW,GAAG,KAAK,KAAK,OAAO,KAAK;AAAA,QAEvD;AAAA,QACA,gBAAc,WAAW;AAAA,QACzB,SAAS,MAAM,CAAC,YAAY,QAAQ,CAAC,MAAM,CAAC,CAAC;AAAA,QAC7C,WAAW;AAAA,UACT;AAAA,UACA;AAAA,UACA,UAAU,UAAa;AAAA,UACvB,YAAY;AAAA,UACZ,WAAW;AAAA,QACb;AAAA,QAEA;AAAA;AAAA,YAAC;AAAA;AAAA,cACC,WAAW;AAAA,gBACT;AAAA;AAAA;AAAA,gBAGA,CAAC,MAAM,UAAU;AAAA,cACnB;AAAA,cAEC;AAAA;AAAA,UACH;AAAA,UACC,YACC;AAAA,YAAC;AAAA;AAAA,cACC,MAAK;AAAA,cACL,UAAU;AAAA,cACV,cAAY,cAAc;AAAA,cAC1B,SAAS,CAAC,MAAM;AACd,kBAAE,gBAAgB;AAClB,yBAAS,CAAC,CAAC;AAAA,cACb;AAAA,cACA,WAAU;AAAA,cAEV,8BAAC,KAAE,WAAU,UAAS;AAAA;AAAA,UACxB,IAEA,oBAAC,gBAAa;AAAA;AAAA;AAAA,IAElB;AAAA,IACA;AAAA,MAAC;AAAA;AAAA,QACC;AAAA,QACA,OAAK;AAAA,QAGL,YAAY,SAAS;AAAA,QACrB;AAAA,QACA;AAAA,QACA;AAAA,QACA,YAAY,CAAC,MAAM,SAAS,IAAI,CAAC;AAAA,QACjC,UAAU;AAAA,QACV;AAAA,QACA,UAAU,MAAM;AACd,qBAAW,CAAC;AACZ,eAAK,SAAS,EAAE;AAAA,QAClB;AAAA,QACA,eAAe,cAAc,YAAY,CAAC,IAAI,gBAAW,CAAC;AAAA;AAAA,IAC5D;AAAA,KACF;AAEJ;","names":[]}
1
+ {"version":3,"sources":["../../src/components/multi-entity-combobox.tsx"],"sourcesContent":["import { useId, useMemo, type ComponentPropsWithoutRef, type ReactNode } from \"react\";\nimport { X } from \"lucide-react\";\nimport { cn } from \"../lib/cn\";\nimport { FieldChevron, FieldLabel, FIELD_TRIGGER, FIELD_FLOATING_PAD, FIELD_INVALID } from \"./ui\";\nimport { ComboboxPanel, useComboboxCore, type ComboOption } from \"./combobox-core\";\nimport {\n DEFAULT_COMBOBOX_LABELS,\n DEFAULT_COMMON_LABELS,\n useKitLabels,\n useKitLocale,\n} from \"../i18n/kit-labels\";\n\n/** `onChange` is the kit's — \"a selection was made\", carrying values — rather\n * than the div's form event, so the DOM's spelling of it is omitted. */\nexport interface MultiEntityComboboxProps<V extends string | number>\n extends Omit<ComponentPropsWithoutRef<\"div\">, \"onChange\"> {\n /** Selected ids. */\n value: V[];\n onChange: (value: V[]) => void;\n /** Already-loaded options (client-side filtered); also resolves selected labels. */\n options?: ComboOption<V>[];\n /** Async option source, debounced + race-safe. When set, `options` resolves\n * labels only. */\n loadOptions?: (query: string) => Promise<ComboOption<V>[]>;\n loading?: boolean;\n label?: ReactNode;\n placeholder?: string;\n searchPlaceholder?: string;\n emptyLabel?: string;\n /** Trigger summary when 1+ selected (default: the labels as a locale-formatted\n * list, or `combobox.selectedCount` when a label can't be resolved). */\n itemLabel?: (count: number) => string;\n clearable?: boolean;\n clearLabel?: string;\n /** The phone sheet's close button. Its own prop rather than a reuse of\n * `clearLabel`: \"clear the selection\" and \"close this screen\" are different\n * actions, and on a full-screen sheet the close button is the only way out —\n * so it is the one control here that MUST be in the reader's language. */\n closeLabel?: string;\n disabled?: boolean;\n /** When set, a \"create\" row appears for a non-empty query with no exact match.\n * The panel stays open (adding the new entity to `value` is the caller's job). */\n onCreate?: (query: string) => void;\n createLabel?: (query: string) => string;\n /** Required and unanswered — {@link FIELD_INVALID}. See {@link Input}'s `invalid`. */\n invalid?: boolean;\n}\n\n/**\n * Multi-value sibling of {@link EntityCombobox}: pick several entities by search,\n * with the same async `loadOptions`/`onCreate` support. Rows show a checkbox and\n * toggle without closing the panel; the trigger summarises the selection.\n */\nexport function MultiEntityCombobox<V extends string | number>({\n value,\n onChange,\n options,\n loadOptions,\n loading,\n label,\n placeholder,\n searchPlaceholder,\n emptyLabel,\n itemLabel,\n clearable,\n clearLabel,\n closeLabel,\n disabled,\n onCreate,\n createLabel,\n className,\n invalid,\n \"aria-label\": ariaLabel,\n ...rest\n}: MultiEntityComboboxProps<V>) {\n const core = useComboboxCore<V>({ options, loadOptions, loading });\n // The props are the per-instance overrides, the provider the app-wide ones; a\n // prop left `undefined` falls through to the provider rather than masking it.\n const labels = useKitLabels(\"combobox\", DEFAULT_COMBOBOX_LABELS, {\n search: searchPlaceholder,\n noResults: emptyLabel,\n clear: clearLabel,\n create: createLabel,\n });\n const common = useKitLabels(\"common\", DEFAULT_COMMON_LABELS);\n const { open, results, resolve, setOpen } = core;\n // One id per instance, generated here rather than in the core: `aria-controls` on\n // the trigger has to name the list while the list is still closed, so the id\n // belongs to whoever renders both ends of it.\n const listboxId = useId();\n\n const valueSet = useMemo(() => new Set(value), [value]);\n // A list of NAMES, not a sentence, so the narrow conjunction: English gets\n // \"Food, Rent, Travel\" (no \"and\" squeezed into a truncating trigger), German and\n // French still get their \"und\"/\"et\" where the language insists on it, and Japanese\n // and Chinese get the ideographic comma \"、\". The `unit` type looked like the\n // natural fit for a summary, but in ja/zh it concatenates the names with nothing\n // between them at all.\n const locale = useKitLocale();\n const listFormat = useMemo(\n () => new Intl.ListFormat(locale, { type: \"conjunction\", style: \"narrow\" }),\n [locale],\n );\n const summary = (() => {\n if (value.length === 0) return placeholder ?? \"\";\n if (itemLabel) return itemLabel(value.length);\n const chosen = value.map(resolve);\n return chosen.every(Boolean)\n ? listFormat.format(chosen.map((o) => o!.label))\n : labels.selectedCount(value.length);\n })();\n\n const q = core.query.trim();\n const showCreate =\n Boolean(onCreate) && q.length > 0 && !results.some((o) => o.label.toLowerCase() === q.toLowerCase());\n const showClear = Boolean(clearable && value.length > 0 && !disabled);\n\n const toggle = (o: ComboOption<V>) => {\n core.cacheRef.current.set(o.value, o);\n if (valueSet.has(o.value)) onChange(value.filter((v) => v !== o.value));\n else onChange([...value, o.value]);\n // Stay open — multi-select keeps adding.\n };\n\n return (\n // `rest` dresses the wrapper, which has no role; the accessible NAME goes on\n // the trigger, which has one. Spread FIRST so the trigger's ARIA and the\n // handlers that open the panel cannot be clobbered from outside.\n <div {...rest} className={cn(\"relative\", className)}>\n {label !== undefined && <FieldLabel>{label}</FieldLabel>}\n <button\n ref={core.triggerRef}\n type=\"button\"\n // A combobox, not a button. The distinction is not pedantry: this control\n // carried `aria-invalid`, which `button` does not support, so a required\n // field left empty painted a rose border and told a reader nothing at all —\n // and ESLint flagged it as exactly that (`role-supports-aria-props`). The\n // fix the audit asked for is the role that describes what this IS: a closed\n // choice that expands into the list named below. `combobox` supports\n // `aria-invalid`, so the border and the announcement finally agree.\n role=\"combobox\"\n aria-expanded={open}\n aria-controls={listboxId}\n aria-haspopup=\"listbox\"\n // The label is a floating <span>, not a <label for>, so without this the\n // trigger's accessible name is whatever value happens to be selected —\n // \"Checking\" with nothing saying it is the account. The label AND the\n // value, because `aria-label` replaces the content rather than adding to\n // it, and a control that announces only its name has lost the answer.\n // A caller's own name wins over the composition: two fields labelled\n // \"Account\" on a transfer form are the from and the to, and only the\n // caller knows which is which.\n aria-label={\n ariaLabel ??\n (typeof label === \"string\" ? common.fieldValue(label, summary) : undefined)\n }\n disabled={disabled}\n aria-invalid={invalid || undefined}\n onClick={() => !disabled && setOpen((o) => !o)}\n // Down/Up opens the list from the closed trigger, per the APG. Enter and\n // Space already do it through the button's own click.\n onKeyDown={(e) => {\n if (disabled) return;\n if (e.key === \"ArrowDown\" || e.key === \"ArrowUp\") {\n e.preventDefault();\n setOpen(true);\n }\n }}\n className={cn(\n FIELD_TRIGGER,\n \"pr-9\",\n label !== undefined && FIELD_FLOATING_PAD,\n disabled && \"cursor-not-allowed opacity-50\",\n invalid && FIELD_INVALID,\n )}\n >\n <span\n className={cn(\n \"min-w-0 truncate\",\n // Inherit FIELD_BASE's ink for a value, keep the placeholder tone\n // (`--text-placeholder`) — the same rule every field on a form row\n // follows (Keksdose dev#477).\n !value.length && \"text-[var(--text-placeholder)]\",\n )}\n >\n {summary}\n </span>\n {showClear ? (\n <span\n role=\"button\"\n tabIndex={-1}\n aria-label={labels.clear}\n onClick={(e) => {\n e.stopPropagation();\n onChange([]);\n }}\n className=\"absolute right-2 top-1/2 -translate-y-1/2 rounded p-0.5 text-[var(--text-placeholder)] hover:text-[var(--text-secondary)]\"\n >\n <X className=\"size-4\" />\n </span>\n ) : (\n <FieldChevron />\n )}\n </button>\n <ComboboxPanel\n core={core}\n listboxId={listboxId}\n multi\n // On a phone the panel becomes a full-screen sheet, which needs the field's\n // own label to say what it is asking for (live #200).\n sheetTitle={label ?? placeholder}\n searchPlaceholder={labels.search}\n emptyLabel={labels.noResults}\n closeLabel={closeLabel}\n isSelected={(v) => valueSet.has(v)}\n onChoose={toggle}\n showCreate={showCreate}\n onCreate={() => {\n onCreate?.(q);\n core.setQuery(\"\");\n }}\n createContent={labels.create(q)}\n />\n </div>\n );\n}\n"],"mappings":"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","names":[]}
@@ -1,30 +1,32 @@
1
1
  import * as react from 'react';
2
- import { ReactNode } from 'react';
2
+ import { ComponentPropsWithoutRef, ReactNode } from 'react';
3
3
 
4
4
  interface MultiSelectOption {
5
5
  value: string | number;
6
6
  label: string;
7
7
  hint?: string;
8
8
  }
9
- interface Props {
9
+ /** `onChange` is the kit's — "the selection changed", carrying the new values —
10
+ * rather than the div's form event. */
11
+ interface MultiSelectProps extends Omit<ComponentPropsWithoutRef<"div">, "onChange"> {
10
12
  options: MultiSelectOption[];
11
13
  values: (string | number)[];
12
14
  onChange: (next: (string | number)[]) => void;
13
- /** Displayed when no value is selected (i.e. "all"). Defaults to "All"; pass a
14
- * translated string for i18n. */
15
+ /** Displayed when no value is selected (i.e. "all"). Defaults to `placeholder`,
16
+ * then to `multiSelect.all` from the {@link UiKitProvider}, then "All". */
15
17
  allLabel?: string;
16
- /** Singular/plural countable label, used when 1+ items are selected. */
18
+ /** Singular/plural countable label, used when 1+ items are selected. Default:
19
+ * `multiSelect.selectedCount` from the provider — the bare count in English. */
17
20
  itemLabel?: (count: number) => string;
18
21
  placeholder?: string;
19
22
  /** Embedded top-boundary label, matching the native Input/Select fields. */
20
23
  label?: ReactNode;
21
- /** Search-box placeholder (default "Search"). */
24
+ /** Search-box placeholder. Default: `multiSelect.search` from the provider, else "Search". */
22
25
  searchLabel?: string;
23
- /** "Select all" action label (default "Select all"). */
26
+ /** "Select all" action label. Default: `multiSelect.selectAll`, else "Select all". */
24
27
  selectAllLabel?: string;
25
- /** "Clear" action label (default "Clear"). */
28
+ /** "Clear" action label. Default: `multiSelect.clear`, else "Clear". */
26
29
  clearLabel?: string;
27
- className?: string;
28
30
  /** Extra classes for the open dropdown PANEL — the way to widen it past its
29
31
  * 16rem default when the rows carry more than a label (Keksdose feedback #147). */
30
32
  panelClassName?: string;
@@ -34,6 +36,6 @@ interface Props {
34
36
  * only the one that happened to have it first. */
35
37
  invalid?: boolean;
36
38
  }
37
- declare function MultiSelect({ options, values, onChange, allLabel, itemLabel, placeholder, label, searchLabel, selectAllLabel, clearLabel, className, panelClassName, invalid, }: Props): react.JSX.Element;
39
+ declare function MultiSelect({ options, values, onChange, allLabel, itemLabel, placeholder, label, searchLabel, selectAllLabel, clearLabel, className, panelClassName, invalid, "aria-label": ariaLabel, ...rest }: MultiSelectProps): react.JSX.Element;
38
40
 
39
- export { MultiSelect, type MultiSelectOption };
41
+ export { MultiSelect, type MultiSelectOption, type MultiSelectProps };
@@ -1,9 +1,16 @@
1
+ "use client";
1
2
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
2
- import { useMemo } from "react";
3
+ import { useId, useMemo, useState } from "react";
3
4
  import { Check } from "lucide-react";
4
5
  import { FieldChevron, FieldLabel, FIELD_TRIGGER, FIELD_INVALID, FIELD_FLOATING_PAD } from "./ui.js";
5
6
  import { cn } from "../lib/cn.js";
6
7
  import { DropdownPanel, DropdownSearchHeader, useDropdownSearch } from "./dropdown.js";
8
+ import { useActiveOptionScroll } from "./combobox-core.js";
9
+ import {
10
+ DEFAULT_COMMON_LABELS,
11
+ DEFAULT_MULTI_SELECT_LABELS,
12
+ useKitLabels
13
+ } from "../i18n/kit-labels.js";
7
14
  function MultiSelect({
8
15
  options,
9
16
  values,
@@ -12,14 +19,27 @@ function MultiSelect({
12
19
  itemLabel,
13
20
  placeholder,
14
21
  label,
15
- searchLabel = "Search",
16
- selectAllLabel = "Select all",
17
- clearLabel = "Clear",
22
+ searchLabel,
23
+ selectAllLabel,
24
+ clearLabel,
18
25
  className,
19
26
  panelClassName,
20
- invalid
27
+ invalid,
28
+ "aria-label": ariaLabel,
29
+ ...rest
21
30
  }) {
22
- const { open, setOpen, wrapperRef, query, setQuery, inputRef } = useDropdownSearch();
31
+ const { open, setOpen, wrapperRef, triggerRef, closeToTrigger, query, setQuery, inputRef } = useDropdownSearch();
32
+ const labels = useKitLabels("multiSelect", DEFAULT_MULTI_SELECT_LABELS, {
33
+ search: searchLabel,
34
+ selectAll: selectAllLabel,
35
+ clear: clearLabel,
36
+ selectedCount: itemLabel
37
+ });
38
+ const common = useKitLabels("common", DEFAULT_COMMON_LABELS);
39
+ const uid = useId();
40
+ const listboxId = `${uid}-listbox`;
41
+ const optionId = (index) => `${uid}-option-${index}`;
42
+ const [active, setActive] = useState(0);
23
43
  const filtered = useMemo(() => {
24
44
  const q = query.trim().toLowerCase();
25
45
  if (!q) return options;
@@ -31,9 +51,8 @@ function MultiSelect({
31
51
  const allChecked = options.length > 0 && options.every((o) => valueSet.has(o.value));
32
52
  const anyChecked = values.length > 0;
33
53
  const selectedSummary = (() => {
34
- if (!anyChecked) return allLabel ?? placeholder ?? "All";
35
- if (allChecked) return allLabel ?? placeholder ?? "All";
36
- return itemLabel ? itemLabel(values.length) : `${values.length}`;
54
+ if (!anyChecked || allChecked) return allLabel ?? placeholder ?? labels.all;
55
+ return labels.selectedCount(values.length);
37
56
  })();
38
57
  const toggle = (val) => {
39
58
  if (valueSet.has(val)) onChange(values.filter((v) => v !== val));
@@ -41,90 +60,148 @@ function MultiSelect({
41
60
  };
42
61
  const selectAll = () => onChange(options.map((o) => o.value));
43
62
  const clearAll = () => onChange([]);
44
- return /* @__PURE__ */ jsxs("div", { ref: wrapperRef, className: cn("relative", className), children: [
45
- label !== void 0 && /* @__PURE__ */ jsx(FieldLabel, { children: label }),
46
- /* @__PURE__ */ jsxs(
47
- "button",
48
- {
49
- type: "button",
50
- onClick: () => setOpen((v) => !v),
51
- "aria-invalid": invalid || void 0,
52
- className: cn(FIELD_TRIGGER, "pr-9", label !== void 0 && FIELD_FLOATING_PAD, invalid && FIELD_INVALID),
53
- children: [
54
- /* @__PURE__ */ jsx("span", { className: "truncate", children: selectedSummary }),
55
- /* @__PURE__ */ jsx(FieldChevron, {})
56
- ]
57
- }
58
- ),
59
- open && /* @__PURE__ */ jsx(
60
- DropdownPanel,
61
- {
62
- className: cn("w-64", panelClassName),
63
- empty: filtered.length === 0,
64
- header: /* @__PURE__ */ jsxs(Fragment, { children: [
65
- /* @__PURE__ */ jsx(
66
- DropdownSearchHeader,
67
- {
68
- query,
69
- onQueryChange: setQuery,
70
- inputRef,
71
- placeholder: searchLabel
63
+ const activeId = active >= 0 && active < filtered.length ? optionId(active) : void 0;
64
+ useActiveOptionScroll(activeId);
65
+ const onListKeyDown = (e) => {
66
+ if (e.key === "ArrowDown") {
67
+ e.preventDefault();
68
+ setActive((i) => Math.min(i + 1, filtered.length - 1));
69
+ } else if (e.key === "ArrowUp") {
70
+ e.preventDefault();
71
+ setActive((i) => Math.max(i - 1, 0));
72
+ } else if (e.key === "Home") {
73
+ e.preventDefault();
74
+ setActive(0);
75
+ } else if (e.key === "End") {
76
+ e.preventDefault();
77
+ setActive(filtered.length - 1);
78
+ } else if (e.key === "Enter") {
79
+ e.preventDefault();
80
+ if (filtered[active]) toggle(filtered[active].value);
81
+ } else if (e.key === "Tab") {
82
+ e.preventDefault();
83
+ closeToTrigger();
84
+ }
85
+ };
86
+ return (
87
+ // `rest` dresses the wrapper; the name belongs to the trigger below it, which is
88
+ // the thing with a role. Spread FIRST so the ARIA the trigger composes, and the
89
+ // handlers that open the list, cannot be replaced by accident from outside.
90
+ /* @__PURE__ */ jsxs("div", { ...rest, ref: wrapperRef, className: cn("relative", className), children: [
91
+ label !== void 0 && /* @__PURE__ */ jsx(FieldLabel, { children: label }),
92
+ /* @__PURE__ */ jsxs(
93
+ "button",
94
+ {
95
+ ref: triggerRef,
96
+ type: "button",
97
+ role: "combobox",
98
+ "aria-expanded": open,
99
+ "aria-controls": listboxId,
100
+ "aria-haspopup": "listbox",
101
+ "aria-label": ariaLabel ?? (typeof label === "string" ? common.fieldValue(label, selectedSummary) : void 0),
102
+ onClick: () => {
103
+ setOpen((v) => !v);
104
+ setActive(0);
105
+ },
106
+ onKeyDown: (e) => {
107
+ if (e.key === "ArrowDown" || e.key === "ArrowUp") {
108
+ e.preventDefault();
109
+ setOpen(true);
110
+ setActive(0);
72
111
  }
73
- ),
74
- /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-2 px-2 py-1.5 text-xs", children: [
112
+ },
113
+ "aria-invalid": invalid || void 0,
114
+ className: cn(FIELD_TRIGGER, "pr-9", label !== void 0 && FIELD_FLOATING_PAD, invalid && FIELD_INVALID),
115
+ children: [
116
+ /* @__PURE__ */ jsx("span", { className: "truncate", children: selectedSummary }),
117
+ /* @__PURE__ */ jsx(FieldChevron, {})
118
+ ]
119
+ }
120
+ ),
121
+ open && /* @__PURE__ */ jsx(
122
+ DropdownPanel,
123
+ {
124
+ className: cn("w-64", panelClassName),
125
+ empty: filtered.length === 0,
126
+ listProps: { id: listboxId, role: "listbox", "aria-multiselectable": true },
127
+ header: /* @__PURE__ */ jsxs(Fragment, { children: [
75
128
  /* @__PURE__ */ jsx(
76
- "button",
129
+ DropdownSearchHeader,
77
130
  {
78
- type: "button",
79
- onClick: selectAll,
80
- disabled: allChecked,
81
- className: "rounded px-2 py-0.5 text-slate-600 hover:bg-slate-100 disabled:opacity-40 dark:text-slate-300 dark:hover:bg-slate-800",
82
- children: selectAllLabel
131
+ query,
132
+ onQueryChange: (v) => {
133
+ setQuery(v);
134
+ setActive(0);
135
+ },
136
+ inputRef,
137
+ placeholder: labels.search,
138
+ listboxId,
139
+ activeId,
140
+ onKeyDown: onListKeyDown
83
141
  }
84
142
  ),
85
- /* @__PURE__ */ jsx(
143
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-2 px-2 py-1.5 text-xs", children: [
144
+ /* @__PURE__ */ jsx(
145
+ "button",
146
+ {
147
+ type: "button",
148
+ onClick: selectAll,
149
+ disabled: allChecked,
150
+ className: "rounded px-2 py-0.5 text-[var(--text-secondary)] hover:bg-[var(--bg-hover)] disabled:opacity-40",
151
+ children: labels.selectAll
152
+ }
153
+ ),
154
+ /* @__PURE__ */ jsx(
155
+ "button",
156
+ {
157
+ type: "button",
158
+ onClick: clearAll,
159
+ disabled: !anyChecked,
160
+ className: "rounded px-2 py-0.5 text-[var(--text-secondary)] hover:bg-[var(--bg-hover)] disabled:opacity-40",
161
+ children: labels.clear
162
+ }
163
+ )
164
+ ] })
165
+ ] }),
166
+ children: filtered.map((o, i) => {
167
+ const checked = valueSet.has(o.value);
168
+ return /* @__PURE__ */ jsx("li", { role: "presentation", children: /* @__PURE__ */ jsx(
86
169
  "button",
87
170
  {
88
171
  type: "button",
89
- onClick: clearAll,
90
- disabled: !anyChecked,
91
- className: "rounded px-2 py-0.5 text-slate-600 hover:bg-slate-100 disabled:opacity-40 dark:text-slate-300 dark:hover:bg-slate-800",
92
- children: clearLabel
93
- }
94
- )
95
- ] })
96
- ] }),
97
- children: filtered.map((o) => {
98
- const checked = valueSet.has(o.value);
99
- return /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsx(
100
- "button",
101
- {
102
- type: "button",
103
- onClick: () => toggle(o.value),
104
- className: cn(
105
- "flex w-full items-center justify-between gap-2 px-3 py-1.5 text-sm text-left hover:bg-slate-50 dark:hover:bg-slate-800",
106
- checked && "bg-slate-100/60 dark:bg-slate-800/60"
107
- ),
108
- children: /* @__PURE__ */ jsxs("span", { className: "flex items-center gap-2 min-w-0", children: [
109
- /* @__PURE__ */ jsx(
110
- "span",
111
- {
112
- className: cn(
113
- "flex size-4 shrink-0 items-center justify-center rounded border",
114
- checked ? "border-slate-900 bg-slate-900 text-white dark:border-slate-100 dark:bg-slate-100 dark:text-slate-900" : "border-slate-300 bg-white dark:border-slate-600 dark:bg-slate-900"
115
- ),
116
- children: checked && /* @__PURE__ */ jsx(Check, { className: "size-3" })
117
- }
172
+ id: optionId(i),
173
+ role: "option",
174
+ "aria-selected": checked,
175
+ tabIndex: -1,
176
+ onClick: () => toggle(o.value),
177
+ onMouseEnter: () => setActive(i),
178
+ className: cn(
179
+ "flex w-full items-center justify-between gap-2 px-3 py-1.5 text-sm text-left hover:bg-[var(--bg-hover)]",
180
+ checked && "bg-[var(--bg-active)]",
181
+ i === active && "bg-[var(--bg-hover)]"
118
182
  ),
119
- /* @__PURE__ */ jsx("span", { className: "truncate text-slate-900 dark:text-slate-100", children: o.label }),
120
- o.hint && /* @__PURE__ */ jsx("span", { className: "truncate text-xs text-slate-400 dark:text-slate-500", children: o.hint })
121
- ] })
122
- }
123
- ) }, o.value);
124
- })
125
- }
126
- )
127
- ] });
183
+ children: /* @__PURE__ */ jsxs("span", { className: "flex items-center gap-2 min-w-0", children: [
184
+ /* @__PURE__ */ jsx(
185
+ "span",
186
+ {
187
+ "aria-hidden": true,
188
+ className: cn(
189
+ "flex size-4 shrink-0 items-center justify-center rounded border",
190
+ checked ? "border-[var(--bg-inverse)] bg-[var(--bg-inverse)] text-[var(--text-inverse)]" : "border-[var(--border-strong)] bg-[var(--bg-surface)]"
191
+ ),
192
+ children: checked && /* @__PURE__ */ jsx(Check, { className: "size-3" })
193
+ }
194
+ ),
195
+ /* @__PURE__ */ jsx("span", { className: "truncate text-[var(--text-primary)]", children: o.label }),
196
+ o.hint && /* @__PURE__ */ jsx("span", { className: "truncate text-xs text-[var(--text-placeholder)]", children: o.hint })
197
+ ] })
198
+ }
199
+ ) }, o.value);
200
+ })
201
+ }
202
+ )
203
+ ] })
204
+ );
128
205
  }
129
206
  export {
130
207
  MultiSelect