@eifi1/ui-kit 0.11.0 → 0.13.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 (397) hide show
  1. package/README.md +59 -34
  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/amount-input.d.ts +40 -4
  12. package/dist/components/amount-input.js +50 -12
  13. package/dist/components/amount-input.js.map +1 -1
  14. package/dist/components/authed-image.d.ts +68 -0
  15. package/dist/components/authed-image.js +117 -0
  16. package/dist/components/authed-image.js.map +1 -0
  17. package/dist/components/breadcrumbs.d.ts +2 -1
  18. package/dist/components/breadcrumbs.js +5 -2
  19. package/dist/components/breadcrumbs.js.map +1 -1
  20. package/dist/components/bulk-action-bar.d.ts +10 -2
  21. package/dist/components/bulk-action-bar.js.map +1 -1
  22. package/dist/components/button-group.d.ts +51 -2
  23. package/dist/components/button-group.js +5 -1
  24. package/dist/components/button-group.js.map +1 -1
  25. package/dist/components/calculator.d.ts +15 -4
  26. package/dist/components/calendar-heatmap.d.ts +41 -3
  27. package/dist/components/calendar-heatmap.js +48 -7
  28. package/dist/components/calendar-heatmap.js.map +1 -1
  29. package/dist/components/chart.js +14 -8
  30. package/dist/components/chart.js.map +1 -1
  31. package/dist/components/chip.d.ts +2 -1
  32. package/dist/components/chip.js +26 -18
  33. package/dist/components/chip.js.map +1 -1
  34. package/dist/components/choice-card.d.ts +35 -3
  35. package/dist/components/choice-card.js +83 -25
  36. package/dist/components/choice-card.js.map +1 -1
  37. package/dist/components/combobox-core.d.ts +3 -1
  38. package/dist/components/combobox-core.js +18 -5
  39. package/dist/components/combobox-core.js.map +1 -1
  40. package/dist/components/combobox.d.ts +2 -2
  41. package/dist/components/combobox.js +31 -9
  42. package/dist/components/combobox.js.map +1 -1
  43. package/dist/components/copy-button.d.ts +51 -70
  44. package/dist/components/copy-button.js +1 -1
  45. package/dist/components/copy-button.js.map +1 -1
  46. package/dist/components/data-table-cells.d.ts +63 -0
  47. package/dist/components/data-table-cells.js +64 -0
  48. package/dist/components/data-table-cells.js.map +1 -0
  49. package/dist/components/data-table-filter-popover.d.ts +1 -1
  50. package/dist/components/data-table-filter-popover.js +8 -1
  51. package/dist/components/data-table-filter-popover.js.map +1 -1
  52. package/dist/components/data-table-filters.d.ts +1 -1
  53. package/dist/components/data-table-filters.js +31 -1
  54. package/dist/components/data-table-filters.js.map +1 -1
  55. package/dist/components/data-table-labels.d.ts +13 -0
  56. package/dist/components/data-table-labels.js +8 -1
  57. package/dist/components/data-table-labels.js.map +1 -1
  58. package/dist/components/data-table.d.ts +1 -1
  59. package/dist/components/data-table.js +125 -27
  60. package/dist/components/data-table.js.map +1 -1
  61. package/dist/components/date-picker.d.ts +3 -1
  62. package/dist/components/date-picker.js +15 -2
  63. package/dist/components/date-picker.js.map +1 -1
  64. package/dist/components/description-list.d.ts +23 -3
  65. package/dist/components/description-list.js +23 -3
  66. package/dist/components/description-list.js.map +1 -1
  67. package/dist/components/dialog-frame.d.ts +1 -0
  68. package/dist/components/entity-combobox.d.ts +1 -1
  69. package/dist/components/entity-combobox.js +40 -20
  70. package/dist/components/entity-combobox.js.map +1 -1
  71. package/dist/components/error-boundary.d.ts +281 -0
  72. package/dist/components/error-boundary.js +302 -0
  73. package/dist/components/error-boundary.js.map +1 -0
  74. package/dist/components/field-sync.js +90 -83
  75. package/dist/components/field-sync.js.map +1 -1
  76. package/dist/components/field.d.ts +30 -4
  77. package/dist/components/field.js +40 -17
  78. package/dist/components/field.js.map +1 -1
  79. package/dist/components/file-button.d.ts +50 -208
  80. package/dist/components/file-dropzone.d.ts +49 -2
  81. package/dist/components/floating-panel.d.ts +13 -4
  82. package/dist/components/floating-panel.js +7 -3
  83. package/dist/components/floating-panel.js.map +1 -1
  84. package/dist/components/form-actions.d.ts +53 -0
  85. package/dist/components/form-actions.js +98 -0
  86. package/dist/components/form-actions.js.map +1 -0
  87. package/dist/components/full-bleed-dialog.js +4 -0
  88. package/dist/components/full-bleed-dialog.js.map +1 -1
  89. package/dist/components/hover-menu.js +64 -56
  90. package/dist/components/hover-menu.js.map +1 -1
  91. package/dist/components/image-grid.d.ts +67 -0
  92. package/dist/components/image-grid.js +112 -0
  93. package/dist/components/image-grid.js.map +1 -0
  94. package/dist/components/lightbox.d.ts +104 -0
  95. package/dist/components/lightbox.js +229 -0
  96. package/dist/components/lightbox.js.map +1 -0
  97. package/dist/components/line-items.d.ts +114 -0
  98. package/dist/components/line-items.js +238 -0
  99. package/dist/components/line-items.js.map +1 -0
  100. package/dist/components/list.d.ts +37 -4
  101. package/dist/components/list.js +35 -14
  102. package/dist/components/list.js.map +1 -1
  103. package/dist/components/loading-state.d.ts +24 -0
  104. package/dist/components/loading-state.js +32 -0
  105. package/dist/components/loading-state.js.map +1 -0
  106. package/dist/components/menu-item.d.ts +2 -1
  107. package/dist/components/menu-item.js +5 -1
  108. package/dist/components/menu-item.js.map +1 -1
  109. package/dist/components/mini-calendar.d.ts +13 -1
  110. package/dist/components/mini-calendar.js +46 -2
  111. package/dist/components/mini-calendar.js.map +1 -1
  112. package/dist/components/modal.d.ts +20 -1
  113. package/dist/components/modal.js +26 -2
  114. package/dist/components/modal.js.map +1 -1
  115. package/dist/components/month-picker.d.ts +21 -1
  116. package/dist/components/month-picker.js +139 -26
  117. package/dist/components/month-picker.js.map +1 -1
  118. package/dist/components/multi-entity-combobox.d.ts +1 -1
  119. package/dist/components/multi-entity-combobox.js +42 -20
  120. package/dist/components/multi-entity-combobox.js.map +1 -1
  121. package/dist/components/nav-pills.d.ts +2 -1
  122. package/dist/components/nav-pills.js +5 -1
  123. package/dist/components/nav-pills.js.map +1 -1
  124. package/dist/components/number-field.d.ts +24 -5
  125. package/dist/components/number-field.js +5 -7
  126. package/dist/components/number-field.js.map +1 -1
  127. package/dist/components/number-input.d.ts +25 -5
  128. package/dist/components/number-input.js +7 -1
  129. package/dist/components/number-input.js.map +1 -1
  130. package/dist/components/numpad-sheet.d.ts +20 -5
  131. package/dist/components/numpad-sheet.js +3 -2
  132. package/dist/components/numpad-sheet.js.map +1 -1
  133. package/dist/components/page-header.d.ts +35 -6
  134. package/dist/components/page-header.js +7 -1
  135. package/dist/components/page-header.js.map +1 -1
  136. package/dist/components/passkeys-setting.d.ts +67 -0
  137. package/dist/components/passkeys-setting.js +214 -0
  138. package/dist/components/passkeys-setting.js.map +1 -0
  139. package/dist/components/picker-sheet.js +5 -1
  140. package/dist/components/picker-sheet.js.map +1 -1
  141. package/dist/components/pie-chart-labels.d.ts +20 -0
  142. package/dist/components/pie-chart-labels.js +12 -0
  143. package/dist/components/pie-chart-labels.js.map +1 -0
  144. package/dist/components/pie-chart.d.ts +101 -0
  145. package/dist/components/pie-chart.js +283 -0
  146. package/dist/components/pie-chart.js.map +1 -0
  147. package/dist/components/popover.js +3 -0
  148. package/dist/components/popover.js.map +1 -1
  149. package/dist/components/progress-bar.d.ts +44 -3
  150. package/dist/components/progress-bar.js +58 -22
  151. package/dist/components/progress-bar.js.map +1 -1
  152. package/dist/components/qr-code.d.ts +34 -0
  153. package/dist/components/qr-code.js +57 -0
  154. package/dist/components/qr-code.js.map +1 -0
  155. package/dist/components/search-field.d.ts +15 -3
  156. package/dist/components/search-field.js +5 -1
  157. package/dist/components/search-field.js.map +1 -1
  158. package/dist/components/settings-fields.d.ts +49 -1
  159. package/dist/components/signed-amount.d.ts +122 -0
  160. package/dist/components/signed-amount.js +152 -0
  161. package/dist/components/signed-amount.js.map +1 -0
  162. package/dist/components/skeleton.d.ts +10 -3
  163. package/dist/components/skeleton.js +12 -2
  164. package/dist/components/skeleton.js.map +1 -1
  165. package/dist/components/stat-tile.d.ts +2 -1
  166. package/dist/components/stat-tile.js +6 -3
  167. package/dist/components/stat-tile.js.map +1 -1
  168. package/dist/components/swipeable-row.js +4 -3
  169. package/dist/components/swipeable-row.js.map +1 -1
  170. package/dist/components/table.d.ts +39 -5
  171. package/dist/components/table.js +45 -11
  172. package/dist/components/table.js.map +1 -1
  173. package/dist/components/text-link.d.ts +238 -0
  174. package/dist/components/text-link.js +144 -0
  175. package/dist/components/text-link.js.map +1 -0
  176. package/dist/components/text.d.ts +26 -2
  177. package/dist/components/text.js +16 -2
  178. package/dist/components/text.js.map +1 -1
  179. package/dist/components/time-input.d.ts +49 -1
  180. package/dist/components/toggle-group.d.ts +35 -0
  181. package/dist/components/toggle-group.js +43 -14
  182. package/dist/components/toggle-group.js.map +1 -1
  183. package/dist/components/toggle-legend.d.ts +53 -3
  184. package/dist/components/toggle-legend.js +54 -1
  185. package/dist/components/toggle-legend.js.map +1 -1
  186. package/dist/components/tooltip.d.ts +51 -1
  187. package/dist/components/tooltip.js +59 -35
  188. package/dist/components/tooltip.js.map +1 -1
  189. package/dist/components/ui.d.ts +51 -779
  190. package/dist/components/ui.js +364 -34
  191. package/dist/components/ui.js.map +1 -1
  192. package/dist/components/use-table-state.d.ts +3 -52
  193. package/dist/components/use-table-state.js +128 -30
  194. package/dist/components/use-table-state.js.map +1 -1
  195. package/dist/{data-table-filters-Dh9uF_S-.d.ts → data-table-Drun8Fk9.d.ts} +305 -61
  196. package/dist/data-table.d.ts +2 -1
  197. package/dist/data-table.js +5 -1
  198. package/dist/data-table.js.map +1 -1
  199. package/dist/feedback/feedback-attachment.js +4 -6
  200. package/dist/feedback/feedback-attachment.js.map +1 -1
  201. package/dist/feedback/feedback-dialog.d.ts +26 -3
  202. package/dist/feedback/feedback-dialog.js +21 -5
  203. package/dist/feedback/feedback-dialog.js.map +1 -1
  204. package/dist/feedback/feedback-inbox.js +86 -75
  205. package/dist/feedback/feedback-inbox.js.map +1 -1
  206. package/dist/feedback/feedback-thread.d.ts +173 -0
  207. package/dist/feedback/feedback-thread.js +264 -0
  208. package/dist/feedback/feedback-thread.js.map +1 -0
  209. package/dist/feedback.d.ts +2 -1
  210. package/dist/feedback.js +1 -0
  211. package/dist/feedback.js.map +1 -1
  212. package/dist/hooks/use-authed-src.d.ts +71 -0
  213. package/dist/hooks/use-authed-src.js +63 -0
  214. package/dist/hooks/use-authed-src.js.map +1 -0
  215. package/dist/hooks/use-file-drop.d.ts +49 -2
  216. package/dist/hooks/use-hotkey.d.ts +59 -0
  217. package/dist/hooks/use-hotkey.js +105 -0
  218. package/dist/hooks/use-hotkey.js.map +1 -0
  219. package/dist/hooks/use-search-param-state.d.ts +83 -0
  220. package/dist/hooks/use-search-param-state.js +94 -0
  221. package/dist/hooks/use-search-param-state.js.map +1 -0
  222. package/dist/i18n/defaults.d.ts +15 -4
  223. package/dist/i18n/defaults.js +31 -1
  224. package/dist/i18n/defaults.js.map +1 -1
  225. package/dist/i18n/kit-labels.d.ts +46 -312
  226. package/dist/i18n/kit-labels.js +20 -5
  227. package/dist/i18n/kit-labels.js.map +1 -1
  228. package/dist/i18n/locales/de-CH-informal.d.ts +15 -4
  229. package/dist/i18n/locales/de-CH.d.ts +15 -4
  230. package/dist/i18n/locales/de-informal.d.ts +15 -4
  231. package/dist/i18n/locales/de.d.ts +15 -4
  232. package/dist/i18n/locales/de.js +164 -3
  233. package/dist/i18n/locales/de.js.map +1 -1
  234. package/dist/i18n/locales/es.d.ts +15 -4
  235. package/dist/i18n/locales/es.js +161 -3
  236. package/dist/i18n/locales/es.js.map +1 -1
  237. package/dist/i18n/locales/fr.d.ts +15 -4
  238. package/dist/i18n/locales/fr.js +161 -3
  239. package/dist/i18n/locales/fr.js.map +1 -1
  240. package/dist/i18n/locales/hu.d.ts +15 -4
  241. package/dist/i18n/locales/hu.js +161 -3
  242. package/dist/i18n/locales/hu.js.map +1 -1
  243. package/dist/i18n/locales/it.d.ts +15 -4
  244. package/dist/i18n/locales/it.js +161 -3
  245. package/dist/i18n/locales/it.js.map +1 -1
  246. package/dist/i18n/locales/zh.d.ts +15 -4
  247. package/dist/i18n/locales/zh.js +161 -3
  248. package/dist/i18n/locales/zh.js.map +1 -1
  249. package/dist/index.d.ts +39 -19
  250. package/dist/index.js +40 -1
  251. package/dist/index.js.map +1 -1
  252. package/dist/kit-labels-Mz8zZHIF.d.ts +1744 -0
  253. package/dist/lib/format.d.ts +162 -0
  254. package/dist/lib/format.js +151 -0
  255. package/dist/lib/format.js.map +1 -0
  256. package/dist/lib/qr-encode.d.ts +44 -0
  257. package/dist/lib/qr-encode.js +338 -0
  258. package/dist/lib/qr-encode.js.map +1 -0
  259. package/dist/rhf/fields.d.ts +283 -0
  260. package/dist/rhf/fields.js +631 -0
  261. package/dist/rhf/fields.js.map +1 -0
  262. package/dist/rhf/form.d.ts +49 -1
  263. package/dist/rhf/line-items.d.ts +31 -0
  264. package/dist/rhf/line-items.js +59 -0
  265. package/dist/rhf/line-items.js.map +1 -0
  266. package/dist/rhf.d.ts +61 -1
  267. package/dist/rhf.js +2 -0
  268. package/dist/rhf.js.map +1 -1
  269. package/dist/search/command-palette.js +20 -14
  270. package/dist/search/command-palette.js.map +1 -1
  271. package/dist/shell/app-shell.d.ts +99 -2
  272. package/dist/shell/app-shell.js +120 -57
  273. package/dist/shell/app-shell.js.map +1 -1
  274. package/dist/shell/auth-layout.d.ts +74 -0
  275. package/dist/shell/auth-layout.js +97 -0
  276. package/dist/shell/auth-layout.js.map +1 -0
  277. package/dist/shell/top-bar-brand.d.ts +81 -0
  278. package/dist/shell/top-bar-brand.js +47 -0
  279. package/dist/shell/top-bar-brand.js.map +1 -0
  280. package/dist/shell/topbar-action-menu.d.ts +21 -4
  281. package/dist/shell/topbar-action-menu.js +18 -2
  282. package/dist/shell/topbar-action-menu.js.map +1 -1
  283. package/dist/shell.d.ts +56 -1
  284. package/dist/shell.js +2 -0
  285. package/dist/shell.js.map +1 -1
  286. package/dist/tour/tour.js +10 -4
  287. package/dist/tour/tour.js.map +1 -1
  288. package/dist/wizard/stepper-nav.d.ts +48 -1
  289. package/dist/wizard/use-wizard.js +4 -2
  290. package/dist/wizard/use-wizard.js.map +1 -1
  291. package/dist/wizard/wizard-context.d.ts +24 -1
  292. package/dist/wizard/wizard-context.js +19 -2
  293. package/dist/wizard/wizard-context.js.map +1 -1
  294. package/dist/wizard/wizard-step.d.ts +30 -5
  295. package/dist/wizard/wizard-step.js +34 -3
  296. package/dist/wizard/wizard-step.js.map +1 -1
  297. package/dist/wizard.d.ts +50 -3
  298. package/package.json +6 -6
  299. package/src/chart.ts +4 -0
  300. package/src/components/account-settings-labels.ts +190 -0
  301. package/src/components/account-settings.tsx +110 -63
  302. package/src/components/amount-input.tsx +109 -10
  303. package/src/components/authed-image.tsx +190 -0
  304. package/src/components/breadcrumbs.tsx +8 -2
  305. package/src/components/bulk-action-bar.tsx +16 -3
  306. package/src/components/button-group.tsx +7 -2
  307. package/src/components/calendar-heatmap.tsx +105 -10
  308. package/src/components/chart.tsx +19 -8
  309. package/src/components/chip.tsx +14 -4
  310. package/src/components/choice-card.tsx +136 -25
  311. package/src/components/combobox-core.tsx +33 -9
  312. package/src/components/combobox.tsx +33 -9
  313. package/src/components/copy-button.tsx +3 -2
  314. package/src/components/data-table-cells.tsx +119 -0
  315. package/src/components/data-table-filter-popover.tsx +13 -5
  316. package/src/components/data-table-filters.ts +77 -0
  317. package/src/components/data-table-labels.ts +23 -0
  318. package/src/components/data-table.tsx +230 -13
  319. package/src/components/date-picker.tsx +19 -3
  320. package/src/components/description-list.tsx +52 -3
  321. package/src/components/entity-combobox.tsx +25 -7
  322. package/src/components/error-boundary.tsx +640 -0
  323. package/src/components/field-sync.tsx +48 -40
  324. package/src/components/field.tsx +56 -5
  325. package/src/components/floating-panel.tsx +20 -7
  326. package/src/components/form-actions.tsx +191 -0
  327. package/src/components/full-bleed-dialog.tsx +4 -0
  328. package/src/components/hover-menu.tsx +26 -17
  329. package/src/components/image-grid.tsx +181 -0
  330. package/src/components/lightbox.tsx +374 -0
  331. package/src/components/line-items.tsx +385 -0
  332. package/src/components/list.tsx +82 -19
  333. package/src/components/loading-state.tsx +47 -0
  334. package/src/components/menu-item.tsx +7 -2
  335. package/src/components/mini-calendar.tsx +72 -3
  336. package/src/components/modal.tsx +65 -3
  337. package/src/components/month-picker.tsx +181 -26
  338. package/src/components/multi-entity-combobox.tsx +27 -7
  339. package/src/components/nav-pills.tsx +8 -1
  340. package/src/components/number-field.tsx +15 -10
  341. package/src/components/number-input.tsx +17 -1
  342. package/src/components/numpad-sheet.tsx +6 -1
  343. package/src/components/page-header.tsx +42 -5
  344. package/src/components/passkeys-setting.tsx +331 -0
  345. package/src/components/picker-sheet.tsx +10 -1
  346. package/src/components/pie-chart-labels.ts +32 -0
  347. package/src/components/pie-chart.tsx +479 -0
  348. package/src/components/popover.tsx +3 -0
  349. package/src/components/progress-bar.tsx +129 -19
  350. package/src/components/qr-code.tsx +83 -0
  351. package/src/components/search-field.tsx +20 -4
  352. package/src/components/signed-amount.tsx +295 -0
  353. package/src/components/skeleton.tsx +21 -3
  354. package/src/components/stat-tile.tsx +11 -3
  355. package/src/components/swipeable-row.tsx +7 -7
  356. package/src/components/table.tsx +102 -13
  357. package/src/components/text-link.tsx +356 -0
  358. package/src/components/text.tsx +46 -2
  359. package/src/components/toggle-group.tsx +93 -20
  360. package/src/components/toggle-legend.tsx +108 -2
  361. package/src/components/tooltip.tsx +79 -8
  362. package/src/components/ui.tsx +707 -49
  363. package/src/components/use-table-state.ts +265 -33
  364. package/src/data-table.ts +10 -0
  365. package/src/feedback/feedback-attachment.tsx +10 -7
  366. package/src/feedback/feedback-dialog.tsx +49 -7
  367. package/src/feedback/feedback-inbox.tsx +7 -0
  368. package/src/feedback/feedback-thread.tsx +510 -0
  369. package/src/feedback.ts +1 -0
  370. package/src/hooks/use-authed-src.ts +161 -0
  371. package/src/hooks/use-hotkey.ts +172 -0
  372. package/src/hooks/use-search-param-state.ts +187 -0
  373. package/src/i18n/defaults.ts +30 -0
  374. package/src/i18n/kit-labels.tsx +118 -8
  375. package/src/i18n/locales/de.ts +161 -0
  376. package/src/i18n/locales/es.ts +158 -0
  377. package/src/i18n/locales/fr.ts +161 -0
  378. package/src/i18n/locales/hu.ts +159 -0
  379. package/src/i18n/locales/it.ts +159 -0
  380. package/src/i18n/locales/zh.ts +158 -0
  381. package/src/index.ts +59 -0
  382. package/src/lib/format.ts +348 -0
  383. package/src/lib/qr-encode.ts +420 -0
  384. package/src/rhf/fields.tsx +1135 -0
  385. package/src/rhf/line-items.tsx +130 -0
  386. package/src/rhf.ts +6 -0
  387. package/src/search/command-palette.tsx +29 -19
  388. package/src/shell/app-shell.tsx +153 -3
  389. package/src/shell/auth-layout.tsx +190 -0
  390. package/src/shell/top-bar-brand.tsx +76 -0
  391. package/src/shell/topbar-action-menu.tsx +32 -4
  392. package/src/shell.ts +2 -0
  393. package/src/tour/tour.tsx +16 -5
  394. package/src/wizard/use-wizard.ts +8 -4
  395. package/src/wizard/wizard-context.tsx +41 -1
  396. package/src/wizard/wizard-step.tsx +75 -6
  397. package/tokens.css +12 -0
