@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
@@ -0,0 +1,269 @@
1
+ import { useMemo, useId } from "react";
2
+ import type { ComponentPropsWithoutRef, ReactNode } from "react";
3
+ import { Check } from "lucide-react";
4
+ import { FieldChevron, FieldLabel, FIELD_TRIGGER, FIELD_INVALID, FIELD_FLOATING_PAD } from "./ui";
5
+ import { cn } from "../lib/cn";
6
+ import { DropdownPanel, DropdownSearchHeader, useDropdownSearch } from "./dropdown";
7
+ import { DEFAULT_CURRENCY_LABELS, useKitLabels, useKitLocale } from "../i18n/kit-labels";
8
+
9
+ export interface CurrencyOption {
10
+ code: string;
11
+ name: string;
12
+ symbol: string;
13
+ /** ISO 3166-1 alpha-2 country code (lowercase) used by flag-icons. */
14
+ country: string;
15
+ }
16
+
17
+ export const CURRENCIES: CurrencyOption[] = [
18
+ { code: "EUR", name: "Euro", symbol: "€", country: "eu" },
19
+ { code: "USD", name: "US Dollar", symbol: "$", country: "us" },
20
+ { code: "GBP", name: "British Pound", symbol: "£", country: "gb" },
21
+ { code: "CHF", name: "Swiss Franc", symbol: "CHF", country: "ch" },
22
+ { code: "JPY", name: "Japanese Yen", symbol: "¥", country: "jp" },
23
+ { code: "CAD", name: "Canadian Dollar", symbol: "CA$", country: "ca" },
24
+ { code: "AUD", name: "Australian Dollar", symbol: "A$", country: "au" },
25
+ { code: "NZD", name: "New Zealand Dollar", symbol: "NZ$", country: "nz" },
26
+ { code: "SEK", name: "Swedish Krona", symbol: "kr", country: "se" },
27
+ { code: "NOK", name: "Norwegian Krone", symbol: "kr", country: "no" },
28
+ { code: "DKK", name: "Danish Krone", symbol: "kr", country: "dk" },
29
+ { code: "PLN", name: "Polish Zloty", symbol: "zł", country: "pl" },
30
+ { code: "CZK", name: "Czech Koruna", symbol: "Kč", country: "cz" },
31
+ { code: "HUF", name: "Hungarian Forint", symbol: "Ft", country: "hu" },
32
+ { code: "RON", name: "Romanian Leu", symbol: "lei", country: "ro" },
33
+ { code: "BGN", name: "Bulgarian Lev", symbol: "лв", country: "bg" },
34
+ { code: "TRY", name: "Turkish Lira", symbol: "₺", country: "tr" },
35
+ { code: "RUB", name: "Russian Ruble", symbol: "₽", country: "ru" },
36
+ { code: "CNY", name: "Chinese Yuan", symbol: "¥", country: "cn" },
37
+ { code: "HKD", name: "Hong Kong Dollar", symbol: "HK$", country: "hk" },
38
+ { code: "SGD", name: "Singapore Dollar", symbol: "S$", country: "sg" },
39
+ { code: "INR", name: "Indian Rupee", symbol: "₹", country: "in" },
40
+ { code: "BRL", name: "Brazilian Real", symbol: "R$", country: "br" },
41
+ { code: "MXN", name: "Mexican Peso", symbol: "MX$", country: "mx" },
42
+ { code: "ZAR", name: "South African Rand", symbol: "R", country: "za" },
43
+ { code: "ILS", name: "Israeli Shekel", symbol: "₪", country: "il" },
44
+ { code: "AED", name: "UAE Dirham", symbol: "د.إ", country: "ae" },
45
+ ];
46
+
47
+ export interface CurrencyFlagProps extends ComponentPropsWithoutRef<"span"> {
48
+ /** ISO 3166-1 alpha-2, lowercase — the class flag-icons keys its sprite by. */
49
+ country: string;
50
+ }
51
+
52
+ export function CurrencyFlag({ country, className, ...rest }: CurrencyFlagProps) {
53
+ return (
54
+ <span
55
+ {...rest}
56
+ // Stays `aria-hidden` whatever a caller passes: the flag is a picture of the
57
+ // code printed next to it, and a reader that met both would hear the currency
58
+ // twice.
59
+ aria-hidden
60
+ className={cn("fi inline-block w-5 h-[15px] rounded-sm shadow-[0_0_0_1px_rgba(0,0,0,0.08)] shrink-0", `fi-${country}`, className)}
61
+ />
62
+ );
63
+ }
64
+
65
+ const CURRENCY_BY_CODE = new Map(CURRENCIES.map((c) => [c.code, c]));
66
+
67
+ export function getCurrency(code: string | null | undefined): CurrencyOption | undefined {
68
+ return code ? CURRENCY_BY_CODE.get(code.toUpperCase()) : undefined;
69
+ }
70
+
71
+ /** The name to SHOW for a currency: the caller's translation if they have one for
72
+ * that code; else, when a `locale` is known, the name `Intl.DisplayNames` has for
73
+ * it in that locale; else the English name {@link CURRENCIES} carries. Shared with
74
+ * AmountInput, which renders the same list from the same data.
75
+ *
76
+ * `Intl` only when a locale was actually GIVEN (by prop or `<UiKitProvider>`): an
77
+ * app that never set one keeps the shipped English names it has always shown,
78
+ * rather than whatever language the test runner or the browser happens to be in. */
79
+ export function currencyName(
80
+ c: CurrencyOption,
81
+ names?: Record<string, string>,
82
+ locale?: string,
83
+ ): string {
84
+ const own = names?.[c.code];
85
+ if (own !== undefined) return own;
86
+ if (locale) {
87
+ try {
88
+ const shown = new Intl.DisplayNames([locale], { type: "currency" }).of(c.code);
89
+ if (shown && shown !== c.code) return shown;
90
+ } catch {
91
+ // A malformed tag, or a runtime without `DisplayNames`: the shipped name.
92
+ }
93
+ }
94
+ return c.name;
95
+ }
96
+
97
+ /** `onChange` is the kit's — "a currency was picked", carrying the code — rather
98
+ * than the div's form event. */
99
+ export interface CurrencySelectProps extends Omit<ComponentPropsWithoutRef<"div">, "onChange"> {
100
+ value: string;
101
+ onChange: (code: string) => void;
102
+ placeholder?: string;
103
+ label?: ReactNode;
104
+ /**
105
+ * Restrict the pickable currencies to these ISO codes (e.g. only the
106
+ * currencies actually present in a budget). Order is preserved. Defaults to
107
+ * the full {@link CURRENCIES} list.
108
+ */
109
+ options?: string[];
110
+ /** Required and unanswered — {@link FIELD_INVALID}, the same rose border the
111
+ * native `Select` has worn since feedback #235. Every field-styled control in
112
+ * this package carries it now, so a form can mark any of its fields rather than
113
+ * only the one that happened to have it first. */
114
+ invalid?: boolean;
115
+ /** Every user-facing string this control owns, shaped like `AmountInput`'s
116
+ * `labels` so the two currency pickers in the kit are configured the same way.
117
+ *
118
+ * `search` exists because `placeholder` used to do BOTH jobs: it named the empty
119
+ * field and, for want of anything else, it was also handed to the search box —
120
+ * so a caller who wrote "Currency" got "Currency" as the prompt for typing a
121
+ * filter, and a caller who wanted to name the filter had to rename the field.
122
+ * `placeholder` still wins for the trigger, so nothing moves for the callers
123
+ * that set it; what changes is that the search box no longer borrows it. */
124
+ labels?: {
125
+ /** The empty trigger, when `placeholder` is not given. */
126
+ currency?: string;
127
+ /** The filter box inside the open panel. */
128
+ search?: string;
129
+ };
130
+ /**
131
+ * Translated currency names, keyed by ISO code — `{ CHF: "Schweizer Franken" }`.
132
+ *
133
+ * {@link CURRENCIES} is data the package ships, so its 27 names were the one
134
+ * string in the row a consumer could not translate: the code and the symbol are
135
+ * language-neutral, and the name beside them stayed English in a German UI. This
136
+ * is a sparse OVERRIDE rather than a replacement list, so an app translates the
137
+ * eight currencies it actually uses and keeps the rest; forking `CURRENCIES` to
138
+ * get there would also fork the flags and symbols, which nobody wants to maintain.
139
+ *
140
+ * The search matches what is SHOWN — type "Franken" and you find CHF — because a
141
+ * filter that only matches the hidden English name is worse than no filter.
142
+ */
143
+ currencyNames?: Record<string, string>;
144
+ }
145
+
146
+ export function CurrencySelect({
147
+ value,
148
+ onChange,
149
+ placeholder,
150
+ className,
151
+ label,
152
+ options,
153
+ invalid,
154
+ labels,
155
+ currencyNames,
156
+ "aria-label": ariaLabel,
157
+ ...rest
158
+ }: CurrencySelectProps) {
159
+ const { open, setOpen, wrapperRef, query, setQuery, inputRef } = useDropdownSearch();
160
+ // `labels` > `currency` from the provider > English.
161
+ const text = useKitLabels("currency", DEFAULT_CURRENCY_LABELS, labels);
162
+ const locale = useKitLocale();
163
+
164
+ const selected = getCurrency(value);
165
+ const pool = useMemo(() => {
166
+ if (!options) return CURRENCIES;
167
+ // Keep the caller's order; drop codes we don't have a definition for.
168
+ return options.map((code) => getCurrency(code)).filter((c): c is CurrencyOption => !!c);
169
+ }, [options]);
170
+ const filtered = useMemo(() => {
171
+ const q = query.trim().toLocaleLowerCase(locale);
172
+ if (!q) return pool;
173
+ return pool.filter(
174
+ (c) =>
175
+ c.code.toLowerCase().includes(q) ||
176
+ currencyName(c, currencyNames, locale).toLocaleLowerCase(locale).includes(q) ||
177
+ c.symbol.toLowerCase().includes(q),
178
+ );
179
+ }, [query, pool, currencyNames, locale]);
180
+
181
+ const listboxId = `${useId()}-listbox`;
182
+
183
+ return (
184
+ // The wrapper takes `rest`; the trigger takes the name. This field's label is a
185
+ // floating <span> rather than a <label for>, so a caller who wants the control to
186
+ // announce more than the code inside it has nowhere else to put it.
187
+ <div {...rest} ref={wrapperRef} className={cn("relative", className)}>
188
+ {label !== undefined && <FieldLabel>{label}</FieldLabel>}
189
+ <button
190
+ type="button"
191
+ aria-label={ariaLabel}
192
+ onClick={() => setOpen((v) => !v)}
193
+ // The implicit `button` role supports neither `aria-expanded` nor
194
+ // `aria-invalid`, so this trigger previously wore a red border and announced
195
+ // nothing about being invalid or about there being a list behind it. The role
196
+ // that DESCRIBES it is the one that also supports the attributes — the same
197
+ // treatment its siblings (MultiSelect, the entity comboboxes) now carry, so a
198
+ // form row of pickers reads the same way throughout.
199
+ role="combobox"
200
+ aria-haspopup="listbox"
201
+ aria-controls={listboxId}
202
+ aria-expanded={open}
203
+ aria-invalid={invalid || undefined}
204
+ className={cn(FIELD_TRIGGER, "pr-9", label !== undefined && FIELD_FLOATING_PAD, invalid && FIELD_INVALID)}
205
+ >
206
+ {/* The trigger stays COMPACT — flag + code only — so it never clips in a
207
+ narrow field; the full names live in the (wider) popup (feedback
208
+ #308). */}
209
+ <span className={cn("flex items-center gap-2 min-w-0", selected ? "text-[var(--text-primary)]" : "text-[var(--text-placeholder)]")}>
210
+ {selected ? (
211
+ <>
212
+ <CurrencyFlag country={selected.country} />
213
+ <span className="font-medium">{selected.code}</span>
214
+ </>
215
+ ) : (
216
+ placeholder ?? text.currency
217
+ )}
218
+ </span>
219
+ <FieldChevron />
220
+ </button>
221
+ {open && (
222
+ <DropdownPanel
223
+ // At least as wide as the trigger, but grows to fit the full currency
224
+ // names and caps so it never runs off-screen. RIGHT-aligned so it grows
225
+ // leftward instead of pushing the page width when there's content to the
226
+ // right of the field (feedback #308).
227
+ className="right-0 min-w-full w-max max-w-[min(20rem,calc(100vw-2rem))]"
228
+ empty={filtered.length === 0}
229
+ listProps={{ id: listboxId }}
230
+ header={
231
+ <DropdownSearchHeader
232
+ query={query}
233
+ onQueryChange={setQuery}
234
+ inputRef={inputRef}
235
+ placeholder={text.search}
236
+ />
237
+ }
238
+ >
239
+ {filtered.map((c) => {
240
+ const active = c.code === selected?.code;
241
+ return (
242
+ <li key={c.code}>
243
+ <button
244
+ type="button"
245
+ onClick={() => {
246
+ onChange(c.code);
247
+ setOpen(false);
248
+ }}
249
+ className={cn(
250
+ "flex w-full items-center justify-between gap-2 px-3 py-2 text-sm text-left hover:bg-[var(--bg-hover)]",
251
+ active && "bg-[var(--bg-active)]",
252
+ )}
253
+ >
254
+ <span className="flex items-center gap-2 min-w-0">
255
+ <CurrencyFlag country={c.country} />
256
+ <span className="font-mono text-[var(--text-muted)] w-8 shrink-0">{c.symbol}</span>
257
+ <span className="font-medium text-[var(--text-primary)]">{c.code}</span>
258
+ <span className="text-[var(--text-muted)] truncate">{currencyName(c, currencyNames, locale)}</span>
259
+ </span>
260
+ {active && <Check className="size-4 text-[var(--text-secondary)] shrink-0" />}
261
+ </button>
262
+ </li>
263
+ );
264
+ })}
265
+ </DropdownPanel>
266
+ )}
267
+ </div>
268
+ );
269
+ }
@@ -0,0 +1,252 @@
1
+ import { X } from "lucide-react";
2
+ import { cn } from "../lib/cn";
3
+ import { MiniCalendar } from "./mini-calendar";
4
+ import { dateRangePresets } from "../lib/dates";
5
+ import { resolveFilter } from "./data-table-filters";
6
+ import type { FilterValue } from "./data-table-filters";
7
+ import type { DataTableColumn } from "./data-table";
8
+ import { resolveDataTableLabels, type DataTableLabels } from "./data-table-labels";
9
+ import { useKitLabelOverrides, useKitLocale } from "../i18n/kit-labels";
10
+
11
+ interface FilterPopoverProps<T> {
12
+ column: DataTableColumn<T>;
13
+ state: FilterValue;
14
+ onChange: (next: FilterValue) => void;
15
+ onClear: () => void;
16
+ selectOptions: { value: string; label: string }[];
17
+ /** BCP-47 locale for the date picker / labels. Falls back to the provider's. */
18
+ locale?: string;
19
+ /** Already resolved by {@link DataTable}. Standalone (it is exported), omit it and
20
+ * the `dataTable` namespace of `<UiKitProvider labels>` is used. */
21
+ labels?: DataTableLabels;
22
+ /**
23
+ * Focus the text filter's input on mount. True, because that is right for the one
24
+ * place this component normally lives: a Popover opened by an explicit press on
25
+ * "Filter", which exists in order to be typed into and which the user has just asked
26
+ * for. It was hard-coded, which made it right there and wrong everywhere else — a
27
+ * browser scrolls whatever takes focus into view, so embedding this control in an
28
+ * ordinary page (the showcase documents it on one) landed the reader 25,000px down a
29
+ * page they had not scrolled. An embedder that is not a popover passes `false`.
30
+ */
31
+ autoFocus?: boolean;
32
+ }
33
+
34
+ /**
35
+ * The contents of a column's filter Popover. Renders the right control set for
36
+ * the column's filter type — text search, select checklist, date range (presets
37
+ * + {@link MiniCalendar} + from/to inputs), or numeric min/max with an abs
38
+ * toggle — driving the shared {@link FilterValue} state.
39
+ */
40
+ export function FilterPopover<T>({
41
+ column,
42
+ state,
43
+ onChange,
44
+ onClear,
45
+ selectOptions,
46
+ locale: localeProp,
47
+ labels: labelsProp,
48
+ autoFocus = true,
49
+ }: FilterPopoverProps<T>) {
50
+ // Both hooks run before the early return below, unconditionally.
51
+ const overrides = useKitLabelOverrides("dataTable");
52
+ const labels = labelsProp ?? resolveDataTableLabels(overrides);
53
+ // No `"en"` fallback any more: that pinned the calendar to English in a table that
54
+ // was never handed a locale, under a provider that had one.
55
+ const locale = useKitLocale(localeProp);
56
+ const filter = resolveFilter(column);
57
+ if (!filter) return null;
58
+
59
+ // `focus:outline-none` with only a 1px border tint to replace it is the same finding
60
+ // the audit records against the numpad's keys: on a panel of four identical fields a
61
+ // border going from --border to --border-strong is not a location. The ring is, and it
62
+ // is the package's brand focus colour, as on every field in ui.tsx.
63
+ const inputBase =
64
+ "block w-full rounded border border-[var(--border)] bg-[var(--bg-surface)] px-2 py-1.5 text-sm text-[var(--text-secondary)] placeholder:text-[var(--text-placeholder)] focus:border-[var(--border-strong)] focus:outline-none focus-visible:ring-2 focus-visible:ring-[var(--brand)]";
65
+ const buttonBase =
66
+ "rounded border border-[var(--border)] bg-[var(--bg-surface)] px-2 py-1 text-xs text-[var(--text-secondary)] hover:bg-[var(--bg-hover)]";
67
+
68
+ const clearRow = (
69
+ <div className="flex justify-end pt-1">
70
+ <button type="button" onClick={onClear} className={cn(buttonBase, "flex items-center gap-1")}>
71
+ <X className="size-3" /> {labels.clearFilter}
72
+ </button>
73
+ </div>
74
+ );
75
+
76
+ if (filter.type === "text") {
77
+ const v = state.type === "text" ? state : { type: "text" as const, q: "" };
78
+ return (
79
+ <div className="space-y-2">
80
+ <input
81
+ type="text"
82
+ // A prop now, defaulting to what the popover needs; see `autoFocus` on the
83
+ // props above. Deliberately NOT an eslint-disable: `jsx-a11y/no-autofocus` is
84
+ // one of the config's ratcheted warnings and this is still one of the four it
85
+ // counts, because the default here is still to take focus. Silencing it would
86
+ // shrink the backlog without emptying it.
87
+ autoFocus={autoFocus}
88
+ value={v.q}
89
+ onChange={(e) => onChange({ type: "text", q: e.target.value })}
90
+ placeholder={labels.filterPlaceholder}
91
+ className={inputBase}
92
+ />
93
+ {clearRow}
94
+ </div>
95
+ );
96
+ }
97
+
98
+ if (filter.type === "select") {
99
+ const v = state.type === "select" ? state : { type: "select" as const, values: [] };
100
+ const toggle = (val: string) => {
101
+ const set = new Set(v.values);
102
+ if (set.has(val)) set.delete(val);
103
+ else set.add(val);
104
+ onChange({ type: "select", values: Array.from(set) });
105
+ };
106
+ return (
107
+ <div className="space-y-2">
108
+ <div className="flex items-center justify-between text-xs text-[var(--text-muted)]">
109
+ <span>{labels.selectFilter}</span>
110
+ <div className="flex gap-1">
111
+ <button
112
+ type="button"
113
+ onClick={() => onChange({ type: "select", values: selectOptions.map((o) => o.value) })}
114
+ className={buttonBase}
115
+ >
116
+ {labels.selectAll}
117
+ </button>
118
+ <button
119
+ type="button"
120
+ onClick={() => onChange({ type: "select", values: [] })}
121
+ className={buttonBase}
122
+ >
123
+ {labels.selectNone}
124
+ </button>
125
+ </div>
126
+ </div>
127
+ <ul className="max-h-56 overflow-y-auto rounded border border-[var(--border)]">
128
+ {selectOptions.map((o) => {
129
+ const checked = v.values.includes(o.value);
130
+ return (
131
+ <li key={o.value}>
132
+ <label className="flex cursor-pointer items-center gap-2 px-2 py-1.5 text-sm hover:bg-[var(--bg-hover)]">
133
+ <input
134
+ type="checkbox"
135
+ checked={checked}
136
+ onChange={() => toggle(o.value)}
137
+ className="size-3.5"
138
+ />
139
+ <span className="truncate">{o.label}</span>
140
+ </label>
141
+ </li>
142
+ );
143
+ })}
144
+ {selectOptions.length === 0 && (
145
+ <li className="px-2 py-2 text-sm text-[var(--text-muted)]">—</li>
146
+ )}
147
+ </ul>
148
+ {clearRow}
149
+ </div>
150
+ );
151
+ }
152
+
153
+ if (filter.type === "date") {
154
+ const v = state.type === "date" ? state : { type: "date" as const, from: "", to: "" };
155
+ const set = (from: string, to: string) => onChange({ type: "date", from, to });
156
+ const presets = dateRangePresets();
157
+ const isActivePreset = (p: { from: string; to: string }) => v.from === p.from && v.to === p.to;
158
+ return (
159
+ <div className="flex gap-3">
160
+ <ul className="flex w-28 shrink-0 flex-col gap-0.5 text-xs">
161
+ {presets.map((p) => {
162
+ const active = isActivePreset(p);
163
+ return (
164
+ <li key={p.key}>
165
+ <button
166
+ type="button"
167
+ onClick={() => set(p.from, p.to)}
168
+ className={cn(
169
+ "block w-full rounded px-2 py-1 text-left transition-colors",
170
+ active
171
+ ? "bg-[var(--brand-bg)] text-[var(--brand-muted)]"
172
+ : "text-[var(--text-secondary)] hover:bg-[var(--bg-hover)]",
173
+ )}
174
+ >
175
+ {labels.presets[p.key] ?? p.key}
176
+ </button>
177
+ </li>
178
+ );
179
+ })}
180
+ </ul>
181
+ <div className="min-w-0 flex-1 space-y-2">
182
+ {/* No `labels` here: the calendar reads `miniCalendar` from the provider
183
+ itself, which is the only way a calendar nested this deep was ever going
184
+ to be translated — the table has no prop to forward them through. */}
185
+ <MiniCalendar from={v.from} to={v.to} locale={locale} onSelect={set} />
186
+ <div className="grid grid-cols-2 gap-2">
187
+ <label className="text-xs text-[var(--text-muted)]">
188
+ {labels.dateFrom}
189
+ <input
190
+ type="date"
191
+ value={v.from}
192
+ onChange={(e) => set(e.target.value, v.to)}
193
+ className={cn("mt-0.5", inputBase)}
194
+ />
195
+ </label>
196
+ <label className="text-xs text-[var(--text-muted)]">
197
+ {labels.dateTo}
198
+ <input
199
+ type="date"
200
+ value={v.to}
201
+ onChange={(e) => set(v.from, e.target.value)}
202
+ className={cn("mt-0.5", inputBase)}
203
+ />
204
+ </label>
205
+ </div>
206
+ {clearRow}
207
+ </div>
208
+ </div>
209
+ );
210
+ }
211
+
212
+ // number
213
+ const v = state.type === "number" ? state : { type: "number" as const, min: "", max: "", abs: false };
214
+ return (
215
+ <div className="space-y-2">
216
+ <div className="grid grid-cols-2 gap-2">
217
+ <label className="text-xs text-[var(--text-muted)]">
218
+ {labels.numberMin} (≥)
219
+ <input
220
+ type="number"
221
+ inputMode="decimal"
222
+ value={v.min}
223
+ onChange={(e) => onChange({ type: "number", min: e.target.value, max: v.max, abs: v.abs })}
224
+ onWheel={(e) => e.currentTarget.blur()}
225
+ className={cn("mt-0.5", inputBase)}
226
+ />
227
+ </label>
228
+ <label className="text-xs text-[var(--text-muted)]">
229
+ {labels.numberMax} (≤)
230
+ <input
231
+ type="number"
232
+ inputMode="decimal"
233
+ value={v.max}
234
+ onChange={(e) => onChange({ type: "number", min: v.min, max: e.target.value, abs: v.abs })}
235
+ onWheel={(e) => e.currentTarget.blur()}
236
+ className={cn("mt-0.5", inputBase)}
237
+ />
238
+ </label>
239
+ </div>
240
+ <label className="flex items-center gap-2 text-sm text-[var(--text-secondary)]">
241
+ <input
242
+ type="checkbox"
243
+ checked={v.abs}
244
+ onChange={(e) => onChange({ type: "number", min: v.min, max: v.max, abs: e.target.checked })}
245
+ className="size-3.5"
246
+ />
247
+ {labels.numberAbs}
248
+ </label>
249
+ {clearRow}
250
+ </div>
251
+ );
252
+ }