@eifi1/ui-kit 0.4.0 → 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 +91 -20
  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 +96 -20
  63. package/dist/components/dropdown.js +77 -33
  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 +38 -6
  75. package/dist/components/file-dropzone.js +34 -9
  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 +28 -2
  105. package/dist/components/number-input.js +12 -2
  106. package/dist/components/number-input.js.map +1 -1
  107. package/dist/components/numpad-sheet.d.ts +32 -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 +15 -4
  176. package/dist/components/user-avatar.js +5 -4
  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 +27 -5
  270. package/dist/theme/palette-presets.js +231 -33
  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 +67 -14
  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,195 @@
1
+ import { useRef, useState } from "react";
2
+ import type { ComponentPropsWithoutRef, KeyboardEvent, ReactNode, RefObject } from "react";
3
+ import { createPortal } from "react-dom";
4
+ import { useAnchoredPanel } from "../hooks/use-anchored-panel";
5
+ import { useEscapeKey, useOutsideClick } from "../hooks/use-dismiss";
6
+ import { useFocusTrap } from "../hooks/use-focus-trap";
7
+ import { cn } from "../lib/cn";
8
+ import { useKitLabels } from "../i18n/kit-labels";
9
+
10
+ /** The panel's accessible name. A `role="dialog"` with no name announces as "dialog"
11
+ * and nothing else, which is worse than the unlabelled div it replaced — so it is a
12
+ * prop with an English default, like every other string this package renders. Callers
13
+ * should pass the control's own name ("Filter", "Choose a date"); the default is only
14
+ * there so an unmigrated call site is still named. */
15
+ export interface PopoverLabels {
16
+ panel: string;
17
+ }
18
+
19
+ // Exported since the kit grew one label tree (`UiKitLabels`, src/i18n): the complete
20
+ // English reference a translator works from has to be able to name every namespace.
21
+ export const DEFAULT_POPOVER_LABELS: PopoverLabels = {
22
+ panel: "Popover",
23
+ };
24
+
25
+ /**
26
+ * Exported, and extending the `<div>` attributes, so a consumer wrapping this — which is
27
+ * what `DateField` and `FilterPopover` in this very package do — can hand anything
28
+ * through to the PANEL: a `data-tour` anchor for the kit's own guided tour, a test id,
29
+ * an `aria-describedby`, or an `aria-label` naming this particular popover.
30
+ *
31
+ * `children` is omitted from those attributes rather than inherited: here it is a render
32
+ * prop taking the panel's `close`, not a `ReactNode`, so the two cannot be reconciled.
33
+ */
34
+ export interface PopoverProps extends Omit<ComponentPropsWithoutRef<"div">, "children"> {
35
+ trigger: (state: { open: boolean; toggle: () => void; ref: RefObject<HTMLButtonElement | null> }) => ReactNode;
36
+ children: (close: () => void) => ReactNode;
37
+ width?: number;
38
+ labels?: Partial<PopoverLabels>;
39
+ /** Id for the panel, so a trigger wearing `role="combobox"` can `aria-controls` it.
40
+ * Required by ARIA on that role, and the panel is rendered here rather than by the
41
+ * caller, so the id has to come in from outside. */
42
+ panelId?: string;
43
+ /** Extra classes for the PANEL. The trigger is the caller's own element. */
44
+ className?: string;
45
+ }
46
+
47
+ const POPOVER_WIDTH = 288;
48
+
49
+ /**
50
+ * Portal-rendered popover anchored under its trigger button. The panel is fixed
51
+ * to the viewport (so table/overflow ancestors can't clip it) and re-aligns on
52
+ * scroll/resize; it closes on outside-click and Escape.
53
+ *
54
+ * ## Trapped, not merely focused — and why, because it is the closer call
55
+ *
56
+ * The audit's finding was that this panel *"portals out of the tab order with no role
57
+ * and no focus move"*: Tab from the trigger went straight past the panel into the page
58
+ * behind it, so the calendar, the calculator keypad and every column filter in the kit
59
+ * were mouse-only. Two shapes fix that, and a popover is not automatically a dialog, so
60
+ * this was chosen rather than inherited:
61
+ *
62
+ * - **The light one** — move focus in on open, hand it back on close, and let Tab walk
63
+ * OUT of the panel into whatever follows the trigger. That is the right shape for a
64
+ * non-modal popover sitting inline in the document.
65
+ * - **The trap** — Tab cycles inside the panel; Escape and a click outside are the
66
+ * ways out. What is here.
67
+ *
68
+ * The deciding fact is the portal. This panel is rendered to the END of `<body>`, not
69
+ * beside its trigger, so "let Tab walk out" does not mean "carry on from the trigger" —
70
+ * it means land at the end of the document, several hundred controls from where the
71
+ * user was. Tabbing out of it would be a worse place to arrive than the one the finding
72
+ * complains about, and fixing that properly needs a sentinel-and-return dance that
73
+ * nothing in this package has. The trap gets the user back to the trigger every time,
74
+ * because `useFocusTrap` restores focus on close.
75
+ *
76
+ * And the panels this kit actually opens are self-contained: pick a date, tap out a
77
+ * sum, narrow a column. Each of them commits and closes, and each already has two
78
+ * documented ways out wired above — Escape and an outside press — so containment costs
79
+ * a keyboard user nothing they had.
80
+ *
81
+ * **It stays non-modal in every other respect**, deliberately: no backdrop, no body
82
+ * scroll lock, and no `aria-modal`, so the page behind is still readable by a screen
83
+ * reader, still scrollable, and still clickable — clicking it is what dismisses this.
84
+ * Only Tab is contained. A popover that took the page away as well would be a
85
+ * {@link Modal}, and there is one of those.
86
+ */
87
+ export function Popover({
88
+ trigger,
89
+ children,
90
+ width = POPOVER_WIDTH,
91
+ labels,
92
+ panelId,
93
+ className,
94
+ style,
95
+ "aria-label": ariaLabel,
96
+ ...rest
97
+ }: PopoverProps) {
98
+ const triggerRef = useRef<HTMLButtonElement>(null);
99
+ const panelRef = useRef<HTMLDivElement>(null);
100
+ const [open, setOpen] = useState(false);
101
+ const close = () => setOpen(false);
102
+ const toggle = () => setOpen((v) => !v);
103
+ // prop > `<UiKitProvider labels={{ popover }}>` > English. The provider's
104
+ // `popover.panel` is only the last-resort name for a panel no caller named.
105
+ const text = useKitLabels("popover", DEFAULT_POPOVER_LABELS, labels);
106
+
107
+ // Align the panel's right edge to the trigger's and clamp it horizontally; the
108
+ // hook owns the vertical half — below by default, flipped above (and height-capped)
109
+ // when that is where the room is, e.g. once a mobile keyboard has eaten the bottom
110
+ // of the screen (feedback #135). It re-measures on scroll/resize.
111
+ const placement = useAnchoredPanel(triggerRef, open);
112
+ const rect = placement.rect;
113
+ const pos = rect
114
+ ? {
115
+ top: placement.top,
116
+ left: Math.min(Math.max(8, rect.right - width), window.innerWidth - width - 8),
117
+ }
118
+ : null;
119
+
120
+ useOutsideClick([triggerRef, panelRef], close, open);
121
+ useEscapeKey(close, open);
122
+
123
+ useFocusTrap(panelRef, {
124
+ active: open && pos !== null,
125
+ // The panel keeps a focus one of its own children has already taken. `FilterPopover`
126
+ // autofocuses its text box on mount — that is what the press on "Filter" was FOR,
127
+ // and it is a prop there precisely so the popover keeps it — and a trap that
128
+ // unconditionally focused the container would take it straight back off again,
129
+ // one effect later and invisibly.
130
+ initialFocus: () => {
131
+ const panel = panelRef.current;
132
+ const active = document.activeElement;
133
+ return panel?.contains(active) ? (active as HTMLElement) : panel;
134
+ },
135
+ });
136
+
137
+ const handleKeyDown = (e: KeyboardEvent<HTMLDivElement>) => {
138
+ if (e.key !== "Escape") return;
139
+ // `useEscapeKey` above listens on the document and still covers the case where
140
+ // focus somehow is not in here. This handler is for the case that focus IS, which
141
+ // is now the normal one — and it exists to STOP the key, because a popover opened
142
+ // from inside a `Modal` (the calculator on an amount field is exactly that) would
143
+ // otherwise have its Escape travel on up the React tree and close the dialog
144
+ // underneath as well. Closing twice is harmless — `close` is a state setter, not a
145
+ // caller's callback — so the two paths cannot double-fire anything.
146
+ e.stopPropagation();
147
+ close();
148
+ };
149
+
150
+ return (
151
+ <>
152
+ {trigger({ open, toggle, ref: triggerRef })}
153
+ {open && pos &&
154
+ createPortal(
155
+ <div
156
+ // `...rest` first: the role, the id the trigger's `aria-controls` points at,
157
+ // the `tabIndex` the trap needs and the measured position are what make this
158
+ // a popover, and a caller reaching for a `data-tour` anchor must not be able
159
+ // to remove one of them by accident.
160
+ {...rest}
161
+ ref={panelRef}
162
+ role="dialog"
163
+ id={panelId}
164
+ // The caller's own `aria-label` wins over {@link PopoverLabels.panel}, which
165
+ // is the package's English fallback for an unnamed panel. Both name the same
166
+ // element; the DOM spelling is the one being standardised on, so where both
167
+ // are present it is the one that survives.
168
+ aria-label={ariaLabel ?? text.panel}
169
+ // Without it the panel cannot take focus at all, and the trap above would
170
+ // silently do nothing on a panel whose children are not yet tabbable.
171
+ tabIndex={-1}
172
+ onKeyDown={handleKeyDown}
173
+ style={{
174
+ // Merged over the caller's, never replaced by it: these four are measured
175
+ // against the trigger every scroll and resize, and a panel that dropped
176
+ // them would paint at the top-left of the viewport.
177
+ ...style,
178
+ position: "fixed",
179
+ top: pos.top,
180
+ left: pos.left,
181
+ width,
182
+ maxHeight: placement.maxHeight,
183
+ }}
184
+ className={cn(
185
+ "z-50 overflow-y-auto rounded-md border border-[var(--border)] bg-[var(--bg-surface)] p-3 shadow-lg outline-none",
186
+ className,
187
+ )}
188
+ >
189
+ {children(close)}
190
+ </div>,
191
+ document.body,
192
+ )}
193
+ </>
194
+ );
195
+ }
@@ -0,0 +1,146 @@
1
+ import { forwardRef, useRef } from "react";
2
+ import type { InputHTMLAttributes } from "react";
3
+ import { Search, X } from "lucide-react";
4
+ import { cn } from "../lib/cn";
5
+ import { FIELD_BASE } from "./ui";
6
+
7
+ interface SearchFieldOwnProps
8
+ extends Omit<
9
+ InputHTMLAttributes<HTMLInputElement>,
10
+ "value" | "onChange" | "type" | "aria-label"
11
+ > {
12
+ value: string;
13
+ /** The typed text. Emitted as a plain string, not an event: every caller of the
14
+ * four this replaced wanted the string, and one of them held it in a URL param
15
+ * rather than in state. */
16
+ onChange: (value: string) => void;
17
+ /**
18
+ * The field's accessible name.
19
+ *
20
+ * @deprecated Pass `aria-label` instead — the DOM spelling this kit standardised on
21
+ * when it found three names for one idea (`ariaLabel`, `aria-label`, and this).
22
+ * Unchanged and still supported; it names the field only when `aria-label` is
23
+ * absent, so a consumer can migrate one call site at a time.
24
+ */
25
+ label?: string;
26
+ /** Names the clear button. Omit it and no clear button is rendered — which is a
27
+ * decision, not a default: a filter you can type into and not untype is the
28
+ * complaint that produced the button on three of these four screens. */
29
+ clearLabel?: string;
30
+ }
31
+
32
+ /**
33
+ * One of the two spellings is REQUIRED, and the union is how that survives the
34
+ * deprecation. Making `label` merely optional would have been the easy change and the
35
+ * wrong one: the whole reason this component exists is that two of the four boxes it
36
+ * replaced were named by nothing but their placeholder — which disappears the moment
37
+ * someone types, i.e. exactly when a screen-reader user is working in the field. A
38
+ * caller now has a choice of spelling; it still has no option to pass neither.
39
+ */
40
+ export type SearchFieldProps = SearchFieldOwnProps &
41
+ ({ "aria-label": string; label?: string } | { label: string; "aria-label"?: string });
42
+
43
+ /**
44
+ * The page-level filter box: a search icon, a `type="search"` input, and a clear
45
+ * "×" that appears once there is something to clear.
46
+ *
47
+ * ## Why it is a component
48
+ *
49
+ * Keksdose had four of these and no two agreed. Two were character-identical
50
+ * twenty-line copies (the settings catalogue filter and the tour list filter) —
51
+ * the same absolute icon at `left-3`, the same `pl-9 pr-9`, the same
52
+ * `[&::-webkit-search-cancel-button]:appearance-none`, the same conditional clear
53
+ * button at `right-2` — differing only in which state setter they called. The
54
+ * third (the admin support queue) was the same idea at `left-2`/`size-3.5`/`pl-7`
55
+ * with no clear button and no `type="search"`, so it had neither the `searchbox`
56
+ * role nor a way to empty itself. The fourth (the bank picker) was a bare `<Input
57
+ * placeholder="Search">`: no icon, no clear, and no accessible name at all once
58
+ * the placeholder went away.
59
+ *
60
+ * None of that is a design decision anyone made; it is four people solving the
61
+ * same problem on four days. A shared component is how the fourth screen gets the
62
+ * clear button the first one earned.
63
+ *
64
+ * ## The two X's
65
+ *
66
+ * `type="search"` is what gives the field its `searchbox` role — and, in Chrome, a
67
+ * NATIVE clear cross that paints right next to ours, so a non-empty box shows two
68
+ * X's an inch apart. Ours is the one that survives: it carries an accessible name
69
+ * and matches the app's other icon buttons, and it returns focus to the input so
70
+ * the next query can be typed straight away.
71
+ */
72
+ export const SearchField = forwardRef<HTMLInputElement, SearchFieldProps>(function SearchField(
73
+ { value, onChange, label, clearLabel, className, placeholder, "aria-label": ariaLabel, ...rest },
74
+ ref,
75
+ ) {
76
+ const innerRef = useRef<HTMLInputElement>(null);
77
+ // Whichever spelling the caller used. It is the accessible name AND the placeholder
78
+ // fallback, so a box named only by `aria-label` still says what it is before anyone
79
+ // has typed in it — the same deal `label` has always had.
80
+ const name = ariaLabel ?? label;
81
+ return (
82
+ <div className={cn("relative", className)}>
83
+ <Search
84
+ aria-hidden
85
+ className="pointer-events-none absolute left-3 top-1/2 size-4 -translate-y-1/2 text-[var(--text-placeholder)]"
86
+ />
87
+ <input
88
+ ref={(node) => {
89
+ innerRef.current = node;
90
+ if (typeof ref === "function") ref(node);
91
+ else if (ref) ref.current = node;
92
+ }}
93
+ type="search"
94
+ value={value}
95
+ onChange={(e) => onChange(e.target.value)}
96
+ aria-label={name}
97
+ placeholder={placeholder ?? name}
98
+ // Keksdose live #333: *"Entering the search here — is that set as password?
99
+ // … I got prompted for remembering my name or mail address"*, typed into the
100
+ // settings filter on a phone.
101
+ //
102
+ // A `type="search"` input with no `name` and no `autocomplete` is a field
103
+ // Chrome's autofill classifies by GUESSWORK, from the labels around it and
104
+ // the other fields on the page — and a settings page has an email and a
105
+ // password on it. Naming the field and declaring the intent is what takes it
106
+ // out of that guess: `autocomplete="off"` says it is not part of any profile,
107
+ // and a `name` of "search" is the strongest hint the heuristics take.
108
+ //
109
+ // The three that follow are not about autofill but about the same keyboard:
110
+ // a filter box that capitalises the first letter and autocorrects "2fa" to
111
+ // "2FA" is fighting the person typing into it. Declared BEFORE `{...rest}` so
112
+ // a caller with a reason can still override any of them.
113
+ name="search"
114
+ autoComplete="off"
115
+ autoCapitalize="none"
116
+ autoCorrect="off"
117
+ spellCheck={false}
118
+ {...rest}
119
+ className={cn(
120
+ FIELD_BASE,
121
+ "pl-9",
122
+ // Room for our own "×" only when there is one; a field that can't be
123
+ // cleared has no reason to reserve the space.
124
+ clearLabel === undefined ? "pr-3" : "pr-9",
125
+ "[&::-webkit-search-cancel-button]:appearance-none",
126
+ )}
127
+ />
128
+ {clearLabel !== undefined && value !== "" && (
129
+ <button
130
+ type="button"
131
+ onClick={() => {
132
+ onChange("");
133
+ // Clearing is the start of the next query far more often than it is the
134
+ // end of this one, so the caret stays where it can be typed into.
135
+ innerRef.current?.focus();
136
+ }}
137
+ aria-label={clearLabel}
138
+ className="absolute right-2 top-1/2 -translate-y-1/2 rounded p-1 text-[var(--text-placeholder)] hover:text-[var(--text-secondary)]"
139
+ >
140
+ <X className="size-4" />
141
+ </button>
142
+ )}
143
+ </div>
144
+ );
145
+ });
146
+ SearchField.displayName = "SearchField";
@@ -0,0 +1,26 @@
1
+ // All user-facing strings for the series chart family — `SeriesChart`, its drag-to-zoom
2
+ // layer and `ToggleLegend` — as the `seriesChart` namespace of `<UiKitProvider labels>`,
3
+ // and per chart the `labels` prop over that.
4
+ //
5
+ // A module of its own, with no recharts in it, so the label registry (src/i18n) can
6
+ // import the defaults without pulling the chart library in behind them.
7
+
8
+ export interface SeriesChartLabels {
9
+ /** The button that puts a zoomed chart back to its full extent. Visible text. */
10
+ resetZoom: string;
11
+ /** Accessible name of the drag surface laid over the plot. It is the only place the
12
+ * gesture is explained, so it says how the drag is read and how to undo it. */
13
+ zoomHint: string;
14
+ /** What a chart with nothing to draw says, when the caller passes no `empty`. */
15
+ empty: string;
16
+ /** Accessible name of a `ToggleLegend` — a group of switches, one per series. */
17
+ legend: string;
18
+ }
19
+
20
+ export const DEFAULT_SERIES_CHART_LABELS: SeriesChartLabels = {
21
+ resetZoom: "Reset zoom",
22
+ zoomHint:
23
+ "Drag to zoom: a roughly square selection zooms both axes, a long thin one only its own. Double-click to reset.",
24
+ empty: "No data",
25
+ legend: "Series",
26
+ };