@eifi1/ui-kit 0.11.0 → 0.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (342) hide show
  1. package/README.md +57 -32
  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 +24 -4
  12. package/dist/components/amount-input.js +5 -2
  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/button-group.d.ts +51 -2
  21. package/dist/components/button-group.js +5 -1
  22. package/dist/components/button-group.js.map +1 -1
  23. package/dist/components/calculator.d.ts +15 -4
  24. package/dist/components/calendar-heatmap.d.ts +41 -3
  25. package/dist/components/calendar-heatmap.js +48 -7
  26. package/dist/components/calendar-heatmap.js.map +1 -1
  27. package/dist/components/chip.d.ts +2 -1
  28. package/dist/components/chip.js +6 -3
  29. package/dist/components/chip.js.map +1 -1
  30. package/dist/components/choice-card.d.ts +35 -3
  31. package/dist/components/choice-card.js +83 -25
  32. package/dist/components/choice-card.js.map +1 -1
  33. package/dist/components/combobox-core.js +2 -0
  34. package/dist/components/combobox-core.js.map +1 -1
  35. package/dist/components/combobox.d.ts +2 -2
  36. package/dist/components/combobox.js +31 -9
  37. package/dist/components/combobox.js.map +1 -1
  38. package/dist/components/copy-button.d.ts +51 -70
  39. package/dist/components/copy-button.js +1 -1
  40. package/dist/components/copy-button.js.map +1 -1
  41. package/dist/components/data-table-cells.d.ts +63 -0
  42. package/dist/components/data-table-cells.js +64 -0
  43. package/dist/components/data-table-cells.js.map +1 -0
  44. package/dist/components/data-table-filter-popover.d.ts +1 -1
  45. package/dist/components/data-table-filters.d.ts +1 -1
  46. package/dist/components/data-table-filters.js +31 -1
  47. package/dist/components/data-table-filters.js.map +1 -1
  48. package/dist/components/data-table-labels.d.ts +13 -0
  49. package/dist/components/data-table-labels.js +8 -1
  50. package/dist/components/data-table-labels.js.map +1 -1
  51. package/dist/components/data-table.d.ts +1 -1
  52. package/dist/components/data-table.js +102 -12
  53. package/dist/components/data-table.js.map +1 -1
  54. package/dist/components/date-picker.d.ts +3 -1
  55. package/dist/components/date-picker.js +15 -2
  56. package/dist/components/date-picker.js.map +1 -1
  57. package/dist/components/description-list.d.ts +23 -3
  58. package/dist/components/description-list.js +23 -3
  59. package/dist/components/description-list.js.map +1 -1
  60. package/dist/components/dialog-frame.d.ts +1 -0
  61. package/dist/components/entity-combobox.d.ts +1 -1
  62. package/dist/components/entity-combobox.js +18 -3
  63. package/dist/components/entity-combobox.js.map +1 -1
  64. package/dist/components/error-boundary.d.ts +92 -0
  65. package/dist/components/error-boundary.js +111 -0
  66. package/dist/components/error-boundary.js.map +1 -0
  67. package/dist/components/field.d.ts +30 -4
  68. package/dist/components/field.js +40 -17
  69. package/dist/components/field.js.map +1 -1
  70. package/dist/components/file-button.d.ts +50 -208
  71. package/dist/components/file-dropzone.d.ts +49 -2
  72. package/dist/components/floating-panel.d.ts +13 -4
  73. package/dist/components/floating-panel.js +7 -3
  74. package/dist/components/floating-panel.js.map +1 -1
  75. package/dist/components/form-actions.d.ts +53 -0
  76. package/dist/components/form-actions.js +98 -0
  77. package/dist/components/form-actions.js.map +1 -0
  78. package/dist/components/image-grid.d.ts +67 -0
  79. package/dist/components/image-grid.js +112 -0
  80. package/dist/components/image-grid.js.map +1 -0
  81. package/dist/components/lightbox.d.ts +104 -0
  82. package/dist/components/lightbox.js +229 -0
  83. package/dist/components/lightbox.js.map +1 -0
  84. package/dist/components/line-items.d.ts +114 -0
  85. package/dist/components/line-items.js +238 -0
  86. package/dist/components/line-items.js.map +1 -0
  87. package/dist/components/list.d.ts +23 -2
  88. package/dist/components/list.js +32 -13
  89. package/dist/components/list.js.map +1 -1
  90. package/dist/components/loading-state.d.ts +24 -0
  91. package/dist/components/loading-state.js +32 -0
  92. package/dist/components/loading-state.js.map +1 -0
  93. package/dist/components/menu-item.d.ts +2 -1
  94. package/dist/components/menu-item.js +5 -1
  95. package/dist/components/menu-item.js.map +1 -1
  96. package/dist/components/mini-calendar.d.ts +13 -1
  97. package/dist/components/mini-calendar.js +46 -2
  98. package/dist/components/mini-calendar.js.map +1 -1
  99. package/dist/components/modal.d.ts +20 -1
  100. package/dist/components/modal.js +26 -2
  101. package/dist/components/modal.js.map +1 -1
  102. package/dist/components/month-picker.d.ts +21 -1
  103. package/dist/components/month-picker.js +139 -26
  104. package/dist/components/month-picker.js.map +1 -1
  105. package/dist/components/multi-entity-combobox.d.ts +1 -1
  106. package/dist/components/multi-entity-combobox.js +19 -3
  107. package/dist/components/multi-entity-combobox.js.map +1 -1
  108. package/dist/components/nav-pills.d.ts +2 -1
  109. package/dist/components/nav-pills.js +5 -1
  110. package/dist/components/nav-pills.js.map +1 -1
  111. package/dist/components/number-field.d.ts +24 -5
  112. package/dist/components/number-field.js +5 -7
  113. package/dist/components/number-field.js.map +1 -1
  114. package/dist/components/number-input.d.ts +25 -5
  115. package/dist/components/number-input.js +7 -1
  116. package/dist/components/number-input.js.map +1 -1
  117. package/dist/components/numpad-sheet.d.ts +15 -4
  118. package/dist/components/passkeys-setting.d.ts +67 -0
  119. package/dist/components/passkeys-setting.js +214 -0
  120. package/dist/components/passkeys-setting.js.map +1 -0
  121. package/dist/components/picker-sheet.js +2 -1
  122. package/dist/components/picker-sheet.js.map +1 -1
  123. package/dist/components/pie-chart-labels.d.ts +20 -0
  124. package/dist/components/pie-chart-labels.js +12 -0
  125. package/dist/components/pie-chart-labels.js.map +1 -0
  126. package/dist/components/pie-chart.d.ts +101 -0
  127. package/dist/components/pie-chart.js +283 -0
  128. package/dist/components/pie-chart.js.map +1 -0
  129. package/dist/components/progress-bar.d.ts +44 -3
  130. package/dist/components/progress-bar.js +58 -22
  131. package/dist/components/progress-bar.js.map +1 -1
  132. package/dist/components/qr-code.d.ts +34 -0
  133. package/dist/components/qr-code.js +57 -0
  134. package/dist/components/qr-code.js.map +1 -0
  135. package/dist/components/search-field.d.ts +15 -3
  136. package/dist/components/search-field.js +5 -1
  137. package/dist/components/search-field.js.map +1 -1
  138. package/dist/components/settings-fields.d.ts +49 -1
  139. package/dist/components/signed-amount.d.ts +121 -0
  140. package/dist/components/signed-amount.js +152 -0
  141. package/dist/components/signed-amount.js.map +1 -0
  142. package/dist/components/skeleton.d.ts +10 -3
  143. package/dist/components/skeleton.js +12 -2
  144. package/dist/components/skeleton.js.map +1 -1
  145. package/dist/components/stat-tile.d.ts +2 -1
  146. package/dist/components/stat-tile.js +5 -2
  147. package/dist/components/stat-tile.js.map +1 -1
  148. package/dist/components/table.d.ts +39 -5
  149. package/dist/components/table.js +45 -11
  150. package/dist/components/table.js.map +1 -1
  151. package/dist/components/text-link.d.ts +176 -0
  152. package/dist/components/text-link.js +109 -0
  153. package/dist/components/text-link.js.map +1 -0
  154. package/dist/components/time-input.d.ts +49 -1
  155. package/dist/components/toggle-group.d.ts +13 -0
  156. package/dist/components/toggle-group.js +35 -6
  157. package/dist/components/toggle-group.js.map +1 -1
  158. package/dist/components/toggle-legend.d.ts +36 -3
  159. package/dist/components/toggle-legend.js +49 -0
  160. package/dist/components/toggle-legend.js.map +1 -1
  161. package/dist/components/ui.d.ts +51 -779
  162. package/dist/components/ui.js +215 -23
  163. package/dist/components/ui.js.map +1 -1
  164. package/dist/components/use-table-state.d.ts +3 -52
  165. package/dist/components/use-table-state.js +128 -30
  166. package/dist/components/use-table-state.js.map +1 -1
  167. package/dist/{data-table-filters-Dh9uF_S-.d.ts → data-table-Drun8Fk9.d.ts} +305 -61
  168. package/dist/data-table.d.ts +2 -1
  169. package/dist/data-table.js +5 -1
  170. package/dist/data-table.js.map +1 -1
  171. package/dist/feedback/feedback-dialog.d.ts +26 -3
  172. package/dist/feedback/feedback-dialog.js +16 -2
  173. package/dist/feedback/feedback-dialog.js.map +1 -1
  174. package/dist/feedback/feedback-thread.d.ts +133 -0
  175. package/dist/feedback/feedback-thread.js +237 -0
  176. package/dist/feedback/feedback-thread.js.map +1 -0
  177. package/dist/feedback.d.ts +2 -1
  178. package/dist/feedback.js +1 -0
  179. package/dist/feedback.js.map +1 -1
  180. package/dist/hooks/use-authed-src.d.ts +71 -0
  181. package/dist/hooks/use-authed-src.js +63 -0
  182. package/dist/hooks/use-authed-src.js.map +1 -0
  183. package/dist/hooks/use-file-drop.d.ts +49 -2
  184. package/dist/hooks/use-hotkey.d.ts +59 -0
  185. package/dist/hooks/use-hotkey.js +105 -0
  186. package/dist/hooks/use-hotkey.js.map +1 -0
  187. package/dist/hooks/use-search-param-state.d.ts +83 -0
  188. package/dist/hooks/use-search-param-state.js +94 -0
  189. package/dist/hooks/use-search-param-state.js.map +1 -0
  190. package/dist/i18n/defaults.d.ts +15 -4
  191. package/dist/i18n/defaults.js +31 -1
  192. package/dist/i18n/defaults.js.map +1 -1
  193. package/dist/i18n/kit-labels.d.ts +46 -312
  194. package/dist/i18n/kit-labels.js +20 -5
  195. package/dist/i18n/kit-labels.js.map +1 -1
  196. package/dist/i18n/locales/de-CH-informal.d.ts +15 -4
  197. package/dist/i18n/locales/de-CH.d.ts +15 -4
  198. package/dist/i18n/locales/de-informal.d.ts +15 -4
  199. package/dist/i18n/locales/de.d.ts +15 -4
  200. package/dist/i18n/locales/de.js +156 -3
  201. package/dist/i18n/locales/de.js.map +1 -1
  202. package/dist/i18n/locales/es.d.ts +15 -4
  203. package/dist/i18n/locales/es.js +153 -3
  204. package/dist/i18n/locales/es.js.map +1 -1
  205. package/dist/i18n/locales/fr.d.ts +15 -4
  206. package/dist/i18n/locales/fr.js +153 -3
  207. package/dist/i18n/locales/fr.js.map +1 -1
  208. package/dist/i18n/locales/hu.d.ts +15 -4
  209. package/dist/i18n/locales/hu.js +153 -3
  210. package/dist/i18n/locales/hu.js.map +1 -1
  211. package/dist/i18n/locales/it.d.ts +15 -4
  212. package/dist/i18n/locales/it.js +153 -3
  213. package/dist/i18n/locales/it.js.map +1 -1
  214. package/dist/i18n/locales/zh.d.ts +15 -4
  215. package/dist/i18n/locales/zh.js +153 -3
  216. package/dist/i18n/locales/zh.js.map +1 -1
  217. package/dist/index.d.ts +37 -17
  218. package/dist/index.js +40 -1
  219. package/dist/index.js.map +1 -1
  220. package/dist/kit-labels-v3biUF1L.d.ts +1645 -0
  221. package/dist/lib/format.d.ts +144 -0
  222. package/dist/lib/format.js +149 -0
  223. package/dist/lib/format.js.map +1 -0
  224. package/dist/lib/qr-encode.d.ts +44 -0
  225. package/dist/lib/qr-encode.js +338 -0
  226. package/dist/lib/qr-encode.js.map +1 -0
  227. package/dist/rhf/fields.d.ts +241 -0
  228. package/dist/rhf/fields.js +601 -0
  229. package/dist/rhf/fields.js.map +1 -0
  230. package/dist/rhf/form.d.ts +49 -1
  231. package/dist/rhf/line-items.d.ts +31 -0
  232. package/dist/rhf/line-items.js +59 -0
  233. package/dist/rhf/line-items.js.map +1 -0
  234. package/dist/rhf.d.ts +61 -1
  235. package/dist/rhf.js +2 -0
  236. package/dist/rhf.js.map +1 -1
  237. package/dist/shell/app-shell.d.ts +99 -2
  238. package/dist/shell/app-shell.js +58 -3
  239. package/dist/shell/app-shell.js.map +1 -1
  240. package/dist/shell/auth-layout.d.ts +74 -0
  241. package/dist/shell/auth-layout.js +97 -0
  242. package/dist/shell/auth-layout.js.map +1 -0
  243. package/dist/shell/top-bar-brand.d.ts +81 -0
  244. package/dist/shell/top-bar-brand.js +47 -0
  245. package/dist/shell/top-bar-brand.js.map +1 -0
  246. package/dist/shell/topbar-action-menu.d.ts +12 -1
  247. package/dist/shell/topbar-action-menu.js +16 -1
  248. package/dist/shell/topbar-action-menu.js.map +1 -1
  249. package/dist/shell.d.ts +56 -1
  250. package/dist/shell.js +2 -0
  251. package/dist/shell.js.map +1 -1
  252. package/dist/wizard/stepper-nav.d.ts +48 -1
  253. package/dist/wizard/wizard-context.d.ts +24 -1
  254. package/dist/wizard/wizard-context.js +19 -2
  255. package/dist/wizard/wizard-context.js.map +1 -1
  256. package/dist/wizard/wizard-step.d.ts +20 -5
  257. package/dist/wizard/wizard-step.js +16 -3
  258. package/dist/wizard/wizard-step.js.map +1 -1
  259. package/dist/wizard.d.ts +50 -3
  260. package/package.json +1 -1
  261. package/src/chart.ts +4 -0
  262. package/src/components/account-settings-labels.ts +190 -0
  263. package/src/components/account-settings.tsx +110 -63
  264. package/src/components/amount-input.tsx +13 -1
  265. package/src/components/authed-image.tsx +190 -0
  266. package/src/components/breadcrumbs.tsx +8 -2
  267. package/src/components/button-group.tsx +7 -2
  268. package/src/components/calendar-heatmap.tsx +105 -10
  269. package/src/components/chip.tsx +8 -4
  270. package/src/components/choice-card.tsx +136 -25
  271. package/src/components/combobox-core.tsx +5 -0
  272. package/src/components/combobox.tsx +31 -9
  273. package/src/components/copy-button.tsx +3 -2
  274. package/src/components/data-table-cells.tsx +119 -0
  275. package/src/components/data-table-filters.ts +77 -0
  276. package/src/components/data-table-labels.ts +23 -0
  277. package/src/components/data-table.tsx +224 -13
  278. package/src/components/date-picker.tsx +19 -3
  279. package/src/components/description-list.tsx +52 -3
  280. package/src/components/entity-combobox.tsx +18 -3
  281. package/src/components/error-boundary.tsx +216 -0
  282. package/src/components/field.tsx +56 -5
  283. package/src/components/floating-panel.tsx +20 -7
  284. package/src/components/form-actions.tsx +191 -0
  285. package/src/components/image-grid.tsx +181 -0
  286. package/src/components/lightbox.tsx +374 -0
  287. package/src/components/line-items.tsx +385 -0
  288. package/src/components/list.tsx +65 -16
  289. package/src/components/loading-state.tsx +47 -0
  290. package/src/components/menu-item.tsx +7 -2
  291. package/src/components/mini-calendar.tsx +72 -3
  292. package/src/components/modal.tsx +61 -3
  293. package/src/components/month-picker.tsx +181 -26
  294. package/src/components/multi-entity-combobox.tsx +19 -3
  295. package/src/components/nav-pills.tsx +8 -1
  296. package/src/components/number-field.tsx +15 -10
  297. package/src/components/number-input.tsx +16 -1
  298. package/src/components/passkeys-setting.tsx +331 -0
  299. package/src/components/picker-sheet.tsx +7 -1
  300. package/src/components/pie-chart-labels.ts +32 -0
  301. package/src/components/pie-chart.tsx +479 -0
  302. package/src/components/progress-bar.tsx +129 -19
  303. package/src/components/qr-code.tsx +83 -0
  304. package/src/components/search-field.tsx +20 -4
  305. package/src/components/signed-amount.tsx +286 -0
  306. package/src/components/skeleton.tsx +21 -3
  307. package/src/components/stat-tile.tsx +8 -2
  308. package/src/components/table.tsx +102 -13
  309. package/src/components/text-link.tsx +248 -0
  310. package/src/components/toggle-group.tsx +58 -10
  311. package/src/components/toggle-legend.tsx +87 -2
  312. package/src/components/ui.tsx +426 -36
  313. package/src/components/use-table-state.ts +265 -33
  314. package/src/data-table.ts +10 -0
  315. package/src/feedback/feedback-dialog.tsx +42 -3
  316. package/src/feedback/feedback-thread.tsx +434 -0
  317. package/src/feedback.ts +1 -0
  318. package/src/hooks/use-authed-src.ts +161 -0
  319. package/src/hooks/use-hotkey.ts +172 -0
  320. package/src/hooks/use-search-param-state.ts +187 -0
  321. package/src/i18n/defaults.ts +30 -0
  322. package/src/i18n/kit-labels.tsx +102 -8
  323. package/src/i18n/locales/de.ts +153 -0
  324. package/src/i18n/locales/es.ts +150 -0
  325. package/src/i18n/locales/fr.ts +153 -0
  326. package/src/i18n/locales/hu.ts +151 -0
  327. package/src/i18n/locales/it.ts +151 -0
  328. package/src/i18n/locales/zh.ts +150 -0
  329. package/src/index.ts +59 -0
  330. package/src/lib/format.ts +325 -0
  331. package/src/lib/qr-encode.ts +420 -0
  332. package/src/rhf/fields.tsx +1054 -0
  333. package/src/rhf/line-items.tsx +130 -0
  334. package/src/rhf.ts +6 -0
  335. package/src/shell/app-shell.tsx +147 -2
  336. package/src/shell/auth-layout.tsx +190 -0
  337. package/src/shell/top-bar-brand.tsx +76 -0
  338. package/src/shell/topbar-action-menu.tsx +21 -0
  339. package/src/shell.ts +2 -0
  340. package/src/wizard/wizard-context.tsx +41 -1
  341. package/src/wizard/wizard-step.tsx +43 -6
  342. package/tokens.css +12 -0
