@eifi1/ui-kit 0.10.0 → 0.12.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 (382) hide show
  1. package/README.md +66 -37
  2. package/dist/chart.d.ts +3 -1
  3. package/dist/chart.js +2 -0
  4. package/dist/chart.js.map +1 -1
  5. package/dist/components/account-settings-labels.d.ts +105 -0
  6. package/dist/components/account-settings-labels.js +81 -0
  7. package/dist/components/account-settings-labels.js.map +1 -0
  8. package/dist/components/account-settings.d.ts +36 -52
  9. package/dist/components/account-settings.js +44 -8
  10. package/dist/components/account-settings.js.map +1 -1
  11. package/dist/components/alert-banner.d.ts +26 -1
  12. package/dist/components/alert-banner.js +10 -7
  13. package/dist/components/alert-banner.js.map +1 -1
  14. package/dist/components/amount-input.d.ts +26 -4
  15. package/dist/components/amount-input.js +5 -2
  16. package/dist/components/amount-input.js.map +1 -1
  17. package/dist/components/authed-image.d.ts +68 -0
  18. package/dist/components/authed-image.js +117 -0
  19. package/dist/components/authed-image.js.map +1 -0
  20. package/dist/components/breadcrumbs.d.ts +2 -1
  21. package/dist/components/breadcrumbs.js +5 -2
  22. package/dist/components/breadcrumbs.js.map +1 -1
  23. package/dist/components/bulk-action-bar.d.ts +51 -5
  24. package/dist/components/bulk-action-bar.js +88 -41
  25. package/dist/components/bulk-action-bar.js.map +1 -1
  26. package/dist/components/button-group.d.ts +107 -3
  27. package/dist/components/button-group.js +60 -5
  28. package/dist/components/button-group.js.map +1 -1
  29. package/dist/components/calculator.d.ts +17 -4
  30. package/dist/components/calendar-heatmap.d.ts +162 -0
  31. package/dist/components/calendar-heatmap.js +336 -0
  32. package/dist/components/calendar-heatmap.js.map +1 -0
  33. package/dist/components/chip.d.ts +2 -1
  34. package/dist/components/chip.js +6 -3
  35. package/dist/components/chip.js.map +1 -1
  36. package/dist/components/choice-card.d.ts +66 -2
  37. package/dist/components/choice-card.js +108 -1
  38. package/dist/components/choice-card.js.map +1 -1
  39. package/dist/components/combobox-core.js +2 -0
  40. package/dist/components/combobox-core.js.map +1 -1
  41. package/dist/components/combobox.d.ts +2 -2
  42. package/dist/components/combobox.js +31 -9
  43. package/dist/components/combobox.js.map +1 -1
  44. package/dist/components/copy-button.d.ts +52 -70
  45. package/dist/components/copy-button.js +1 -1
  46. package/dist/components/copy-button.js.map +1 -1
  47. package/dist/components/data-table-cells.d.ts +63 -0
  48. package/dist/components/data-table-cells.js +64 -0
  49. package/dist/components/data-table-cells.js.map +1 -0
  50. package/dist/components/data-table-filter-popover.d.ts +1 -1
  51. package/dist/components/data-table-filters.d.ts +1 -1
  52. package/dist/components/data-table-filters.js +31 -1
  53. package/dist/components/data-table-filters.js.map +1 -1
  54. package/dist/components/data-table-labels.d.ts +13 -0
  55. package/dist/components/data-table-labels.js +8 -1
  56. package/dist/components/data-table-labels.js.map +1 -1
  57. package/dist/components/data-table.d.ts +1 -1
  58. package/dist/components/data-table.js +104 -12
  59. package/dist/components/data-table.js.map +1 -1
  60. package/dist/components/date-picker.d.ts +3 -1
  61. package/dist/components/date-picker.js +15 -2
  62. package/dist/components/date-picker.js.map +1 -1
  63. package/dist/components/description-list.d.ts +23 -3
  64. package/dist/components/description-list.js +23 -3
  65. package/dist/components/description-list.js.map +1 -1
  66. package/dist/components/dialog-frame.d.ts +1 -0
  67. package/dist/components/entity-combobox.d.ts +1 -1
  68. package/dist/components/entity-combobox.js +18 -3
  69. package/dist/components/entity-combobox.js.map +1 -1
  70. package/dist/components/error-boundary.d.ts +92 -0
  71. package/dist/components/error-boundary.js +111 -0
  72. package/dist/components/error-boundary.js.map +1 -0
  73. package/dist/components/field.d.ts +69 -0
  74. package/dist/components/field.js +75 -0
  75. package/dist/components/field.js.map +1 -0
  76. package/dist/components/file-button.d.ts +51 -208
  77. package/dist/components/file-dropzone.d.ts +50 -2
  78. package/dist/components/floating-panel.d.ts +156 -4
  79. package/dist/components/floating-panel.js +193 -29
  80. package/dist/components/floating-panel.js.map +1 -1
  81. package/dist/components/form-actions.d.ts +53 -0
  82. package/dist/components/form-actions.js +98 -0
  83. package/dist/components/form-actions.js.map +1 -0
  84. package/dist/components/image-grid.d.ts +67 -0
  85. package/dist/components/image-grid.js +112 -0
  86. package/dist/components/image-grid.js.map +1 -0
  87. package/dist/components/lightbox.d.ts +104 -0
  88. package/dist/components/lightbox.js +229 -0
  89. package/dist/components/lightbox.js.map +1 -0
  90. package/dist/components/line-items.d.ts +114 -0
  91. package/dist/components/line-items.js +238 -0
  92. package/dist/components/line-items.js.map +1 -0
  93. package/dist/components/list.d.ts +37 -3
  94. package/dist/components/list.js +39 -14
  95. package/dist/components/list.js.map +1 -1
  96. package/dist/components/loading-state.d.ts +24 -0
  97. package/dist/components/loading-state.js +32 -0
  98. package/dist/components/loading-state.js.map +1 -0
  99. package/dist/components/menu-item.d.ts +13 -2
  100. package/dist/components/menu-item.js +11 -2
  101. package/dist/components/menu-item.js.map +1 -1
  102. package/dist/components/mini-calendar.d.ts +82 -5
  103. package/dist/components/mini-calendar.js +212 -64
  104. package/dist/components/mini-calendar.js.map +1 -1
  105. package/dist/components/modal.d.ts +20 -1
  106. package/dist/components/modal.js +26 -2
  107. package/dist/components/modal.js.map +1 -1
  108. package/dist/components/month-picker.d.ts +21 -1
  109. package/dist/components/month-picker.js +139 -26
  110. package/dist/components/month-picker.js.map +1 -1
  111. package/dist/components/multi-entity-combobox.d.ts +1 -1
  112. package/dist/components/multi-entity-combobox.js +19 -3
  113. package/dist/components/multi-entity-combobox.js.map +1 -1
  114. package/dist/components/nav-pills.d.ts +67 -0
  115. package/dist/components/nav-pills.js +69 -0
  116. package/dist/components/nav-pills.js.map +1 -0
  117. package/dist/components/number-field.d.ts +26 -5
  118. package/dist/components/number-field.js +5 -7
  119. package/dist/components/number-field.js.map +1 -1
  120. package/dist/components/number-input.d.ts +27 -5
  121. package/dist/components/number-input.js +7 -1
  122. package/dist/components/number-input.js.map +1 -1
  123. package/dist/components/numpad-sheet.d.ts +17 -4
  124. package/dist/components/passkeys-setting.d.ts +67 -0
  125. package/dist/components/passkeys-setting.js +214 -0
  126. package/dist/components/passkeys-setting.js.map +1 -0
  127. package/dist/components/picker-sheet.js +2 -1
  128. package/dist/components/picker-sheet.js.map +1 -1
  129. package/dist/components/pie-chart-labels.d.ts +20 -0
  130. package/dist/components/pie-chart-labels.js +12 -0
  131. package/dist/components/pie-chart-labels.js.map +1 -0
  132. package/dist/components/pie-chart.d.ts +101 -0
  133. package/dist/components/pie-chart.js +283 -0
  134. package/dist/components/pie-chart.js.map +1 -0
  135. package/dist/components/progress-bar.d.ts +73 -3
  136. package/dist/components/progress-bar.js +110 -8
  137. package/dist/components/progress-bar.js.map +1 -1
  138. package/dist/components/qr-code.d.ts +34 -0
  139. package/dist/components/qr-code.js +57 -0
  140. package/dist/components/qr-code.js.map +1 -0
  141. package/dist/components/search-field.d.ts +15 -3
  142. package/dist/components/search-field.js +5 -1
  143. package/dist/components/search-field.js.map +1 -1
  144. package/dist/components/series-chart.d.ts +16 -0
  145. package/dist/components/series-chart.js +4 -1
  146. package/dist/components/series-chart.js.map +1 -1
  147. package/dist/components/settings-fields.d.ts +50 -1
  148. package/dist/components/signed-amount.d.ts +121 -0
  149. package/dist/components/signed-amount.js +152 -0
  150. package/dist/components/signed-amount.js.map +1 -0
  151. package/dist/components/skeleton.d.ts +10 -3
  152. package/dist/components/skeleton.js +12 -2
  153. package/dist/components/skeleton.js.map +1 -1
  154. package/dist/components/stat-tile.d.ts +2 -1
  155. package/dist/components/stat-tile.js +5 -2
  156. package/dist/components/stat-tile.js.map +1 -1
  157. package/dist/components/status-dot.d.ts +6 -2
  158. package/dist/components/status-dot.js +6 -1
  159. package/dist/components/status-dot.js.map +1 -1
  160. package/dist/components/table.d.ts +77 -11
  161. package/dist/components/table.js +85 -17
  162. package/dist/components/table.js.map +1 -1
  163. package/dist/components/text-link.d.ts +176 -0
  164. package/dist/components/text-link.js +109 -0
  165. package/dist/components/text-link.js.map +1 -0
  166. package/dist/components/text.d.ts +8 -1
  167. package/dist/components/text.js +1 -0
  168. package/dist/components/text.js.map +1 -1
  169. package/dist/components/time-input.d.ts +50 -1
  170. package/dist/components/toggle-group.d.ts +28 -0
  171. package/dist/components/toggle-group.js +58 -9
  172. package/dist/components/toggle-group.js.map +1 -1
  173. package/dist/components/toggle-legend.d.ts +36 -3
  174. package/dist/components/toggle-legend.js +49 -0
  175. package/dist/components/toggle-legend.js.map +1 -1
  176. package/dist/components/tooltip.d.ts +11 -3
  177. package/dist/components/tooltip.js +2 -0
  178. package/dist/components/tooltip.js.map +1 -1
  179. package/dist/components/ui.d.ts +52 -729
  180. package/dist/components/ui.js +296 -27
  181. package/dist/components/ui.js.map +1 -1
  182. package/dist/components/use-table-state.d.ts +3 -52
  183. package/dist/components/use-table-state.js +128 -30
  184. package/dist/components/use-table-state.js.map +1 -1
  185. package/dist/components/user-avatar.d.ts +25 -3
  186. package/dist/components/user-avatar.js +32 -3
  187. package/dist/components/user-avatar.js.map +1 -1
  188. package/dist/{data-table-filters-Dh9uF_S-.d.ts → data-table-Drun8Fk9.d.ts} +305 -61
  189. package/dist/data-table.d.ts +2 -1
  190. package/dist/data-table.js +5 -1
  191. package/dist/data-table.js.map +1 -1
  192. package/dist/feedback/feedback-dialog.d.ts +26 -3
  193. package/dist/feedback/feedback-dialog.js +16 -2
  194. package/dist/feedback/feedback-dialog.js.map +1 -1
  195. package/dist/feedback/feedback-thread.d.ts +133 -0
  196. package/dist/feedback/feedback-thread.js +237 -0
  197. package/dist/feedback/feedback-thread.js.map +1 -0
  198. package/dist/feedback.d.ts +2 -1
  199. package/dist/feedback.js +1 -0
  200. package/dist/feedback.js.map +1 -1
  201. package/dist/hooks/use-authed-src.d.ts +71 -0
  202. package/dist/hooks/use-authed-src.js +63 -0
  203. package/dist/hooks/use-authed-src.js.map +1 -0
  204. package/dist/hooks/use-file-drop.d.ts +50 -2
  205. package/dist/hooks/use-hotkey.d.ts +59 -0
  206. package/dist/hooks/use-hotkey.js +105 -0
  207. package/dist/hooks/use-hotkey.js.map +1 -0
  208. package/dist/hooks/use-search-param-state.d.ts +83 -0
  209. package/dist/hooks/use-search-param-state.js +94 -0
  210. package/dist/hooks/use-search-param-state.js.map +1 -0
  211. package/dist/i18n/defaults.d.ts +17 -4
  212. package/dist/i18n/defaults.js +33 -1
  213. package/dist/i18n/defaults.js.map +1 -1
  214. package/dist/i18n/kit-labels.d.ts +47 -310
  215. package/dist/i18n/kit-labels.js +20 -5
  216. package/dist/i18n/kit-labels.js.map +1 -1
  217. package/dist/i18n/locales/de-CH-informal.d.ts +17 -4
  218. package/dist/i18n/locales/de-CH.d.ts +17 -4
  219. package/dist/i18n/locales/de-informal.d.ts +17 -4
  220. package/dist/i18n/locales/de.d.ts +17 -4
  221. package/dist/i18n/locales/de.js +165 -4
  222. package/dist/i18n/locales/de.js.map +1 -1
  223. package/dist/i18n/locales/es.d.ts +17 -4
  224. package/dist/i18n/locales/es.js +162 -4
  225. package/dist/i18n/locales/es.js.map +1 -1
  226. package/dist/i18n/locales/fr.d.ts +17 -4
  227. package/dist/i18n/locales/fr.js +162 -4
  228. package/dist/i18n/locales/fr.js.map +1 -1
  229. package/dist/i18n/locales/hu.d.ts +17 -4
  230. package/dist/i18n/locales/hu.js +162 -4
  231. package/dist/i18n/locales/hu.js.map +1 -1
  232. package/dist/i18n/locales/it.d.ts +17 -4
  233. package/dist/i18n/locales/it.js +162 -4
  234. package/dist/i18n/locales/it.js.map +1 -1
  235. package/dist/i18n/locales/zh.d.ts +17 -4
  236. package/dist/i18n/locales/zh.js +162 -4
  237. package/dist/i18n/locales/zh.js.map +1 -1
  238. package/dist/index.d.ts +45 -21
  239. package/dist/index.js +43 -1
  240. package/dist/index.js.map +1 -1
  241. package/dist/kit-labels-v3biUF1L.d.ts +1645 -0
  242. package/dist/lib/clipping.d.ts +21 -5
  243. package/dist/lib/clipping.js +3 -0
  244. package/dist/lib/clipping.js.map +1 -1
  245. package/dist/lib/dates.d.ts +17 -1
  246. package/dist/lib/dates.js +19 -0
  247. package/dist/lib/dates.js.map +1 -1
  248. package/dist/lib/format.d.ts +144 -0
  249. package/dist/lib/format.js +149 -0
  250. package/dist/lib/format.js.map +1 -0
  251. package/dist/lib/qr-encode.d.ts +44 -0
  252. package/dist/lib/qr-encode.js +338 -0
  253. package/dist/lib/qr-encode.js.map +1 -0
  254. package/dist/rhf/fields.d.ts +241 -0
  255. package/dist/rhf/fields.js +601 -0
  256. package/dist/rhf/fields.js.map +1 -0
  257. package/dist/rhf/form.d.ts +50 -1
  258. package/dist/rhf/line-items.d.ts +31 -0
  259. package/dist/rhf/line-items.js +59 -0
  260. package/dist/rhf/line-items.js.map +1 -0
  261. package/dist/rhf/use-rhf-wizard-step.d.ts +22 -0
  262. package/dist/rhf/use-rhf-wizard-step.js +38 -0
  263. package/dist/rhf/use-rhf-wizard-step.js.map +1 -0
  264. package/dist/rhf.d.ts +63 -1
  265. package/dist/rhf.js +3 -0
  266. package/dist/rhf.js.map +1 -1
  267. package/dist/shell/app-shell.d.ts +99 -2
  268. package/dist/shell/app-shell.js +58 -3
  269. package/dist/shell/app-shell.js.map +1 -1
  270. package/dist/shell/auth-layout.d.ts +74 -0
  271. package/dist/shell/auth-layout.js +97 -0
  272. package/dist/shell/auth-layout.js.map +1 -0
  273. package/dist/shell/top-bar-brand.d.ts +81 -0
  274. package/dist/shell/top-bar-brand.js +47 -0
  275. package/dist/shell/top-bar-brand.js.map +1 -0
  276. package/dist/shell/topbar-action-menu.d.ts +84 -3
  277. package/dist/shell/topbar-action-menu.js +115 -27
  278. package/dist/shell/topbar-action-menu.js.map +1 -1
  279. package/dist/shell.d.ts +58 -2
  280. package/dist/shell.js +2 -0
  281. package/dist/shell.js.map +1 -1
  282. package/dist/wizard/stepper-nav.d.ts +49 -1
  283. package/dist/wizard/wizard-context.d.ts +30 -1
  284. package/dist/wizard/wizard-context.js +23 -2
  285. package/dist/wizard/wizard-context.js.map +1 -1
  286. package/dist/wizard/wizard-step.d.ts +20 -5
  287. package/dist/wizard/wizard-step.js +16 -3
  288. package/dist/wizard/wizard-step.js.map +1 -1
  289. package/dist/wizard.d.ts +51 -3
  290. package/package.json +1 -1
  291. package/src/chart.ts +4 -0
  292. package/src/components/account-settings-labels.ts +190 -0
  293. package/src/components/account-settings.tsx +110 -63
  294. package/src/components/alert-banner.tsx +36 -7
  295. package/src/components/amount-input.tsx +13 -1
  296. package/src/components/authed-image.tsx +190 -0
  297. package/src/components/breadcrumbs.tsx +8 -2
  298. package/src/components/bulk-action-bar.tsx +163 -57
  299. package/src/components/button-group.tsx +129 -4
  300. package/src/components/calendar-heatmap.tsx +599 -0
  301. package/src/components/chip.tsx +8 -4
  302. package/src/components/choice-card.tsx +208 -1
  303. package/src/components/combobox-core.tsx +5 -0
  304. package/src/components/combobox.tsx +31 -9
  305. package/src/components/copy-button.tsx +3 -2
  306. package/src/components/data-table-cells.tsx +119 -0
  307. package/src/components/data-table-filters.ts +77 -0
  308. package/src/components/data-table-labels.ts +23 -0
  309. package/src/components/data-table.tsx +231 -13
  310. package/src/components/date-picker.tsx +19 -3
  311. package/src/components/description-list.tsx +52 -3
  312. package/src/components/entity-combobox.tsx +18 -3
  313. package/src/components/error-boundary.tsx +216 -0
  314. package/src/components/field.tsx +188 -0
  315. package/src/components/floating-panel.tsx +380 -16
  316. package/src/components/form-actions.tsx +191 -0
  317. package/src/components/image-grid.tsx +181 -0
  318. package/src/components/lightbox.tsx +374 -0
  319. package/src/components/line-items.tsx +385 -0
  320. package/src/components/list.tsx +93 -18
  321. package/src/components/loading-state.tsx +47 -0
  322. package/src/components/menu-item.tsx +29 -3
  323. package/src/components/mini-calendar.tsx +317 -57
  324. package/src/components/modal.tsx +61 -3
  325. package/src/components/month-picker.tsx +181 -26
  326. package/src/components/multi-entity-combobox.tsx +19 -3
  327. package/src/components/nav-pills.tsx +152 -0
  328. package/src/components/number-field.tsx +15 -10
  329. package/src/components/number-input.tsx +16 -1
  330. package/src/components/passkeys-setting.tsx +331 -0
  331. package/src/components/picker-sheet.tsx +7 -1
  332. package/src/components/pie-chart-labels.ts +32 -0
  333. package/src/components/pie-chart.tsx +479 -0
  334. package/src/components/progress-bar.tsx +241 -13
  335. package/src/components/qr-code.tsx +83 -0
  336. package/src/components/search-field.tsx +20 -4
  337. package/src/components/series-chart.tsx +21 -0
  338. package/src/components/signed-amount.tsx +286 -0
  339. package/src/components/skeleton.tsx +21 -3
  340. package/src/components/stat-tile.tsx +8 -2
  341. package/src/components/status-dot.tsx +10 -2
  342. package/src/components/table.tsx +186 -23
  343. package/src/components/text-link.tsx +248 -0
  344. package/src/components/text.tsx +9 -1
  345. package/src/components/toggle-group.tsx +106 -13
  346. package/src/components/toggle-legend.tsx +87 -2
  347. package/src/components/tooltip.tsx +17 -3
  348. package/src/components/ui.tsx +572 -44
  349. package/src/components/use-table-state.ts +265 -33
  350. package/src/components/user-avatar.tsx +57 -3
  351. package/src/data-table.ts +10 -0
  352. package/src/feedback/feedback-dialog.tsx +42 -3
  353. package/src/feedback/feedback-thread.tsx +434 -0
  354. package/src/feedback.ts +1 -0
  355. package/src/hooks/use-authed-src.ts +161 -0
  356. package/src/hooks/use-hotkey.ts +172 -0
  357. package/src/hooks/use-search-param-state.ts +187 -0
  358. package/src/i18n/defaults.ts +32 -0
  359. package/src/i18n/kit-labels.tsx +104 -8
  360. package/src/i18n/locales/de.ts +162 -0
  361. package/src/i18n/locales/es.ts +159 -0
  362. package/src/i18n/locales/fr.ts +162 -0
  363. package/src/i18n/locales/hu.ts +160 -0
  364. package/src/i18n/locales/it.ts +160 -0
  365. package/src/i18n/locales/zh.ts +159 -0
  366. package/src/index.ts +64 -0
  367. package/src/lib/clipping.ts +22 -4
  368. package/src/lib/dates.ts +37 -0
  369. package/src/lib/format.ts +325 -0
  370. package/src/lib/qr-encode.ts +420 -0
  371. package/src/rhf/fields.tsx +1054 -0
  372. package/src/rhf/line-items.tsx +130 -0
  373. package/src/rhf/use-rhf-wizard-step.ts +113 -0
  374. package/src/rhf.ts +8 -0
  375. package/src/shell/app-shell.tsx +147 -2
  376. package/src/shell/auth-layout.tsx +190 -0
  377. package/src/shell/top-bar-brand.tsx +76 -0
  378. package/src/shell/topbar-action-menu.tsx +260 -49
  379. package/src/shell.ts +2 -0
  380. package/src/wizard/wizard-context.tsx +50 -1
  381. package/src/wizard/wizard-step.tsx +43 -6
  382. package/tokens.css +12 -0