package/src/index.ts CHANGED
@@ -7,6 +7,11 @@
7
7
  export * from "./lib/calc";
8
8
  export { cn } from "./lib/cn";
9
9
  export { logger, setStoreLog } from "./lib/logger";
10
+ // Intl number / money / percent / date / relative-time formatting, and `useKitFormat()`
11
+ // for the same bound to the provider's locale (kastlan's formatters.ts, English "5m ago").
12
+ export * from "./lib/format";
13
+ // localStorage that never throws (Safari private mode, blocked site data).
14
+ export { readStored, writeStored } from "./lib/safe-storage";
10
15
  // Date helpers are also available via the "@eifi1/ui-kit/dates" subpath.
11
16
 
12
17
  // ── hooks ────────────────────────────────────────────────────────────────────
@@ -38,9 +43,19 @@ export type { UseFileDropOptions, UseFileDropReturn, FileDropProps } from "./hoo
38
43
  // kastlan's search boxes each carried a hand-rolled copy, two without the cleanup.
39
44
  export { useDebounce, useDebouncedCallback } from "./hooks/use-debounce";
40
45
  export type { DebouncedCallbackOptions, DebouncedFunction } from "./hooks/use-debounce";
46
+ // One search param as state, the active tab in `?tab=`, and a dialog's open state in the
47
+ // URL (Modal's `urlParam`) — kastlan's and keksdose's use-search-param-state, in the kit.
48
+ export { useSearchParamState, useTabParam, useDialogParam } from "./hooks/use-search-param-state";
49
+ export type { SearchParamStateOptions, DialogParam } from "./hooks/use-search-param-state";
41
50
  // Copy that reports whether it worked — keksdose's copy buttons said "Copied" when it had not.