@@ -1,781 +1,53 @@
1
- import * as react from 'react';
2
- import { ButtonHTMLAttributes, Ref, ReactNode, SelectHTMLAttributes, InputHTMLAttributes, ComponentPropsWithoutRef, TextareaHTMLAttributes } from 'react';
3
- import { TooltipSide } from './tooltip.js';
1
+ import 'react';
2
+ import './tooltip.js';
3
+ export { B as Button, a as ButtonClassesOptions, b as ButtonLinkProps, c as ButtonProps, d as ButtonSize, e as ButtonTone, f as ButtonVariant, g as Card, h as CardAction, i as CardActionProps, j as CardContent, k as CardContentProps, l as CardDescription, m as CardDescriptionProps, n as CardFooter, o as CardFooterProps, p as CardHeader, q as CardHeaderProps, r as CardProps, s as CardTitle, t as CardTitleProps, u as CardTone, P as DEFAULT_PASSWORD_REVEAL_LABELS, S as DEFAULT_TABS_LABELS, V as EmptyState, W as EmptyStateProps, X as FIELD_BASE, Y as FIELD_DISPLAY, Z as FIELD_FLOATING_PAD, _ as FIELD_INVALID, $ as FIELD_TRIGGER, a0 as FIELD_WRITABLE_LOOK, a1 as FLOATING_INPUT_CLASS, a2 as FLOATING_LABEL_CLASS, a3 as FLOATING_LABEL_STATIC, a4 as FieldChevron, a5 as FieldChevronProps, a6 as FieldHint, a7 as FieldHintProps, a8 as FieldLabel, a9 as FieldLabelProps, ah as FloatingField, ai as FloatingFieldProps, ap as IconButton, aq as IconButtonGlyphSize, ar as IconButtonProps, as as IconButtonSize, at as IconButtonTone, au as IconButtonVariant, av as Input, aw as InputProps, az as Label, aB as LabelProps, aD as PHONE_QUERY, aE as PasswordRevealLabels, aG as Select, aH as SelectProps, aI as Spinner, aJ as SpinnerProps, aL as TabItem, aM as Tabs, aN as TabsLabels, aO as TabsProps, aP as Textarea, aQ as TextareaProps, aY as buttonClasses, b0 as resolvePasswordRevealLabels } from '../kit-labels-v3biUF1L.js';
4
4
  import '../hooks/use-anchored-rect.js';
