@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,342 @@
1
+ import { useEffect, useMemo, useRef, useState } from "react";
2
+ import type { ReactNode } from "react";
3
+ import { createPortal } from "react-dom";
4
+ import { Search } from "lucide-react";
5
+ import { cn } from "../lib/cn";
6
+ import { useOverlayHistory } from "../hooks/use-overlay-history";
7
+ import { useKitLabels } from "../i18n/kit-labels";
8
+
9
+ export interface CommandItem {
10
+ id: string;
11
+ /** Primary text. */
12
+ label: string;
13
+ /** Secondary text on the right (e.g. an amount, a date, a group hint). */
14
+ hint?: string;
15
+ /** Heading this item is grouped under. */
16
+ group: string;
17
+ /** Leading icon / element. */
18
+ icon?: ReactNode;
19
+ /** Invoked when the item is chosen (navigate, run an action, …). */
20
+ onSelect: () => void;
21
+ /** Where this item leads, when it is a navigation rather than an action. Renders
22
+ * the row as a real anchor so it can be middle-/⌘-clicked into a new tab
23
+ * (Keksdose feedback #451); `onSelect` still runs for a plain click and for ↵, so
24
+ * in-app navigation stays client-side. `href` only adds what the browser can do
25
+ * with a link and JavaScript cannot fake — the rejected alternative, an
26
+ * `onAuxClick` calling `window.open`, gets the middle click and nothing else. */
27
+ href?: string;
28
+ }
29
+
30
+ export interface CommandPaletteLabels {
31
+ placeholder: string;
32
+ empty: string;
33
+ loading: string;
34
+ /** Hint shown in the footer, e.g. "↑↓ to navigate · ↵ to select · esc to close". */
35
+ hint?: string;
36
+ /**
37
+ * Accessible name of the dialog itself. It used to borrow `placeholder`, which is
38
+ * written to sit greyed-out in an empty box ("Search…", "Type a command"), not to
39
+ * name a window — and a reader heard the ellipsis read out as part of the name.
40
+ *
41
+ * Optional, unlike `placeholder`, so a translation annotated as
42
+ * `CommandPaletteLabels` keeps compiling; the default always carries it.
43
+ */
44
+ dialog?: string;
45
+ }
46
+
47
+ export const DEFAULT_COMMAND_PALETTE_LABELS: CommandPaletteLabels = {
48
+ placeholder: "Search…",
49
+ empty: "No results",
50
+ loading: "Searching…",
51
+ dialog: "Search",
52
+ };
53
+
54
+ /** The defaults minus `dialog`, so a resolved `dialog` means someone SUPPLIED one —
55
+ * see the dialog's `aria-label` for why that distinction matters. */
56
+ const { dialog: DEFAULT_DIALOG_NAME, ...DEFAULTS_WITHOUT_DIALOG } = DEFAULT_COMMAND_PALETTE_LABELS;
57
+
58
+ interface CommandPaletteProps {
59
+ open: boolean;
60
+ onClose: () => void;
61
+ /**
62
+ * Returns results for a query (called debounced as the user types; an empty
63
+ * query is allowed — return defaults like pages/recent). Sync or async; stale
64
+ * async responses are ignored.
65
+ *
66
+ * Its IDENTITY is not a re-run signal — it is read through a ref, so the palette
67
+ * never re-searches merely because the caller rebuilt the callback. That is
68
+ * deliberate: a provider closing over a list rendered from a query allocates a new
69
+ * function on most renders, and re-running the search on each would make typing
70
+ * stutter. Use {@link CommandPaletteProps.revision} when the DATA behind the
71
+ * provider changes.
72
+ */
73
+ search: (query: string) => CommandItem[] | Promise<CommandItem[]>;
74
+ /**
75
+ * Anything whose change means the current results are out of date — typically the
76
+ * arrays the provider searches over. Compared by identity (as an effect dependency
77
+ * would be), so pass stable references: a `data = []` default allocates a fresh
78
+ * array on every render while a query is pending and would re-search forever.
79
+ *
80
+ * Without it, a provider whose data arrives AFTER the palette opened never gets
81
+ * asked again: the user's last keystroke settles the debounce, the queries resolve a
82
+ * moment later, and the groups they would have contributed are simply absent until
83
+ * another keystroke. That is the ordinary first open on a cold cache.
84
+ */
85
+ revision?: unknown;
86
+ labels?: Partial<CommandPaletteLabels>;
87
+ }
88
+
89
+ /** Registers a global ⌘K / Ctrl-K shortcut that calls `onOpen`. */
90
+ export function useCommandKey(onOpen: () => void): void {
91
+ useEffect(() => {
92
+ const handler = (e: KeyboardEvent) => {
93
+ if ((e.metaKey || e.ctrlKey) && (e.key === "k" || e.key === "K")) {
94
+ e.preventDefault();
95
+ onOpen();
96
+ }
97
+ };
98
+ document.addEventListener("keydown", handler);
99
+ return () => document.removeEventListener("keydown", handler);
100
+ }, [onOpen]);
101
+ }
102
+
103
+ /**
104
+ * A ⌘K-style command palette: a top-centred modal with a search field and a
105
+ * grouped, keyboard-navigable result list. Domain-free — the app supplies
106
+ * results via the `search` provider (pages, actions, records, …); each result
107
+ * carries its own `onSelect`.
108
+ */
109
+ export function CommandPalette({
110
+ open,
111
+ onClose,
112
+ search,
113
+ revision,
114
+ labels,
115
+ }: CommandPaletteProps) {
116
+ const l = useKitLabels("commandPalette", DEFAULTS_WITHOUT_DIALOG, labels);
117
+ // `dialog` is new, and until it existed the dialog was named by `placeholder`. An
118
+ // app that translated `placeholder` and has not heard of `dialog` would otherwise
119
+ // go from a German name to an English "Search" on update — so a translated
120
+ // placeholder keeps doing the job until a `dialog` is supplied.
121
+ const dialogName =
122
+ l.dialog ??
123
+ (l.placeholder !== DEFAULT_COMMAND_PALETTE_LABELS.placeholder ? l.placeholder : DEFAULT_DIALOG_NAME);
124
+ const [query, setQuery] = useState("");
125
+ const [results, setResults] = useState<CommandItem[]>([]);
126
+ const [loading, setLoading] = useState(false);
127
+ const [active, setActive] = useState(0);
128
+ const inputRef = useRef<HTMLInputElement>(null);
129
+ const listRef = useRef<HTMLUListElement>(null);
130
+ const searchRef = useRef(search);
131
+ searchRef.current = search;
132
+ const reqId = useRef(0);
133
+
134
+ // Back dismisses the palette (Keksdose feedback #172). Declared with the other
135
+ // hooks, above the `if (!open) return null` below — a hook past a conditional
136
+ // return changes the hook order between renders. Passing `open` rather than
137
+ // mounting-while-open is what lets it sit here.
138
+ useOverlayHistory(open, onClose);
139
+
140
+ // Reset + focus when opened.
141
+ useEffect(() => {
142
+ if (!open) return;
143
+ setQuery("");
144
+ setActive(0);
145
+ const id = requestAnimationFrame(() => inputRef.current?.focus());
146
+ return () => cancelAnimationFrame(id);
147
+ }, [open]);
148
+
149
+ // Debounced, race-safe search.
150
+ useEffect(() => {
151
+ if (!open) return;
152
+ const id = ++reqId.current;
153
+ setLoading(true);
154
+ const run = async () => {
155
+ try {
156
+ const r = await Promise.resolve(searchRef.current(query));
157
+ if (reqId.current === id) {
158
+ setResults(r);
159
+ setActive(0);
160
+ }
161
+ } finally {
162
+ if (reqId.current === id) setLoading(false);
163
+ }
164
+ };
165
+ const t = setTimeout(run, query ? 150 : 0);
166
+ return () => clearTimeout(t);
167
+ // `revision`, not `search`: see the prop's docstring. The debounce is re-armed on a
168
+ // revision change, which is right — data arriving is not a keystroke and there is
169
+ // nothing to feel laggy about.
170
+ }, [query, open, revision]);
171
+
172
+ // Group results, preserving first-seen group order; keep a flat list for nav.
173
+ const { groups, flat } = useMemo(() => {
174
+ const order: string[] = [];
175
+ const byGroup = new Map<string, CommandItem[]>();
176
+ for (const item of results) {
177
+ if (!byGroup.has(item.group)) {
178
+ byGroup.set(item.group, []);
179
+ order.push(item.group);
180
+ }
181
+ byGroup.get(item.group)!.push(item);
182
+ }
183
+ return {
184
+ groups: order.map((g) => ({ group: g, items: byGroup.get(g)! })),
185
+ flat: order.flatMap((g) => byGroup.get(g)!),
186
+ };
187
+ }, [results]);
188
+
189
+ const choose = (item: CommandItem | undefined) => {
190
+ if (!item) return;
191
+ onClose();
192
+ item.onSelect();
193
+ };
194
+
195
+ const onKeyDown = (e: React.KeyboardEvent) => {
196
+ if (e.key === "ArrowDown") {
197
+ e.preventDefault();
198
+ setActive((i) => Math.min(i + 1, flat.length - 1));
199
+ } else if (e.key === "ArrowUp") {
200
+ e.preventDefault();
201
+ setActive((i) => Math.max(i - 1, 0));
202
+ } else if (e.key === "Enter") {
203
+ e.preventDefault();
204
+ choose(flat[active]);
205
+ } else if (e.key === "Escape") {
206
+ e.preventDefault();
207
+ onClose();
208
+ }
209
+ };
210
+
211
+ // Keep the active row scrolled into view.
212
+ useEffect(() => {
213
+ const el = listRef.current?.querySelector<HTMLElement>(`[data-index="${active}"]`);
214
+ el?.scrollIntoView({ block: "nearest" });
215
+ }, [active]);
216
+
217
+ if (!open) return null;
218
+
219
+ let flatIndex = -1;
220
+ return createPortal(
221
+ <div
222
+ className="fixed inset-0 z-[70] flex items-start justify-center bg-black/40 p-4 pt-[10vh]"
223
+ onMouseDown={(e) => {
224
+ if (e.target === e.currentTarget) onClose();
225
+ }}
226
+ >
227
+ <div
228
+ role="dialog"
229
+ aria-modal="true"
230
+ aria-label={dialogName}
231
+ className="flex max-h-[70vh] w-full max-w-xl flex-col overflow-hidden rounded-xl border border-[var(--border)] bg-[var(--bg-surface)] shadow-2xl"
232
+ >
233
+ <div className="flex items-center gap-2 border-b border-[var(--border)] px-3">
234
+ <Search className="size-4 shrink-0 text-[var(--text-placeholder)]" />
235
+ <input
236
+ ref={inputRef}
237
+ role="combobox"
238
+ aria-expanded
239
+ aria-controls="command-palette-list"
240
+ aria-activedescendant={flat[active] ? `command-item-${flat[active].id}` : undefined}
241
+ value={query}
242
+ onChange={(e) => setQuery(e.target.value)}
243
+ onKeyDown={onKeyDown}
244
+ placeholder={l.placeholder}
245
+ className="w-full bg-transparent py-3 text-sm outline-none placeholder:text-[var(--text-placeholder)] text-[var(--text-primary)]"
246
+ />
247
+ {loading && <span className="shrink-0 text-[11px] text-[var(--text-placeholder)]">{l.loading}</span>}
248
+ </div>
249
+
250
+ <ul id="command-palette-list" ref={listRef} role="listbox" className="min-h-0 flex-1 overflow-y-auto py-1">
251
+ {flat.length === 0 && !loading && (
252
+ <li className="px-3 py-6 text-center text-sm text-[var(--text-muted)]">{l.empty}</li>
253
+ )}
254
+ {groups.map(({ group, items }) => (
255
+ <li key={group}>
256
+ <div className="px-3 pb-0.5 pt-2 text-[10px] font-semibold uppercase tracking-wide text-[var(--text-placeholder)]">
257
+ {group}
258
+ </div>
259
+ <ul>
260
+ {items.map((item) => {
261
+ flatIndex += 1;
262
+ const idx = flatIndex;
263
+ const isActive = idx === active;
264
+ // Written once and worn by either tag below, so the anchor and the
265
+ // button can never drift apart in looks or in listbox semantics.
266
+ // `role="option"` on an <a href> is fine: the browser's middle-click
267
+ // behaviour keys off the element, not the ARIA role.
268
+ const shared = {
269
+ id: `command-item-${item.id}`,
270
+ role: "option" as const,
271
+ "aria-selected": isActive,
272
+ "data-index": idx,
273
+ onMouseMove: () => setActive(idx),
274
+ className: cn(
275
+ "flex w-full items-center gap-2.5 px-3 py-2 text-left text-sm",
276
+ isActive
277
+ ? "bg-[var(--bg-active)] text-[var(--text-primary)]"
278
+ : "text-[var(--text-secondary)]",
279
+ ),
280
+ };
281
+ const inner = (
282
+ <>
283
+ {item.icon && (
284
+ <span className="flex size-4 shrink-0 items-center justify-center text-[var(--text-placeholder)]">
285
+ {item.icon}
286
+ </span>
287
+ )}
288
+ <span className="min-w-0 flex-1 truncate">{item.label}</span>
289
+ {item.hint && (
290
+ <span className="shrink-0 text-xs text-[var(--text-placeholder)]">{item.hint}</span>
291
+ )}
292
+ </>
293
+ );
294
+ return (
295
+ <li key={item.id}>
296
+ {item.href ? (
297
+ <a
298
+ {...shared}
299
+ href={item.href}
300
+ draggable={false}
301
+ onClick={(e) => {
302
+ // A modified click belongs to the browser (feedback
303
+ // #451) — and the palette deliberately stays OPEN for
304
+ // it: ⌘-clicking three results in a row is the reason to
305
+ // want this, and closing after the first would undo it.
306
+ if (
307
+ e.button !== 0 ||
308
+ e.metaKey ||
309
+ e.ctrlKey ||
310
+ e.shiftKey ||
311
+ e.altKey
312
+ )
313
+ return;
314
+ e.preventDefault();
315
+ choose(item);
316
+ }}
317
+ >
318
+ {inner}
319
+ </a>
320
+ ) : (
321
+ <button type="button" {...shared} onClick={() => choose(item)}>
322
+ {inner}
323
+ </button>
324
+ )}
325
+ </li>
326
+ );
327
+ })}
328
+ </ul>
329
+ </li>
330
+ ))}
331
+ </ul>
332
+
333
+ {l.hint && (
334
+ <div className="border-t border-[var(--border)] px-3 py-1.5 text-[11px] text-[var(--text-placeholder)]">
335
+ {l.hint}
336
+ </div>
337
+ )}
338
+ </div>
339
+ </div>,
340
+ document.body,
341
+ );
342
+ }
package/src/search.ts ADDED
@@ -0,0 +1,3 @@
1
+ // `@eifi1/ui-kit/search` — the command palette / global search.
2
+ // A re-slicing of the main barrel, not a new API.
3
+ export * from "./search/command-palette";