42
51
  export { useCopyToClipboard, copyToClipboard } from "./hooks/use-copy-to-clipboard";
43
52
  export type { CopyState, UseCopyToClipboardOptions, UseCopyToClipboardReturn } from "./hooks/use-copy-to-clipboard";
53
+ // Keyboard shortcuts ("Mod+K", "Ctrl+Shift+F") that leave typing in fields alone.
54
+ export { useHotkey, parseHotkey, matchesHotkey } from "./hooks/use-hotkey";
55
+ export type { Hotkey, ParsedHotkey, UseHotkeyOptions } from "./hooks/use-hotkey";
56
+ // A protected file as an object URL, fetched with the app's own auth (kastlan's AuthedImage).
57
+ export { useAuthedSrc } from "./hooks/use-authed-src";
58
+ export type { AuthedFetcher, AuthedSrcStatus, UseAuthedSrcOptions, UseAuthedSrcResult } from "./hooks/use-authed-src";
44
59
 
45
60
  // ── theme / palettes ─────────────────────────────────────────────────────────
46
61
  export * from "./theme/chart-palette";
@@ -125,7 +140,15 @@ export * from "./components/chart-zoom";
125
140
  export * from "./components/toggle-legend";
126
141
  export * from "./components/facing-pair";
127
142
  export * from "./components/series-chart-labels";