@@ -0,0 +1,216 @@
1
+ import { Component } from "react";
2
+ import type { ErrorInfo, ReactNode } from "react";
3
+ import { AlertTriangle } from "lucide-react";
4
+ import { useKitLabels } from "../i18n/kit-labels";
5
+ import { Button, EmptyState } from "./ui";
6
+ import { Disclosure } from "./disclosure";
7
+
8
+ /** The words of the default fallback. */
9
+ export interface ErrorBoundaryLabels {
10
+ /** The fallback's heading. */
11
+ title: string;
12
+ /** The line under it. */
13
+ message: string;
14
+ /** The button that clears the error and renders the children again. */
15
+ retry: string;
16
+ /** The disclosure that opens the error's own text (`showDetails`). */
17
+ details: string;
18
+ }
19
+
20
+ export const DEFAULT_ERROR_BOUNDARY_LABELS: ErrorBoundaryLabels = {
21
+ title: "Something went wrong",
22
+ message: "This part of the page could not be shown. Try again, or reload the page.",
23
+ retry: "Try again",
24
+ details: "Error details",
25
+ };
26
+
27
+ /**
28
+ * What was thrown, as flat strings, read ONCE when it arrives.
29
+ *
30
+ * React stores whatever was thrown, hostile or not — `throw { get message() { throw … } }`,
31
+ * `throw { name: Symbol() }`, `throw undefined` — and reading one of those during the
32
+ * fallback's own render would throw INSIDE the boundary, escalating to the next one up
33
+ * (keksdose's error-boundary.tsx learned this as feedback #160: at the app level there is
34
+ * none, and the result is the white screen the boundary exists to prevent).
35
+ */
36
+ export interface ErrorBoundaryDetails {
37
+ name: string;
38
+ message: string;
39
+ stack?: string;
40
+ }
41
+
42
+ function read(get: () => unknown): string | undefined {
43
+ try {
44
+ const v = get();
45
+ return typeof v === "string" ? v : v === undefined || v === null ? undefined : String(v);
46
+ } catch {
47
+ return undefined;
48
+ }
49
+ }
50
+
51
+ /** {@link ErrorBoundaryDetails} of any thrown value, without ever throwing. */
52
+ export function describeThrown(error: unknown): ErrorBoundaryDetails {
53
+ if (typeof error === "object" && error !== null) {
54
+ const e = error as { name?: unknown; message?: unknown; stack?: unknown };
55
+ return {
56
+ name: read(() => e.name) ?? "Error",
57
+ message: read(() => e.message) ?? "",
58
+ stack: read(() => e.stack),
59
+ };
60
+ }
61
+ return { name: "Error", message: read(() => error) ?? "" };
62
+ }
63
+
64
+ /** What a render-prop `fallback` is handed. */
65
+ export interface ErrorBoundaryFallbackProps {
66
+ /** The thrown value, as it was thrown. Read it with care — see {@link describeThrown}. */
67
+ error: unknown;
68
+ details: ErrorBoundaryDetails;
69
+ /** Clear the error and render the children again. */
70
+ reset: () => void;
71
+ }
72
+
73
+ export interface ErrorBoundaryProps {
74
+ children?: ReactNode;
75
+ /** Replaces the default fallback: a node, or a render function handed the error and
76
+ * a `reset`. */
77
+ fallback?: ReactNode | ((props: ErrorBoundaryFallbackProps) => ReactNode);
78
+ /** Called once per caught error — log it, file a crash report. */
79
+ onError?: (error: unknown, info: ErrorInfo) => void;
80
+ /** Called when the error is cleared, by Retry, `reset` or a `resetKeys` change —
81
+ * drop a cache, refetch. */
82
+ onReset?: () => void;
83
+ /**
84
+ * When any of these changes (compared with `Object.is`) AFTER an error was caught, the
85
+ * error is cleared and the children render again — a change in the same update that
86
+ * threw does not count (it is what the error was shown under) — pass the route path so leaving a broken page does not
87
+ * carry its fallback to the next one (keksdose's `resetKey`).
88
+ */
89
+ resetKeys?: ReadonlyArray<unknown>;
90
+ /** A disclosure under the message with the error's name, message and stack — for an
91
+ * internal tool, or while developing. Off by default: a stack means nothing to a user. */
92
+ showDetails?: boolean;
93
+ /** The default fallback's heading level. Default `h2`: the fallback often IS the page. */
94
+ headingAs?: "h2" | "h3" | "h4" | "h5" | "h6";
95
+ /** Classes for the default fallback's box (`m-8 min-h-[400px]`, kastlan). */
96
+ className?: string;
97
+ labels?: Partial<ErrorBoundaryLabels>;
98
+ }
99
+
100
+ interface ErrorBoundaryState {
101
+ /** A flag of its own, so `throw undefined` is still an error shown. */
102
+ failed: boolean;
103
+ error: unknown;
104
+ details: ErrorBoundaryDetails | null;
105
+ }
106
+
107
+ const CLEAR: ErrorBoundaryState = { failed: false, error: null, details: null };
108
+
109
+ function keysChanged(a: ReadonlyArray<unknown> | undefined, b: ReadonlyArray<unknown> | undefined): boolean {
110
+ if (a === b) return false;
111
+ if (!a || !b || a.length !== b.length) return true;
112
+ return a.some((v, i) => !Object.is(v, b[i]));
113
+ }
114
+
115
+ /**
116
+ * Catch a render error below it and show a fallback instead of an empty page.
117
+ *
118
+ * kastlan (shared/components/error-boundary.tsx) and keksdose (app/error-boundary.tsx)
119
+ * each built one on `EmptyState tone="danger"`: a warning glyph, a title, a line of
120
+ * explanation and a Retry. This is that fallback, translated through the provider
121
+ * (`errorBoundary.*`), with the hooks an app hangs its own behaviour on — `onError`
122
+ * for the crash report, `resetKeys` for the route change, `fallback` for anything else.
123
+ *
124
+ * The fallback is `role="alert"`: a page replaced by an error is news, and a reader
125
+ * that was on it is told.
126
+ */
127
+ export class ErrorBoundary extends Component<ErrorBoundaryProps, ErrorBoundaryState> {
128
+ state: ErrorBoundaryState = CLEAR;
129
+
130
+ static getDerivedStateFromError(error: unknown): Partial<ErrorBoundaryState> {
131
+ return { failed: true, error, details: describeThrown(error) };
132
+ }
133
+
134
+ componentDidCatch(error: unknown, info: ErrorInfo): void {
135
+ this.props.onError?.(error, info);
136
+ }
137
+
138
+ componentDidUpdate(prev: ErrorBoundaryProps, prevState: ErrorBoundaryState): void {
139
+ // Only a change AFTER the error was caught clears it (react-error-boundary's rule).
140
+ // `prevState.failed` is false in the update that caught it: that update may well be
141
+ // the one that changed the keys too — a navigation to a page that throws, with
142
+ // `resetKeys={[pathname]}` — and resetting then would fire `onReset` for nothing and
143
+ // render the failing child a second time. From then on `prev.resetKeys` are the
144
+ // keys the error was shown under, so any change clears it.
145
+ if (this.state.failed && prevState.failed && keysChanged(prev.resetKeys, this.props.resetKeys)) {
146
+ this.reset();
147
+ }
148
+ }
149
+
150
+ reset = (): void => {
151
+ this.props.onReset?.();
152
+ this.setState(CLEAR);
153
+ };
154
+
155
+ render(): ReactNode {
156
+ const { failed, error, details } = this.state;
157
+ if (!failed || !details) return this.props.children;
158
+ const { fallback } = this.props;
159
+ if (typeof fallback === "function") return fallback({ error, details, reset: this.reset });
160
+ if (fallback !== undefined) return fallback;
161
+ return (
162
+ <ErrorFallback
163
+ details={details}
164
+ reset={this.reset}
165
+ showDetails={this.props.showDetails ?? false}
166
+ headingAs={this.props.headingAs ?? "h2"}
167
+ className={this.props.className}
168
+ labels={this.props.labels}
169
+ />
170
+ );
171
+ }
172
+ }
173
+
174
+ function ErrorFallback({
175
+ details,
176
+ reset,
177
+ showDetails,
178
+ headingAs,
179
+ className,
180
+ labels,
181
+ }: {
182
+ details: ErrorBoundaryDetails;
183
+ reset: () => void;
184
+ showDetails: boolean;
185
+ headingAs: NonNullable<ErrorBoundaryProps["headingAs"]>;
186
+ className?: string;
187
+ labels?: Partial<ErrorBoundaryLabels>;
188
+ }) {
189
+ const text = useKitLabels("errorBoundary", DEFAULT_ERROR_BOUNDARY_LABELS, labels);
190
+ return (
191
+ <EmptyState
192
+ role="alert"
193
+ tone="danger"
194
+ headingAs={headingAs}
195
+ className={className}
196
+ icon={<AlertTriangle />}
197
+ title={text.title}
198
+ hint={text.message}
199
+ action={
200
+ <>
201
+ <Button variant="secondary" onClick={reset}>
202
+ {text.retry}
203
+ </Button>
204
+ {showDetails && (
205
+ <Disclosure variant="bare" title={text.details} className="basis-full text-start">
206
+ <pre className="max-h-60 overflow-auto whitespace-pre-wrap break-words text-xs text-[var(--text-secondary)]">
207
+ {details.message ? `${details.name}: ${details.message}` : details.name}
208
+ {details.stack ? `\n\n${details.stack}` : null}
209
+ </pre>
210
+ </Disclosure>
211
+ )}
212
+ </>
213
+ }
214
+ />
215
+ );
216
+ }
@@ -0,0 +1,188 @@
1
+ /**
2
+ * A field group with its label ABOVE the control: label, control, hint, error.
3
+ *
4
+ * ```tsx
5
+ * <Field label="IBAN" required hint="22 characters" error={errors.iban}>
6
+ * {(ids) => <Input {...ids} value={iban} onChange={…} />}
7
+ * </Field>
8
+ * ```
9
+ *
10
+ * The kit's `Input` and `Select` float their label inside the field, and the pickers
11
+ * draw theirs the same way. That is the wrong shape for a control the kit did not
12
+ * render, a radio group, a range slider, or a form that sets its labels above the
13
+ * fields throughout — which is what kastlan's wizards are, and why it kept its own
14
+ * `WizardField` after the kit dropped the one it had never rendered. This is that
15
+ * group, with the wiring the old one left to the caller: the label names the control
16
+ * through `htmlFor`, and the control is described by the hint and the error that are
17
+ * actually on screen.
18
+ *
19
+ * ## Wiring
20
+ *
21
+ * Pass a function as `children` and it receives {@link FieldControlProps} — `id`,
22
+ * `aria-describedby`, `aria-invalid` and `aria-required` — to spread on the control.
23
+ * Plain children render as they are, for a control that wires itself (a checkbox that
24
+ * carries its own label, a group with `aria-labelledby`); `htmlFor` then points the
25
+ * label at the control's own id, and the hint and error are on screen but describe
26
+ * nothing — the same as the `WizardField` this replaces.
27
+ *
28
+ * ## Decoupled
29
+ *
30
+ * Deliberately knows neither the wizard nor react-hook-form: `error` is a plain
31
+ * value. Pass `wizard.fieldErrors.iban`, a step-local error, or
32
+ * `fieldState.error?.message` from a `FormField` render — the markup is the same
33
+ * whichever of them gates the step, so a step can move between them without its
34
+ * fields changing. (Inside a react-hook-form form, `FormItem` + `FormLabel` from
35
+ * `@eifi1/ui-kit/rhf` do the same job with the ids taken from the form.)
36
+ *
37
+ * The error is NOT `role="alert"`, for the reason the kit's `error` prop gives (see
38
+ * `useFieldError` in components/ui.tsx): it is read when focus reaches the control.
39
+ */
40
+ import { useId, type ReactNode } from "react";
41
+ import { cn } from "../lib/cn";
42
+ import { Label } from "./ui";
43
+
44
+ /** What a `Field` hands its render-prop children, to spread on the control. */
45
+ export interface FieldControlProps {
46
+ /** The id the label's `htmlFor` points at. */
47
+ id: string;
48
+ /** The ids of the hint and the error that are rendered, or `undefined` when
49
+ * neither is. */
50
+ "aria-describedby": string | undefined;
51
+ /** `true` while the field has an error, `undefined` otherwise. */
52
+ "aria-invalid": true | undefined;
53
+ /** `true` when the field is `required`. The label's star is `aria-hidden`; this
54
+ * is what a screen reader hears instead. */
55
+ "aria-required": true | undefined;
56
+ }
57
+
58
+ /**
59
+ * The second argument of a `Field`'s render-prop children: what a GROUP needs that
60
+ * a single control does not. `htmlFor` names an `<input>`, but a `<label>` pointing
61
+ * at a `role="radiogroup"` div names nothing — so a group (a {@link ToggleGroup})
62
+ * takes `aria-labelledby={labelId}` instead. Kept out of {@link FieldControlProps}
63
+ * so the spread every existing control takes is unchanged.
64
+ */
65
+ export interface FieldRenderMeta {
66
+ /** The label element's id, or `undefined` when the field has no label. */
67
+ labelId: string | undefined;
68
+ }
69
+
70
+ /**
71
+ * Where the label is visible. `"visible"` (default); `"sr-only"`: never drawn, still
72
+ * the control's name; `"below-sm"` / `"below-md"` / `"below-lg"`: drawn only under
73
+ * that breakpoint and screen-reader-only from it up.
74
+ *
75
+ * For table-like editors — kastlan's journal entry lines
76
+ * (accounting/components/wizard/journal-entry-lines-step.tsx) — where a desktop
77
+ * row sits under column headers that already say "Account" and "Amount", but the
78
+ * phone layout stacks each line as a card and needs the labels back. The name stays
79
+ * on the control at every width; only the ink changes.
80
+ */
81
+ export type FieldLabelVisibility = "visible" | "sr-only" | "below-sm" | "below-md" | "below-lg";
82
+
83
+ // Literal class strings so Tailwind's scanner sees each one.
84
+ const LABEL_VISIBILITY: Record<FieldLabelVisibility, string | undefined> = {
85
+ visible: undefined,
86
+ "sr-only": "sr-only",
87
+ "below-sm": "sm:sr-only",
88
+ "below-md": "md:sr-only",
89
+ "below-lg": "lg:sr-only",
90
+ };
91
+
92
+ export interface FieldProps {
93
+ /** The label above the control. Omit it for a group whose control is labelled
94
+ * some other way; the hint and error still render. */
95
+ label?: ReactNode;
96
+ /** Draws the kit's required mark after the label, and sets `aria-required` on the
97
+ * render-prop control. */
98
+ required?: boolean;
99
+ /** Muted helper text below the control. */
100
+ hint?: ReactNode;
101
+ /** The error below the control. `null`, `false` and `""` are no error — what a
102
+ * `touched && errors.x` evaluates to on the happy path. */
103
+ error?: ReactNode;
104
+ /** The control's id. Defaults to a generated one; set it when the control carries
105
+ * its own id (plain children). */
106
+ htmlFor?: string;
107
+ /** Dims the label with its control. */
108
+ disabled?: boolean;
109
+ /** The label's size: `sm` for a dense row, `md` otherwise. */
110
+ labelSize?: "sm" | "md";
111
+ /** See {@link FieldLabelVisibility}. */
112
+ labelVisibility?: FieldLabelVisibility;
113
+ /** Wrapper className. */
114
+ className?: string;
115
+ /** The control. A function receives {@link FieldControlProps} to spread on it, and
116
+ * {@link FieldRenderMeta} (the label's id, for a group). */
117
+ children: ReactNode | ((control: FieldControlProps, meta: FieldRenderMeta) => ReactNode);
118
+ }
119
+
120
+ const isShown = (node: ReactNode) =>
121
+ node !== undefined && node !== null && node !== false && node !== "";
122
+
123
+ export function Field({
124
+ label,
125
+ required,
126
+ hint,
127
+ error,
128
+ htmlFor,
129
+ disabled,
130
+ labelSize,
131
+ labelVisibility = "visible",
132
+ className,
133
+ children,
134
+ }: FieldProps) {
135
+ const generated = useId();
136
+ const id = htmlFor ?? `${generated}-control`;
137
+ const hintId = `${generated}-hint`;
138
+ const errorId = `${generated}-error`;
139
+ const labelId = `${generated}-label`;
140
+ const hasHint = isShown(hint);
141
+ const hasError = isShown(error);
142
+ const describedBy = [hasHint && hintId, hasError && errorId].filter(Boolean).join(" ");
143
+ const control: FieldControlProps = {
144
+ id,
145
+ "aria-describedby": describedBy || undefined,
146
+ "aria-invalid": hasError || undefined,
147
+ "aria-required": required || undefined,
148
+ };
149
+ return (
150
+ <div
151
+ data-slot="field"
152
+ className={cn(
153
+ "grid gap-1.5",
154
+ // A hidden label is `position: absolute`; `relative` keeps its containing
155
+ // block here rather than the page's (see sr-only-containment.test).
156
+ labelVisibility !== "visible" && "relative",
157
+ className,
158
+ )}
159
+ >
160
+ {label !== undefined && (
161
+ <Label
162
+ id={labelId}
163
+ htmlFor={id}
164
+ required={required}
165
+ disabled={disabled}
166
+ size={labelSize}
167
+ data-error={hasError || undefined}
168
+ className={cn("data-[error=true]:text-[var(--danger)]", LABEL_VISIBILITY[labelVisibility])}
169
+ >
170
+ {label}
171
+ </Label>
172
+ )}
173
+ {typeof children === "function"
174
+ ? children(control, { labelId: label !== undefined ? labelId : undefined })
175
+ : children}
176
+ {hasHint && (
177
+ <p id={hintId} className="text-[11px] leading-tight text-[var(--text-muted)]">
178
+ {hint}
179
+ </p>
180
+ )}
181
+ {hasError && (
182
+ <p id={errorId} className="text-[11px] leading-tight text-[var(--danger)]">
183
+ {error}
184
+ </p>
185
+ )}
186
+ </div>
187
+ );
188
+ }