@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
@@ -0,0 +1,1744 @@
1
+ import * as react from 'react';
2
+ import { AnchorHTMLAttributes, Ref, ReactElement, ButtonHTMLAttributes, ReactNode, SelectHTMLAttributes, InputHTMLAttributes, ComponentPropsWithoutRef, TextareaHTMLAttributes } from 'react';
3
+ import { DataTableLabels } from './components/data-table-labels.js';
4
+ import { MiniCalendarLabels, WeekDay } from './components/mini-calendar.js';
5
+ import { CalendarHeatmapLabels } from './components/calendar-heatmap.js';
6
+ import { PopoverLabels } from './components/popover.js';
7
+ import { ChipInputLabels } from './components/chip.js';
8
+ import { FieldSyncLabels } from './components/field-sync.js';
9
+ import { TooltipSide } from './components/tooltip.js';
10
+ import { WizardLabels } from './wizard/types.js';
11
+ import { TourLabels } from './tour/tour.js';
12
+ import { CommandPaletteLabels } from './search/command-palette.js';
13
+ import { GlobalSearchLabels } from './search/global-search.js';
14
+ import { MonthPickerLabels } from './components/month-picker.js';
15
+ import { PageContentsLabels } from './components/page-contents.js';
16
+ import { SeriesChartLabels } from './components/series-chart-labels.js';
17
+ import { PieChartLabels } from './components/pie-chart-labels.js';
18
+ import { SparklineLabels } from './components/sparkline.js';
19
+ import { StatTileLabels } from './components/stat-tile.js';
20
+ import { SignaturePadLabels } from './components/signature-pad.js';
21
+ import { PasswordStrengthLabels } from './components/password-strength.js';
22
+ import { DangerConfirmLabels } from './components/danger-confirm.js';
23
+ import { SwatchPickerLabels } from './components/swatch-picker.js';
24
+ import { IconPickerLabels } from './components/icon-picker.js';
25
+ import { DialogFrameLabels } from './components/dialog-frame.js';
26
+ import { MeasuredGridLabels } from './components/measured-grid.js';
27
+ import { FeedbackAttachmentFieldLabels } from './feedback/feedback-attachment.js';
28
+ import { FeedbackDialogTextLabels } from './feedback/feedback-dialog.js';
29
+ import { FeedbackThreadLabels, FeedbackComposerLabels } from './feedback/feedback-thread.js';
30
+ import { AccountSettingsLabels } from './components/account-settings-labels.js';
31
+ import { ConfirmDialogLabels } from './components/confirm-dialog.js';
32
+ import { FloatingPanelLabels } from './components/floating-panel.js';
33
+ import { BulkActionBarLabels } from './components/bulk-action-bar.js';
34
+ import { ListLabels } from './components/list.js';
35
+ import { BreadcrumbsLabels } from './components/breadcrumbs.js';
36
+ import { ToastLabels } from './components/toast.js';
37
+ import { DescriptionListLabels } from './components/description-list.js';
38
+ import { LineItemsLabels } from './components/line-items.js';
39
+ import { ProgressBarLabels } from './components/progress-bar.js';
40
+ import { SignedAmountLabels } from './components/signed-amount.js';
41
+ import { ErrorBoundaryLabels } from './components/error-boundary.js';
42
+ import { AuthedImageLabels } from './components/authed-image.js';
43
+ import { ImageGridLabels } from './components/image-grid.js';
44
+ import { LightboxLabels } from './components/lightbox.js';
45
+
46
+ type ButtonVariant = "primary" | "secondary" | "ghost" | "danger" | "brand" | "link";
47
+ type ButtonSize = "sm" | "md";
48
+ /**
49
+ * The text colour of a `link` or `ghost` button, over the variant's own.
50
+ *
51
+ * `muted` is the QUIET link keksdose hand-rolls in six places (tours-page:321's "Mark
52
+ * undone", transaction-fields:176/215's "Add line" / "Fill total",
53
+ * invoice-lines-table:376, invoice-review:464): secondary text that darkens to the
54
+ * body colour under the pointer, for an action that must be findable but not compete
55
+ * with the brand-coloured one beside it. `danger` is the same quiet look turning
56
+ * `--danger` on hover — transaction-editor:401's "Remove split", which is a text
57
+ * action that destroys something.
58
+ *
59
+ * A `tone` rather than a `link-muted` variant, because it is the axis IconButton
60
+ * already has (`tone="muted"`, `tone="danger"`, same quiet-until-hover meaning), and
61
+ * because the same two looks are wanted on `ghost`.
62
+ *
63
+ * `danger` also reaches the two NEUTRAL boxed variants, where the box carries it:
64
+ * `secondary` becomes an outlined destructive button (`--danger` text and a
65
+ * `--danger-border` outline, the quiet danger fill on hover) and `primary` a soft one
66
+ * (the quiet fill at rest, solid `--danger` on hover). kastlan's meeting-invitations-
67
+ * tab.tsx "Remove all" is `variant="secondary" className="text-destructive"` — a
68
+ * destructive action that must sit in a row of secondary buttons without shouting
69
+ * like the solid `variant="danger"` would, and whose red text alone left the border
70
+ * and hover saying "neutral". `muted` stays a text look and does nothing on a box, and
71
+ * `danger`, `brand` and `link`'s own colours are left alone: a tone that recoloured a
72
+ * solid danger or brand button would be a second variant under another name.
73
+ */
74
+ type ButtonTone = "default" | "muted" | "danger";
75
+ /** The second argument of {@link buttonClasses} in its options form. */
76
+ interface ButtonClassesOptions {
77
+ /** See {@link ButtonProps.size}. */
78
+ size?: ButtonSize;
79
+ className?: string;
80
+ }
81
+ /**
82
+ * Button classes for the rare case where the styling must land on a non-`<button>`
83
+ * element that {@link Button} can't render — e.g. a router `<Link>` or a Radix
84
+ * AlertDialog Action/Cancel (which must stay the Radix element). Everywhere a real
85
+ * button works, prefer `<Button>`. Draws from the same base, size and variant maps as
86
+ * `<Button>`, so the two stay in lockstep.
87
+ *
88
+ * The second argument is either the extra classes (the pre-0.8.0 form) or
89
+ * `{ size, className }` — `buttonClasses("secondary", { size: "sm" })` for keksdose's
90
+ * compact toolbar links.
91
+ */
92
+ declare function buttonClasses(variant?: ButtonVariant, classNameOrOptions?: string | ButtonClassesOptions): string;
93
+ /**
94
+ * ⚠️ No default `type`: like a native `<button>`, a `Button` inside a `<form>` SUBMITS
95
+ * it unless you pass `type="button"`. Kept native on purpose — a form's own submit
96
+ * button relies on it, and changing the default would silently stop those forms from
97
+ * submitting. Pass `type="button"` for every other action in a form (keksdose had six
98
+ * link buttons submitting their form).
99
+ */
100
+ interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
101
+ variant?: ButtonVariant;
102
+ /** `md` (default) is the page's action button. `sm` is the compact one — 12px text
103
+ * and `px-2 py-1` — for the secondary actions in a toolbar, a card header or a
104
+ * table's header row (keksdose writes `px-2 py-1 text-xs` over `secondary` by hand
105
+ * there). Every variant takes either size. */
106
+ size?: ButtonSize;
107
+ /** In a flex row next to a taller labelled field, fill the field's height so the
108
+ * two line up. No effect outside a flex row. */
109
+ stretch?: boolean;
110
+ /** The `<button>` element. React 19 passes `ref` to a function component as an
111
+ * ordinary prop, so it rides `...rest` onto the element with no `forwardRef` —
112
+ * declared here only because `ButtonHTMLAttributes` does not carry it. */
113
+ ref?: Ref<HTMLButtonElement>;
114
+ /** Text colour for `link` and `ghost`: `muted` (quiet, body colour on hover) or
115
+ * `danger` (quiet, `--danger` on hover). `danger` also makes `secondary` an
116
+ * outlined destructive button and `primary` a soft one; ignored by the other
117
+ * variants. See {@link ButtonTone}. */
118
+ tone?: ButtonTone;
119
+ /**
120
+ * Make it a toggle button, as {@link IconButton}'s `pressed` does. `true` sets
121
+ * `aria-pressed="true"` and draws the "on" look — on a `link`, the brand colour and a
122
+ * medium weight; on the boxed variants, the quiet brand fill IconButton uses — and
123
+ * `false` sets `aria-pressed="false"` with the ordinary look. Left out, no
124
+ * `aria-pressed` (or the caller's own). For lenkbank's "All speeds" text toggle
125
+ * (gear/hysteresis-charts.tsx:482), which is a hand-rolled `<button aria-pressed>`
126
+ * swapping two class strings; with `variant="link" tone="muted"` it is this.
127
+ */
128
+ pressed?: boolean;
129
+ /**
130
+ * Why the action is not available — the button's half of DangerConfirm's
131
+ * `lockedReason` (kastlan handover-detail-page.tsx:197 locks a signed handover).
132
+ *
133
+ * A disabled button that cannot say why is a dead end: the native `disabled` takes it
134
+ * out of the tab order, so a keyboard user never lands on it, and a pointer gets a
135
+ * `not-allowed` cursor and nothing else. With a reason the button is `aria-disabled`
136
+ * instead — still focusable, still hoverable — clicks (and the Enter/Space and form
137
+ * submission they stand for) do nothing, and the reason is shown in the kit
138
+ * {@link Tooltip} and attached with `aria-describedby`, so a screen reader hears it
139
+ * on focus. It wins over `disabled`: passing both keeps the button reachable, which is
140
+ * the point of giving a reason.
141
+ *
142
+ * The description is a `hidden` copy of the reason rather than the bubble itself: the
143
+ * bubble exists only while hovered or focused once it is portalled, and a description
144
+ * that comes and goes is read inconsistently. The bubble is kept visual, the way
145
+ * DangerConfirm keeps its own.
146
+ */
147
+ disabledReason?: ReactNode;
148
+ /**
149
+ * Busy — the save is in flight: a spinner, `aria-busy`, and no second submit.
150
+ *
151
+ * kastlan's FormActions (form-actions.tsx:50) disables its submit and swaps in a
152
+ * `submittingLabel`, so the button jumps width mid-click and a screen reader hears
153
+ * the name change instead of "busy". Here the label STAYS — it is still the
154
+ * accessible name and still holds the button's width — and the spinner is drawn over
155
+ * it in the text colour, so nothing beside the button moves.
156
+ *
157
+ * The same prop {@link FileButton} has (`pending`), with one difference: this one is
158
+ * `aria-disabled`, not `disabled`. The button being pressed is the one that has focus,
159
+ * and a native `disabled` drops that focus to `<body>` the moment the save starts;
160
+ * clicks, and the Enter that submits a form through it, are swallowed instead.
161
+ */
162
+ pending?: boolean;
163
+ /** Only on the link form — see {@link ButtonLinkProps}. */
164
+ href?: never;
165
+ renderLink?: never;
166
+ external?: never;
167
+ replace?: never;
168
+ reloadDocument?: never;
169
+ }
170
+ /**
171
+ * `<Button href>`: a link that looks like a button — for an action that NAVIGATES.
172
+ *
173
+ * kastlan paints `buttonClasses(…)` onto router Links by hand in six places (the
174
+ * invoice / lease / unit preview dialogs' "Open" at invoice-preview-dialog.tsx:36,
175
+ * platform-companies-page.tsx:111, verify-email-page.tsx:86's "Log in",
176
+ * billing-history-page.tsx:85's PDF download), and has 26 `<Button onClick={() =>
177
+ * navigate(…)}>` that are links in all but markup: no middle-click, no "open in new
178
+ * tab", no URL on hover, and a screen reader announces a button that then changes the
179
+ * page. With `href` it is an `<a>`: the provider's router link
180
+ * ({@link UiKitProvider}'s `linkComponent`), or `renderLink` if given, else a plain one.
181
+ *
182
+ * `type` and `form` are typed out: they are a `<button>`'s, and a link never submits.
183
+ */
184
+ interface ButtonLinkProps extends Omit<AnchorHTMLAttributes<HTMLAnchorElement>, "type" | "href"> {
185
+ href: string;
186
+ variant?: ButtonVariant;
187
+ size?: ButtonSize;
188
+ stretch?: boolean;
189
+ tone?: ButtonTone;
190
+ /** The in-app router link, over the provider's `linkComponent` (see
191
+ * {@link KitLinkComponent}). Ignored when `external`. */
192
+ renderLink?: KitLinkComponent;
193
+ /** Leaves the app: a plain `<a target="_blank" rel="noopener noreferrer">`, never the
194
+ * router link, and "(opens in a new tab)" read after the label — billing-history's
195
+ * Stripe PDF. A `target="_blank"` of your own is announced the same way. */
196
+ external?: boolean;
197
+ /**
198
+ * Navigate by replacing the current history entry, so Back does not return here —
199
+ * keksdose F1: the "Continue" out of a one-shot page (an emailed confirmation link, a
200
+ * payment return URL) that must not be landed on again. Handed to the router link as
201
+ * `replace` (see `KitLinkProps.replace` for mapping it); on a plain `<a>` a plain click
202
+ * becomes `location.replace(href)`. Ignored when `external`.
203
+ */
204
+ replace?: boolean;
205
+ /**
206
+ * A plain `<a>` for an in-app `href`, so the browser loads the whole document instead
207
+ * of the router swapping the view — keksdose F1: a route the SPA does not own (a
208
+ * server-rendered export, a logout that must drop every in-memory cache). Wins over
209
+ * `renderLink` and the provider's `linkComponent`.
210
+ */
211
+ reloadDocument?: boolean;
212
+ /** A link cannot be `disabled`, so this renders an `<a>` with NO `href` —
213
+ * `role="link"` and `aria-disabled`, out of the tab order like a disabled button,
214
+ * with the disabled look. The router link is not used: it needs somewhere to go. */
215
+ disabled?: boolean;
216
+ ref?: Ref<HTMLAnchorElement>;
217
+ type?: never;
218
+ form?: never;
219
+ pending?: never;
220
+ pressed?: never;
221
+ disabledReason?: never;
222
+ }
223
+ declare function Button(props: ButtonLinkProps): ReactElement;
224
+ declare function Button(props: ButtonProps): ReactElement;
225
+ type IconButtonSize = "2xl" | "xl" | "lg" | "md" | "sm" | "xs" | "2xs";
226
+ type IconButtonTone = "default" | "muted" | "danger" | "warning" | "info" | "success" | "custom";
227
+ type IconButtonVariant = ButtonVariant | "overlay" | "shutter";
228
+ /**
229
+ * The glyph's size in px, over the one the box size implies — see
230
+ * {@link IconButtonProps.glyphSize}.
231
+ */
232
+ type IconButtonGlyphSize = 12 | 14 | 16 | 20 | 24 | 28;
233
+ interface IconButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
234
+ /** Any {@link ButtonVariant}; `overlay` — a round translucent disc for use over
235
+ * an image (see `ICON_BUTTON_OVERLAY`); or `shutter` — a camera's release, a ringed
236
+ * disc over the viewfinder (see `ICON_BUTTON_SHUTTER`; pair it with `size="2xl"`). */
237
+ variant?: IconButtonVariant;
238
+ /** Box size: 2xl = 64px with a 28px icon (a camera shutter), xl = 48px with a 24px
239
+ * icon, lg = 44px (a phone's touch target), md = 36px (matches the top bar),
240
+ * sm = 32px — all three with a 20px icon;
241
+ * xs = 28px with a 16px icon (an action in a list row), 2xs = 24px with a 14px
242
+ * icon (an action on a chip or a tab). `glyphSize` overrides the icon. */
243
+ size?: IconButtonSize;
244
+ /**
245
+ * The icon's size in px, when the box's own is not the one wanted. keksdose's sync
246
+ * chip (app/sync-status-indicator.tsx:142) is a 24px round chip round a 20px cloud —
247
+ * `size="2xs" glyphSize={20} shape="round"` — where `2xs` alone draws 14px. A number
248
+ * over a new size step because the box and the glyph are two independent choices; a
249
+ * step per combination (`"sm-lg"`) would have to be named for every one.
250
+ */
251
+ glyphSize?: IconButtonGlyphSize;
252
+ /**
253
+ * A small mark on the button's corner — the sync chip's 12px check over its cloud
254
+ * ("all good", feedback #67). Rendered OUTSIDE the `<button>`, in a wrapper beside
255
+ * it, so the box's `[&_svg]:size-*` rule that sizes the glyph does not blow the badge
256
+ * up to the glyph's size; a bare `<Check />` comes out 12px, on a
257
+ * `--bg-surface` disc that keeps it legible over the glyph. Decorative
258
+ * (`aria-hidden`): the name (`label` / `aria-label`) must already carry the state.
259
+ * With a badge, `className` still lands on the button; the wrapper is the flex item.
260
+ */
261
+ badge?: ReactNode;
262
+ /**
263
+ * `keep`: a disabled button keeps its full colour (no 50% drop) and an ordinary
264
+ * cursor. For a STATUS that is also an action — the sync chip shows "in sync" in
265
+ * green and refreshes on click, and is disabled while offline or syncing, when the
266
+ * state it shows matters most; dimmed it read as broken (keksdose wrote
267
+ * `disabled:cursor-default` by hand and left out the dimming). Default `dim`.
268
+ */
269
+ disabledStyle?: "dim" | "keep";
270
+ /**
271
+ * Fill the height of the flex row it stands in, keeping its width — the delete at
272
+ * the end of keksdose's split line (transaction-fields:162), level with the labelled
273
+ * fields beside it rather than floating at half their height. {@link Button}'s
274
+ * `stretch`; no effect outside a flex row. With a `label`, the tooltip's wrapper is
275
+ * what stretches.
276
+ */
277
+ stretch?: boolean;
278
+ /** `round`: a circle instead of the rounded square — a compact status chip in a top
279
+ * bar (keksdose's sync indicator). `overlay` is always round. */
280
+ shape?: "square" | "round";
281
+ /**
282
+ * The colour of `tone="custom"`, as any CSS colour — a token (`var(--warning)`),
283
+ * ideally. Sets `--icon-button-tone` on the button, so passing a different one per
284
+ * state re-colours the glyph, its hover fill and its focus ring together. Passing it
285
+ * implies `tone="custom"`. Or leave it out and set the variable yourself from a class
286
+ * (`className="[--icon-button-tone:var(--success)]"`).
287
+ *
288
+ * Or a `{ light, dark }` pair, one colour per theme — keksdose: a raw colour that reads
289
+ * on the light surface (`#be123c`) is too dark on the dark one, and an inline style
290
+ * cannot say "in dark mode". See {@link IconButtonToneColor}.
291
+ */
292
+ toneColor?: IconButtonToneColor;
293
+ /** Glyph colour over the variant. `muted`: placeholder grey, full text colour on
294
+ * hover. `danger`: the same grey at rest, `--danger` on hover and focus — for a
295
+ * remove/delete that repeats down a list. `warning`: amber at rest — a flag that
296
+ * wants attention. `info`: sky at rest — a notice-worthy but harmless action
297
+ * (keksdose's reconcile). `success`: green at rest — a good state worth seeing.
298
+ * `custom`: the colour of `toneColor` (see there). Default: the variant's own colours. See `quiet` for
299
+ * turning a coloured tone's resting look the other way. */
300
+ tone?: IconButtonTone;
301
+ /**
302
+ * Whether a coloured tone (`danger`, `warning`, `info`) waits for the pointer:
303
+ * `true` is placeholder grey at rest and the tone's colour on hover and focus;
304
+ * `false` wears the tone's colour at rest. Left out, each tone keeps its own
305
+ * default — `danger` quiet, `warning` and `info` not. Ignored for `muted` (quiet by
306
+ * definition) and `default` (no tone).
307
+ *
308
+ * `quiet={false}` on `danger` is for a destructive action that stands ALONE, where
309
+ * hover-to-reveal hides it: keksdose's phone bulk bar (mobile-bulk-bar.tsx) has one
310
+ * "delete all" and a touch screen that never hovers, so it painted rose by hand.
311
+ * `quiet` on `warning`/`info` is the same switch the other way, for a flag repeated
312
+ * down a list. A boolean over the tone rather than a new tone (`danger-solid`) or an
313
+ * `emphasis` scale: there are exactly two resting looks, every coloured tone has
314
+ * both, and which one fits is a question about the SITE (alone or repeated, touch or
315
+ * pointer), not about the tone — so it is one switch the family shares.
316
+ */
317
+ quiet?: boolean;
318
+ /**
319
+ * Make it a toggle button. `true` sets `aria-pressed="true"` and draws the "on" look
320
+ * (brand glyph on the quiet brand fill); `false` sets `aria-pressed="false"` with the
321
+ * normal look, so a screen reader still hears a toggle that is off. Left out, it is
322
+ * an ordinary button with no `aria-pressed` — or whatever `aria-pressed` the caller
323
+ * passes. For keksdose's budget share toggle (budgets-page:291), which paints its own
324
+ * brand colour over a ghost button today. Keep the `aria-label` the same in both
325
+ * states ("Share budget"): the pressed state already says whether it is on.
326
+ */
327
+ pressed?: boolean;
328
+ /** Keep the click (and the Enter/Space that produces it) from reaching an
329
+ * ancestor's handler — for an action inside a clickable table row or card.
330
+ *
331
+ * **Not a licence to put this inside another `<button>`.** HTML forbids any
332
+ * interactive content, and any element with a `tabindex`, inside a button — so
333
+ * the `<span role="button" tabIndex={0}>` workaround is invalid too — and ARIA
334
+ * makes a button's children presentational, so a screen reader flattens the
335
+ * inner one into the outer one's name and it cannot be reached at all. Make the
336
+ * row's main action a button that fills the row and put this one BESIDE it, on
337
+ * top:
338
+ *
339
+ * ```tsx
340
+ * <div className="relative">
341
+ * <button className="w-full pe-16 …" onClick={open}>…row…</button>
342
+ * <div className="absolute inset-y-0 end-2 flex items-center gap-0.5">
343
+ * <IconButton size="xs" tone="danger" aria-label="Delete" onClick={remove}>
344
+ * <Trash2 />
345
+ * </IconButton>
346
+ * </div>
347
+ * </div>
348
+ * ```
349
+ *
350
+ * The pair look exactly like the nested version, click exactly like it, and
351
+ * are two tab stops a screen reader can tell apart. */
352
+ stopPropagation?: boolean;
353
+ /**
354
+ * The button's name, said once: it becomes the `aria-label` AND the text of a kit
355
+ * {@link Tooltip} round the button.
356
+ *
357
+ * Every app writes the pair by hand. lenkbank's nine icon actions all carry
358
+ * `aria-label={t(x)} title={t(x)}` (shared/lib/table-columns.tsx:103,
359
+ * projects-page.tsx:83, setpoint/segment-list.tsx:198/207, profile-bar.tsx:128/142,
360
+ * shortcut-dialog.tsx:815/857/1007) — and `title` is the browser's own tooltip,
361
+ * which shows late, never on focus and never on touch, and looks like no other
362
+ * label in the kit. kastlan's RowAction (shared/components/data-table/row-action.tsx)
363
+ * and keksdose's icon wrappers each exist to put a Tooltip round an IconButton.
364
+ *
365
+ * A caller's own `aria-label` still wins, for the rare name that should be longer
366
+ * than the bubble. The bubble is visual only: it would otherwise describe the button
367
+ * with its own name, and a screen reader would read the same word twice. Tooltip's
368
+ * default placement applies — in place, or portalled inside a scroll container —
369
+ * unless `tooltipPortal` says otherwise.
370
+ */
371
+ label?: string;
372
+ /** Show `label` as a tooltip. Default `true`; `false` keeps `label` as the
373
+ * accessible name only — for a button whose glyph is universally read (a close ✕ in
374
+ * a dialog header) or that already sits under a tooltip of its own. */
375
+ tooltip?: boolean;
376
+ /** Where the `label` tooltip opens. See {@link Tooltip}'s `side`. */
377
+ tooltipSide?: TooltipSide;
378
+ /** Passed to the `label` tooltip's `portal`. Left out, Tooltip decides (see there). */
379
+ tooltipPortal?: boolean;
380
+ /**
381
+ * Why the action is not available — {@link Button}'s `disabledReason`, on the icon
382
+ * button: keksdose's accounts page cannot hide an account with a balance or delete
383
+ * one with bookings (accounts-page:879/913 — "hide requires zero", "delete blocked"),
384
+ * nor its budgets page delete the only budget (budgets-page:330), and each wrapped a
385
+ * natively disabled IconButton in a Tooltip that swaps its label for the reason — a
386
+ * bubble a keyboard never opens, since `disabled` leaves the tab order, and a reason
387
+ * a screen reader never hears.
388
+ *
389
+ * The same contract as Button's: `aria-disabled` instead of `disabled`, so it stays
390
+ * focusable and hoverable; clicks (and the Enter/Space they stand for) are swallowed
391
+ * — `stopPropagation` still applies, so a locked action in a clickable row does not
392
+ * open the row either; the reason shows in the kit {@link Tooltip} and is attached
393
+ * through `aria-describedby` as a `hidden` copy. The NAME stays `aria-label` /
394
+ * `label` — "Delete", described by "The active budget cannot be deleted" — and the
395
+ * bubble shows the reason in place of the label, since the glyph already says what
396
+ * the button is and the reason is the news. Shown even with `tooltip={false}`: a
397
+ * reason nobody can see is not one. Wins over `disabled`, as on Button.
398
+ */
399
+ disabledReason?: ReactNode;
400
+ /** The `<button>` element — a prop in React 19, as on {@link Button}. */
401
+ ref?: Ref<HTMLButtonElement>;
402
+ /** Only on the link form — see {@link IconButtonLinkProps}. */
403
+ href?: never;
404
+ renderLink?: never;
405
+ external?: never;
406
+ replace?: never;
407
+ reloadDocument?: never;
408
+ }
409
+ /**
410
+ * `<IconButton href>`: an icon-only LINK — keksdose F2 and kastlan 45, whose icon
411
+ * actions that navigate (a row's "open", a card's "edit" that goes to the edit page, a
412
+ * top bar's settings cog) were `<IconButton onClick={() => navigate(…)}>`: a button that
413
+ * changes the page, with no middle click, no "open in new tab" and no URL on hover.
414
+ *
415
+ * The same rule as {@link Button}'s `href` (see {@link ButtonLinkProps}): an in-app
416
+ * `href` goes through `renderLink`, else the provider's `linkComponent`; an external one
417
+ * or an in-page `#anchor` stays a plain `<a>`; `external` opens a new tab and says so to
418
+ * a screen reader; `replace` and `reloadDocument` as on Button. The look, the `label` →
419
+ * `aria-label` + Tooltip, the badge, `toneColor` and `stopPropagation` are the button's.
420
+ *
421
+ * `disabled` renders an inert `<a>` with no `href` — `role="link"`, `aria-disabled`, out
422
+ * of the tab order, dimmed — like Button's disabled link. `pressed`, `disabledReason` and
423
+ * `disabledStyle` are a button's and are typed out: a link is not a toggle, and a link
424
+ * that explains why it cannot be followed is a job for a disabled BUTTON.
425
+ */
426
+ interface IconButtonLinkProps extends Omit<AnchorHTMLAttributes<HTMLAnchorElement>, "type" | "href">, Pick<IconButtonProps, "variant" | "size" | "glyphSize" | "badge" | "stretch" | "shape" | "toneColor" | "tone" | "quiet" | "stopPropagation" | "label" | "tooltip" | "tooltipSide" | "tooltipPortal"> {
427
+ href: string;
428
+ /** See {@link ButtonLinkProps.renderLink}. */
429
+ renderLink?: KitLinkComponent;
430
+ /** See {@link ButtonLinkProps.external}. */
431
+ external?: boolean;
432
+ /** See {@link ButtonLinkProps.replace}. */
433
+ replace?: boolean;
434
+ /** See {@link ButtonLinkProps.reloadDocument}. */
435
+ reloadDocument?: boolean;
436
+ /** See {@link ButtonLinkProps.disabled}. */
437
+ disabled?: boolean;
438
+ ref?: Ref<HTMLAnchorElement>;
439
+ type?: never;
440
+ form?: never;
441
+ pressed?: never;
442
+ disabledReason?: never;
443
+ disabledStyle?: never;
444
+ }
445
+ /**
446
+ * `toneColor`: one CSS colour, or one per theme.
447
+ *
448
+ * The pair is two custom properties on the element — `--icon-button-tone-light` and
449
+ * `--icon-button-tone-dark` — and a class that points `--icon-button-tone` at one or the
450
+ * other by the kit's `dark` variant (`.dark` on an ancestor, tokens.css), so the colour
451
+ * follows a theme switch in CSS alone, with no re-render and no JS reading the theme.
452
+ * Both values are any CSS colour; a token that already flips (`var(--warning)`) needs no
453
+ * pair.
454
+ */
455
+ type IconButtonToneColor = string | {
456
+ light: string;
457
+ dark: string;
458
+ };
459
+ declare function IconButton(props: IconButtonLinkProps): ReactElement;
460
+ declare function IconButton(props: IconButtonProps): ReactElement;
461
+ declare namespace IconButton {
462
+ var displayName: string;
463
+ }
464
+ declare const FIELD_BASE: string;
465
+ /** Cancels FIELD_BASE's read-only treatment for a field that is `readOnly` for a
466
+ * reason other than "you may not edit this". Attribute-scoped for the same reason
467
+ * FIELD_BASE is — the two have to cancel on the identical selector or twMerge
468
+ * cannot make the later one win. */
469
+ declare const FIELD_WRITABLE_LOOK = "[&[readonly]]:bg-[var(--bg-surface)] [&[readonly]]:text-[var(--text-primary)]";
470
+ declare const FIELD_FLOATING_PAD = "pt-4 pb-1";
471
+ declare const FIELD_INVALID = "border-[var(--danger-border)] ring-1 ring-[var(--danger-border)] focus:border-[var(--danger)] focus:ring-[var(--danger)]";
472
+ declare const FLOATING_INPUT_CLASS: string;
473
+ /** The phone breakpoint the display treatment below keys off — the same one the
474
+ * numpad sheet uses, kept in one place so the two can't drift apart. */
475
+ declare const PHONE_QUERY = "(max-width: 767px)";
476
+ /**
477
+ * The PHONE display treatment (Keksdose feedback #176, carried across the entry
478
+ * forms by #179): the field chrome removed so the ONE input a form is actually
479
+ * about reads as the thing itself, not as another boxed row in a stack.
480
+ *
481
+ * Every control that takes `variant="display"` — {@link Input}, `NumberInput`,
482
+ * `AmountInput` — means exactly the same thing by it: the treatment applies below
483
+ * {@link PHONE_QUERY} and the field is untouched above it, so a caller never has
484
+ * to ask the viewport, and a form can't end up half-treated across breakpoints.
485
+ *
486
+ * What stays, deliberately:
487
+ * - A hairline baseline. With the box gone something still has to say "you can
488
+ * type here"; it takes the brand colour on focus, where a bordered field would
489
+ * light its whole outline. Each edge is set exactly once (`border-x-0
490
+ * border-t-0 border-b`) so the rule can't hinge on utility order.
491
+ * - The muted placeholder colour, because a display field is usually empty at
492
+ * the moment it matters most and has nothing else to show.
493
+ * - The label, moved to `sr-only` rather than dropped: display type is legible
494
+ * to the eye, not to a screen reader.
495
+ *
496
+ * Size and weight are NOT here — an amount wants display type, a subject line
497
+ * wants a heading — so each control adds its own on top.
498
+ */
499
+ declare const FIELD_DISPLAY = "block w-full border-x-0 border-t-0 border-b border-[var(--border)] bg-transparent px-0 pt-0 pb-1 text-[var(--text-primary)] shadow-none placeholder:text-[var(--text-placeholder)] focus:border-[var(--brand)] focus:outline-none focus:ring-0 disabled:opacity-60";
500
+ declare const FIELD_TRIGGER: string;
501
+ /** An alias rather than an interface: the chevron adds nothing of its own to an
502
+ * `<svg>`'s props, and an interface declaring no members is the same type wearing a
503
+ * name that suggests otherwise. */
504
+ type FieldChevronProps = Omit<ComponentPropsWithoutRef<"svg">, "children">;
505
+ /** The dropdown chevron, shared by the native {@link Select} and every custom
506
+ * {@link FIELD_TRIGGER} control.
507
+ *
508
+ * Absolutely positioned and centred on the FIELD box. As an ordinary flex child
509
+ * it centres on the *content* box instead, and `FIELD_FLOATING_PAD` (pt-4 pb-1)
510
+ * pushes that box's midline down — so a labelled currency/multi-select chevron
511
+ * sat visibly lower than the native select's right beside it (feedback #400).
512
+ * Pair it with `pe-9` on the trigger so the value can't run underneath. At the
513
+ * inline END, not the right: a right-to-left form reads the value from the right,
514
+ * and a chevron parked on top of its first word hid exactly the part that says
515
+ * what was picked. */
516
+ declare function FieldChevron({ className, ...rest }: FieldChevronProps): react.JSX.Element;
517
+ declare const FLOATING_LABEL_CLASS: string;
518
+ declare const FLOATING_LABEL_STATIC: string;
519
+ /**
520
+ * The one place that assembles a labelled field: a `relative` wrapper around the
521
+ * control (`children`) plus a floating label inside the top strip. Pass `staticLabel`
522
+ * for controls that always have a value (selects); omit it for free-text fields whose
523
+ * label animates from centred→up. Used by Input/Select/Textarea/NumberInput; the
524
+ * dropdown controls that need a ref + menu keep their own wrapper but the same label
525
+ * (via {@link FieldLabel}) and trigger ({@link FIELD_TRIGGER}) styles.
526
+ */
527
+ interface FloatingFieldProps extends ComponentPropsWithoutRef<"div"> {
528
+ htmlFor?: string;
529
+ label?: ReactNode;
530
+ staticLabel?: boolean;
531
+ /** Keep the label in the accessibility tree but out of the layout — what
532
+ * {@link FIELD_DISPLAY} needs, since a floating label inside a field with no
533
+ * field left would have nothing to float in. */
534
+ srOnlyLabel?: boolean;
535
+ /** Something interactive that belongs to the LABEL rather than to the value —
536
+ * in practice a {@link FieldHint} "?" (dev#468). It is rendered in a flex row
537
+ * with the label, so it is centred on the label's line by the layout instead
538
+ * of by a hand-tuned `top-…`, and it can never drift when the type changes.
539
+ *
540
+ * It rides an ANIMATED label too, and the two cases place it differently on
541
+ * purpose. A static label has a field-wide strip to itself, so the hint sits
542
+ * at the far end of it and a long label truncates into the gap. An animated
543
+ * one belongs to a control with something at the end edge of the field — a
544
+ * calculator, a stepper — so the hint follows the label instead, and it is
545
+ * the label that gives way (steering-design feedback #48). */
546
+ hint?: ReactNode;
547
+ children: ReactNode;
548
+ }
549
+ declare function FloatingField({ className, htmlFor, label, staticLabel, srOnlyLabel, hint, children, ...rest }: FloatingFieldProps): react.JSX.Element;
550
+ /**
551
+ * The "?" that explains a field, on the field's own label line (dev#468).
552
+ *
553
+ * Pass it to a labelled {@link Select} / {@link Input} as `hint`. It exists as a
554
+ * component rather than as a snippet each form repeats because the previous
555
+ * version was exactly that snippet — an absolutely-positioned button whose
556
+ * `top-1.5` was one guess at where an 11px label sits — and the reporter's
557
+ * follow-up was *"question mark is not centered. Is it part of the hoc?
558
+ * positioning problems seem quite frequently."* It was not part of the HOC. Now
559
+ * it is, and there is one place left where the answer can be wrong.
560
+ *
561
+ * A `<button>` rather than a bare icon: hover alone puts the explanation out of
562
+ * reach of a keyboard and of every touch device, and the tooltip shows on focus
563
+ * too. The text is also its accessible name, so a screen reader gets it without
564
+ * the bubble ever opening.
565
+ */
566
+ interface FieldHintProps extends Omit<ComponentPropsWithoutRef<"button">, "children"> {
567
+ /** The explanation. It is both the tooltip's text and, by default, the button's
568
+ * accessible name, so a screen reader gets it without the bubble ever opening. */
569
+ label: string;
570
+ /** Default `"start"`: before the hint in the reading direction (the left in LTR, the
571
+ * right in RTL). `left` / `right` stay physical. */
572
+ side?: TooltipSide;
573
+ }
574
+ declare function FieldHint({ label, side, className, "aria-label": ariaLabel, ...rest }: FieldHintProps): react.JSX.Element;
575
+ /**
576
+ * The floating label for a custom-dropdown trigger (a `<span>`, since the trigger is a
577
+ * button not a labelable input). Same placement as {@link FloatingField}'s static label,
578
+ * so every labelled field lines up. Render inside a `relative` wrapper, before the trigger.
579
+ */
580
+ interface FieldLabelProps extends ComponentPropsWithoutRef<"span"> {
581
+ children: ReactNode;
582
+ }
583
+ declare function FieldLabel({ children, className, ...rest }: FieldLabelProps): react.JSX.Element;
584
+ interface LabelProps extends ComponentPropsWithoutRef<"label"> {
585
+ /** Draw the required mark after the text. The mark is `aria-hidden`: what a
586
+ * screen reader hears is the control's own `required` / `aria-required`, which
587
+ * the caller still sets — a "star" read out as the last word of every name is
588
+ * noise, and a control that is not marked required is not required however its
589
+ * label looks. */
590
+ required?: boolean;
591
+ /** `sm` for a toolbar or a dense filter row (12px), `md` otherwise (14px). */
592
+ size?: "sm" | "md";
593
+ /** Dim the label with its control. A label ABOVE its field is not the field's
594
+ * `peer`, so `peer-disabled:` cannot reach it the way it reaches a floating one. */
595
+ disabled?: boolean;
596
+ }
597
+ /**
598
+ * The label ABOVE a field, for the places a floating label does not fit: a control
599
+ * the kit did not render (a third-party picker, a range slider, a group of radios),
600
+ * a filter bar whose fields are unlabelled {@link Select}s, a form that sets labels
601
+ * above its fields throughout.
602
+ *
603
+ * A new component rather than a mode of {@link FieldLabel}, because the two share
604
+ * nothing but the word. `FieldLabel` is an absolutely positioned `<span>` inside a
605
+ * trigger's `relative` box and has to stay exactly that for every dropdown that
606
+ * already lines up with it; this is a real `<label>` in normal flow, with `htmlFor`,
607
+ * that a click focuses the control through. Spacing below it belongs to the caller's
608
+ * layout (`space-y-1.5` on the pair, typically), as it would for any block.
609
+ */
610
+ declare function Label({ required, size, disabled, className, children, ...rest }: LabelProps): react.JSX.Element;
611
+ /** The two names the password reveal toggle can wear. See {@link Input}. */
612
+ interface PasswordRevealLabels {
613
+ /** While the value is hidden — activating the toggle will show it. */
614
+ show: string;
615
+ /** While the value is shown. */
616
+ hide: string;
617
+ }
618
+ declare const DEFAULT_PASSWORD_REVEAL_LABELS: PasswordRevealLabels;
619
+ /** Caller's labels over the English defaults — the same shape as
620
+ * `resolveDataTableLabels`, so a consumer translates every kit string one way. */
621
+ declare function resolvePasswordRevealLabels(partial?: Partial<PasswordRevealLabels>): PasswordRevealLabels;
622
+ interface InputProps extends InputHTMLAttributes<HTMLInputElement> {
623
+ label?: ReactNode;
624
+ /** Classes for the `<input>` itself, as distinct from `className`, which
625
+ * styles the field WRAPPER once a `label` turns this into a FloatingField.
626
+ * Without it a labelled Input had no way to reach its own element — so
627
+ * `tabular-nums` on a numeric text field, which NumberInput has supported
628
+ * all along through the identically named prop, was simply unavailable. */
629
+ inputClassName?: string;
630
+ /** {@link FIELD_DISPLAY} — on a phone, drop the chrome and set the value as a
631
+ * heading. For the one field a form is about (a feedback subject, an account
632
+ * name), never for a stack of them. Labelled fields only: the label is what
633
+ * the placeholder falls back to once it goes `sr-only`. */
634
+ variant?: "field" | "display";
635
+ /** The field is required and unanswered, or holds something that cannot be
636
+ * saved — {@link FIELD_INVALID}, the same rose border/ring `Select` has worn
637
+ * since feedback #235.
638
+ *
639
+ * It exists here because writing `aria-invalid` by hand did NOT do this. The
640
+ * attribute spreads onto the element and nothing styles it — there is no
641
+ * `[aria-invalid]` rule in this package or in either consumer's stylesheet —
642
+ * so three Keksdose dialogs flagged a mismatched passphrase to a screen reader
643
+ * and painted the field exactly as if it were fine. Setting the prop sets the
644
+ * attribute too, so the two can no longer be spelled separately. */
645
+ invalid?: boolean;
646
+ /** What is wrong with the value, in the caller's own words ("That IBAN has 21
647
+ * digits"). Rendered under the field, pointed at by the control's
648
+ * `aria-describedby` — MERGED with any the caller already passed — and implies
649
+ * `invalid`, so the field paints as well as announces. `invalid` alone still
650
+ * covers the case where the message lives elsewhere. See {@link useFieldError}. */
651
+ error?: ReactNode;
652
+ /** Names for the password reveal toggle, English by default — it is the one
653
+ * string this component renders on its own behalf, and a German form was
654
+ * reading it out in English. See {@link PasswordRevealLabels}. */
655
+ passwordLabels?: Partial<PasswordRevealLabels>;
656
+ }
657
+ declare const Input: react.ForwardRefExoticComponent<InputProps & react.RefAttributes<HTMLInputElement>>;
658
+ interface SelectProps extends Omit<SelectHTMLAttributes<HTMLSelectElement>, "size"> {
659
+ label?: ReactNode;
660
+ /** See {@link Input}'s `invalid`. */
661
+ invalid?: boolean;
662
+ /** See {@link Input}'s `error`. */
663
+ error?: ReactNode;
664
+ /** A {@link FieldHint} for the label line — see {@link FloatingField}. */
665
+ hint?: ReactNode;
666
+ /**
667
+ * `"sm"`: a 28px, 12px-type select for a toolbar or a table header, where the
668
+ * 36px field stands a head taller than the buttons beside it. `"md"` (default)
669
+ * is the field every form uses. Unlabelled selects only — a floating label needs
670
+ * the tall box to float in, so a labelled Select ignores `"sm"`.
671
+ *
672
+ * A NUMBER is still the native attribute (the rows of a list box) and is passed
673
+ * straight through, so the one HTML meaning of `size` keeps working. Above 1 (as
674
+ * with `multiple`) the browser draws a list box, and the Select drops its chevron
675
+ * and the end padding reserved for it.
676
+ */
677
+ size?: "sm" | "md" | number;
678
+ /** Classes for the `<select>` itself. `className` styles the WRAPPER — the box
679
+ * the chevron is positioned against — so it could set a width and nothing
680
+ * else; this is the way to the element. See {@link Input}'s `inputClassName`. */
681
+ selectClassName?: string;
682
+ }
683
+ declare const Select: react.ForwardRefExoticComponent<SelectProps & react.RefAttributes<HTMLSelectElement>>;
684
+ interface TextareaProps extends TextareaHTMLAttributes<HTMLTextAreaElement> {
685
+ label?: ReactNode;
686
+ /** See {@link Input}'s `invalid`. */
687
+ invalid?: boolean;
688
+ /** See {@link Input}'s `error`. */
689
+ error?: ReactNode;
690
+ }
691
+ declare const Textarea: react.ForwardRefExoticComponent<TextareaProps & react.RefAttributes<HTMLTextAreaElement>>;
692
+ interface CardProps extends ComponentPropsWithoutRef<"div"> {
693
+ children: ReactNode;
694
+ /**
695
+ * When true, drop the card chrome (border, rounded corners, shadow) on mobile so the
696
+ * card spans edge-to-edge. Chrome reappears at the `md` breakpoint. Use for primary
697
+ * content cards on data pages; leave off for centered dialog/panel cards.
698
+ */
699
+ flush?: boolean;
700
+ /**
701
+ * `inset`: a panel INSIDE a card rather than a card on the page — the raised
702
+ * `--bg-surface-2`, a small radius, `p-3` of its own and no border or shadow, since
703
+ * it is already sitting on the card that has them. Lenkbank's results blocks and
704
+ * corner panels write this by hand (`rounded-md bg-surface-2 p-3`) under a Card of
705
+ * their own. Unlike the default card it carries its padding, because every one of
706
+ * those copies wanted the same one; a caller's `p-*` still wins. `flush` is ignored:
707
+ * an inset panel never runs edge to edge.
708
+ */
709
+ variant?: "default" | "inset" | "outline";
710
+ /**
711
+ * The card's own padding: `none`, `sm` (`p-3`) or `md` (`p-4`).
712
+ *
713
+ * `outline` is the bordered box with no shadow — a row or a block INSIDE a section
714
+ * rather than a card on the page: kastlan's rent breakdown rows
715
+ * (tenancy/components/breakdown-row.tsx, `rounded-lg border p-4`) and the meters
716
+ * list's inline add row (meters/components/meter-add-row.tsx). It takes `md` by
717
+ * default, as every one of those copies does; `sm` is the dense row.
718
+ *
719
+ * Left out, the default card keeps no padding (its CardHeader / CardContent own the
720
+ * rhythm) and `inset` keeps its `p-3`; passing it sets theirs too. A caller's `p-*`
721
+ * in `className` still wins.
722
+ */
723
+ padding?: "none" | "sm" | "md";
724
+ /**
725
+ * A card that is a WARNING (or other status) as a whole: the border in the tone's
726
+ * `-border` colour and the {@link CardTitle} in the tone's text colour. kastlan's
727
+ * dunning summary (invoice-detail-page.tsx:231-235) writes both by hand —
728
+ * `border-[var(--warning-border)]` on the Card, `text-[var(--warning)]` on the title
729
+ * — and the next status card would copy the pair (`outline` takes it the same way).
730
+ * An `inset` panel has no border, so it takes the tone's quiet `-bg` fill instead.
731
+ * `data-tone` is set for a caller's own
732
+ * selectors.
733
+ */
734
+ tone?: CardTone;
735
+ }
736
+ type CardTone = "warning" | "danger" | "info" | "success";
737
+ declare function Card({ className, children, flush, variant, tone, padding, ...rest }: CardProps): react.JSX.Element;
738
+ /** The sub-parts add nothing to a `<div>`'s props — they are the SAME element with a
739
+ * `data-slot` and a padding rhythm — so each name is an alias rather than an empty
740
+ * interface pretending to be more. They exist so a consumer's own wrapper can say
741
+ * `CardHeaderProps` instead of `ComponentProps<typeof CardHeader>`. */
742
+ type CardHeaderProps = ComponentPropsWithoutRef<"div">;
743
+ type CardTitleProps = ComponentPropsWithoutRef<"div">;
744
+ type CardDescriptionProps = ComponentPropsWithoutRef<"div">;
745
+ type CardActionProps = ComponentPropsWithoutRef<"div">;
746
+ type CardContentProps = ComponentPropsWithoutRef<"div">;
747
+ type CardFooterProps = ComponentPropsWithoutRef<"div">;
748
+ declare function CardHeader({ className, ...props }: CardHeaderProps): react.JSX.Element;
749
+ declare function CardTitle({ className, ...props }: CardTitleProps): react.JSX.Element;
750
+ declare function CardDescription({ className, ...props }: CardDescriptionProps): react.JSX.Element;
751
+ declare function CardAction({ className, ...props }: CardActionProps): react.JSX.Element;
752
+ declare function CardContent({ className, ...props }: CardContentProps): react.JSX.Element;
753
+ declare function CardFooter({ className, ...props }: CardFooterProps): react.JSX.Element;
754
+ /** A `<span>`'s props plus the words a reader hears — see {@link CardHeaderProps}.
755
+ * The ring is drawn with a border, so there is nothing inside it to put children in. */
756
+ interface SpinnerProps extends Omit<ComponentPropsWithoutRef<"span">, "children"> {
757
+ /**
758
+ * What the spinner means, for a screen reader. Default: `common.loading` from the
759
+ * {@link UiKitProvider}, else "Loading…".
760
+ *
761
+ * `null` makes it DECORATIVE — no role, no text, hidden from assistive tech. Use it
762
+ * whenever words are already there: beside visible "Loading…" text (otherwise it is
763
+ * announced twice), inside a labelled button (otherwise its text joins the button's
764
+ * name), or inside a live region of the app's own (otherwise that region and this
765
+ * one both announce).
766
+ */
767
+ label?: string | null;
768
+ /**
769
+ * Show the label as TEXT beside the ring, not only to a screen reader. kastlan's
770
+ * LoadingState (feedback/loading-state.tsx:14) builds exactly this by hand — a
771
+ * `role="status"` wrapper, the spinner at `label={null}`, a `<p>` of text — so it
772
+ * has to get the three-part aria arrangement right itself. Here the visible text IS
773
+ * the live region's content (the label stays the accessible name, said once), and
774
+ * the ring is decorative. `className` still sizes the ring; the other props go on
775
+ * the wrapper. Ignored with `label={null}`: there are no words to show.
776
+ */
777
+ showLabel?: boolean;
778
+ /** Where the shown label sits: `end` (default) — beside the ring, for a row or a
779
+ * button; `below` — under it, centred, for a panel's loading state. */
780
+ labelPosition?: "end" | "below";
781
+ }
782
+ /**
783
+ * A spinning ring that also SAYS it is loading.
784
+ *
785
+ * It used to be a bordered span and nothing else, so a reader met no element at all
786
+ * where a sighted user saw the page working — or, beside an emptied list, met the
787
+ * empty list and concluded there was nothing there. `role="status"` makes it a polite
788
+ * live region, and the text inside is what that region announces. Text rather than
789
+ * `aria-label`: a live region's announcement is its CONTENT, and several readers
790
+ * ignore a name on one.
791
+ *
792
+ * `relative` so the `sr-only` text has a local containing block (see
793
+ * sr-only-containment.test). Where the words are already on screen, pass
794
+ * `label={null}`: announcing is right for a spinner standing alone and wrong for one
795
+ * beside text, inside a button, or inside the app's own live region.
796
+ */
797
+ declare function Spinner({ className, label, showLabel, labelPosition, ...rest }: SpinnerProps): react.JSX.Element;
798
+ interface EmptyStateProps extends Omit<ComponentPropsWithoutRef<"div">, "children" | "title"> {
799
+ /** The box renders `title` and `hint` in its own two-line rhythm, which is what makes
800
+ * every empty state in three apps look like the same thing — so there is no
801
+ * `children` slot to put arbitrary content in. The two slots below are the only
802
+ * other things an empty state has turned out to need, and each has a fixed place. */
803
+ /** Nodes rather than strings, so a title can carry a code, a link or an emphasised
804
+ * word — keksdose's error boundary shows the error's name in `<code>` — while the
805
+ * box still sets the type. */
806
+ title: ReactNode;
807
+ hint?: ReactNode;
808
+ /**
809
+ * Render the title as a heading of this level. Left out it is a `<div>`, as before:
810
+ * most empty states are a message inside a section that already has its heading.
811
+ * keksdose's error boundary IS the page when it trips, and its `<h2>` went missing
812
+ * when it moved onto EmptyState — so the page's heading outline lost the one line
813
+ * that says what happened. Only the element changes; the look is the box's.
814
+ */
815
+ headingAs?: "h2" | "h3" | "h4" | "h5" | "h6";
816
+ /** A glyph ABOVE the title — kastlan's InboxEmptyState (an inbox), keksdose's
817
+ * offline card (a cloud with a slash). Sized by the box (`[&_svg]:size-8`) and
818
+ * muted, so five call sites cannot pick five sizes; hidden from assistive tech,
819
+ * because the title already says what it shows. */
820
+ icon?: ReactNode;
821
+ /** What to do about it, BELOW the hint: keksdose's query-state cards (Retry), its
822
+ * error boundary (Retry / Reload / Go home — pass all three in a fragment, they
823
+ * wrap and centre as one row), kastlan's "Create a rule". Buttons or links the
824
+ * caller renders; the box only places them. */
825
+ action?: ReactNode;
826
+ /**
827
+ * `box` (default): the dashed, padded card that stands in for a whole section.
828
+ * `inline`: one quiet line with no box — for the empty state INSIDE something that
829
+ * already has its own frame, where a dashed card would be a box in a box. keksdose
830
+ * writes that line by hand five times as a muted `<p className="py-6 text-center">`:
831
+ * the notification inbox (notification-inbox:140), a support thread
832
+ * (support-thread:144), the assistant (assistant-page:321), the admin support panel
833
+ * (support-panel:265) and the funding dialog (funding-dialog:89). The icon shrinks
834
+ * to the text's size and sits before the title, the hint follows on the same line,
835
+ * and the row wraps only when it must. Centred like the box; `className="justify-start"`
836
+ * for a list that reads from the start edge (funding-dialog).
837
+ */
838
+ variant?: "box" | "inline";
839
+ /**
840
+ * Colour the icon and the title: `danger` for an error standing where the content
841
+ * should be — kastlan's ErrorState (shared/components/feedback/error-state.tsx) and
842
+ * its error boundary's fallback (error-boundary.tsx:16), both a red triangle over a
843
+ * message — and `success` for an empty state that is GOOD news, kastlan's "No defects
844
+ * recorded for this room" (handover defects-step.tsx:142, a green check). Left out,
845
+ * both stay muted. The hint stays muted either way: it is the explanation, not the
846
+ * verdict.
847
+ */
848
+ tone?: "danger" | "success";
849
+ /**
850
+ * `inline` only (0.11.0). `sm`: 12px, start-aligned and tight (`py-1`) — the empty
851
+ * line INSIDE a detail panel, under a panel heading and among `text-xs` rows, where
852
+ * the default's 14px centred line with `py-4` read as a section of its own.
853
+ * keksdose's holdings panel (holdings-panel:563), asset-loan panel
854
+ * (asset-loan-panel:108) and loan-payment panel (loan-payment-panel:191) each wrote
855
+ * `className="justify-start py-1"` over it and still got body-size text. Default `md`,
856
+ * the look it had. Ignored by the box.
857
+ */
858
+ size?: "sm" | "md";
859
+ }
860
+ declare function EmptyState({ title, hint, icon, action, headingAs, variant, tone, size, className, ...rest }: EmptyStateProps): react.JSX.Element;
861
+ /** One tab of a {@link Tabs} strip. */
862
+ interface TabItem<T extends string> {
863
+ id: T;
864
+ /** A ReactNode so a tab can pair an icon with text. */
865
+ label: ReactNode;
866
+ /** Optional trailing node (e.g. a count pill). */
867
+ badge?: ReactNode;
868
+ /** Marks a tab that IS a route — see {@link TabsProps}. */
869
+ href?: string;
870
+ /** Leading decoration: an icon, or the colour swatches of the lines this tab
871
+ * draws in a chart above the strip. Hidden from assistive tech — it repeats
872
+ * what the label says, or it says something the label must say instead. */
873
+ icon?: ReactNode;
874
+ /** A second, smaller line under the label — the one fact that tells two tabs
875
+ * with similar labels apart ("80 km/h", "Level 3"). Part of the tab's name. */
876
+ detail?: ReactNode;
877
+ /** A tab that exists as a slot with nothing in it yet, drawn with a dashed
878
+ * outline so a gap in a sequence looks like a gap. Visual only: say "empty" in
879
+ * the label or `detail` if a screen reader needs to know. */
880
+ empty?: boolean;
881
+ /** `false` keeps this one tab when the strip has `onRemove` — the one sheet a
882
+ * workbook cannot be without. Default: removable. */
883
+ removable?: boolean;
884
+ /** Plain-text name for the labels the strip composes about this tab ("Remove
885
+ * …"), for when `label` is not a string. Default: `label` if it is a string,
886
+ * else the id. */
887
+ name?: string;
888
+ }
889
+ /** The strings the strip renders on its own behalf — only when it can add or
890
+ * remove tabs. */
891
+ interface TabsLabels {
892
+ /** The "add" button's visible text when the caller gives no `addLabel`. */
893
+ add: string;
894
+ /** Accessible name of a tab's × — "Remove <tab>". */
895
+ remove: (tab: string) => string;
896
+ }
897
+ declare const DEFAULT_TABS_LABELS: TabsLabels;
898
+ /**
899
+ * The strip's own props sit on a `<div>`: the tablist IS the root element, so anything
900
+ * a caller hangs on it — a `data-tour` anchor for the kit's guided tour, a test id, an
901
+ * `aria-describedby` — lands there. `onChange` is omitted from the div's props because
902
+ * this component's `onChange` hands over the chosen TAB ID, not a DOM event, and
903
+ * `children` because the strip renders `tabs` — a caller who wants a PANEL wires it up
904
+ * through `panelId`, which is the whole point of that prop.
905
+ */
906
+ interface TabsProps<T extends string> extends Omit<ComponentPropsWithoutRef<"div">, "onChange" | "children"> {
907
+ /** `label` is a ReactNode so tabs can pair an icon with text; `badge` is an
908
+ * optional trailing node (e.g. a count pill). `href` marks a tab that IS a route:
909
+ * it renders as an anchor so it can be middle-/⌘-clicked into a new tab (Keksdose
910
+ * feedback #451), while a plain click still goes through `onChange` and stays
911
+ * client-side. Tabs that only flip local state leave it unset — a link to a URL
912
+ * that does not select the tab would be worse than no link. */
913
+ tabs: TabItem<T>[];
914
+ active: T;
915
+ onChange: (id: T) => void;
916
+ className?: string;
917
+ /** Let the strip WRAP onto as many rows as it needs below `md`, instead of
918
+ * scrolling sideways — for a strip carrying more tabs than fit on a phone row.
919
+ * Keksdose live #262: ten reports on a 406px screen showed about three, and the
920
+ * round-2 answer (a <select> below `md`) cost a tap to open and a tap to choose.
921
+ * *"I would rather keep the tabs but have multirow tabs depending on the screen
922
+ * size, I will not loose the function to see all reports at once and navigate by
923
+ * single click rather by double click."*
924
+ *
925
+ * The active MARKER has to change with the layout, which is why this cannot be
926
+ * done from a call site with a `className`: the default marker is a `border-b-2`
927
+ * underline riding the container's own bottom rule, and a tab sitting in a row
928
+ * that does not touch that rule cannot wear it — every row but the last would
929
+ * show a stray line floating mid-strip, and `-mb-px` would pull each chip a
930
+ * pixel into the row beneath it. A wrapped strip marks the active tab with a
931
+ * filled brand chip instead, which is also the only thing findable at a glance
932
+ * among ten same-weight labels on three rows.
933
+ *
934
+ * From `md` up NOTHING changes: same single-line underline strip, same paddings,
935
+ * same colours as every other strip in the app.
936
+ *
937
+ * Opt-in on purpose. The three-tab strips (invoices, statements, bank imports)
938
+ * already fit a phone row, and turning those into chips would be an unrequested
939
+ * redesign of three other pages. */
940
+ wrap?: boolean;
941
+ /**
942
+ * Accessible name for the `role="tablist"` container. Every tab carries its own
943
+ * text, so this names the GROUP, not the tabs; leave it unset where a visible
944
+ * heading immediately above already does that job.
945
+ *
946
+ * @deprecated Pass `aria-label` instead. This kit had three spellings for one idea
947
+ * — `ariaLabel`, `aria-label` and this `label` — and settled on the DOM one, which
948
+ * is also the one that arrives for free now that the strip spreads its rest props.
949
+ * `label` still works and is unchanged; it names the strip only when `aria-label`
950
+ * is absent.
951
+ */
952
+ label?: string;
953
+ /**
954
+ * `id` of the element the caller renders the open tab's content into — the missing
955
+ * half of `role="tab"`. A tab that controls nothing is a tab in name only: a screen
956
+ * reader announces "tab, 2 of 5" and then has no way to take the user to what it
957
+ * opened, and no way back.
958
+ *
959
+ * **This component does not render the panel, on purpose.** `Tabs` is the STRIP; the
960
+ * content lives wherever the caller put it — three Keksdose pages render it in a
961
+ * sibling `<Card>`, one renders it through a router outlet, and a tab can BE a route
962
+ * (`href`), in which case the panel is a whole page this component never sees.
963
+ * Wrapping `children` here would mean either moving that content into the strip's
964
+ * subtree — a layout change on every page that uses tabs — or shipping a wrapper
965
+ * that only some callers could use. So the two halves are joined by an id instead.
966
+ *
967
+ * Wire the other end yourself:
968
+ *
969
+ * ```tsx
970
+ * <Tabs tabs={tabs} active={active} onChange={setActive} panelId="report-panel" />
971
+ * <div id="report-panel" role="tabpanel" aria-labelledby="report-panel-tab" tabIndex={-1}>
972
+ * ```
973
+ *
974
+ * `aria-controls` goes on the OPEN tab only, and `${panelId}-tab` is its id — the
975
+ * closed tabs' panels are not in the document, and a dangling `aria-controls`
976
+ * describes a tab as opening something that is not there.
977
+ */
978
+ panelId?: string;
979
+ /**
980
+ * Makes the tabs removable: an × on the OPEN tab, and Delete on a focused tab
981
+ * (only on the open one with `removeOn="active"`).
982
+ *
983
+ * The × is on the open tab only — a row of tabs each carrying one is a row of
984
+ * things to hit by accident, and the tab you can remove should be the one you
985
+ * are looking at — but its room is reserved on every removable tab, so opening a
986
+ * tab never widens it and never pushes the rest of the strip along (lenkbank
987
+ * feedback #72). It is named "Remove <tab>" and is not a tab stop of its own: the
988
+ * strip stays one stop, and the keyboard path is Delete (announced through
989
+ * `aria-keyshortcuts`), which removes whichever tab has focus (see `removeOn`).
990
+ *
991
+ * The strip only ASKS: it calls this, and the tab goes when the caller drops it
992
+ * from `tabs` — after a confirmation, a request, whatever it needs. When it does,
993
+ * focus that was on the removed tab (or its ×) moves to the open tab if the
994
+ * removed one was open, else to its neighbour, instead of falling to the page.
995
+ */
996
+ onRemove?: (id: T) => void;
997
+ /**
998
+ * Which tabs the Delete key removes.
999
+ *
1000
+ * - `"every"` (default): any focused removable tab — the ARIA pattern's reading.
1001
+ * - `"active"`: only the OPEN tab, the same one that wears the ×. For a strip whose
1002
+ * remove is immediate and unconfirmed (lenkbank's sheet tabs delete a measurement
1003
+ * sheet at once), so arrowing across the strip and pressing Delete cannot take
1004
+ * out a sheet the user is not looking at. Delete on a closed tab does nothing,
1005
+ * and only the open tab announces the shortcut (`aria-keyshortcuts`).
1006
+ *
1007
+ * Either way the × is drawn on the open tab only and its room stays reserved on
1008
+ * every removable tab, so the strip never relayouts when the selection moves.
1009
+ */
1010
+ removeOn?: "every" | "active";
1011
+ /** Renders an "add" button after the last tab, outside the tablist — it is an
1012
+ * action, not a tab, and a tablist may own only tabs. */
1013
+ onAdd?: () => void;
1014
+ /** The add button's text. Say what is added — "Add loop", "Add report" says
1015
+ * more than "Add". Default: `tabs.add` from the {@link UiKitProvider}. */
1016
+ addLabel?: string;
1017
+ /** Overrides for this strip's strings. See {@link TabsLabels}. */
1018
+ labels?: Partial<TabsLabels>;
1019
+ /** An add or a remove is in flight: the × and the add button are disabled and
1020
+ * Delete is ignored, so a double click cannot remove two tabs. Nothing moves. */
1021
+ busy?: boolean;
1022
+ /**
1023
+ * `"vertical"`: the same strip as a SIDE NAV — one item per row, the open one
1024
+ * filled, ↑/↓ walking it (Home/End as before) and `aria-orientation="vertical"` on
1025
+ * the tablist. For keksdose's admin page and settings page, which each hand-built
1026
+ * the identical sidebar with none of this strip's keyboard, roving tab stop or
1027
+ * routed-`href` handling. Everything else — `href` tabs, `badge`, `panelId`,
1028
+ * `onRemove`, `onAdd` — means exactly what it means on the horizontal strip; the
1029
+ * badge moves to the row's end.
1030
+ *
1031
+ * **On a phone ({@link PHONE_QUERY}) it becomes the horizontal strip**, `wrap` and
1032
+ * all, rather than staying a column. A side nav only works beside its content; on
1033
+ * a phone it has to go ABOVE it, and eight full-width rows there push the panel
1034
+ * the user picked below the fold on every visit — the two keksdose pages both
1035
+ * collapsed theirs into a scrolling row by hand for exactly that reason. The
1036
+ * orientation is switched in JavaScript, not with `md:` classes, so the
1037
+ * `aria-orientation` a screen reader hears and the arrow keys that work always
1038
+ * match the layout on screen. The caller's own two-column layout has to stack at
1039
+ * the same breakpoint (`md:grid-cols-[14rem_1fr]`).
1040
+ */
1041
+ orientation?: "horizontal" | "vertical";
1042
+ }
1043
+ declare function Tabs<T extends string>({ tabs, active, onChange, className, wrap, label, panelId, onRemove, removeOn, onAdd, addLabel, labels, busy, orientation, "aria-label": ariaLabel, ...rest }: TabsProps<T>): react.JSX.Element;
1044
+
1045
+ /**
1046
+ * A button that opens the file picker — the shape all three apps kept writing by hand
1047
+ * as a `<Button>` plus a hidden `<input type="file">` plus a ref between them (seven
1048
+ * copies in keksdose, two each in kastlan and lenkbank).
1049
+ *
1050
+ * Every copy had to remember the same four things, and each one forgot at least one:
1051
+ *
1052
+ * 1. **Reset the input after every pick.** A file input fires `change` only when its
1053
+ * value CHANGES, so picking the same file twice in a row — the retry after a failed
1054
+ * upload, the second photo of the same receipt — did nothing at all, which reads as
1055
+ * a broken button. `value = ""` after each pick; always, not per call site.
1056
+ * 2. **`type="button"`.** `<Button>` does not set it, and inside a form a bare button
1057
+ * submits the form before the picker opens.
1058
+ * 3. **Check the file.** `accept` filters the DIALOG, not the result: the dialog's
1059
+ * "All files" switch, a drop, and a mobile share sheet all hand over whatever the
1060
+ * user chose. So `accept` is re-checked here, along with `maxSize`, `maxFiles` and
1061
+ * an optional `isValid`.
1062
+ * 4. **Say no without a toast.** A rejection is reported through `onReject`, with a
1063
+ * translated message per file, and spoken through a live region — the kit does not
1064
+ * decide how an app surfaces errors (keksdose's proposal §1 asked for exactly that).
1065
+ *
1066
+ * The part that is not a button — the hidden input, the check, the announcement — is
1067
+ * {@link useFilePicker}, for the case where the thing that opens the picker is someone
1068
+ * else's control (a card's "Add" action, a menu item, a second button for the camera).
1069
+ */
1070
+ /** Every string the file pickers ({@link FileButton}, `FileDropzone`) render or speak.
1071
+ * Messages are functions of the file name so a translation can put it anywhere. */
1072
+ interface FilePickerLabels {
1073
+ /** The file's type is not in `accept`, when no `accept` is known to name (see
1074
+ * `rejectedTypeOnly`). */
1075
+ rejectedType: (name: string) => string;
1076
+ /** The file's type is not in `accept`, naming what IS accepted: `accept`'s tokens,
1077
+ * lower-cased and joined with ", " (`".csv"` => "Only .csv files"). Used whenever
1078
+ * `accept` is set — kastlan kept a custom `isValid` + `invalidMessage` per form only
1079
+ * to say this, and `accept` already knows it. */
1080
+ rejectedTypeOnly: (accept: string, name: string) => string;
1081
+ /** The file is larger than `maxSize`; `maxSize` arrives formatted ("5 MB"). */
1082
+ rejectedSize: (name: string, maxSize: string) => string;
1083
+ /** The file was one too many for `maxFiles`. */
1084
+ rejectedCount: (name: string, maxFiles: number) => string;
1085
+ /** `isValid` said no and the caller gave no `invalidMessage`. */
1086
+ rejectedInvalid: (name: string) => string;
1087
+ /** Spoken instead of the per-file message when more than one file was refused. */
1088
+ rejectedMany: (count: number) => string;
1089
+ /** Spoken (and shown, on a dropzone) when `onPick` refused the WHOLE pick.
1090
+ * Receives the number of files in it. */
1091
+ rejectedPick: (count: number) => string;
1092
+ /** Spoken after a pick the component itself echoes (the dropzone). */
1093
+ selected: (count: number, firstName: string) => string;
1094
+ /** The dropzone's remove button for one file. */
1095
+ remove: (name: string) => string;
1096
+ /** The dropzone's remove-everything button in `multiple` mode. */
1097
+ clearAll: string;
1098
+ /** Spoken after a remove / clear, since the button that was pressed is gone. */
1099
+ removed: (name: string) => string;
1100
+ cleared: string;
1101
+ /** The dropzone's accessible name, when the host passes no `dropLabel`. */
1102
+ dropzone: string;
1103
+ /** The dropzone's Browse button, when the host passes no `browseLabel`. */
1104
+ browse: string;
1105
+ /** What an empty single-file dropzone says (no `emptyLabel` given). */
1106
+ empty: string;
1107
+ /** …and an empty `multiple` one. */
1108
+ emptyMultiple: string;
1109
+ /** The dropzone's second line (no `hint` given). Receives `accept` as a person
1110
+ * reads it (`".pdf, image/png"`), or `""` when anything goes. */
1111
+ hint: (accept: string) => string;
1112
+ /** What a `busy` dropzone says in place of its body ("Uploading…"). */
1113
+ busy: string;
1114
+ }
1115
+ declare const DEFAULT_FILE_PICKER_LABELS: FilePickerLabels;
1116
+ type FileRejectionReason = "type" | "size" | "count" | "invalid";
1117
+ /** One file the picker refused, and why. `message` is already translated (see
1118
+ * {@link FilePickerLabels}, or the caller's `invalidMessage`), so a host that just
1119
+ * wants to show it can render `rejections[0].message` as is. */
1120
+ interface FileRejection {
1121
+ file: File;
1122
+ reason: FileRejectionReason;
1123
+ message: string;
1124
+ }
1125
+ /**
1126
+ * Does `file` satisfy an `accept` string, the way the browser's dialog reads it?
1127
+ * Comma-separated tokens: `.ext` (case-insensitive suffix of the name), `type/*`
1128
+ * (a MIME family) or an exact MIME type. An empty or absent `accept` takes anything.
1129
+ *
1130
+ * A file with no `type` — common for `.step`, `.dat`, anything the OS has no MIME
1131
+ * mapping for — can only match by extension, which is why lenkbank lists `.stp` AND
1132
+ * `model/step`: that is how `accept` has to be written for the dialog anyway.
1133
+ */
1134
+ declare function matchesAccept(file: File, accept: string | undefined): boolean;
1135
+ /** @internal `" .PDF, image/png ,"` => `".pdf, image/png"` — `accept` as a person reads
1136
+ * it. Also the dropzone's default hint. */
1137
+ declare function formatAccept(accept: string): string;
1138
+ /** What the pickers screen a pick with. All optional; nothing set accepts everything. */
1139
+ interface FileScreenOptions {
1140
+ accept?: string;
1141
+ /** Bytes. Larger files are refused with reason `"size"`. */
1142
+ maxSize?: number;
1143
+ /** How many files one pick may deliver. The rest are refused with reason `"count"`
1144
+ * — first come, first kept. For a running cap ("at most 5 attachments") pass what
1145
+ * is LEFT: `maxFiles={5 - attachments.length}`. */
1146
+ maxFiles?: number;
1147
+ /** The caller's own check, after `accept` and `maxSize`. */
1148
+ isValid?: (file: File) => boolean;
1149
+ /** The message for an `isValid` refusal; `labels.rejectedInvalid` otherwise. */
1150
+ invalidMessage?: string;
1151
+ }
1152
+ /** @internal Split a pick into the files that pass and the ones that do not. */
1153
+ declare function screenFiles(files: readonly File[], opts: FileScreenOptions, labels: FilePickerLabels, formatSize: (bytes: number) => string): {
1154
+ accepted: File[];
1155
+ rejected: FileRejection[];
1156
+ };
1157
+ /**
1158
+ * Judges a pick as a whole — keksdose's "refuse the pick if any file is bad", which
1159
+ * with `onFiles` + `onReject` alone had to be rebuilt from two calls in a microtask.
1160
+ *
1161
+ * Called ONCE per pick, after screening and before `onFiles` / `onReject`, with both
1162
+ * halves (either may be empty). Return `false` to refuse the whole pick: the accepted
1163
+ * files are not delivered, `onReject` still receives what screening refused, and one
1164
+ * sentence (`labels.rejectedPick`) is spoken for the pick instead of a per-file one.
1165
+ * Return nothing to let the pick through as usual.
1166
+ *
1167
+ * ```tsx
1168
+ * onPick={(ok, bad) => bad.length === 0 || false} // all or nothing
1169
+ * ```
1170
+ */
1171
+ type FilePickHandler = (accepted: File[], rejected: FileRejection[]) => boolean | void;
1172
+ /** @internal The shared tail of both pickers: run `onPick`, and say whether the pick
1173
+ * stands. A pick with nothing accepted has nothing to refuse, so `false` there is
1174
+ * treated as no verdict and the usual per-file messages are kept. */
1175
+ declare function judgePick(onPick: FilePickHandler | undefined, accepted: File[], rejected: FileRejection[]): boolean;
1176
+ /** One sentence for a whole batch of refusals: the file's own message for one, a
1177
+ * count for several — reading out five sentences in a row helps nobody. */
1178
+ declare function summariseRejections(rejected: readonly FileRejection[], labels: FilePickerLabels): string;
1179
+ interface UseFilePickerOptions extends FileScreenOptions {
1180
+ /** Let one pick deliver several files. Without it a drop of several keeps the first. */
1181
+ multiple?: boolean;
1182
+ /**
1183
+ * Ask a phone for its camera instead of the file chooser: `"environment"` is the
1184
+ * back camera, `"user"` the front. A HINT — desktop browsers ignore it, and Chrome
1185
+ * drops it when `multiple` is also set (a camera cannot deliver a list), which is why
1186
+ * keksdose's camera input has no `multiple`. Pass one or the other.
1187
+ */
1188
+ capture?: boolean | "user" | "environment";
1189
+ /** The files that passed, in pick order. Never called with an empty array.
1190
+ * Optional since 0.7 for a caller that takes the pick through `onPick` alone. */
1191
+ onFiles?: (files: File[]) => void;
1192
+ /** See {@link FilePickHandler}: the whole pick at once, with the power to refuse it. */
1193
+ onPick?: FilePickHandler;
1194
+ /** The files that did not, with a translated message each. The refusals are also
1195
+ * spoken through a live region, so this is for SHOWING them, not for a11y. */
1196
+ onReject?: (rejections: FileRejection[]) => void;
1197
+ /** `open()` and `take()` do nothing while set. */
1198
+ disabled?: boolean;
1199
+ /** Per-instance overrides of the `filePicker` label namespace. */
1200
+ labels?: Partial<FilePickerLabels>;
1201
+ }
1202
+ interface UseFilePickerReturn {
1203
+ /** Open the system picker. Call it from a click handler — browsers only open a file
1204
+ * dialog in response to a user gesture. */
1205
+ open: () => void;
1206
+ /** Screen and deliver files that arrived some other way — a drop, a paste. Honours
1207
+ * `multiple` (only the first file without it) and every check. */
1208
+ take: (files: ArrayLike<File> | null | undefined) => void;
1209
+ /** The hidden input and the live region. Render it once, anywhere — it takes no
1210
+ * space and needs no positioned ancestor. */
1211
+ element: ReactElement;
1212
+ }
1213
+ /**
1214
+ * The headless half of {@link FileButton}: a hidden file input you can open from any
1215
+ * control, with the reset, the screening and the announcement built in.
1216
+ *
1217
+ * ```tsx
1218
+ * const picker = useFilePicker({ accept: ".pdf", onFiles: ([f]) => upload(f) });
1219
+ * return <>{picker.element}<DetailTableCard onAdd={picker.open} … /></>;
1220
+ * ```
1221
+ */
1222
+ declare function useFilePicker({ accept, multiple, capture, maxSize, maxFiles, isValid, invalidMessage, onFiles, onPick, onReject, disabled, labels: labelsProp, }: UseFilePickerOptions): UseFilePickerReturn;
1223
+ type ButtonOwnProps = ComponentPropsWithoutRef<typeof Button>;
1224
+ interface FileButtonProps extends Omit<UseFilePickerOptions, "disabled">, Omit<ButtonOwnProps, "type" | "children" | "accept" | "capture" | "multiple"> {
1225
+ /** The button's content — usually an icon and a word. It is the accessible name. */
1226
+ children: ReactNode;
1227
+ /** Busy: disabled, `aria-busy`, and a spinner before the content — for "uploading". */
1228
+ pending?: boolean;
1229
+ /** Also accept files dropped ON the button (lenkbank's "drop onto the button").
1230
+ * Off by default: a button that silently takes drops is a surprise on a page that
1231
+ * has a real drop target elsewhere. */
1232
+ droppable?: boolean;
1233
+ }
1234
+ /**
1235
+ * {@link useFilePicker} behind a {@link Button}: `variant` and every other button prop
1236
+ * pass through, and the ref is the `<button>` (so `ref.current.click()` opens the
1237
+ * picker from elsewhere too).
1238
+ *
1239
+ * ```tsx
1240
+ * <FileButton accept="image/*,application/pdf" capture="environment" variant="secondary"
1241
+ * maxSize={10_000_000} onFiles={([f]) => upload(f)} onReject={([r]) => setError(r.message)}>
1242
+ * <Camera aria-hidden /> Photograph receipt
1243
+ * </FileButton>
1244
+ * ```
1245
+ */
1246
+ declare const FileButton: react.ForwardRefExoticComponent<FileButtonProps & react.RefAttributes<HTMLButtonElement>>;
1247
+
1248
+ /** The words a copy button says. The visible three are short on purpose (they
1249
+ * replace the button's own text in the `label` variant); the two announcements
1250
+ * are the sentences a screen reader hears, since the button's focus never moves. */
1251
+ interface CopyButtonLabels {
1252
+ copy: string;
1253
+ copied: string;
1254
+ failed: string;
1255
+ copiedAnnouncement: string;
1256
+ failedAnnouncement: string;
1257
+ }
1258
+ declare const DEFAULT_COPY_BUTTON_LABELS: CopyButtonLabels;
1259
+ interface CopyButtonProps extends Omit<ButtonHTMLAttributes<HTMLButtonElement>, "children" | "onClick"> {
1260
+ /** What to copy. A function is read at click time — for a value that is expensive
1261
+ * to build (a CSV of the table) or that changes under the button. */
1262
+ text: string | (() => string);
1263
+ /** `icon` (default): a square icon button whose tooltip states the result.
1264
+ * `label`: a text button whose words change to the result. */
1265
+ variant?: "icon" | "label";
1266
+ /** The idle accessible name — and, in the `label` variant, the visible text:
1267
+ * "Copy IBAN" says WHAT is copied, which a row of three copy icons needs. Default
1268
+ * `copyButton.copy`. */
1269
+ label?: string;
1270
+ /** The button's own look. Default: `ghost` for the icon, `secondary` for the label. */
1271
+ buttonVariant?: ButtonVariant;
1272
+ /** The icon variant's IconButton size (default `sm`). The `label` variant takes a
1273
+ * Button size, `sm` or `md` — passed only when given, so an existing label button
1274
+ * keeps its look (keksdose budget-share-card and jobs-panel wanted the compact one). */
1275
+ size?: IconButtonSize | ButtonSize;
1276
+ /** The icon variant's IconButton tone (e.g. `muted`, so a row of copy icons is no
1277
+ * heavier than the row's other actions); in the `label` variant a Button tone,
1278
+ * which applies to `link` / `ghost`. The copied / failed colour still wins. */
1279
+ tone?: IconButtonTone | ButtonTone;
1280
+ /** Keep the click and Enter/Space from reaching a clickable row around the button,
1281
+ * as IconButton's own `stopPropagation` does (keksdose feedback-page, users-panel). */
1282
+ stopPropagation?: boolean;
1283
+ /** Icon variant only: where the result tooltip opens. */
1284
+ tooltipSide?: TooltipSide;
1285
+ /** Pass through to the tooltip. Left unset, the Tooltip decides on its own — it
1286
+ * portals inside a clipping container (a DataTable's `data-clips` scroller). */
1287
+ tooltipPortal?: boolean;
1288
+ /** ms until the button returns to idle. Default 2000. */
1289
+ resetAfter?: number;
1290
+ /** Called with the real outcome once the copy settles. */
1291
+ onCopied?: (ok: boolean) => void;
1292
+ labels?: Partial<CopyButtonLabels>;
1293
+ }
1294
+ /**
1295
+ * A button that copies a value and tells the truth about whether it did.
1296
+ *
1297
+ * keksdose asked for it after two of its own copy buttons said "Copied" on a build
1298
+ * where the copy had failed (no Clipboard API over plain http). This one takes its
1299
+ * state from {@link useCopyToClipboard}, which reads the result, and has a `failed`
1300
+ * state that looks and sounds different from success.
1301
+ *
1302
+ * The result is spoken through {@link useAnnounce}: activating the button moves no
1303
+ * focus, so without a live region a screen-reader user hears nothing at all. A
1304
+ * failure goes through an ASSERTIVE region and a success through a polite one — the
1305
+ * failure is the news that changes what they do next.
1306
+ *
1307
+ * The icon variant keeps the SAME accessible name in every state. A button renaming
1308
+ * itself under the focus is announced inconsistently across readers; the live region
1309
+ * is the channel that is actually heard.
1310
+ */
1311
+ declare function CopyButton({ text, variant, label, buttonVariant, size, tone, stopPropagation, tooltipSide, tooltipPortal, resetAfter, onCopied, labels, className, disabled, ...rest }: CopyButtonProps): react.JSX.Element;
1312
+
1313
+ /** The words {@link FormActions} renders on its own behalf — the `form` namespace. */
1314
+ interface FormActionsLabels {
1315
+ save: string;
1316
+ cancel: string;
1317
+ }
1318
+ declare const DEFAULT_FORM_ACTIONS_LABELS: FormActionsLabels;
1319
+ type FormActionsAlign = "start" | "center" | "end" | "between";
1320
+ /**
1321
+ * Where the row sits:
1322
+ * - `inline` (default): under the form's last field, `pt-4` above it.
1323
+ * - `sticky`: stuck to the bottom of the scrolling page or pane, on the surface
1324
+ * colour with a rule above, clear of the phone's home indicator — for a long form
1325
+ * whose Save should not be a scroll away.
1326
+ * - `dialog`: no spacing of its own, for a dialog's footer slot (DialogFrame,
1327
+ * FullBleedDialog's `footer`), which already pads and rules it.
1328
+ */
1329
+ type FormActionsPlacement = "inline" | "sticky" | "dialog";
1330
+ /** The start-side action of {@link FormActionsProps.destructive}, as data. */
1331
+ interface FormActionsDestructive {
1332
+ label: ReactNode;
1333
+ onClick: () => void;
1334
+ disabled?: boolean;
1335
+ /** See Button's `disabledReason`. */
1336
+ disabledReason?: ReactNode;
1337
+ }
1338
+ interface FormActionsProps extends Omit<ComponentPropsWithoutRef<"div">, "children"> {
1339
+ /** Called by Cancel. Omitted: no Cancel button (a form that only saves). */
1340
+ onCancel?: () => void;
1341
+ /**
1342
+ * Given: the save button is `type="button"` and calls this — for a dialog or a row
1343
+ * that saves from a handler. Omitted: it is `type="submit"` and submits the
1344
+ * enclosing `<form>` (or the one named by {@link form}).
1345
+ */
1346
+ onSubmit?: () => void;
1347
+ /** The save button's text. Default `form.save` from the provider, else "Save". */
1348
+ submitLabel?: ReactNode;
1349
+ /** The cancel button's text. Default `form.cancel` from the provider, else "Cancel". */
1350
+ cancelLabel?: ReactNode;
1351
+ /** The save button's text while `pending` ("Saving…"). Default: `submitLabel`. */
1352
+ pendingLabel?: ReactNode;
1353
+ /**
1354
+ * The save is running: a spinner in the save button, `aria-busy` on it, and the
1355
+ * button disabled, so a second click cannot send the form twice. Cancel stays
1356
+ * usable.
1357
+ */
1358
+ pending?: boolean;
1359
+ /** Disable save for a reason of the form's own (an unbalanced entry). */
1360
+ submitDisabled?: boolean;
1361
+ /** Why save is disabled — see Button's `disabledReason`. Keeps it focusable. */
1362
+ submitDisabledReason?: ReactNode;
1363
+ /** The save button's variant. Default `brand`; `danger` for a save that destroys
1364
+ * (kastlan's `destructive` flag on its own FormActions). */
1365
+ submitVariant?: ButtonVariant;
1366
+ /**
1367
+ * A destructive action at the START of the row, apart from Save and Cancel — the
1368
+ * "Delete" of an edit dialog. Pass `{ label, onClick }` for the kit's quiet danger
1369
+ * button, or an element of your own (a {@link DangerConfirm}, say). With it the row
1370
+ * is `between`-aligned unless `align` says otherwise.
1371
+ */
1372
+ destructive?: ReactNode | FormActionsDestructive;
1373
+ /** Horizontal alignment. Default `end` (`between` with a `destructive` action). */
1374
+ align?: FormActionsAlign;
1375
+ /** See {@link FormActionsPlacement}. Default `inline`. */
1376
+ placement?: FormActionsPlacement;
1377
+ /** The `id` of the `<form>` the save button submits, when the row is rendered
1378
+ * outside it (a dialog's footer slot). */
1379
+ form?: string;
1380
+ /** Extra actions, placed before Cancel. */
1381
+ children?: ReactNode;
1382
+ }
1383
+ /**
1384
+ * The Save / Cancel row every form, wizard-step form and dialog ends in — kastlan's
1385
+ * `FormActions`, with the labels from the provider, a pending state and a place for
1386
+ * the destructive action.
1387
+ *
1388
+ * ```tsx
1389
+ * <form onSubmit={form.handleSubmit(save)}>
1390
+ * …
1391
+ * <FormActions onCancel={close} pending={mutation.isPending} />
1392
+ * </form>
1393
+ * ```
1394
+ *
1395
+ * The spinner is the kit's {@link Spinner}, drawn inside the button with `label={null}`
1396
+ * so the button's name stays its text; `aria-busy` is what says it is working.
1397
+ */
1398
+ declare function FormActions({ onCancel, onSubmit, submitLabel, cancelLabel, pendingLabel, pending, submitDisabled, submitDisabledReason, submitVariant, destructive, align, placement, form, children, className, style, ...rest }: FormActionsProps): react.JSX.Element;
1399
+
1400
+ /**
1401
+ * EVERY string the kit renders, as one typed tree — and an optional provider that
1402
+ * hands it to every component at once.
1403
+ *
1404
+ * WHY THIS EXISTS. The kit ships no catalogue and resolves no strings, and that stays
1405
+ * true: the app supplies every word. What was wrong was HOW it had to supply them —
1406
+ * one `labels` prop per component instance, under a different prop name per
1407
+ * component (`labels`, `calendarLabels`, `passwordLabels`, `clearLabel`,
1408
+ * `searchPlaceholder`, `ariaLabel` …). A translated app therefore rendered English
1409
+ * wherever one call site forgot one prop, and nothing told it so: a German showcase
1410
+ * still said "Rows per page", "Collapse sidebar" and "Popover" in seven languages.
1411
+ *
1412
+ * Now there is one key per string, addressed by a dot path (`dataTable.pageSize`,
1413
+ * `datePicker.today`), and three sources in a fixed order of precedence:
1414
+ *
1415
+ * the component's own prop > <UiKitProvider labels> > English default
1416
+ *
1417
+ * A consumer writes its translation ONCE, as a `UiKitLabels` (or a partial of it),
1418
+ * mounts `<UiKitProvider labels={…} locale={…}>` at the root, and every kit component
1419
+ * below speaks that language — including the ones nested inside other kit components
1420
+ * (the calendar inside the data table's date filter, the popover inside the date
1421
+ * picker), which no prop at the outer call site could reach before.
1422
+ *
1423
+ * Messages that carry a value are FUNCTIONS of that value, never a template to fill:
1424
+ * a number glued into an English sentence is untranslatable, because the grammar
1425
+ * around it moves with it in most languages.
1426
+ */
1427
+ /** The words several components share. A component's own namespace wins over these
1428
+ * where both exist; these are what a new component reaches for first. */
1429
+ interface CommonLabels {
1430
+ close: string;
1431
+ clear: string;
1432
+ search: string;
1433
+ done: string;
1434
+ cancel: string;
1435
+ save: string;
1436
+ back: string;
1437
+ next: string;
1438
+ remove: string;
1439
+ loading: string;
1440
+ noResults: string;
1441
+ /** "Name: value" — how a field's accessible name is composed with its value.
1442
+ * A colon-and-space is not universal punctuation (French puts a space before
1443
+ * the colon, Chinese uses a full-width one). */
1444
+ fieldValue: (field: string, value: string) => string;
1445
+ /** The × that puts away a banner or a notice (`AlertBanner onDismiss`). Not `close`:
1446
+ * nothing opened, and "Close" on a banner reads as closing the page it sits on. */
1447
+ dismiss: string;
1448
+ /** Read after a link that opens a new tab (`<Button href external>`, TextLink):
1449
+ * nothing else tells a screen reader the page is about to change tabs. */
1450
+ opensInNewTab: string;
1451
+ }
1452
+ /** `DatePicker` / `DateRangePicker` chrome. The calendar inside has its own
1453
+ * namespace, `miniCalendar`. */
1454
+ interface DatePickerLabels {
1455
+ /** Accessible name of the popover panel the calendar opens in. */
1456
+ panel: string;
1457
+ /** The same, for `DateRangePicker`'s panel. */
1458
+ rangePanel: string;
1459
+ clear: string;
1460
+ previousDay: string;
1461
+ nextDay: string;
1462
+ today: string;
1463
+ /** `DateRangePicker commit="apply"`: the button that commits the drafted range. */
1464
+ apply: string;
1465
+ /** …and the one that discards it. */
1466
+ cancel: string;
1467
+ /** Accessible name of `DateRangePicker`'s preset column. */
1468
+ presets: string;
1469
+ }
1470
+ /** The whole combobox family: `Combobox`, `EntityCombobox`,
1471
+ * `MultiEntityCombobox`, `InlineEntityCombobox`, `Autocomplete`. */
1472
+ interface ComboboxLabels {
1473
+ search: string;
1474
+ noResults: string;
1475
+ clear: string;
1476
+ loading: string;
1477
+ /** The "add this" row when free entry is allowed. */
1478
+ create: (query: string) => string;
1479
+ /** Trigger summary once more than one value is picked. */
1480
+ selectedCount: (count: number) => string;
1481
+ /** An async lookup (`loadOptions`) failed. */
1482
+ loadError: string;
1483
+ /** Announced (live region) when the list settles on `count` > 0 rows. */
1484
+ resultCount: (count: number) => string;
1485
+ /** The query is shorter than the `minChars` a lookup needs. */
1486
+ minChars: (count: number) => string;
1487
+ }
1488
+ interface MultiSelectLabels {
1489
+ search: string;
1490
+ selectAll: string;
1491
+ clear: string;
1492
+ /** Trigger text when nothing is picked, which a multi-select reads as "all". */
1493
+ all: string;
1494
+ /** Trigger summary once some (but not all) values are picked. The English
1495
+ * default is the bare count, which is what the trigger has always shown. */
1496
+ selectedCount: (count: number) => string;
1497
+ }
1498
+ /** `CalculatorButton` (desktop popover keypad) and `NumberPadSheet` (phone sheet). */
1499
+ interface CalculatorLabels {
1500
+ /** The button that opens the calculator. */
1501
+ open: string;
1502
+ /** The popover / sheet itself. */
1503
+ panel: string;
1504
+ calculation: string;
1505
+ backspace: string;
1506
+ clear: string;
1507
+ equals: string;
1508
+ /** The primary key of the phone pad — visible text, not an aria-label. */
1509
+ done: string;
1510
+ /** Names of the operator keys and the decimal key. Their glyphs are not names a
1511
+ * screen reader agrees on — "÷" is read as "division sign", "divided by" or
1512
+ * nothing at all depending on the reader — so each says what it does. */
1513
+ plus: string;
1514
+ minus: string;
1515
+ times: string;
1516
+ divide: string;
1517
+ decimal: string;
1518
+ }
1519
+ /** `CurrencySelect` and the currency half of `AmountInput`. */
1520
+ interface CurrencyLabels {
1521
+ currency: string;
1522
+ search: string;
1523
+ }
1524
+ interface AppShellLabels {
1525
+ collapse: string;
1526
+ expand: string;
1527
+ /** Accessible name of an inline group's disclosure button (`subNav="inline"`). */
1528
+ toggleGroup: (groupLabel: string) => string;
1529
+ }
1530
+ interface TopBarLabels {
1531
+ theme: string;
1532
+ palette: string;
1533
+ language: string;
1534
+ switchRole: string;
1535
+ /** The role switcher's tooltip, given the active role's name. */
1536
+ role: (value: string) => string;
1537
+ }
1538
+ interface PickerSheetLabels {
1539
+ close: string;
1540
+ }
1541
+ interface SwipeableRowLabels {
1542
+ actions: string;
1543
+ }
1544
+ interface FileLabels {
1545
+ /** A file size for display, given its size in BYTES. The default uses
1546
+ * `Intl.NumberFormat`'s unit formatting in the provider's locale. */
1547
+ size: (bytes: number) => string;
1548
+ }
1549
+ /** The complete tree. `UiKitProvider` takes any partial of it. */
1550
+ interface UiKitLabels {
1551
+ common: CommonLabels;
1552
+ dataTable: DataTableLabels;
1553
+ miniCalendar: MiniCalendarLabels;
1554
+ calendarHeatmap: CalendarHeatmapLabels;
1555
+ datePicker: DatePickerLabels;
1556
+ monthPicker: MonthPickerLabels;
1557
+ popover: PopoverLabels;
1558
+ combobox: ComboboxLabels;
1559
+ multiSelect: MultiSelectLabels;
1560
+ calculator: CalculatorLabels;
1561
+ currency: CurrencyLabels;
1562
+ chipInput: ChipInputLabels;
1563
+ fieldSync: FieldSyncLabels;
1564
+ passwordReveal: PasswordRevealLabels;
1565
+ tabs: TabsLabels;
1566
+ appShell: AppShellLabels;
1567
+ pageContents: PageContentsLabels;
1568
+ topBar: TopBarLabels;
1569
+ pickerSheet: PickerSheetLabels;
1570
+ swipeableRow: SwipeableRowLabels;
1571
+ file: FileLabels;
1572
+ wizard: WizardLabels;
1573
+ tour: TourLabels;
1574
+ commandPalette: CommandPaletteLabels;
1575
+ globalSearch: GlobalSearchLabels;
1576
+ seriesChart: SeriesChartLabels;
1577
+ pieChart: PieChartLabels;
1578
+ sparkline: SparklineLabels;
1579
+ statTile: StatTileLabels;
1580
+ signaturePad: SignaturePadLabels;
1581
+ passwordStrength: PasswordStrengthLabels;
1582
+ dangerConfirm: DangerConfirmLabels;
1583
+ swatchPicker: SwatchPickerLabels;
1584
+ iconPicker: IconPickerLabels;
1585
+ dialogFrame: DialogFrameLabels;
1586
+ filePicker: FilePickerLabels;
1587
+ measuredGrid: MeasuredGridLabels;
1588
+ feedbackAttachment: FeedbackAttachmentFieldLabels;
1589
+ /** 0.12.0: `FeedbackDialog`'s own strings; `FeedbackThread` / `FeedbackComposer`. */
1590
+ feedbackDialog: FeedbackDialogTextLabels;
1591
+ feedbackThread: FeedbackThreadLabels;
1592
+ feedbackComposer: FeedbackComposerLabels;
1593
+ /** 0.12.0: `ProfileSetting`, `PasswordSetting`, `TwoFactorSetting`, `PasskeysSetting`
1594
+ * — one record per section. The provider takes a whole section record; the
1595
+ * components merge it key by key, as a prop. */
1596
+ accountSettings: AccountSettingsLabels;
1597
+ confirmDialog: ConfirmDialogLabels;
1598
+ floatingPanel: FloatingPanelLabels;
1599
+ copyButton: CopyButtonLabels;
1600
+ bulkActionBar: BulkActionBarLabels;
1601
+ list: ListLabels;
1602
+ breadcrumbs: BreadcrumbsLabels;
1603
+ toast: ToastLabels;
1604
+ form: FormActionsLabels;
1605
+ descriptionList: DescriptionListLabels;
1606
+ lineItems: LineItemsLabels;
1607
+ progressBar: ProgressBarLabels;
1608
+ signedAmount: SignedAmountLabels;
1609
+ errorBoundary: ErrorBoundaryLabels;
1610
+ authedImage: AuthedImageLabels;
1611
+ imageGrid: ImageGridLabels;
1612
+ lightbox: LightboxLabels;
1613
+ }
1614
+ /**
1615
+ * A label override, as deep as the labels go: an object of labels (a namespace, or a
1616
+ * record inside one such as `accountSettings.passkeys` or `dataTable.presets`) may be
1617
+ * partial at every level, because the provider merges it key by key at every level.
1618
+ * A leaf stays as it is: a string, a function-valued label (`(n) => …`), and anything
1619
+ * that is not purely an object (a `ReactNode` union) is given whole.
1620
+ */
1621
+ type LabelOverride<T> = [T] extends [(...args: never[]) => unknown] ? T : [T] extends [readonly unknown[]] ? T : [T] extends [object] ? {
1622
+ [K in keyof T]?: LabelOverride<T[K]>;
1623
+ } : T;
1624
+ /** Any subset of the tree — each namespace may be partial, and so may any record
1625
+ * inside one (`accountSettings.passkeys`, `dataTable.presets`): they are merged key
1626
+ * by key, at every depth. */
1627
+ type UiKitLabelOverrides = {
1628
+ [K in keyof UiKitLabels]?: LabelOverride<UiKitLabels[K]>;
1629
+ };
1630
+ declare const DEFAULT_COMMON_LABELS: CommonLabels;
1631
+ declare const DEFAULT_DATE_PICKER_LABELS: DatePickerLabels;
1632
+ declare const DEFAULT_COMBOBOX_LABELS: ComboboxLabels;
1633
+ declare const DEFAULT_MULTI_SELECT_LABELS: MultiSelectLabels;
1634
+ declare const DEFAULT_CALCULATOR_LABELS: CalculatorLabels;
1635
+ declare const DEFAULT_CURRENCY_LABELS: CurrencyLabels;
1636
+ declare const DEFAULT_APP_SHELL_LABELS: AppShellLabels;
1637
+ declare const DEFAULT_TOP_BAR_LABELS: TopBarLabels;
1638
+ declare const DEFAULT_PICKER_SHEET_LABELS: PickerSheetLabels;
1639
+ declare const DEFAULT_SWIPEABLE_ROW_LABELS: SwipeableRowLabels;
1640
+ /** Formats with the locale the provider was given (see {@link useKitFileLabels}).
1641
+ * This static default has no locale to hand and falls back to the runtime's. */
1642
+ declare const DEFAULT_FILE_LABELS: FileLabels;
1643
+ /** "12 kB", "3.4 MB" — `Intl`'s unit formatting, so the digits, the decimal mark and
1644
+ * the unit's spelling all follow `locale`. */
1645
+ declare function formatFileSize(bytes: number, locale?: string): string;
1646
+ /**
1647
+ * What every kit link receives: the anchor attributes the kit decided (href, class,
1648
+ * aria-current, handlers, children, ref). An app's router link maps them —
1649
+ * `({ href, ...p }) => <Link to={href} {...p} />` — ONCE, on the provider.
1650
+ *
1651
+ * `replace` is the one prop that is not an anchor attribute: see there. Map it
1652
+ * explicitly if your router link is not react-router's —
1653
+ * `({ href, replace, ...p }) => <Link to={href} replace={replace} {...p} />` — and never
1654
+ * spread it onto a DOM `<a>`, where React warns about an unknown attribute.
1655
+ */
1656
+ type KitLinkProps = AnchorHTMLAttributes<HTMLAnchorElement> & {
1657
+ href: string;
1658
+ ref?: Ref<HTMLAnchorElement>;
1659
+ /**
1660
+ * Navigate by REPLACING the current history entry, so Back skips it — keksdose F1:
1661
+ * a one-shot URL (a magic-link landing, `?checkout=done`, a wizard's "finish" hop)
1662
+ * that must not come back when the user presses Back. Present ONLY when the kit link
1663
+ * was given `replace` (`true`), so a provider link that spreads its props onto a
1664
+ * plain `<a>` keeps working for every link that never asks for it. react-router's
1665
+ * `<Link>` takes the same prop, so `({ href, ...p }) => <Link to={href} {...p} />`
1666
+ * honours it with no change; any other router maps it to its own replace option.
1667
+ * The kit never puts it on a DOM element itself.
1668
+ */
1669
+ replace?: boolean;
1670
+ };
1671
+ type KitLinkComponent = (props: KitLinkProps) => ReactElement;
1672
+ interface UiKitProviderProps {
1673
+ /** Any part of {@link UiKitLabels}. Missing keys fall back to English. */
1674
+ labels?: UiKitLabelOverrides;
1675
+ /**
1676
+ * BCP 47 tag used by every kit component that formats something — dates, numbers,
1677
+ * month and weekday names, file sizes — when it is not handed a `locale` prop of
1678
+ * its own. Without a provider, and without a prop, those use the runtime default.
1679
+ */
1680
+ locale?: string;
1681
+ /**
1682
+ * The first day of the week (0 = Sunday, 1 = Monday …) for every calendar below —
1683
+ * `MiniCalendar` and the pickers built on it — that is not handed a `weekStartsOn`
1684
+ * of its own. Without it the week start follows `locale`'s week info.
1685
+ *
1686
+ * Separate from `locale` because the two are separate decisions: a German-built
1687
+ * app running in English (`locale="en"`) would otherwise start its weeks on Sunday,
1688
+ * and switching it to `en-GB` to get Monday changes every date and number format.
1689
+ */
1690
+ weekStartsOn?: WeekDay;
1691
+ /**
1692
+ * The app's router link, used by every kit component that renders an in-app link
1693
+ * and was not handed its own `renderLink` — so `renderLink` stops being repeated on
1694
+ * each ListItem, Breadcrumbs, StatTile, Chip… (kastlan). A component's own
1695
+ * `renderLink` wins; external links and in-page `#anchor`s stay plain `<a>`, and a
1696
+ * hash-router `#/path` is handed to it like `/path` (see `pickLinkRenderer`).
1697
+ */
1698
+ linkComponent?: KitLinkComponent;
1699
+ children: ReactNode;
1700
+ }
1701
+ /**
1702
+ * Hand every kit component below this point its strings and its locale.
1703
+ *
1704
+ * Optional: a component outside any provider behaves exactly as it did before the
1705
+ * provider existed. Nesting works as a merge — an inner provider overrides only what
1706
+ * it names, so a page can re-label one table's `dataTable.table` without restating
1707
+ * the language.
1708
+ */
1709
+ declare function UiKitProvider({ labels, locale, weekStartsOn, linkComponent, children, }: UiKitProviderProps): react.JSX.Element;
1710
+ /** What the nearest provider says about one namespace — `undefined` outside one.
1711
+ * For a component whose own resolver does more than a merge (the data table
1712
+ * derives `columnsCount` from `columns`): feed `{ ...overrides, ...props }` to it. */
1713
+ declare function useKitLabelOverrides<K extends keyof UiKitLabels>(ns: K): Partial<UiKitLabels[K]> | undefined;
1714
+ /**
1715
+ * One namespace, resolved: `defaults`, then the provider, then the component's own
1716
+ * `prop`. Record-valued keys are merged key by key at each step.
1717
+ */
1718
+ declare function useKitLabels<K extends keyof UiKitLabels>(ns: K, defaults: UiKitLabels[K], prop?: Partial<UiKitLabels[K]>): UiKitLabels[K];
1719
+ /** The component's own `locale` prop, else the provider's, else `undefined` (which
1720
+ * every `Intl` API reads as "the runtime's default"). */
1721
+ declare function useKitLocale(prop?: string): string | undefined;
1722
+ /** The week start the nearest `<UiKitProvider weekStartsOn>` pins, else `undefined`
1723
+ * (the caller then asks the locale). A component's own prop goes first:
1724
+ * `prop ?? useKitWeekStart()`. */
1725
+ /** The provider's router link, or `undefined` — a component's own `renderLink` should
1726
+ * win over it: `const Link = renderLink ?? useKitLink()`. */
1727
+ declare function useKitLink(): KitLinkComponent | undefined;
1728
+ declare function useKitWeekStart(): WeekDay | undefined;
1729
+ /** {@link DEFAULT_FILE_LABELS}, but formatting in the provider's locale. */
1730
+ declare function useKitFileLabels(prop?: Partial<FileLabels>): FileLabels;
1731
+ /**
1732
+ * The dot paths of every key `labels` does NOT supply — the ones a translated app is
1733
+ * still showing in English. Assert on it in the app's own test:
1734
+ *
1735
+ * expect(missingKitLabels(de)).toEqual([]);
1736
+ *
1737
+ * `reference` is the complete tree to check against — pass
1738
+ * {@link DEFAULT_UI_KIT_LABELS} (src/i18n/defaults.ts). It is a parameter rather than an import
1739
+ * because the defaults live beside their components, and this module is imported BY
1740
+ * those components.
1741
+ */
1742
+ declare function missingKitLabels(labels: UiKitLabelOverrides | undefined, reference: UiKitLabels): string[];
1743
+
1744
+ export { FIELD_TRIGGER as $, type AppShellLabels as A, Button as B, type CalculatorLabels as C, type CurrencyLabels as D, DEFAULT_APP_SHELL_LABELS as E, DEFAULT_CALCULATOR_LABELS as F, DEFAULT_COMBOBOX_LABELS as G, DEFAULT_COMMON_LABELS as H, DEFAULT_COPY_BUTTON_LABELS as I, DEFAULT_CURRENCY_LABELS as J, DEFAULT_DATE_PICKER_LABELS as K, DEFAULT_FILE_LABELS as L, DEFAULT_FILE_PICKER_LABELS as M, DEFAULT_FORM_ACTIONS_LABELS as N, DEFAULT_MULTI_SELECT_LABELS as O, DEFAULT_PASSWORD_REVEAL_LABELS as P, DEFAULT_PICKER_SHEET_LABELS as Q, DEFAULT_SWIPEABLE_ROW_LABELS as R, DEFAULT_TABS_LABELS as S, DEFAULT_TOP_BAR_LABELS as T, type DatePickerLabels as U, EmptyState as V, type EmptyStateProps as W, FIELD_BASE as X, FIELD_DISPLAY as Y, FIELD_FLOATING_PAD as Z, FIELD_INVALID as _, type ButtonClassesOptions as a, formatFileSize as a$, FIELD_WRITABLE_LOOK as a0, FLOATING_INPUT_CLASS as a1, FLOATING_LABEL_CLASS as a2, FLOATING_LABEL_STATIC as a3, FieldChevron as a4, type FieldChevronProps as a5, FieldHint as a6, type FieldHintProps as a7, FieldLabel as a8, type FieldLabelProps as a9, type KitLinkProps as aA, Label as aB, type LabelOverride as aC, type LabelProps as aD, type MultiSelectLabels as aE, PHONE_QUERY as aF, type PasswordRevealLabels as aG, type PickerSheetLabels as aH, Select as aI, type SelectProps as aJ, Spinner as aK, type SpinnerProps as aL, type SwipeableRowLabels as aM, type TabItem as aN, Tabs as aO, type TabsLabels as aP, type TabsProps as aQ, Textarea as aR, type TextareaProps as aS, type TopBarLabels as aT, type UiKitLabelOverrides as aU, type UiKitLabels as aV, UiKitProvider as aW, type UiKitProviderProps as aX, type UseFilePickerOptions as aY, type UseFilePickerReturn as aZ, buttonClasses as a_, FileButton as aa, type FileButtonProps as ab, type FileLabels as ac, type FilePickerLabels as ad, type FileRejection as ae, type FileRejectionReason as af, type FileScreenOptions as ag, FloatingField as ah, type FloatingFieldProps as ai, FormActions as aj, type FormActionsAlign as ak, type FormActionsDestructive as al, type FormActionsLabels as am, type FormActionsPlacement as an, type FormActionsProps as ao, IconButton as ap, type IconButtonGlyphSize as aq, type IconButtonLinkProps as ar, type IconButtonProps as as, type IconButtonSize as at, type IconButtonTone as au, type IconButtonToneColor as av, type IconButtonVariant as aw, Input as ax, type InputProps as ay, type KitLinkComponent as az, type ButtonLinkProps as b, matchesAccept as b0, missingKitLabels as b1, resolvePasswordRevealLabels as b2, useFilePicker as b3, useKitFileLabels as b4, useKitLabelOverrides as b5, useKitLabels as b6, useKitLink as b7, useKitLocale as b8, useKitWeekStart as b9, type FilePickHandler as ba, formatAccept as bb, judgePick as bc, screenFiles as bd, summariseRejections as be, type ButtonProps as c, type ButtonSize as d, type ButtonTone as e, type ButtonVariant as f, Card as g, CardAction as h, type CardActionProps as i, CardContent as j, type CardContentProps as k, CardDescription as l, type CardDescriptionProps as m, CardFooter as n, type CardFooterProps as o, CardHeader as p, type CardHeaderProps as q, type CardProps as r, CardTitle as s, type CardTitleProps as t, type CardTone as u, type ComboboxLabels as v, type CommonLabels as w, CopyButton as x, type CopyButtonLabels as y, type CopyButtonProps as z };