143
+ export * from "./components/pie-chart";
144
+ export * from "./components/pie-chart-labels";
128
145
  export * from "./components/account-settings";
146
+ // 0.12.0: the fourth account section, and the QR code TwoFactorSetting draws its
147
+ // otpauth URI with (encoder in lib/qr-encode.ts — no dependency, see there).
148
+ export * from "./components/passkeys-setting";
149
+ export * from "./components/qr-code";
150
+ export { encodeQr } from "./lib/qr-encode";
151
+ export type { QrEncodeOptions, QrErrorCorrection, QrMatrix } from "./lib/qr-encode";
129
152
  export * from "./components/alert-banner";
130
153
  export * from "./components/toggle-group";
131
154
  // A wrapping set of links/buttons marked `aria-current` — Tabs-like looks, not tabs.
@@ -147,11 +170,23 @@ export * from "./components/tree-view";
147
170
  export * from "./components/chart";
148
171
  // 0.8.0 layout and feedback primitives the apps hand-rolled or took from shadcn/Radix.
149
172
  export * from "./components/description-list";
173
+ // The Save / Cancel row forms and dialogs end in, and the editable line-item repeater
174
+ // (journal lines, lease components, invoice positions). 0.12.0.
175
+ export * from "./components/form-actions";
176
+ export * from "./components/line-items";
150
177
  export * from "./components/progress-bar";
