@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,345 @@
1
+ /**
2
+ * The multi-step wizard state machine: step index, completed/skipped sets,
3
+ * per-step async validators, collected data, field errors, submit and the
4
+ * cancel-confirm gate.
5
+ *
6
+ * Domain-free, but NOT router-free: it mirrors the active step to `?step=N`, so
7
+ * it must be mounted inside a react-router context — the same requirement
8
+ * `DataTable` already carries. It only ever WRITES that param (see the note on
9
+ * `initialStep` below for why it deliberately does not read it back).
10
+ */
11
+ import { useCallback, useEffect, useReducer, useRef, useState } from "react";
12
+ import { useSearchParams } from "react-router";
13
+ import { useKitLabels } from "../i18n/kit-labels";
14
+ import { DEFAULT_WIZARD_LABELS } from "./types";
15
+ import type {
16
+ FieldErrors,
17
+ StepStatus,
18
+ UseWizardOptions,
19
+ UseWizardReturn,
20
+ ValidateResult,
21
+ WizardStepConfig,
22
+ } from "./types";
23
+
24
+ interface WizardState<TData> {
25
+ currentStepIndex: number;
26
+ completedSteps: Set<number>;
27
+ skippedSteps: Set<number>;
28
+ data: TData;
29
+ isSubmitting: boolean;
30
+ error: string | null;
31
+ fieldErrors: FieldErrors;
32
+ showCancelDialog: boolean;
33
+ }
34
+
35
+ type WizardAction<TData> =
36
+ | { type: "GO_NEXT" }
37
+ | { type: "GO_BACK" }
38
+ | { type: "GO_TO_STEP"; index: number }
39
+ | { type: "SKIP" }
40
+ | { type: "UPDATE_DATA"; partial: Partial<TData> }
41
+ | { type: "SET_SUBMITTING"; value: boolean }
42
+ | { type: "SET_ERROR"; error: string | null }
43
+ | { type: "SET_FIELD_ERRORS"; errors: FieldErrors }
44
+ | { type: "SHOW_CANCEL_DIALOG"; show: boolean };
45
+
46
+ function createReducer<TData>(stepCount: number) {
47
+ return function reducer(
48
+ state: WizardState<TData>,
49
+ action: WizardAction<TData>,
50
+ ): WizardState<TData> {
51
+ switch (action.type) {
52
+ case "GO_NEXT": {
53
+ const completed = new Set(state.completedSteps);
54
+ completed.add(state.currentStepIndex);
55
+ const nextIndex = Math.min(state.currentStepIndex + 1, stepCount - 1);
56
+ return { ...state, currentStepIndex: nextIndex, completedSteps: completed, fieldErrors: {} };
57
+ }
58
+ case "GO_BACK": {
59
+ const prevIndex = Math.max(state.currentStepIndex - 1, 0);
60
+ return { ...state, currentStepIndex: prevIndex, fieldErrors: {} };
61
+ }
62
+ case "GO_TO_STEP": {
63
+ if (action.index < 0 || action.index >= stepCount) return state;
64
+ if (!state.completedSteps.has(action.index) && action.index > state.currentStepIndex) {
65
+ return state;
66
+ }
67
+ return { ...state, currentStepIndex: action.index };
68
+ }
69
+ case "SKIP": {
70
+ const skipped = new Set(state.skippedSteps);
71
+ skipped.add(state.currentStepIndex);
72
+ const nextIndex = Math.min(state.currentStepIndex + 1, stepCount - 1);
73
+ return { ...state, currentStepIndex: nextIndex, skippedSteps: skipped };
74
+ }
75
+ case "UPDATE_DATA": {
76
+ // Clear error for any field that is now being edited (so typing makes
77
+ // the red message disappear).
78
+ const nextErrors = { ...state.fieldErrors };
79
+ for (const key of Object.keys(action.partial)) {
80
+ delete nextErrors[key];
81
+ }
82
+ return { ...state, data: { ...state.data, ...action.partial }, fieldErrors: nextErrors };
83
+ }
84
+ case "SET_SUBMITTING":
85
+ return { ...state, isSubmitting: action.value };
86
+ case "SET_ERROR":
87
+ return { ...state, error: action.error };
88
+ case "SET_FIELD_ERRORS":
89
+ return { ...state, fieldErrors: action.errors };
90
+ case "SHOW_CANCEL_DIALOG":
91
+ return { ...state, showCancelDialog: action.show };
92
+ default:
93
+ return state;
94
+ }
95
+ };
96
+ }
97
+
98
+ export function useWizard<TData extends Record<string, unknown>>(
99
+ options: UseWizardOptions<TData>,
100
+ ): UseWizardReturn<TData> {
101
+ const { steps, initialData, onComplete, onCancel, missingRequiredMessage, onValidationFailed } =
102
+ options;
103
+ const stepsRef = useRef<WizardStepConfig[]>(steps);
104
+ stepsRef.current = steps;
105
+ // A hook, not a component, but it produces two sentences of its own — the
106
+ // "fill in the required fields" toast and the fallback submit error — and both
107
+ // reach the user. So it reads the provider like any component does; only the two
108
+ // strings are depended on below, so the callbacks stay stable across renders.
109
+ // The `??` is for the type only: both keys are optional on `WizardLabels` (see
110
+ // there), and the merge never replaces a default with `undefined`.
111
+ const wizardLabels = useKitLabels("wizard", DEFAULT_WIZARD_LABELS, {
112
+ missingRequired: missingRequiredMessage,
113
+ });
114
+ const missingRequired = wizardLabels.missingRequired ?? DEFAULT_WIZARD_LABELS.missingRequired;
115
+ const genericError = wizardLabels.genericError ?? DEFAULT_WIZARD_LABELS.genericError;
116
+
117
+ // Validators registered by the CURRENT step's mounted components (RHF steps
118
+ // register via `useRhfWizardStep` through the wizard context). Only the active
119
+ // step's validators are present — each unregisters on unmount. `goNext` and
120
+ // `finish` run them (via `runStepValidators`) alongside the step config's own
121
+ // `validate`.
122
+ const validatorsRef = useRef(
123
+ new Map<symbol, () => ValidateResult | Promise<ValidateResult>>(),
124
+ );
125
+ const registerStepValidate = useCallback(
126
+ (fn: () => ValidateResult | Promise<ValidateResult>) => {
127
+ const token = Symbol();
128
+ validatorsRef.current.set(token, fn);
129
+ return () => {
130
+ validatorsRef.current.delete(token);
131
+ };
132
+ },
133
+ [],
134
+ );
135
+
136
+ const [, setUrlParams] = useSearchParams();
137
+ // The wizard's entered `data` and `completedSteps` are NOT persisted across a
138
+ // reload / deep-link (the reducer re-inits to empty defaults on every mount).
139
+ // Restoring a later step index from `?step=N` would therefore leave the wizard
140
+ // on (or past) the last step with an EMPTY data set, letting Finish submit an
141
+ // empty payload. Always start at step 0 so a reload re-runs the wizard from a
142
+ // valid state instead of losing the entered data silently.
143
+ const initialStep = 0;
144
+
145
+ const reducer = createReducer<TData>(steps.length);
146
+ const [state, dispatch] = useReducer(reducer, {
147
+ currentStepIndex: initialStep,
148
+ completedSteps: new Set<number>(),
149
+ skippedSteps: new Set<number>(),
150
+ data: (initialData ?? {}) as TData,
151
+ isSubmitting: false,
152
+ error: null,
153
+ fieldErrors: {},
154
+ showCancelDialog: false,
155
+ });
156
+
157
+ // A mounted step can DISABLE forward navigation (Next/Skip) while its own
158
+ // state is invalid (e.g. tenant shares != 100%). Defaults to false, so
159
+ // wizards that never call setNextBlocked behave exactly as before.
160
+ const [nextBlocked, setNextBlockedState] = useState(false);
161
+ const setNextBlocked = useCallback(
162
+ (blocked: boolean) => setNextBlockedState(blocked),
163
+ [],
164
+ );
165
+
166
+ // Sync step index to URL
167
+ useEffect(() => {
168
+ setUrlParams((prev) => {
169
+ prev.set("step", String(state.currentStepIndex));
170
+ return prev;
171
+ }, { replace: true });
172
+ }, [state.currentStepIndex, setUrlParams]);
173
+
174
+ const currentStep = stepsRef.current[state.currentStepIndex];
175
+ const isFirstStep = state.currentStepIndex === 0;
176
+ const isLastStep = state.currentStepIndex === stepsRef.current.length - 1;
177
+ // Finish is only valid once every step before the last has actually been
178
+ // walked through (completed or skipped). Guards against a restored/last-step
179
+ // index submitting a payload built from empty defaults.
180
+ const canFinish = stepsRef.current.every(
181
+ (_, index) =>
182
+ index === stepsRef.current.length - 1 ||
183
+ state.completedSteps.has(index) ||
184
+ state.skippedSteps.has(index),
185
+ );
186
+
187
+ // Runs the active step's config `validate` plus every validator its mounted
188
+ // components registered (RHF steps via useRhfWizardStep), AND-combining the
189
+ // results and merging any per-field errors. Returns `true` when the step
190
+ // passes (or registered no validators). Shared by `goNext` and `finish` so a
191
+ // form-bearing last step is validated (and its values flushed) on Finish too.
192
+ const runStepValidators = useCallback(async (): Promise<boolean> => {
193
+ const step = stepsRef.current[state.currentStepIndex];
194
+ const validators = [...validatorsRef.current.values()];
195
+ if (step.validate) validators.push(step.validate);
196
+ if (validators.length === 0) return true;
197
+ let ok = true;
198
+ let errors: FieldErrors = {};
199
+ try {
200
+ for (const validate of validators) {
201
+ const result = await validate();
202
+ const vOk = typeof result === "boolean" ? result : result.ok;
203
+ if (!vOk) {
204
+ ok = false;
205
+ if (typeof result !== "boolean" && result.errors) {
206
+ errors = { ...errors, ...result.errors };
207
+ }
208
+ }
209
+ }
210
+ } catch (err) {
211
+ // FALL THROUGH, do not return. A bare `return false` here blocked Next while
212
+ // skipping the `if (!ok)` block below — the only thing that shows a message,
213
+ // and whose own comment is "say something anyway, so the user is not left with
214
+ // a Next button that silently does nothing". So a validator that threw (an
215
+ // async one whose request rejected, a null dereference in a field rule)
216
+ // produced exactly the outcome that block was written to prevent: no field
217
+ // errors, no toast, `onValidationFailed` never called, nothing in the console.
218
+ //
219
+ // The step still must not advance; `ok = false` is what says so. Whatever
220
+ // partial `errors` the loop had collected before the throw are kept — they are
221
+ // real, and an empty set is what routes this to the generic message.
222
+ console.error("[wizard] a step validator threw", err);
223
+ ok = false;
224
+ }
225
+ if (!ok) {
226
+ dispatch({ type: "SET_FIELD_ERRORS", errors });
227
+ if (Object.keys(errors).length === 0) {
228
+ // No per-field detail (e.g. an RHF step surfacing its own inline
229
+ // message) — say something anyway, so the user is not left with a Next
230
+ // button that silently does nothing.
231
+ const message = missingRequired;
232
+ if (onValidationFailed) {
233
+ onValidationFailed(message);
234
+ } else {
235
+ // sonner is an optional peer: imported here, on the failure path only,
236
+ // so an app that never trips this never has to install it.
237
+ const { toast } = await import("sonner");
238
+ toast.error(message);
239
+ }
240
+ }
241
+ return false;
242
+ }
243
+ return true;
244
+ }, [state.currentStepIndex, missingRequired, onValidationFailed]);
245
+
246
+ const goNext = useCallback(async () => {
247
+ if (await runStepValidators()) {
248
+ dispatch({ type: "GO_NEXT" });
249
+ }
250
+ }, [runStepValidators]);
251
+
252
+ const goBack = useCallback(() => {
253
+ dispatch({ type: "GO_BACK" });
254
+ }, []);
255
+
256
+ const goToStep = useCallback((index: number) => {
257
+ dispatch({ type: "GO_TO_STEP", index });
258
+ }, []);
259
+
260
+ const skip = useCallback(() => {
261
+ dispatch({ type: "SKIP" });
262
+ }, []);
263
+
264
+ const cancel = useCallback(() => {
265
+ dispatch({ type: "SHOW_CANCEL_DIALOG", show: true });
266
+ }, []);
267
+
268
+ const confirmCancel = useCallback(() => {
269
+ dispatch({ type: "SHOW_CANCEL_DIALOG", show: false });
270
+ onCancel?.();
271
+ }, [onCancel]);
272
+
273
+ const dismissCancel = useCallback(() => {
274
+ dispatch({ type: "SHOW_CANCEL_DIALOG", show: false });
275
+ }, []);
276
+
277
+ const updateData = useCallback((partial: Partial<TData>) => {
278
+ dispatch({ type: "UPDATE_DATA", partial });
279
+ }, []);
280
+
281
+ const clearError = useCallback(() => {
282
+ dispatch({ type: "SET_ERROR", error: null });
283
+ }, []);
284
+
285
+ const clearFieldErrors = useCallback(() => {
286
+ dispatch({ type: "SET_FIELD_ERRORS", errors: {} });
287
+ }, []);
288
+
289
+ const finish = useCallback(async () => {
290
+ if (!onComplete || !canFinish) return;
291
+ // Validate the (possibly form-bearing) last step before submitting, matching
292
+ // the Next gate — review-only steps register no validators and pass through.
293
+ if (!(await runStepValidators())) return;
294
+ dispatch({ type: "SET_SUBMITTING", value: true });
295
+ dispatch({ type: "SET_ERROR", error: null });
296
+ try {
297
+ await onComplete(state.data);
298
+ } catch (err) {
299
+ const message = err instanceof Error && err.message ? err.message : genericError;
300
+ dispatch({ type: "SET_ERROR", error: message });
301
+ } finally {
302
+ dispatch({ type: "SET_SUBMITTING", value: false });
303
+ }
304
+ }, [onComplete, canFinish, state.data, runStepValidators, genericError]);
305
+
306
+ const stepStatus = useCallback(
307
+ (index: number): StepStatus => {
308
+ if (index === state.currentStepIndex) return "active";
309
+ if (state.skippedSteps.has(index)) return "skipped";
310
+ if (state.completedSteps.has(index)) return "completed";
311
+ return "upcoming";
312
+ },
313
+ [state.currentStepIndex, state.completedSteps, state.skippedSteps],
314
+ );
315
+
316
+ return {
317
+ currentStepIndex: state.currentStepIndex,
318
+ currentStep,
319
+ steps: stepsRef.current,
320
+ isFirstStep,
321
+ isLastStep,
322
+ canFinish,
323
+ goNext,
324
+ goBack,
325
+ goToStep,
326
+ skip,
327
+ cancel,
328
+ finish,
329
+ registerStepValidate,
330
+ nextBlocked,
331
+ setNextBlocked,
332
+ stepStatus,
333
+ completedSteps: state.completedSteps,
334
+ data: state.data,
335
+ updateData,
336
+ isSubmitting: state.isSubmitting,
337
+ error: state.error,
338
+ clearError,
339
+ fieldErrors: state.fieldErrors,
340
+ clearFieldErrors,
341
+ showCancelDialog: state.showCancelDialog,
342
+ confirmCancel,
343
+ dismissCancel,
344
+ };
345
+ }
@@ -0,0 +1,37 @@
1
+ import type { ValidateResult, FieldErrors } from "./types";
2
+
3
+ export interface RequiredFieldSpec<T> {
4
+ key: keyof T;
5
+ /**
6
+ * Custom predicate (e.g. numeric `> 0` or range checks). Defaults to a
7
+ * "present" check: a non-empty trimmed string, or a non-null id/enum.
8
+ */
9
+ valid?: (value: unknown, data: T) => boolean;
10
+ /** Per-field message override (defaults to the shared `defaultMessage`). */
11
+ message?: string;
12
+ }
13
+
14
+ /**
15
+ * Folds the copy-pasted `const errors = {}; if (!data.x) errors.x = required; …`
16
+ * step-validate block reimplemented across the single-file entity wizards.
17
+ * Pure-required fields need only `{ key }`; numeric constraints (amount `> 0`,
18
+ * rate in range) pass a `valid` predicate. The message is caller-supplied so
19
+ * each page keeps its own namespace (e.g. `t("common:validation.required")`).
20
+ */
21
+ export function requiredFieldsValidator<T extends Record<string, unknown>>(
22
+ data: T,
23
+ fields: RequiredFieldSpec<T>[],
24
+ defaultMessage: string,
25
+ ): ValidateResult {
26
+ const errors: FieldErrors = {};
27
+ for (const f of fields) {
28
+ const v = data[f.key];
29
+ const ok = f.valid
30
+ ? f.valid(v, data)
31
+ : typeof v === "string"
32
+ ? v.trim() !== ""
33
+ : v != null;
34
+ if (!ok) errors[String(f.key)] = f.message ?? defaultMessage;
35
+ }
36
+ return { ok: Object.keys(errors).length === 0, errors };
37
+ }
@@ -0,0 +1,29 @@
1
+ import { createContext, useContext } from "react";
2
+ import type { ValidateResult } from "./types";
3
+
4
+ export interface WizardContextValue {
5
+ /**
6
+ * Register a validator for the currently-mounted step. Returns an unregister
7
+ * fn (call it in a useEffect cleanup). `goNext` runs every registered
8
+ * validator for the active step (plus the step config's own `validate`),
9
+ * AND-combining the results — this replaces the per-page `useRef` +
10
+ * `validate: () => ref.current?.()` bridge each RHF step used to wire.
11
+ */
12
+ registerStepValidate: (
13
+ fn: () => ValidateResult | Promise<ValidateResult>,
14
+ ) => () => void;
15
+ /** Lets the mounted step disable Next/Skip (e.g. invalid tenant shares). */
16
+ setNextBlocked: (blocked: boolean) => void;
17
+ }
18
+
19
+ const WizardContext = createContext<WizardContextValue | null>(null);
20
+
21
+ export const WizardContextProvider = WizardContext.Provider;
22
+
23
+ export function useWizardContext(): WizardContextValue {
24
+ const ctx = useContext(WizardContext);
25
+ if (!ctx) {
26
+ throw new Error("useWizardContext must be used within a <StepperNav>");
27
+ }
28
+ return ctx;
29
+ }
@@ -0,0 +1,14 @@
1
+ import type { ReactNode } from "react";
2
+ import { cn } from "../lib/cn";
3
+
4
+ /** The vertical rhythm one wizard step's content sits on. A component rather
5
+ * than a class string so every step in every app spaces its fields the same. */
6
+ export function WizardStep({
7
+ children,
8
+ className,
9
+ }: {
10
+ children: ReactNode;
11
+ className?: string;
12
+ }) {
13
+ return <div className={cn("space-y-6", className)}>{children}</div>;
14
+ }
@@ -0,0 +1,69 @@
1
+ import { Pencil } from "lucide-react";
2
+ import { Card, CardAction, CardContent, CardHeader, CardTitle, IconButton } from "../components/ui";
3
+ import type { SummarySection, WizardLabels } from "./types";
4
+ import { DEFAULT_WIZARD_LABELS } from "./types";
5
+ import { useKitLabels } from "../i18n/kit-labels";
6
+
7
+ /**
8
+ * The review step: one card per section, each row a label/value pair, each card
9
+ * carrying an edit button back to the step it came from.
10
+ *
11
+ * Sections are data, not children, because "jump back to step N" is the whole
12
+ * point of the component and a caller assembling its own cards invariably drops
13
+ * it. Labels and values are `ReactNode`, so a value can be a badge or a link.
14
+ */
15
+ export function WizardSummary({
16
+ sections,
17
+ onEditStep,
18
+ labels,
19
+ }: {
20
+ sections: SummarySection[];
21
+ onEditStep: (stepIndex: number) => void;
22
+ labels?: Partial<WizardLabels>;
23
+ }) {
24
+ const l = useKitLabels("wizard", DEFAULT_WIZARD_LABELS, labels);
25
+
26
+ return (
27
+ <div className="space-y-4">
28
+ <h2 className="text-lg font-semibold text-[var(--text-primary)]">{l.reviewTitle}</h2>
29
+ {sections.map((section, sIdx) => (
30
+ <Card key={sIdx}>
31
+ {/* CardHeader is a grid, and CardAction is the top-right column it
32
+ reserves — the edit button has to go through it or it stacks under
33
+ the title instead of sitting beside it. */}
34
+ <CardHeader className="pb-2">
35
+ <CardTitle className="text-base">{section.label}</CardTitle>
36
+ <CardAction>
37
+ <IconButton
38
+ size="sm"
39
+ variant="ghost"
40
+ aria-label={l.edit}
41
+ title={l.edit}
42
+ onClick={() => onEditStep(section.stepIndex)}
43
+ >
44
+ <Pencil />
45
+ </IconButton>
46
+ </CardAction>
47
+ </CardHeader>
48
+ <CardContent>
49
+ <dl className="grid gap-2 text-sm">
50
+ {section.items.map((item, iIdx) => (
51
+ <div key={iIdx}>
52
+ {iIdx > 0 && (
53
+ <div className="mb-2 border-t border-[var(--border)]" role="presentation" />
54
+ )}
55
+ <div className="flex items-start justify-between gap-4">
56
+ <dt className="text-[var(--text-muted)]">{item.label}</dt>
57
+ <dd className="text-right font-medium text-[var(--text-primary)]">
58
+ {item.value}
59
+ </dd>
60
+ </div>
61
+ </div>
62
+ ))}
63
+ </dl>
64
+ </CardContent>
65
+ </Card>
66
+ ))}
67
+ </div>
68
+ );
69
+ }
package/src/wizard.ts ADDED
@@ -0,0 +1,14 @@
1
+ // `@eifi1/ui-kit/wizard` — the multi-step engine, its chrome and its review step.
2
+ //
3
+ // A re-slicing of the main barrel, not a new API. This entry point requires
4
+ // `react-router`: useWizard syncs the active step to `?step=` via useSearchParams.
5
+ //
6
+ // NOT the same thing as `WizardStepper` in components/wizard-stepper, which is a bare
7
+ // two-step indicator with no engine and stays on the main barrel.
8
+ export * from "./wizard/types";
9
+ export * from "./wizard/use-wizard";
10
+ export * from "./wizard/wizard-context";
11
+ export * from "./wizard/validation";
12
+ export * from "./wizard/wizard-step";
13
+ export * from "./wizard/wizard-summary";
14
+ export * from "./wizard/stepper-nav";