5
5
  import '../lib/clipping.js';
6
-
7
- type ButtonVariant = "primary" | "secondary" | "ghost" | "danger" | "brand" | "link";
8
- type ButtonSize = "sm" | "md";
9
- /**
10
- * The text colour of a `link` or `ghost` button, over the variant's own.
11
- *
12
- * `muted` is the QUIET link keksdose hand-rolls in six places (tours-page:321's "Mark
13
- * undone", transaction-fields:176/215's "Add line" / "Fill total",
14
- * invoice-lines-table:376, invoice-review:464): secondary text that darkens to the
15
- * body colour under the pointer, for an action that must be findable but not compete
16
- * with the brand-coloured one beside it. `danger` is the same quiet look turning
17
- * `--danger` on hover — transaction-editor:401's "Remove split", which is a text
18
- * action that destroys something.
19
- *
20
- * A `tone` rather than a `link-muted` variant, because it is the axis IconButton
21
- * already has (`tone="muted"`, `tone="danger"`, same quiet-until-hover meaning), and
22
- * because the same two looks are wanted on `ghost`. The filled and bordered variants
23
- * carry their meaning in the box, not the text, so a tone on them does nothing.
24
- */
25
- type ButtonTone = "default" | "muted" | "danger";
26
- /** The second argument of {@link buttonClasses} in its options form. */
27
- interface ButtonClassesOptions {
28
- /** See {@link ButtonProps.size}. */
29
- size?: ButtonSize;
30
- className?: string;
31
- }
32
- /**
33
- * Button classes for the rare case where the styling must land on a non-`<button>`
34
- * element that {@link Button} can't render — e.g. a router `<Link>` or a Radix
35
- * AlertDialog Action/Cancel (which must stay the Radix element). Everywhere a real
36
- * button works, prefer `<Button>`. Draws from the same base, size and variant maps as
37
- * `<Button>`, so the two stay in lockstep.
38
- *
39
- * The second argument is either the extra classes (the pre-0.8.0 form) or
40
- * `{ size, className }` — `buttonClasses("secondary", { size: "sm" })` for keksdose's
41
- * compact toolbar links.
42
- */
43
- declare function buttonClasses(variant?: ButtonVariant, classNameOrOptions?: string | ButtonClassesOptions): string;
44
- /**
45
- * ⚠️ No default `type`: like a native `<button>`, a `Button` inside a `<form>` SUBMITS
46
- * it unless you pass `type="button"`. Kept native on purpose — a form's own submit
47
- * button relies on it, and changing the default would silently stop those forms from
48
- * submitting. Pass `type="button"` for every other action in a form (keksdose had six
49
- * link buttons submitting their form).
50
- */
51
- interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
52
- variant?: ButtonVariant;
53
- /** `md` (default) is the page's action button. `sm` is the compact one — 12px text
54
- * and `px-2 py-1` — for the secondary actions in a toolbar, a card header or a
55
- * table's header row (keksdose writes `px-2 py-1 text-xs` over `secondary` by hand
56
- * there). Every variant takes either size. */
57
- size?: ButtonSize;
58
- /** In a flex row next to a taller labelled field, fill the field's height so the
59
- * two line up. No effect outside a flex row. */
60
- stretch?: boolean;
61
- /** The `<button>` element. React 19 passes `ref` to a function component as an
62
- * ordinary prop, so it rides `...rest` onto the element with no `forwardRef` —
63
- * declared here only because `ButtonHTMLAttributes` does not carry it. */
64
- ref?: Ref<HTMLButtonElement>;
65
- /** Text colour for `link` and `ghost`: `muted` (quiet, body colour on hover) or
66
- * `danger` (quiet, `--danger` on hover). Ignored by the other variants. See
67
- * {@link ButtonTone}. */
68
- tone?: ButtonTone;
69
- /**
70
- * Make it a toggle button, as {@link IconButton}'s `pressed` does. `true` sets
71
- * `aria-pressed="true"` and draws the "on" look — on a `link`, the brand colour and a
72
- * medium weight; on the boxed variants, the quiet brand fill IconButton uses — and
73
- * `false` sets `aria-pressed="false"` with the ordinary look. Left out, no
74
- * `aria-pressed` (or the caller's own). For lenkbank's "All speeds" text toggle
75
- * (gear/hysteresis-charts.tsx:482), which is a hand-rolled `<button aria-pressed>`
76
- * swapping two class strings; with `variant="link" tone="muted"` it is this.
77
- */
78
- pressed?: boolean;
79
- /**
80
- * Why the action is not available — the button's half of DangerConfirm's
81
- * `lockedReason` (kastlan handover-detail-page.tsx:197 locks a signed handover).
82
- *
83
- * A disabled button that cannot say why is a dead end: the native `disabled` takes it
84
- * out of the tab order, so a keyboard user never lands on it, and a pointer gets a
85
- * `not-allowed` cursor and nothing else. With a reason the button is `aria-disabled`
86
- * instead — still focusable, still hoverable — clicks (and the Enter/Space and form
87
- * submission they stand for) do nothing, and the reason is shown in the kit
88
- * {@link Tooltip} and attached with `aria-describedby`, so a screen reader hears it
89
- * on focus. It wins over `disabled`: passing both keeps the button reachable, which is
90
- * the point of giving a reason.
91
- *
92
- * The description is a `hidden` copy of the reason rather than the bubble itself: the
93
- * bubble exists only while hovered or focused once it is portalled, and a description
94
- * that comes and goes is read inconsistently. The bubble is kept visual, the way
95
- * DangerConfirm keeps its own.
96
- */
97
- disabledReason?: ReactNode;
98
- }
99
- declare function Button({ variant, size, stretch, tone, pressed, disabledReason, className, onClick, ...rest }: ButtonProps): react.JSX.Element;
100
- type IconButtonSize = "xl" | "lg" | "md" | "sm" | "xs" | "2xs";
101
- type IconButtonTone = "default" | "muted" | "danger" | "warning" | "info" | "success" | "custom";
102
- interface IconButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
103
- /** Any {@link ButtonVariant}, or `overlay` — a round translucent disc for use over
104
- * an image (see `ICON_BUTTON_OVERLAY`). */
105
- variant?: ButtonVariant | "overlay";
106
- /** Box size: lg = 44px (a phone's touch target), md = 36px (matches the top bar),
107
- * sm = 32px — all three with a 20px icon;
108
- * xs = 28px with a 16px icon (an action in a list row), 2xs = 24px with a 14px
109
- * icon (an action on a chip or a tab). */
110
- size?: IconButtonSize;
111
- /**
112
- * Fill the height of the flex row it stands in, keeping its width — the delete at
113
- * the end of keksdose's split line (transaction-fields:162), level with the labelled
114
- * fields beside it rather than floating at half their height. {@link Button}'s
115
- * `stretch`; no effect outside a flex row. With a `label`, the tooltip's wrapper is
116
- * what stretches.
117
- */
118
- stretch?: boolean;
119
- /** `round`: a circle instead of the rounded square — a compact status chip in a top
120
- * bar (keksdose's sync indicator). `overlay` is always round. */
121
- shape?: "square" | "round";
122
- /**
123
- * The colour of `tone="custom"`, as any CSS colour — a token (`var(--warning)`),
124
- * ideally. Sets `--icon-button-tone` on the button, so passing a different one per
125
- * state re-colours the glyph, its hover fill and its focus ring together. Passing it
126
- * implies `tone="custom"`. Or leave it out and set the variable yourself from a class
127
- * (`className="[--icon-button-tone:var(--success)]"`).
128
- */
129
- toneColor?: string;
130
- /** Glyph colour over the variant. `muted`: placeholder grey, full text colour on
131
- * hover. `danger`: the same grey at rest, `--danger` on hover and focus — for a
132
- * remove/delete that repeats down a list. `warning`: amber at rest — a flag that
133
- * wants attention. `info`: sky at rest — a notice-worthy but harmless action
134
- * (keksdose's reconcile). `success`: green at rest — a good state worth seeing.
135
- * `custom`: the colour of `toneColor` (see there). Default: the variant's own colours. See `quiet` for
136
- * turning a coloured tone's resting look the other way. */
137
- tone?: IconButtonTone;
138
- /**
139
- * Whether a coloured tone (`danger`, `warning`, `info`) waits for the pointer:
140
- * `true` is placeholder grey at rest and the tone's colour on hover and focus;
141
- * `false` wears the tone's colour at rest. Left out, each tone keeps its own
142
- * default — `danger` quiet, `warning` and `info` not. Ignored for `muted` (quiet by
143
- * definition) and `default` (no tone).
144
- *
145
- * `quiet={false}` on `danger` is for a destructive action that stands ALONE, where
146
- * hover-to-reveal hides it: keksdose's phone bulk bar (mobile-bulk-bar.tsx) has one
147
- * "delete all" and a touch screen that never hovers, so it painted rose by hand.
148
- * `quiet` on `warning`/`info` is the same switch the other way, for a flag repeated
149
- * down a list. A boolean over the tone rather than a new tone (`danger-solid`) or an
150
- * `emphasis` scale: there are exactly two resting looks, every coloured tone has
151
- * both, and which one fits is a question about the SITE (alone or repeated, touch or
152
- * pointer), not about the tone — so it is one switch the family shares.
153
- */
154
- quiet?: boolean;
155
- /**
156
- * Make it a toggle button. `true` sets `aria-pressed="true"` and draws the "on" look
157
- * (brand glyph on the quiet brand fill); `false` sets `aria-pressed="false"` with the
158
- * normal look, so a screen reader still hears a toggle that is off. Left out, it is
159
- * an ordinary button with no `aria-pressed` — or whatever `aria-pressed` the caller
160
- * passes. For keksdose's budget share toggle (budgets-page:291), which paints its own
161
- * brand colour over a ghost button today. Keep the `aria-label` the same in both
162
- * states ("Share budget"): the pressed state already says whether it is on.
163
- */
164
- pressed?: boolean;
165
- /** Keep the click (and the Enter/Space that produces it) from reaching an
166
- * ancestor's handler — for an action inside a clickable table row or card.
167
- *
168
- * **Not a licence to put this inside another `<button>`.** HTML forbids any
169
- * interactive content, and any element with a `tabindex`, inside a button — so
170
- * the `<span role="button" tabIndex={0}>` workaround is invalid too — and ARIA
171
- * makes a button's children presentational, so a screen reader flattens the
172
- * inner one into the outer one's name and it cannot be reached at all. Make the
173
- * row's main action a button that fills the row and put this one BESIDE it, on
174
- * top:
175
- *
176
- * ```tsx
177
- * <div className="relative">
178
- * <button className="w-full pe-16 …" onClick={open}>…row…</button>
179
- * <div className="absolute inset-y-0 end-2 flex items-center gap-0.5">
180
- * <IconButton size="xs" tone="danger" aria-label="Delete" onClick={remove}>
181
- * <Trash2 />
182
- * </IconButton>
183
- * </div>
184
- * </div>
185
- * ```
186
- *
187
- * The pair look exactly like the nested version, click exactly like it, and
188
- * are two tab stops a screen reader can tell apart. */
189
- stopPropagation?: boolean;
190
- /**
191
- * The button's name, said once: it becomes the `aria-label` AND the text of a kit
192
- * {@link Tooltip} round the button.
193
- *
194
- * Every app writes the pair by hand. lenkbank's nine icon actions all carry
195
- * `aria-label={t(x)} title={t(x)}` (shared/lib/table-columns.tsx:103,
196
- * projects-page.tsx:83, setpoint/segment-list.tsx:198/207, profile-bar.tsx:128/142,
197
- * shortcut-dialog.tsx:815/857/1007) — and `title` is the browser's own tooltip,
198
- * which shows late, never on focus and never on touch, and looks like no other
199
- * label in the kit. kastlan's RowAction (shared/components/data-table/row-action.tsx)
200
- * and keksdose's icon wrappers each exist to put a Tooltip round an IconButton.
201
- *
202
- * A caller's own `aria-label` still wins, for the rare name that should be longer
203
- * than the bubble. The bubble is visual only: it would otherwise describe the button
204
- * with its own name, and a screen reader would read the same word twice. Tooltip's
205
- * default placement applies — in place, or portalled inside a scroll container —
206
- * unless `tooltipPortal` says otherwise.
207
- */
208
- label?: string;
209
- /** Show `label` as a tooltip. Default `true`; `false` keeps `label` as the
210
- * accessible name only — for a button whose glyph is universally read (a close ✕ in
211
- * a dialog header) or that already sits under a tooltip of its own. */
212
- tooltip?: boolean;
213
- /** Where the `label` tooltip opens. See {@link Tooltip}'s `side`. */
214
- tooltipSide?: TooltipSide;
215
- /** Passed to the `label` tooltip's `portal`. Left out, Tooltip decides (see there). */
216
- tooltipPortal?: boolean;
217
- /**
218
- * Why the action is not available — {@link Button}'s `disabledReason`, on the icon
219
- * button: keksdose's accounts page cannot hide an account with a balance or delete
220
- * one with bookings (accounts-page:879/913 — "hide requires zero", "delete blocked"),
221
- * nor its budgets page delete the only budget (budgets-page:330), and each wrapped a
222
- * natively disabled IconButton in a Tooltip that swaps its label for the reason — a
223
- * bubble a keyboard never opens, since `disabled` leaves the tab order, and a reason
224
- * a screen reader never hears.
225
- *
226
- * The same contract as Button's: `aria-disabled` instead of `disabled`, so it stays
227
- * focusable and hoverable; clicks (and the Enter/Space they stand for) are swallowed
228
- * — `stopPropagation` still applies, so a locked action in a clickable row does not
229
- * open the row either; the reason shows in the kit {@link Tooltip} and is attached
230
- * through `aria-describedby` as a `hidden` copy. The NAME stays `aria-label` /
231
- * `label` — "Delete", described by "The active budget cannot be deleted" — and the
232
- * bubble shows the reason in place of the label, since the glyph already says what
233
- * the button is and the reason is the news. Shown even with `tooltip={false}`: a
234
- * reason nobody can see is not one. Wins over `disabled`, as on Button.
235
- */
236
- disabledReason?: ReactNode;
237
- }
238
- declare const IconButton: react.ForwardRefExoticComponent<IconButtonProps & react.RefAttributes<HTMLButtonElement>>;
239
- declare const FIELD_BASE: string;
240
- /** Cancels FIELD_BASE's read-only treatment for a field that is `readOnly` for a
241
- * reason other than "you may not edit this". Attribute-scoped for the same reason
242
- * FIELD_BASE is — the two have to cancel on the identical selector or twMerge
243
- * cannot make the later one win. */
244
- declare const FIELD_WRITABLE_LOOK = "[&[readonly]]:bg-[var(--bg-surface)] [&[readonly]]:text-[var(--text-primary)]";
245
- declare const FIELD_FLOATING_PAD = "pt-4 pb-1";
246
- declare const FIELD_INVALID = "border-[var(--danger-border)] ring-1 ring-[var(--danger-border)] focus:border-[var(--danger)] focus:ring-[var(--danger)]";
247
- declare const FLOATING_INPUT_CLASS: string;
248
- /** The phone breakpoint the display treatment below keys off — the same one the
249
- * numpad sheet uses, kept in one place so the two can't drift apart. */
250
- declare const PHONE_QUERY = "(max-width: 767px)";
251
- /**
252
- * The PHONE display treatment (Keksdose feedback #176, carried across the entry
253
- * forms by #179): the field chrome removed so the ONE input a form is actually
254
- * about reads as the thing itself, not as another boxed row in a stack.
255
- *
256
- * Every control that takes `variant="display"` — {@link Input}, `NumberInput`,
257
- * `AmountInput` — means exactly the same thing by it: the treatment applies below
258
- * {@link PHONE_QUERY} and the field is untouched above it, so a caller never has
259
- * to ask the viewport, and a form can't end up half-treated across breakpoints.
260
- *
261
- * What stays, deliberately:
262
- * - A hairline baseline. With the box gone something still has to say "you can
263
- * type here"; it takes the brand colour on focus, where a bordered field would
264
- * light its whole outline. Each edge is set exactly once (`border-x-0
265
- * border-t-0 border-b`) so the rule can't hinge on utility order.
266
- * - The muted placeholder colour, because a display field is usually empty at
267
- * the moment it matters most and has nothing else to show.
268
- * - The label, moved to `sr-only` rather than dropped: display type is legible
269
- * to the eye, not to a screen reader.
270
- *
271
- * Size and weight are NOT here — an amount wants display type, a subject line
272
- * wants a heading — so each control adds its own on top.
273
- */
274
- 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";
275
- declare const FIELD_TRIGGER: string;
276
- /** An alias rather than an interface: the chevron adds nothing of its own to an
277
- * `<svg>`'s props, and an interface declaring no members is the same type wearing a
278
- * name that suggests otherwise. */
279
- type FieldChevronProps = Omit<ComponentPropsWithoutRef<"svg">, "children">;
280
- /** The dropdown chevron, shared by the native {@link Select} and every custom
281
- * {@link FIELD_TRIGGER} control.
282
- *
283
- * Absolutely positioned and centred on the FIELD box. As an ordinary flex child
284
- * it centres on the *content* box instead, and `FIELD_FLOATING_PAD` (pt-4 pb-1)
285
- * pushes that box's midline down — so a labelled currency/multi-select chevron
286
- * sat visibly lower than the native select's right beside it (feedback #400).
287
- * Pair it with `pe-9` on the trigger so the value can't run underneath. At the
288
- * inline END, not the right: a right-to-left form reads the value from the right,
289
- * and a chevron parked on top of its first word hid exactly the part that says
290
- * what was picked. */
291
- declare function FieldChevron({ className, ...rest }: FieldChevronProps): react.JSX.Element;
292
- declare const FLOATING_LABEL_CLASS: string;
293
- declare const FLOATING_LABEL_STATIC: string;
294
- /**
295
- * The one place that assembles a labelled field: a `relative` wrapper around the
296
- * control (`children`) plus a floating label inside the top strip. Pass `staticLabel`
297
- * for controls that always have a value (selects); omit it for free-text fields whose
298
- * label animates from centred→up. Used by Input/Select/Textarea/NumberInput; the
299
- * dropdown controls that need a ref + menu keep their own wrapper but the same label
300
- * (via {@link FieldLabel}) and trigger ({@link FIELD_TRIGGER}) styles.
301
- */
302
- interface FloatingFieldProps extends ComponentPropsWithoutRef<"div"> {
303
- htmlFor?: string;
304
- label?: ReactNode;
305
- staticLabel?: boolean;
306
- /** Keep the label in the accessibility tree but out of the layout — what
307
- * {@link FIELD_DISPLAY} needs, since a floating label inside a field with no
308
- * field left would have nothing to float in. */
309
- srOnlyLabel?: boolean;
310
- /** Something interactive that belongs to the LABEL rather than to the value —
311
- * in practice a {@link FieldHint} "?" (dev#468). It is rendered in a flex row
312
- * with the label, so it is centred on the label's line by the layout instead
313
- * of by a hand-tuned `top-…`, and it can never drift when the type changes.
314
- *
315
- * It rides an ANIMATED label too, and the two cases place it differently on
316
- * purpose. A static label has a field-wide strip to itself, so the hint sits
317
- * at the far end of it and a long label truncates into the gap. An animated
318
- * one belongs to a control with something at the end edge of the field — a
319
- * calculator, a stepper — so the hint follows the label instead, and it is
320
- * the label that gives way (steering-design feedback #48). */
321
- hint?: ReactNode;
322
- children: ReactNode;
323
- }
324
- declare function FloatingField({ className, htmlFor, label, staticLabel, srOnlyLabel, hint, children, ...rest }: FloatingFieldProps): react.JSX.Element;
325
- /**
326
- * The "?" that explains a field, on the field's own label line (dev#468).
327
- *
328
- * Pass it to a labelled {@link Select} / {@link Input} as `hint`. It exists as a
329
- * component rather than as a snippet each form repeats because the previous
330
- * version was exactly that snippet — an absolutely-positioned button whose
331
- * `top-1.5` was one guess at where an 11px label sits — and the reporter's
332
- * follow-up was *"question mark is not centered. Is it part of the hoc?
333
- * positioning problems seem quite frequently."* It was not part of the HOC. Now
334
- * it is, and there is one place left where the answer can be wrong.
335
- *
336
- * A `<button>` rather than a bare icon: hover alone puts the explanation out of
337
- * reach of a keyboard and of every touch device, and the tooltip shows on focus
338
- * too. The text is also its accessible name, so a screen reader gets it without
339
- * the bubble ever opening.
340
- */
341
- interface FieldHintProps extends Omit<ComponentPropsWithoutRef<"button">, "children"> {
342
- /** The explanation. It is both the tooltip's text and, by default, the button's
343
- * accessible name, so a screen reader gets it without the bubble ever opening. */
344
- label: string;
345
- /** Default `"start"`: before the hint in the reading direction (the left in LTR, the
346
- * right in RTL). `left` / `right` stay physical. */
347
- side?: TooltipSide;
348
- }
349
- declare function FieldHint({ label, side, className, "aria-label": ariaLabel, ...rest }: FieldHintProps): react.JSX.Element;
350
- /**
351
- * The floating label for a custom-dropdown trigger (a `<span>`, since the trigger is a
352
- * button not a labelable input). Same placement as {@link FloatingField}'s static label,
353
- * so every labelled field lines up. Render inside a `relative` wrapper, before the trigger.
354
- */
355
- interface FieldLabelProps extends ComponentPropsWithoutRef<"span"> {
356
- children: ReactNode;
357
- }
358
- declare function FieldLabel({ children, className, ...rest }: FieldLabelProps): react.JSX.Element;
359
- interface LabelProps extends ComponentPropsWithoutRef<"label"> {
360
- /** Draw the required mark after the text. The mark is `aria-hidden`: what a
361
- * screen reader hears is the control's own `required` / `aria-required`, which
362
- * the caller still sets — a "star" read out as the last word of every name is
363
- * noise, and a control that is not marked required is not required however its
364
- * label looks. */
365
- required?: boolean;
366
- /** `sm` for a toolbar or a dense filter row (12px), `md` otherwise (14px). */
367
- size?: "sm" | "md";
368
- /** Dim the label with its control. A label ABOVE its field is not the field's
369
- * `peer`, so `peer-disabled:` cannot reach it the way it reaches a floating one. */
370
- disabled?: boolean;
371
- }
372
- /**
373
- * The label ABOVE a field, for the places a floating label does not fit: a control
374
- * the kit did not render (a third-party picker, a range slider, a group of radios),
375
- * a filter bar whose fields are unlabelled {@link Select}s, a form that sets labels
376
- * above its fields throughout.
377
- *
378
- * A new component rather than a mode of {@link FieldLabel}, because the two share
379
- * nothing but the word. `FieldLabel` is an absolutely positioned `<span>` inside a
380
- * trigger's `relative` box and has to stay exactly that for every dropdown that
381
- * already lines up with it; this is a real `<label>` in normal flow, with `htmlFor`,
382
- * that a click focuses the control through. Spacing below it belongs to the caller's
383
- * layout (`space-y-1.5` on the pair, typically), as it would for any block.
384
- */
385
- declare function Label({ required, size, disabled, className, children, ...rest }: LabelProps): react.JSX.Element;
386
- /** The two names the password reveal toggle can wear. See {@link Input}. */
387
- interface PasswordRevealLabels {
388
- /** While the value is hidden — activating the toggle will show it. */
389
- show: string;
390
- /** While the value is shown. */
391
- hide: string;
392
- }
393
- declare const DEFAULT_PASSWORD_REVEAL_LABELS: PasswordRevealLabels;
394
- /** Caller's labels over the English defaults — the same shape as
395
- * `resolveDataTableLabels`, so a consumer translates every kit string one way. */
396
- declare function resolvePasswordRevealLabels(partial?: Partial<PasswordRevealLabels>): PasswordRevealLabels;
397
- interface InputProps extends InputHTMLAttributes<HTMLInputElement> {
398
- label?: ReactNode;
399
- /** Classes for the `<input>` itself, as distinct from `className`, which
400
- * styles the field WRAPPER once a `label` turns this into a FloatingField.
401
- * Without it a labelled Input had no way to reach its own element — so
402
- * `tabular-nums` on a numeric text field, which NumberInput has supported
403
- * all along through the identically named prop, was simply unavailable. */
404
- inputClassName?: string;
405
- /** {@link FIELD_DISPLAY} — on a phone, drop the chrome and set the value as a
406
- * heading. For the one field a form is about (a feedback subject, an account
407
- * name), never for a stack of them. Labelled fields only: the label is what
408
- * the placeholder falls back to once it goes `sr-only`. */
409
- variant?: "field" | "display";
410
- /** The field is required and unanswered, or holds something that cannot be
411
- * saved — {@link FIELD_INVALID}, the same rose border/ring `Select` has worn
412
- * since feedback #235.
413
- *
414
- * It exists here because writing `aria-invalid` by hand did NOT do this. The
415
- * attribute spreads onto the element and nothing styles it — there is no
416
- * `[aria-invalid]` rule in this package or in either consumer's stylesheet —
417
- * so three Keksdose dialogs flagged a mismatched passphrase to a screen reader
418
- * and painted the field exactly as if it were fine. Setting the prop sets the
419
- * attribute too, so the two can no longer be spelled separately. */
420
- invalid?: boolean;
421
- /** What is wrong with the value, in the caller's own words ("That IBAN has 21
422
- * digits"). Rendered under the field, pointed at by the control's
423
- * `aria-describedby` — MERGED with any the caller already passed — and implies
424
- * `invalid`, so the field paints as well as announces. `invalid` alone still
425
- * covers the case where the message lives elsewhere. See {@link useFieldError}. */
426
- error?: ReactNode;
427
- /** Names for the password reveal toggle, English by default — it is the one
428
- * string this component renders on its own behalf, and a German form was
429
- * reading it out in English. See {@link PasswordRevealLabels}. */
430
- passwordLabels?: Partial<PasswordRevealLabels>;
431
- }
432
- declare const Input: react.ForwardRefExoticComponent<InputProps & react.RefAttributes<HTMLInputElement>>;
433
- interface SelectProps extends Omit<SelectHTMLAttributes<HTMLSelectElement>, "size"> {
434
- label?: ReactNode;
435
- /** See {@link Input}'s `invalid`. */
436
- invalid?: boolean;
437
- /** See {@link Input}'s `error`. */
438
- error?: ReactNode;
439
- /** A {@link FieldHint} for the label line — see {@link FloatingField}. */
440
- hint?: ReactNode;
441
- /**
442
- * `"sm"`: a 28px, 12px-type select for a toolbar or a table header, where the
443
- * 36px field stands a head taller than the buttons beside it. `"md"` (default)
444
- * is the field every form uses. Unlabelled selects only — a floating label needs
445
- * the tall box to float in, so a labelled Select ignores `"sm"`.
446
- *
447
- * A NUMBER is still the native attribute (the rows of a list box) and is passed
448
- * straight through, so the one HTML meaning of `size` keeps working. Above 1 (as
449
- * with `multiple`) the browser draws a list box, and the Select drops its chevron
450
- * and the end padding reserved for it.
451
- */
452
- size?: "sm" | "md" | number;
453
- /** Classes for the `<select>` itself. `className` styles the WRAPPER — the box
454
- * the chevron is positioned against — so it could set a width and nothing
455
- * else; this is the way to the element. See {@link Input}'s `inputClassName`. */
456
- selectClassName?: string;
457
- }
458
- declare const Select: react.ForwardRefExoticComponent<SelectProps & react.RefAttributes<HTMLSelectElement>>;
459
- interface TextareaProps extends TextareaHTMLAttributes<HTMLTextAreaElement> {
460
- label?: ReactNode;
461
- /** See {@link Input}'s `invalid`. */
462
- invalid?: boolean;
463
- /** See {@link Input}'s `error`. */
464
- error?: ReactNode;
465
- }
466
- declare const Textarea: react.ForwardRefExoticComponent<TextareaProps & react.RefAttributes<HTMLTextAreaElement>>;
467
- interface CardProps extends ComponentPropsWithoutRef<"div"> {
468
- children: ReactNode;
469
- /**
470
- * When true, drop the card chrome (border, rounded corners, shadow) on mobile so the
471
- * card spans edge-to-edge. Chrome reappears at the `md` breakpoint. Use for primary
472
- * content cards on data pages; leave off for centered dialog/panel cards.
473
- */
474
- flush?: boolean;
475
- /**
476
- * `inset`: a panel INSIDE a card rather than a card on the page — the raised
477
- * `--bg-surface-2`, a small radius, `p-3` of its own and no border or shadow, since
478
- * it is already sitting on the card that has them. Lenkbank's results blocks and
479
- * corner panels write this by hand (`rounded-md bg-surface-2 p-3`) under a Card of
480
- * their own. Unlike the default card it carries its padding, because every one of
481
- * those copies wanted the same one; a caller's `p-*` still wins. `flush` is ignored:
482
- * an inset panel never runs edge to edge.
483
- */
484
- variant?: "default" | "inset";
485
- }
486
- declare function Card({ className, children, flush, variant, ...rest }: CardProps): react.JSX.Element;
487
- /** The sub-parts add nothing to a `<div>`'s props — they are the SAME element with a
488
- * `data-slot` and a padding rhythm — so each name is an alias rather than an empty
489
- * interface pretending to be more. They exist so a consumer's own wrapper can say
490
- * `CardHeaderProps` instead of `ComponentProps<typeof CardHeader>`. */
491
- type CardHeaderProps = ComponentPropsWithoutRef<"div">;
492
- type CardTitleProps = ComponentPropsWithoutRef<"div">;
493
- type CardDescriptionProps = ComponentPropsWithoutRef<"div">;
494
- type CardActionProps = ComponentPropsWithoutRef<"div">;
495
- type CardContentProps = ComponentPropsWithoutRef<"div">;
496
- type CardFooterProps = ComponentPropsWithoutRef<"div">;
497
- declare function CardHeader({ className, ...props }: CardHeaderProps): react.JSX.Element;
498
- declare function CardTitle({ className, ...props }: CardTitleProps): react.JSX.Element;
499
- declare function CardDescription({ className, ...props }: CardDescriptionProps): react.JSX.Element;
500
- declare function CardAction({ className, ...props }: CardActionProps): react.JSX.Element;
501
- declare function CardContent({ className, ...props }: CardContentProps): react.JSX.Element;
502
- declare function CardFooter({ className, ...props }: CardFooterProps): react.JSX.Element;
503
- /** A `<span>`'s props plus the words a reader hears — see {@link CardHeaderProps}.
504
- * The ring is drawn with a border, so there is nothing inside it to put children in. */
505
- interface SpinnerProps extends Omit<ComponentPropsWithoutRef<"span">, "children"> {
506
- /**
507
- * What the spinner means, for a screen reader. Default: `common.loading` from the
508
- * {@link UiKitProvider}, else "Loading…".
509
- *
510
- * `null` makes it DECORATIVE — no role, no text, hidden from assistive tech. Use it
511
- * whenever words are already there: beside visible "Loading…" text (otherwise it is
512
- * announced twice), inside a labelled button (otherwise its text joins the button's
513
- * name), or inside a live region of the app's own (otherwise that region and this
514
- * one both announce).
515
- */
516
- label?: string | null;
517
- }
518
- /**
519
- * A spinning ring that also SAYS it is loading.
520
- *
521
- * It used to be a bordered span and nothing else, so a reader met no element at all
522
- * where a sighted user saw the page working — or, beside an emptied list, met the
523
- * empty list and concluded there was nothing there. `role="status"` makes it a polite
524
- * live region, and the text inside is what that region announces. Text rather than
525
- * `aria-label`: a live region's announcement is its CONTENT, and several readers
526
- * ignore a name on one.
527
- *
528
- * `relative` so the `sr-only` text has a local containing block (see
529
- * sr-only-containment.test). Where the words are already on screen, pass
530
- * `label={null}`: announcing is right for a spinner standing alone and wrong for one
531
- * beside text, inside a button, or inside the app's own live region.
532
- */
533
- declare function Spinner({ className, label, ...rest }: SpinnerProps): react.JSX.Element;
534
- interface EmptyStateProps extends Omit<ComponentPropsWithoutRef<"div">, "children" | "title"> {
535
- /** The box renders `title` and `hint` in its own two-line rhythm, which is what makes
536
- * every empty state in three apps look like the same thing — so there is no
537
- * `children` slot to put arbitrary content in. The two slots below are the only
538
- * other things an empty state has turned out to need, and each has a fixed place. */
539
- /** Nodes rather than strings, so a title can carry a code, a link or an emphasised
540
- * word — keksdose's error boundary shows the error's name in `<code>` — while the
541
- * box still sets the type. */
542
- title: ReactNode;
543
- hint?: ReactNode;
544
- /**
545
- * Render the title as a heading of this level. Left out it is a `<div>`, as before:
546
- * most empty states are a message inside a section that already has its heading.
547
- * keksdose's error boundary IS the page when it trips, and its `<h2>` went missing
548
- * when it moved onto EmptyState — so the page's heading outline lost the one line
549
- * that says what happened. Only the element changes; the look is the box's.
550
- */
551
- headingAs?: "h2" | "h3" | "h4" | "h5" | "h6";
552
- /** A glyph ABOVE the title — kastlan's InboxEmptyState (an inbox), keksdose's
553
- * offline card (a cloud with a slash). Sized by the box (`[&_svg]:size-8`) and
554
- * muted, so five call sites cannot pick five sizes; hidden from assistive tech,
555
- * because the title already says what it shows. */
556
- icon?: ReactNode;
557
- /** What to do about it, BELOW the hint: keksdose's query-state cards (Retry), its
558
- * error boundary (Retry / Reload / Go home — pass all three in a fragment, they
559
- * wrap and centre as one row), kastlan's "Create a rule". Buttons or links the
560
- * caller renders; the box only places them. */
561
- action?: ReactNode;
562
- /**
563
- * `box` (default): the dashed, padded card that stands in for a whole section.
564
- * `inline`: one quiet line with no box — for the empty state INSIDE something that
565
- * already has its own frame, where a dashed card would be a box in a box. keksdose
566
- * writes that line by hand five times as a muted `<p className="py-6 text-center">`:
567
- * the notification inbox (notification-inbox:140), a support thread
568
- * (support-thread:144), the assistant (assistant-page:321), the admin support panel
569
- * (support-panel:265) and the funding dialog (funding-dialog:89). The icon shrinks
570
- * to the text's size and sits before the title, the hint follows on the same line,
571
- * and the row wraps only when it must. Centred like the box; `className="justify-start"`
572
- * for a list that reads from the start edge (funding-dialog).
573
- */
574
- variant?: "box" | "inline";
575
- /**
576
- * Colour the icon and the title: `danger` for an error standing where the content
577
- * should be — kastlan's ErrorState (shared/components/feedback/error-state.tsx) and
578
- * its error boundary's fallback (error-boundary.tsx:16), both a red triangle over a
579
- * message — and `success` for an empty state that is GOOD news, kastlan's "No defects
580
- * recorded for this room" (handover defects-step.tsx:142, a green check). Left out,
581
- * both stay muted. The hint stays muted either way: it is the explanation, not the
582
- * verdict.
583
- */
584
- tone?: "danger" | "success";
585
- /**
586
- * `inline` only (0.11.0). `sm`: 12px, start-aligned and tight (`py-1`) — the empty
587
- * line INSIDE a detail panel, under a panel heading and among `text-xs` rows, where
588
- * the default's 14px centred line with `py-4` read as a section of its own.
589
- * keksdose's holdings panel (holdings-panel:563), asset-loan panel
590
- * (asset-loan-panel:108) and loan-payment panel (loan-payment-panel:191) each wrote
591
- * `className="justify-start py-1"` over it and still got body-size text. Default `md`,
592
- * the look it had. Ignored by the box.
593
- */
594
- size?: "sm" | "md";
595
- }
596
- declare function EmptyState({ title, hint, icon, action, headingAs, variant, tone, size, className, ...rest }: EmptyStateProps): react.JSX.Element;
597
- /** One tab of a {@link Tabs} strip. */
598
- interface TabItem<T extends string> {
599
- id: T;
600
- /** A ReactNode so a tab can pair an icon with text. */
601
- label: ReactNode;
602
- /** Optional trailing node (e.g. a count pill). */
603
- badge?: ReactNode;
604
- /** Marks a tab that IS a route — see {@link TabsProps}. */
605
- href?: string;
606
- /** Leading decoration: an icon, or the colour swatches of the lines this tab
607
- * draws in a chart above the strip. Hidden from assistive tech — it repeats
608
- * what the label says, or it says something the label must say instead. */
609
- icon?: ReactNode;
610
- /** A second, smaller line under the label — the one fact that tells two tabs
611
- * with similar labels apart ("80 km/h", "Level 3"). Part of the tab's name. */
612
- detail?: ReactNode;
613
- /** A tab that exists as a slot with nothing in it yet, drawn with a dashed
614
- * outline so a gap in a sequence looks like a gap. Visual only: say "empty" in
615
- * the label or `detail` if a screen reader needs to know. */
616
- empty?: boolean;
617
- /** `false` keeps this one tab when the strip has `onRemove` — the one sheet a
618
- * workbook cannot be without. Default: removable. */
619
- removable?: boolean;
620
- /** Plain-text name for the labels the strip composes about this tab ("Remove
621
- * …"), for when `label` is not a string. Default: `label` if it is a string,
622
- * else the id. */
623
- name?: string;
624
- }
625
- /** The strings the strip renders on its own behalf — only when it can add or
626
- * remove tabs. */
627
- interface TabsLabels {
628
- /** The "add" button's visible text when the caller gives no `addLabel`. */
629
- add: string;
630
- /** Accessible name of a tab's × — "Remove <tab>". */
631
- remove: (tab: string) => string;
632
- }
633
- declare const DEFAULT_TABS_LABELS: TabsLabels;
634
- /**
635
- * The strip's own props sit on a `<div>`: the tablist IS the root element, so anything
636
- * a caller hangs on it — a `data-tour` anchor for the kit's guided tour, a test id, an
637
- * `aria-describedby` — lands there. `onChange` is omitted from the div's props because
638
- * this component's `onChange` hands over the chosen TAB ID, not a DOM event, and
639
- * `children` because the strip renders `tabs` — a caller who wants a PANEL wires it up
640
- * through `panelId`, which is the whole point of that prop.
641
- */
642
- interface TabsProps<T extends string> extends Omit<ComponentPropsWithoutRef<"div">, "onChange" | "children"> {
643
- /** `label` is a ReactNode so tabs can pair an icon with text; `badge` is an
644
- * optional trailing node (e.g. a count pill). `href` marks a tab that IS a route:
645
- * it renders as an anchor so it can be middle-/⌘-clicked into a new tab (Keksdose
646
- * feedback #451), while a plain click still goes through `onChange` and stays
647
- * client-side. Tabs that only flip local state leave it unset — a link to a URL
648
- * that does not select the tab would be worse than no link. */
649
- tabs: TabItem<T>[];
650
- active: T;
651
- onChange: (id: T) => void;
652
- className?: string;
653
- /** Let the strip WRAP onto as many rows as it needs below `md`, instead of
654
- * scrolling sideways — for a strip carrying more tabs than fit on a phone row.
655
- * Keksdose live #262: ten reports on a 406px screen showed about three, and the
656
- * round-2 answer (a <select> below `md`) cost a tap to open and a tap to choose.
657
- * *"I would rather keep the tabs but have multirow tabs depending on the screen
658
- * size, I will not loose the function to see all reports at once and navigate by
659
- * single click rather by double click."*
660
- *
661
- * The active MARKER has to change with the layout, which is why this cannot be
662
- * done from a call site with a `className`: the default marker is a `border-b-2`
663
- * underline riding the container's own bottom rule, and a tab sitting in a row
664
- * that does not touch that rule cannot wear it — every row but the last would
665
- * show a stray line floating mid-strip, and `-mb-px` would pull each chip a
666
- * pixel into the row beneath it. A wrapped strip marks the active tab with a
667
- * filled brand chip instead, which is also the only thing findable at a glance
668
- * among ten same-weight labels on three rows.
669
- *
670
- * From `md` up NOTHING changes: same single-line underline strip, same paddings,
671
- * same colours as every other strip in the app.
672
- *
673
- * Opt-in on purpose. The three-tab strips (invoices, statements, bank imports)
674
- * already fit a phone row, and turning those into chips would be an unrequested
675
- * redesign of three other pages. */
676
- wrap?: boolean;
677
- /**
678
- * Accessible name for the `role="tablist"` container. Every tab carries its own
679
- * text, so this names the GROUP, not the tabs; leave it unset where a visible
680
- * heading immediately above already does that job.
681
- *
682
- * @deprecated Pass `aria-label` instead. This kit had three spellings for one idea
683
- * — `ariaLabel`, `aria-label` and this `label` — and settled on the DOM one, which
684
- * is also the one that arrives for free now that the strip spreads its rest props.
685
- * `label` still works and is unchanged; it names the strip only when `aria-label`
686
- * is absent.
687
- */
688
- label?: string;
689
- /**
690
- * `id` of the element the caller renders the open tab's content into — the missing
691
- * half of `role="tab"`. A tab that controls nothing is a tab in name only: a screen
692
- * reader announces "tab, 2 of 5" and then has no way to take the user to what it
693
- * opened, and no way back.
694
- *
695
- * **This component does not render the panel, on purpose.** `Tabs` is the STRIP; the
696
- * content lives wherever the caller put it — three Keksdose pages render it in a
697
- * sibling `<Card>`, one renders it through a router outlet, and a tab can BE a route
698
- * (`href`), in which case the panel is a whole page this component never sees.
699
- * Wrapping `children` here would mean either moving that content into the strip's
700
- * subtree — a layout change on every page that uses tabs — or shipping a wrapper
701
- * that only some callers could use. So the two halves are joined by an id instead.
702
- *
703
- * Wire the other end yourself:
704
- *
705
- * ```tsx
706
- * <Tabs tabs={tabs} active={active} onChange={setActive} panelId="report-panel" />
707
- * <div id="report-panel" role="tabpanel" aria-labelledby="report-panel-tab" tabIndex={-1}>
708
- * ```
709
- *
710
- * `aria-controls` goes on the OPEN tab only, and `${panelId}-tab` is its id — the
711
- * closed tabs' panels are not in the document, and a dangling `aria-controls`
712
- * describes a tab as opening something that is not there.
713
- */
714
- panelId?: string;
715
- /**
716
- * Makes the tabs removable: an × on the OPEN tab, and Delete on a focused tab
717
- * (only on the open one with `removeOn="active"`).
718
- *
719
- * The × is on the open tab only — a row of tabs each carrying one is a row of
720
- * things to hit by accident, and the tab you can remove should be the one you
721
- * are looking at — but its room is reserved on every removable tab, so opening a
722
- * tab never widens it and never pushes the rest of the strip along (lenkbank
723
- * feedback #72). It is named "Remove <tab>" and is not a tab stop of its own: the
724
- * strip stays one stop, and the keyboard path is Delete (announced through
725
- * `aria-keyshortcuts`), which removes whichever tab has focus (see `removeOn`).
726
- *
727
- * The strip only ASKS: it calls this, and the tab goes when the caller drops it
728
- * from `tabs` — after a confirmation, a request, whatever it needs. When it does,
729
- * focus that was on the removed tab (or its ×) moves to the open tab if the
730
- * removed one was open, else to its neighbour, instead of falling to the page.
731
- */
732
- onRemove?: (id: T) => void;
733
- /**
734
- * Which tabs the Delete key removes.
735
- *
736
- * - `"every"` (default): any focused removable tab — the ARIA pattern's reading.
737
- * - `"active"`: only the OPEN tab, the same one that wears the ×. For a strip whose
738
- * remove is immediate and unconfirmed (lenkbank's sheet tabs delete a measurement
739
- * sheet at once), so arrowing across the strip and pressing Delete cannot take
740
- * out a sheet the user is not looking at. Delete on a closed tab does nothing,
741
- * and only the open tab announces the shortcut (`aria-keyshortcuts`).
742
- *
743
- * Either way the × is drawn on the open tab only and its room stays reserved on
744
- * every removable tab, so the strip never relayouts when the selection moves.
745
- */
746
- removeOn?: "every" | "active";
747
- /** Renders an "add" button after the last tab, outside the tablist — it is an
748
- * action, not a tab, and a tablist may own only tabs. */
749
- onAdd?: () => void;
750
- /** The add button's text. Say what is added — "Add loop", "Add report" says
751
- * more than "Add". Default: `tabs.add` from the {@link UiKitProvider}. */
752
- addLabel?: string;
753
- /** Overrides for this strip's strings. See {@link TabsLabels}. */
754
- labels?: Partial<TabsLabels>;
755
- /** An add or a remove is in flight: the × and the add button are disabled and
756
- * Delete is ignored, so a double click cannot remove two tabs. Nothing moves. */
757
- busy?: boolean;
758
- /**
759
- * `"vertical"`: the same strip as a SIDE NAV — one item per row, the open one
760
- * filled, ↑/↓ walking it (Home/End as before) and `aria-orientation="vertical"` on
761
- * the tablist. For keksdose's admin page and settings page, which each hand-built
762
- * the identical sidebar with none of this strip's keyboard, roving tab stop or
763
- * routed-`href` handling. Everything else — `href` tabs, `badge`, `panelId`,
764
- * `onRemove`, `onAdd` — means exactly what it means on the horizontal strip; the
765
- * badge moves to the row's end.
766
- *
767
- * **On a phone ({@link PHONE_QUERY}) it becomes the horizontal strip**, `wrap` and
768
- * all, rather than staying a column. A side nav only works beside its content; on
769
- * a phone it has to go ABOVE it, and eight full-width rows there push the panel
770
- * the user picked below the fold on every visit — the two keksdose pages both
771
- * collapsed theirs into a scrolling row by hand for exactly that reason. The
772
- * orientation is switched in JavaScript, not with `md:` classes, so the
773
- * `aria-orientation` a screen reader hears and the arrow keys that work always
774
- * match the layout on screen. The caller's own two-column layout has to stack at
775
- * the same breakpoint (`md:grid-cols-[14rem_1fr]`).
776
- */
777
- orientation?: "horizontal" | "vertical";
778
- }
779
- 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;
780
-
781
- export { Button, type ButtonClassesOptions, type ButtonProps, type ButtonSize, type ButtonTone, type ButtonVariant, Card, CardAction, type CardActionProps, CardContent, type CardContentProps, CardDescription, type CardDescriptionProps, CardFooter, type CardFooterProps, CardHeader, type CardHeaderProps, type CardProps, CardTitle, type CardTitleProps, DEFAULT_PASSWORD_REVEAL_LABELS, DEFAULT_TABS_LABELS, EmptyState, type EmptyStateProps, FIELD_BASE, FIELD_DISPLAY, FIELD_FLOATING_PAD, FIELD_INVALID, FIELD_TRIGGER, FIELD_WRITABLE_LOOK, FLOATING_INPUT_CLASS, FLOATING_LABEL_CLASS, FLOATING_LABEL_STATIC, FieldChevron, type FieldChevronProps, FieldHint, type FieldHintProps, FieldLabel, type FieldLabelProps, FloatingField, type FloatingFieldProps, IconButton, type IconButtonProps, type IconButtonSize, type IconButtonTone, Input, type InputProps, Label, type LabelProps, PHONE_QUERY, type PasswordRevealLabels, Select, type SelectProps, Spinner, type SpinnerProps, type TabItem, Tabs, type TabsLabels, type TabsProps, Textarea, type TextareaProps, buttonClasses, resolvePasswordRevealLabels };
6
+ import './data-table-labels.js';
7
+ import './mini-calendar.js';
8
+ import './calendar-heatmap.js';
9
+ import './popover.js';
10
+ import './chip.js';
11
+ import 'lucide-react';
12
+ import './field-sync.js';
13
+ import '../wizard/types.js';
14
+ import '../tour/tour.js';
15
+ import '../search/command-palette.js';
16
+ import '../search/global-search.js';
17
+ import '../search/search-index.js';
18
+ import './month-picker.js';
19
+ import './page-contents.js';
20
+ import './series-chart-labels.js';
21
+ import './pie-chart-labels.js';
22
+ import './sparkline.js';
23
+ import './stat-tile.js';
24
+ import './signature-pad.js';
25
+ import './password-strength.js';
26
+ import './danger-confirm.js';
27
+ import './swatch-picker.js';
28
+ import './tile-radio.js';
29
+ import './icon-picker.js';
30
+ import './dialog-frame.js';
31
+ import './modal.js';
32
+ import '../hooks/use-search-param-state.js';
33
+ import './measured-grid.js';
34
+ import '../feedback/feedback-attachment.js';
35
+ import '../feedback/feedback-dialog.js';
36
+ import '../feedback/feedback-thread.js';
37
+ import '../feedback/feedback-inbox.js';
38
+ import './account-settings-labels.js';
39
+ import './confirm-dialog.js';
40
+ import './floating-panel.js';
41
+ import './bulk-action-bar.js';
42
+ import './list.js';
43
+ import './breadcrumbs.js';
44
+ import './toast.js';
45
+ import './description-list.js';
46
+ import './line-items.js';
47
+ import './progress-bar.js';
48
+ import './signed-amount.js';
49
+ import './error-boundary.js';
50
+ import './authed-image.js';
51
+ import '../hooks/use-authed-src.js';
52
+ import './image-grid.js';
53
+ import './lightbox.js';