151
178
  // Named: skeleton.tsx also holds SKELETON_CLASS, the look StatTile shares — internal.
152
179
  export { Skeleton } from "./components/skeleton";
153
180
  export type { SkeletonProps, SkeletonShape } from "./components/skeleton";
181
+ // 0.12.0: a centred spinner with words, signed figures and deltas, a render-error fallback.
182
+ export * from "./components/loading-state";
183
+ export * from "./components/signed-amount";
184
+ export * from "./components/error-boundary";
154
185
  export * from "./components/copy-button";
186
+ export { AuthedImage, DEFAULT_AUTHED_IMAGE_LABELS } from "./components/authed-image";
187
+ export type { AuthedImageProps, AuthedImageLabels } from "./components/authed-image";
188
+ export * from "./components/image-grid";
189
+ export * from "./components/lightbox";
155
190
  export * from "./components/button-group";
156
191
  export * from "./components/table";
157
192
  export * from "./components/separator";
@@ -165,6 +200,16 @@ export * from "./components/text";
165
200
  export * from "./components/status-dot";
166
201
  export * from "./components/page-header";
167
202
  export * from "./components/breadcrumbs";
203
+ // The one inline link (kastlan's EntityLink / CellLink / legal and source links), through
204
+ // `<UiKitProvider linkComponent>`. Named: the file also holds the kit's internal link picker.
205
+ export { TextLink } from "./components/text-link";
206
+ export type {
207
+ TextLinkProps,
208
+ TextLinkRenderProps,
209
+ TextLinkTone,
210
+ TextLinkUnderline,
211
+ TextLinkCurrent,
212
+ } from "./components/text-link";
168
213
  // The toast layer over sonner (an OPTIONAL peer, loaded lazily): `toast` mirrors sonner's
169
214
  // API so apps migrate by swapping the import; `<Toaster>` carries the placement, theme,
170
215
  // tones and z-index both apps had wired by hand.
@@ -202,7 +247,18 @@ export type {
202
247
  MobileSwipeActions,
203
248
  DataTableDensity,
204
249
  DataTableChrome,
250
+ DataTableRowAction,
205
251
  } from "./components/data-table";
252
+ // 0.12.0: the yes/no cell, and a server table's state held by its owner, URL-synced.
253
+ export * from "./components/data-table-cells";
254
+ export { useTableUrlState, readTableUrlState } from "./components/use-table-state";
255
+ export type {
256
+ TableUrlState,
257
+ TableUrlSync,
258
+ TableUrlStateProps,
259
+ UseTableUrlStateOptions,
260
+ UseTableUrlStateReturn,
261
+ } from "./components/use-table-state";
206
262
 
207
263
  // ── shell (composable app chrome) ────────────────────────────────────────────
208
264
  export * from "./shell/topbar-controls";
@@ -211,6 +267,8 @@ export * from "./shell/app-shell";
211
267
  export * from "./shell/option-switcher-menu";
212
268
  export * from "./shell/role-switcher";
213
269
  export * from "./shell/topbar-action-menu";
270
+ export * from "./shell/top-bar-brand";
271
+ export * from "./shell/auth-layout";
214
272
 
215
273
  // ── feedback ─────────────────────────────────────────────────────────────────
216
274
  // The form somebody files a report with, and the parts an inbox is built from:
@@ -222,6 +280,7 @@ export * from "./shell/topbar-action-menu";
222
280
  export * from "./feedback/feedback-attachment";
223
281
  export * from "./feedback/feedback-dialog";
224
282
  export * from "./feedback/feedback-inbox";
283
+ export * from "./feedback/feedback-thread";
225
284
 
226
285
  // ── multi-step wizard ────────────────────────────────────────────────────────
227
286
  // The engine (step machine, validators, collected data), the chrome around it
@@ -0,0 +1,348 @@
1
+ import { useMemo } from "react";
2
+ import { useKitLocale } from "../i18n/kit-labels";
3
+ import { parseIsoDate } from "./dates";
4
+
5
+ /**
6
+ * Number, money, percentage, date and relative-time formatting — `Intl`, in one place,
7
+ * for the figures an app prints OUTSIDE a kit component.
8
+ *
9
+ * Each app grew its own: kastlan's `utils/formatters.ts` (a Swiss-pinned
10
+ * `formatNumber`/`formatCurrency`, a date-fns `formatDate`, and a `formatRelativeTime`
11
+ * whose "5m ago" / "3h ago" / "2d ago" stayed English in every language), its tenancy
12
+ * `format.ts` (`formatChf`, a `formatPct` gluing a "+" onto `toFixed(2)`), keksdose's
13
+ * own set. Every one of them is `Intl` with a locale bolted on, so the only thing
14
+ * worth sharing is the locale — and the kit already has one, the provider's.
15
+ *
16
+ * The plain functions take a `locale` (none: the runtime default, as every `Intl` API
17
+ * reads `undefined`). Inside a component, {@link useKitFormat} hands back the same
18
+ * functions bound to the `<UiKitProvider locale>`, so a page formats exactly like the
19
+ * kit components on it.
20
+ *
21
+ * A missing value — `null`, `undefined`, `NaN`, an unparseable date — formats as
22
+ * `empty` (default "—", the dash `StatTile` shows for no value), never as "NaN" or
23
+ * "Invalid Date".
24
+ */
25
+
26
+ /** What every formatter here prints for a missing value, unless told otherwise. */
27
+ export const EMPTY_FORMATTED_VALUE = "—";
28
+
29
+ /** Fraction digits: a number pins both the minimum and the maximum (`2` → "3.10");
30
+ * an object sets them apart (a meter reading, 1–3). */
31
+ export type FormatDigits = number | { min?: number; max?: number };
32
+
33
+ interface BaseOptions {
34
+ /** BCP 47 tag. Left out: the runtime default (the hook: the provider's). */
35
+ locale?: string;
36
+ /** Printed for a missing value. Default {@link EMPTY_FORMATTED_VALUE}. */
37
+ empty?: string;
38
+ }
39
+
40
+ /** The options every number formatter shares (money and percent carry their own unit). */
41
+ export interface FormatNumberBaseOptions extends BaseOptions {
42
+ digits?: FormatDigits;
43
+ /** Compact notation: 12 400 → "12K". */
44
+ compact?: boolean;
45
+ /** `"exceptZero"` for a change ("+3", "−3", "0"). Default: the minus only. */
46
+ signDisplay?: Intl.NumberFormatOptions["signDisplay"];
47
+ /** Any further `Intl.NumberFormat` options, merged over the ones above. */
48
+ options?: Intl.NumberFormatOptions;
49
+ }
50
+
51
+ /** The options {@link formatNumber} takes: the shared ones plus a free-text `unit`. */
52
+ export interface FormatNumberOptions extends FormatNumberBaseOptions {
53
+ /**
54
+ * A unit printed after the figure, joined by a narrow no-break space (U+202F):
55
+ * "3.400 N", "12 kg", "4,5 m²". lenkbank (P8) prints forces, masses and lengths in
56
+ * every table and summary, and each call site glued `${n} N` together with a plain
57
+ * space — which wraps the "N" onto the next line in a narrow column and reads loose
58
+ * beside the digit grouping. The narrow no-break space is the SI brochure's and the
59
+ * Swiss typesetting rule's separator between value and unit, and it never breaks.
60
+ *
61
+ * Free text, not `Intl`'s `style: "unit"`: that one only knows the ECMA-402
62
+ * sanctioned units (no "N", no "kN", no "m³/h"), so an app would still be gluing.
63
+ * A missing value prints `empty` ALONE — "— N" says there is a newton-shaped
64
+ * nothing, which is noise in a column that already heads its unit.
65
+ */
66
+ unit?: string;
67
+ }
68
+
69
+ /** Narrow no-break space (U+202F): between a figure and its {@link FormatNumberOptions.unit}. */
70
+ const UNIT_SEPARATOR = "\u202F";
71
+
72
+ export interface FormatMoneyOptions extends FormatNumberBaseOptions {
73
+ /** `"symbol"` (default), `"narrowSymbol"`, `"code"` ("CHF 12.00") or `"name"`. */
74
+ currencyDisplay?: Intl.NumberFormatOptions["currencyDisplay"];
75
+ }
76
+
77
+ export interface FormatPercentOptions extends FormatNumberBaseOptions {
78
+ /**
79
+ * `true` (default): `value` is a RATIO, as `Intl` and `StatTile`'s percent delta read
80
+ * it — `0.12` is 12 %. `false`: it is already a percentage — `12` is 12 %
81
+ * (kastlan's rent `change_pct`).
82
+ */
83
+ ratio?: boolean;
84
+ }
85
+
86
+ // One formatter per option set: a table formats the same way thousands of times, and
87
+ // constructing an Intl formatter is the expensive part.
88
+ const numberFormatters = new Map<string, Intl.NumberFormat>();
89
+ function numberFormatter(locale: string | undefined, options: Intl.NumberFormatOptions): Intl.NumberFormat {
90
+ const key = `${locale ?? ""}|${JSON.stringify(options)}`;
91
+ let f = numberFormatters.get(key);
92
+ if (!f) {
93
+ f = new Intl.NumberFormat(locale, options);
94
+ numberFormatters.set(key, f);
95
+ }
96
+ return f;
97
+ }
98
+
99
+ function digitOptions(digits: FormatDigits | undefined): Intl.NumberFormatOptions {
100
+ if (digits === undefined) return {};
101
+ if (typeof digits === "number") return { minimumFractionDigits: digits, maximumFractionDigits: digits };
102
+ return {
103
+ ...(digits.min !== undefined ? { minimumFractionDigits: digits.min } : null),
104
+ ...(digits.max !== undefined ? { maximumFractionDigits: digits.max } : null),
105
+ };
106
+ }
107
+
108
+ function numberOptions(o: FormatNumberBaseOptions): Intl.NumberFormatOptions {
109
+ return {
110
+ ...digitOptions(o.digits),
111
+ ...(o.compact ? { notation: "compact" } : null),
112
+ ...(o.signDisplay ? { signDisplay: o.signDisplay } : null),
113
+ ...o.options,
114
+ };
115
+ }
116
+
117
+ function isMissing(value: number | null | undefined): value is null | undefined {
118
+ return value === null || value === undefined || Number.isNaN(value);
119
+ }
120
+
121
+ /** A number in `locale`: "1,234.5", "1.234,5", "1’234.5" — with `unit`, "3.400 N". */
122
+ export function formatNumber(value: number | null | undefined, options: FormatNumberOptions = {}): string {
123
+ if (isMissing(value)) return options.empty ?? EMPTY_FORMATTED_VALUE;
124
+ const figure = numberFormatter(options.locale, numberOptions(options)).format(value);
125
+ return options.unit ? `${figure}${UNIT_SEPARATOR}${options.unit}` : figure;
126
+ }
127
+
128
+ /** An amount of `currency` (ISO 4217) in `locale`: "CHF 1’234.50", "1.234,50 €". */
129
+ export function formatMoney(
130
+ value: number | null | undefined,
131
+ currency: string,
132
+ options: FormatMoneyOptions = {},
133
+ ): string {
134
+ if (isMissing(value)) return options.empty ?? EMPTY_FORMATTED_VALUE;
135
+ return numberFormatter(options.locale, {
136
+ style: "currency",
137
+ currency,
138
+ ...(options.currencyDisplay ? { currencyDisplay: options.currencyDisplay } : null),
139
+ ...numberOptions(options),
140
+ }).format(value);
141
+ }
142
+
143
+ /** A percentage in `locale` ("12%", "12 %", "+1.25%"). See {@link FormatPercentOptions.ratio}
144
+ * for what `value` means. Default digits: at most one. */
145
+ export function formatPercent(value: number | null | undefined, options: FormatPercentOptions = {}): string {
146
+ if (isMissing(value)) return options.empty ?? EMPTY_FORMATTED_VALUE;
147
+ const ratio = options.ratio === false ? value / 100 : value;
148
+ return numberFormatter(options.locale, {
149
+ style: "percent",
150
+ ...(options.digits === undefined ? { maximumFractionDigits: 1 } : null),
151
+ ...numberOptions(options),
152
+ }).format(ratio);
153
+ }
154
+
155
+ /* ── Dates ─────────────────────────────────────────────────────────────────── */
156
+
157
+ /**
158
+ * The shapes a date is printed in, each a set of `Intl.DateTimeFormat` options:
159
+ * - `short` / `medium` / `long` / `full` — the date alone, at that `dateStyle`
160
+ * ("08/07/2026", "8 Jul 2026", "8 July 2026", "Wednesday, 8 July 2026" in en-GB).
161
+ * - `dateTime` — medium date and short time: a timestamp ("8 Jul 2026, 14:05").
162
+ * - `time` — the short time alone ("14:05").
163
+ * - `monthYear` — "July 2026".
164
+ *
165
+ * Or pass `Intl.DateTimeFormatOptions` for anything else. A date-fns PATTERN
166
+ * ("dd.MM.yyyy") is deliberately not accepted: a pattern is one locale's order baked
167
+ * into a string, which is how an English page ends up with German dates.
168
+ */
169
+ export type FormatDateStyle = "short" | "medium" | "long" | "full" | "dateTime" | "time" | "monthYear";
170
+
171
+ const DATE_STYLES: Record<FormatDateStyle, Intl.DateTimeFormatOptions> = {
172
+ short: { dateStyle: "short" },
173
+ medium: { dateStyle: "medium" },
174
+ long: { dateStyle: "long" },
175
+ full: { dateStyle: "full" },
176
+ dateTime: { dateStyle: "medium", timeStyle: "short" },
177
+ time: { timeStyle: "short" },
178
+ monthYear: { month: "long", year: "numeric" },
179
+ };
180
+
181
+ export interface FormatDateOptions extends BaseOptions {
182
+ /** An IANA zone ("Europe/Zurich") for a timestamp. Left out: the device's. */
183
+ timeZone?: string;
184
+ }
185
+
186
+ /** What {@link formatDate} and {@link formatRelativeTime} accept. */
187
+ export type DateInput = string | number | Date | null | undefined;
188
+
189
+ /**
190
+ * `value` as a Date, or null. A bare "YYYY-MM-DD" is a CALENDAR day and is read on the
191
+ * local calendar (`parseIsoDate`) — `new Date("2026-07-08")` is UTC midnight, which is
192
+ * the 7th anywhere west of Greenwich. Anything else (a full ISO timestamp, epoch
193
+ * milliseconds, a Date) is an instant and goes through `Date` as is.
194
+ */
195
+ export function toDate(value: DateInput): Date | null {
196
+ if (value === null || value === undefined || value === "") return null;
197
+ if (typeof value === "string" && /^\d{4}-\d{2}-\d{2}$/.test(value)) return parseIsoDate(value);
198
+ const d = value instanceof Date ? value : new Date(value);
199
+ return Number.isNaN(d.getTime()) ? null : d;
200
+ }
201
+
202
+ const dateFormatters = new Map<string, Intl.DateTimeFormat>();
203
+ function dateFormatter(locale: string | undefined, options: Intl.DateTimeFormatOptions): Intl.DateTimeFormat {
204
+ const key = `${locale ?? ""}|${JSON.stringify(options)}`;
205
+ let f = dateFormatters.get(key);
206
+ if (!f) {
207
+ f = new Intl.DateTimeFormat(locale, options);
208
+ dateFormatters.set(key, f);
209
+ }
210
+ return f;
211
+ }
212
+
213
+ /**
214
+ * A date or timestamp in `locale`. Takes an ISO date ("2026-07-08", read on the local
215
+ * calendar), an ISO timestamp ("2026-07-08T14:05:00Z"), epoch milliseconds or a Date.
216
+ * `style` defaults to `medium`; see {@link FormatDateStyle}.
217
+ *
218
+ * `formatIsoDate` (`@eifi1/ui-kit/dates`) remains for a plain "YYYY-MM-DD" with an
219
+ * explicit locale; this is its superset for timestamps and the named styles.
220
+ */
221
+ export function formatDate(
222
+ value: DateInput,
223
+ style: FormatDateStyle | Intl.DateTimeFormatOptions = "medium",
224
+ options: FormatDateOptions = {},
225
+ ): string {
226
+ const d = toDate(value);
227
+ if (!d) return options.empty ?? EMPTY_FORMATTED_VALUE;
228
+ const base = typeof style === "string" ? DATE_STYLES[style] : style;
229
+ return dateFormatter(options.locale, options.timeZone ? { ...base, timeZone: options.timeZone } : base).format(d);
230
+ }
231
+
232
+ export interface FormatRelativeTimeOptions extends BaseOptions {
233
+ /** The moment it is measured from. Default: now. */
234
+ now?: DateInput;
235
+ /** `"auto"` (default): "yesterday", "now", "next week". `"always"`: "1 day ago". */
236
+ numeric?: Intl.RelativeTimeFormatNumeric;
237
+ /** `"long"` (default) "3 hours ago", `"short"` "3 hr. ago", `"narrow"` "3h ago". */
238
+ style?: Intl.RelativeTimeFormatStyle;
239
+ /**
240
+ * Past this many days either way, print the date instead ({@link formatDate} in
241
+ * `absoluteStyle`) — kastlan's feed switches to the date after a week, because "5
242
+ * weeks ago" makes a reader do sums. Default: never.
243
+ */
244
+ absoluteAfterDays?: number;
245
+ /** The style of that date. Default `medium`. */
246
+ absoluteStyle?: FormatDateStyle | Intl.DateTimeFormatOptions;
247
+ }
248
+
249
+ const SECOND = 1000;
250
+ const MINUTE = 60 * SECOND;
251
+ const HOUR = 60 * MINUTE;
252
+ const DAY = 24 * HOUR;
253
+
254
+ /** [the largest |difference| a unit is used for, the unit, its length]. */
255
+ const RELATIVE_STEPS: Array<[number, Intl.RelativeTimeFormatUnit, number]> = [
256
+ [45 * SECOND, "second", SECOND],
257
+ [45 * MINUTE, "minute", MINUTE],
258
+ [22 * HOUR, "hour", HOUR],
259
+ [6.5 * DAY, "day", DAY],
260
+ [26 * DAY, "week", 7 * DAY],
261
+ [320 * DAY, "month", 30.44 * DAY],
262
+ [Infinity, "year", 365.25 * DAY],
263
+ ];
264
+
265
+ const relativeFormatters = new Map<string, Intl.RelativeTimeFormat>();
266
+ function relativeFormatter(
267
+ locale: string | undefined,
268
+ numeric: Intl.RelativeTimeFormatNumeric,
269
+ style: Intl.RelativeTimeFormatStyle,
270
+ ): Intl.RelativeTimeFormat {
271
+ const key = `${locale ?? ""}|${numeric}|${style}`;
272
+ let f = relativeFormatters.get(key);
273
+ if (!f) {
274
+ f = new Intl.RelativeTimeFormat(locale, { numeric, style });
275
+ relativeFormatters.set(key, f);
276
+ }
277
+ return f;
278
+ }
279
+
280
+ /**
281
+ * How long ago (or until) `value` is, in `locale`'s words, through
282
+ * `Intl.RelativeTimeFormat`: "3 hours ago", "vor 3 Stunden", "il y a 3 heures",
283
+ * "in 2 days", and under 45 seconds "now". The unit is picked by size — seconds,
284
+ * minutes, hours, days, weeks, months, years — and the count is rounded.
285
+ */
286
+ export function formatRelativeTime(value: DateInput, options: FormatRelativeTimeOptions = {}): string {
287
+ const d = toDate(value);
288
+ if (!d) return options.empty ?? EMPTY_FORMATTED_VALUE;
289
+ const now = toDate(options.now ?? new Date()) ?? new Date();
290
+ const diff = d.getTime() - now.getTime();
291
+ const abs = Math.abs(diff);
292
+ if (options.absoluteAfterDays !== undefined && abs > options.absoluteAfterDays * DAY) {
293
+ return formatDate(d, options.absoluteStyle ?? "medium", options);
294
+ }
295
+ const rtf = relativeFormatter(options.locale, options.numeric ?? "auto", options.style ?? "long");
296
+ const [, unit, size] = RELATIVE_STEPS.find(([limit]) => abs < limit)!;
297
+ // Under 45 seconds is "now" rather than "12 seconds ago": a feed that ticks through
298
+ // the seconds is noise, and nothing reads a timestamp to the second.
299
+ const count = unit === "second" ? 0 : Math.round(diff / size);
300
+ // `+ 0` folds -0 into 0: `format(-0, …)` says "0 seconds ago" instead of "now".
301
+ return rtf.format(count + 0, unit);
302
+ }
303
+
304
+ /* ── Bound to the provider ─────────────────────────────────────────────────── */
305
+
306
+ type WithoutLocale<T> = Omit<T, "locale"> & { locale?: string };
307
+
308
+ /** What {@link useKitFormat} returns: the formatters above with the provider's locale
309
+ * as their default (an explicit `locale` in the options still wins). */
310
+ export interface KitFormat {
311
+ /** The locale they format in — the provider's, or `undefined` (runtime default). */
312
+ locale: string | undefined;
313
+ formatNumber: (value: number | null | undefined, options?: WithoutLocale<FormatNumberOptions>) => string;
314
+ formatMoney: (
315
+ value: number | null | undefined,
316
+ currency: string,
317
+ options?: WithoutLocale<FormatMoneyOptions>,
318
+ ) => string;
319
+ formatPercent: (value: number | null | undefined, options?: WithoutLocale<FormatPercentOptions>) => string;
320
+ formatDate: (
321
+ value: DateInput,
322
+ style?: FormatDateStyle | Intl.DateTimeFormatOptions,
323
+ options?: WithoutLocale<FormatDateOptions>,
324
+ ) => string;
325
+ formatRelativeTime: (value: DateInput, options?: WithoutLocale<FormatRelativeTimeOptions>) => string;
326
+ }
327
+
328
+ /**
329
+ * The formatters, bound to the `<UiKitProvider locale>` (or to `locale`, which wins
330
+ * over it — the same precedence as a component's `locale` prop):
331
+ *
332
+ * const { formatMoney, formatRelativeTime } = useKitFormat();
333
+ * formatMoney(row.balance, "CHF");
334
+ */
335
+ export function useKitFormat(locale?: string): KitFormat {
336
+ const kitLocale = useKitLocale(locale);
337
+ return useMemo<KitFormat>(
338
+ () => ({
339
+ locale: kitLocale,
340
+ formatNumber: (value, options) => formatNumber(value, { locale: kitLocale, ...options }),
341
+ formatMoney: (value, currency, options) => formatMoney(value, currency, { locale: kitLocale, ...options }),
342
+ formatPercent: (value, options) => formatPercent(value, { locale: kitLocale, ...options }),
343
+ formatDate: (value, style, options) => formatDate(value, style, { locale: kitLocale, ...options }),
344
+ formatRelativeTime: (value, options) => formatRelativeTime(value, { locale: kitLocale, ...options }),
345
+ }),
346
+ [kitLocale],
347
+ );
348
+ }