@eifi1/ui-kit 0.10.0 → 0.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (382) hide show
  1. package/README.md +66 -37
  2. package/dist/chart.d.ts +3 -1
  3. package/dist/chart.js +2 -0
  4. package/dist/chart.js.map +1 -1
  5. package/dist/components/account-settings-labels.d.ts +105 -0
  6. package/dist/components/account-settings-labels.js +81 -0
  7. package/dist/components/account-settings-labels.js.map +1 -0
  8. package/dist/components/account-settings.d.ts +36 -52
  9. package/dist/components/account-settings.js +44 -8
  10. package/dist/components/account-settings.js.map +1 -1
  11. package/dist/components/alert-banner.d.ts +26 -1
  12. package/dist/components/alert-banner.js +10 -7
  13. package/dist/components/alert-banner.js.map +1 -1
  14. package/dist/components/amount-input.d.ts +26 -4
  15. package/dist/components/amount-input.js +5 -2
  16. package/dist/components/amount-input.js.map +1 -1
  17. package/dist/components/authed-image.d.ts +68 -0
  18. package/dist/components/authed-image.js +117 -0
  19. package/dist/components/authed-image.js.map +1 -0
  20. package/dist/components/breadcrumbs.d.ts +2 -1
  21. package/dist/components/breadcrumbs.js +5 -2
  22. package/dist/components/breadcrumbs.js.map +1 -1
  23. package/dist/components/bulk-action-bar.d.ts +51 -5
  24. package/dist/components/bulk-action-bar.js +88 -41
  25. package/dist/components/bulk-action-bar.js.map +1 -1
  26. package/dist/components/button-group.d.ts +107 -3
  27. package/dist/components/button-group.js +60 -5
  28. package/dist/components/button-group.js.map +1 -1
  29. package/dist/components/calculator.d.ts +17 -4
  30. package/dist/components/calendar-heatmap.d.ts +162 -0
  31. package/dist/components/calendar-heatmap.js +336 -0
  32. package/dist/components/calendar-heatmap.js.map +1 -0
  33. package/dist/components/chip.d.ts +2 -1
  34. package/dist/components/chip.js +6 -3
  35. package/dist/components/chip.js.map +1 -1
  36. package/dist/components/choice-card.d.ts +66 -2
  37. package/dist/components/choice-card.js +108 -1
  38. package/dist/components/choice-card.js.map +1 -1
  39. package/dist/components/combobox-core.js +2 -0
  40. package/dist/components/combobox-core.js.map +1 -1
  41. package/dist/components/combobox.d.ts +2 -2
  42. package/dist/components/combobox.js +31 -9
  43. package/dist/components/combobox.js.map +1 -1
  44. package/dist/components/copy-button.d.ts +52 -70
  45. package/dist/components/copy-button.js +1 -1
  46. package/dist/components/copy-button.js.map +1 -1
  47. package/dist/components/data-table-cells.d.ts +63 -0
  48. package/dist/components/data-table-cells.js +64 -0
  49. package/dist/components/data-table-cells.js.map +1 -0
  50. package/dist/components/data-table-filter-popover.d.ts +1 -1
  51. package/dist/components/data-table-filters.d.ts +1 -1
  52. package/dist/components/data-table-filters.js +31 -1
  53. package/dist/components/data-table-filters.js.map +1 -1
  54. package/dist/components/data-table-labels.d.ts +13 -0
  55. package/dist/components/data-table-labels.js +8 -1
  56. package/dist/components/data-table-labels.js.map +1 -1
  57. package/dist/components/data-table.d.ts +1 -1
  58. package/dist/components/data-table.js +104 -12
  59. package/dist/components/data-table.js.map +1 -1
  60. package/dist/components/date-picker.d.ts +3 -1
  61. package/dist/components/date-picker.js +15 -2
  62. package/dist/components/date-picker.js.map +1 -1
  63. package/dist/components/description-list.d.ts +23 -3
  64. package/dist/components/description-list.js +23 -3
  65. package/dist/components/description-list.js.map +1 -1
  66. package/dist/components/dialog-frame.d.ts +1 -0
  67. package/dist/components/entity-combobox.d.ts +1 -1
  68. package/dist/components/entity-combobox.js +18 -3
  69. package/dist/components/entity-combobox.js.map +1 -1
  70. package/dist/components/error-boundary.d.ts +92 -0
  71. package/dist/components/error-boundary.js +111 -0
  72. package/dist/components/error-boundary.js.map +1 -0
  73. package/dist/components/field.d.ts +69 -0
  74. package/dist/components/field.js +75 -0
  75. package/dist/components/field.js.map +1 -0
  76. package/dist/components/file-button.d.ts +51 -208
  77. package/dist/components/file-dropzone.d.ts +50 -2
  78. package/dist/components/floating-panel.d.ts +156 -4
  79. package/dist/components/floating-panel.js +193 -29
  80. package/dist/components/floating-panel.js.map +1 -1
  81. package/dist/components/form-actions.d.ts +53 -0
  82. package/dist/components/form-actions.js +98 -0
  83. package/dist/components/form-actions.js.map +1 -0
  84. package/dist/components/image-grid.d.ts +67 -0
  85. package/dist/components/image-grid.js +112 -0
  86. package/dist/components/image-grid.js.map +1 -0
  87. package/dist/components/lightbox.d.ts +104 -0
  88. package/dist/components/lightbox.js +229 -0
  89. package/dist/components/lightbox.js.map +1 -0
  90. package/dist/components/line-items.d.ts +114 -0
  91. package/dist/components/line-items.js +238 -0
  92. package/dist/components/line-items.js.map +1 -0
  93. package/dist/components/list.d.ts +37 -3
  94. package/dist/components/list.js +39 -14
  95. package/dist/components/list.js.map +1 -1
  96. package/dist/components/loading-state.d.ts +24 -0
  97. package/dist/components/loading-state.js +32 -0
  98. package/dist/components/loading-state.js.map +1 -0
  99. package/dist/components/menu-item.d.ts +13 -2
  100. package/dist/components/menu-item.js +11 -2
  101. package/dist/components/menu-item.js.map +1 -1
  102. package/dist/components/mini-calendar.d.ts +82 -5
  103. package/dist/components/mini-calendar.js +212 -64
  104. package/dist/components/mini-calendar.js.map +1 -1
  105. package/dist/components/modal.d.ts +20 -1
  106. package/dist/components/modal.js +26 -2
  107. package/dist/components/modal.js.map +1 -1
  108. package/dist/components/month-picker.d.ts +21 -1
  109. package/dist/components/month-picker.js +139 -26
  110. package/dist/components/month-picker.js.map +1 -1
  111. package/dist/components/multi-entity-combobox.d.ts +1 -1
  112. package/dist/components/multi-entity-combobox.js +19 -3
  113. package/dist/components/multi-entity-combobox.js.map +1 -1
  114. package/dist/components/nav-pills.d.ts +67 -0
  115. package/dist/components/nav-pills.js +69 -0
  116. package/dist/components/nav-pills.js.map +1 -0
  117. package/dist/components/number-field.d.ts +26 -5
  118. package/dist/components/number-field.js +5 -7
  119. package/dist/components/number-field.js.map +1 -1
  120. package/dist/components/number-input.d.ts +27 -5
  121. package/dist/components/number-input.js +7 -1
  122. package/dist/components/number-input.js.map +1 -1
  123. package/dist/components/numpad-sheet.d.ts +17 -4
  124. package/dist/components/passkeys-setting.d.ts +67 -0
  125. package/dist/components/passkeys-setting.js +214 -0
  126. package/dist/components/passkeys-setting.js.map +1 -0
  127. package/dist/components/picker-sheet.js +2 -1
  128. package/dist/components/picker-sheet.js.map +1 -1
  129. package/dist/components/pie-chart-labels.d.ts +20 -0
  130. package/dist/components/pie-chart-labels.js +12 -0
  131. package/dist/components/pie-chart-labels.js.map +1 -0
  132. package/dist/components/pie-chart.d.ts +101 -0
  133. package/dist/components/pie-chart.js +283 -0
  134. package/dist/components/pie-chart.js.map +1 -0
  135. package/dist/components/progress-bar.d.ts +73 -3
  136. package/dist/components/progress-bar.js +110 -8
  137. package/dist/components/progress-bar.js.map +1 -1
  138. package/dist/components/qr-code.d.ts +34 -0
  139. package/dist/components/qr-code.js +57 -0
  140. package/dist/components/qr-code.js.map +1 -0
  141. package/dist/components/search-field.d.ts +15 -3
  142. package/dist/components/search-field.js +5 -1
  143. package/dist/components/search-field.js.map +1 -1
  144. package/dist/components/series-chart.d.ts +16 -0
  145. package/dist/components/series-chart.js +4 -1
  146. package/dist/components/series-chart.js.map +1 -1
  147. package/dist/components/settings-fields.d.ts +50 -1
  148. package/dist/components/signed-amount.d.ts +121 -0
  149. package/dist/components/signed-amount.js +152 -0
  150. package/dist/components/signed-amount.js.map +1 -0
  151. package/dist/components/skeleton.d.ts +10 -3
  152. package/dist/components/skeleton.js +12 -2
  153. package/dist/components/skeleton.js.map +1 -1
  154. package/dist/components/stat-tile.d.ts +2 -1
  155. package/dist/components/stat-tile.js +5 -2
  156. package/dist/components/stat-tile.js.map +1 -1
  157. package/dist/components/status-dot.d.ts +6 -2
  158. package/dist/components/status-dot.js +6 -1
  159. package/dist/components/status-dot.js.map +1 -1
  160. package/dist/components/table.d.ts +77 -11
  161. package/dist/components/table.js +85 -17
  162. package/dist/components/table.js.map +1 -1
  163. package/dist/components/text-link.d.ts +176 -0
  164. package/dist/components/text-link.js +109 -0
  165. package/dist/components/text-link.js.map +1 -0
  166. package/dist/components/text.d.ts +8 -1
  167. package/dist/components/text.js +1 -0
  168. package/dist/components/text.js.map +1 -1
  169. package/dist/components/time-input.d.ts +50 -1
  170. package/dist/components/toggle-group.d.ts +28 -0
  171. package/dist/components/toggle-group.js +58 -9
  172. package/dist/components/toggle-group.js.map +1 -1
  173. package/dist/components/toggle-legend.d.ts +36 -3
  174. package/dist/components/toggle-legend.js +49 -0
  175. package/dist/components/toggle-legend.js.map +1 -1
  176. package/dist/components/tooltip.d.ts +11 -3
  177. package/dist/components/tooltip.js +2 -0
  178. package/dist/components/tooltip.js.map +1 -1
  179. package/dist/components/ui.d.ts +52 -729
  180. package/dist/components/ui.js +296 -27
  181. package/dist/components/ui.js.map +1 -1
  182. package/dist/components/use-table-state.d.ts +3 -52
  183. package/dist/components/use-table-state.js +128 -30
  184. package/dist/components/use-table-state.js.map +1 -1
  185. package/dist/components/user-avatar.d.ts +25 -3
  186. package/dist/components/user-avatar.js +32 -3
  187. package/dist/components/user-avatar.js.map +1 -1
  188. package/dist/{data-table-filters-Dh9uF_S-.d.ts → data-table-Drun8Fk9.d.ts} +305 -61
  189. package/dist/data-table.d.ts +2 -1
  190. package/dist/data-table.js +5 -1
  191. package/dist/data-table.js.map +1 -1
  192. package/dist/feedback/feedback-dialog.d.ts +26 -3
  193. package/dist/feedback/feedback-dialog.js +16 -2
  194. package/dist/feedback/feedback-dialog.js.map +1 -1
  195. package/dist/feedback/feedback-thread.d.ts +133 -0
  196. package/dist/feedback/feedback-thread.js +237 -0
  197. package/dist/feedback/feedback-thread.js.map +1 -0
  198. package/dist/feedback.d.ts +2 -1
  199. package/dist/feedback.js +1 -0
  200. package/dist/feedback.js.map +1 -1
  201. package/dist/hooks/use-authed-src.d.ts +71 -0
  202. package/dist/hooks/use-authed-src.js +63 -0
  203. package/dist/hooks/use-authed-src.js.map +1 -0
  204. package/dist/hooks/use-file-drop.d.ts +50 -2
  205. package/dist/hooks/use-hotkey.d.ts +59 -0
  206. package/dist/hooks/use-hotkey.js +105 -0
  207. package/dist/hooks/use-hotkey.js.map +1 -0
  208. package/dist/hooks/use-search-param-state.d.ts +83 -0
  209. package/dist/hooks/use-search-param-state.js +94 -0
  210. package/dist/hooks/use-search-param-state.js.map +1 -0
  211. package/dist/i18n/defaults.d.ts +17 -4
  212. package/dist/i18n/defaults.js +33 -1
  213. package/dist/i18n/defaults.js.map +1 -1
  214. package/dist/i18n/kit-labels.d.ts +47 -310
  215. package/dist/i18n/kit-labels.js +20 -5
  216. package/dist/i18n/kit-labels.js.map +1 -1
  217. package/dist/i18n/locales/de-CH-informal.d.ts +17 -4
  218. package/dist/i18n/locales/de-CH.d.ts +17 -4
  219. package/dist/i18n/locales/de-informal.d.ts +17 -4
  220. package/dist/i18n/locales/de.d.ts +17 -4
  221. package/dist/i18n/locales/de.js +165 -4
  222. package/dist/i18n/locales/de.js.map +1 -1
  223. package/dist/i18n/locales/es.d.ts +17 -4
  224. package/dist/i18n/locales/es.js +162 -4
  225. package/dist/i18n/locales/es.js.map +1 -1
  226. package/dist/i18n/locales/fr.d.ts +17 -4
  227. package/dist/i18n/locales/fr.js +162 -4
  228. package/dist/i18n/locales/fr.js.map +1 -1
  229. package/dist/i18n/locales/hu.d.ts +17 -4
  230. package/dist/i18n/locales/hu.js +162 -4
  231. package/dist/i18n/locales/hu.js.map +1 -1
  232. package/dist/i18n/locales/it.d.ts +17 -4
  233. package/dist/i18n/locales/it.js +162 -4
  234. package/dist/i18n/locales/it.js.map +1 -1
  235. package/dist/i18n/locales/zh.d.ts +17 -4
  236. package/dist/i18n/locales/zh.js +162 -4
  237. package/dist/i18n/locales/zh.js.map +1 -1
  238. package/dist/index.d.ts +45 -21
  239. package/dist/index.js +43 -1
  240. package/dist/index.js.map +1 -1
  241. package/dist/kit-labels-v3biUF1L.d.ts +1645 -0
  242. package/dist/lib/clipping.d.ts +21 -5
  243. package/dist/lib/clipping.js +3 -0
  244. package/dist/lib/clipping.js.map +1 -1
  245. package/dist/lib/dates.d.ts +17 -1
  246. package/dist/lib/dates.js +19 -0
  247. package/dist/lib/dates.js.map +1 -1
  248. package/dist/lib/format.d.ts +144 -0
  249. package/dist/lib/format.js +149 -0
  250. package/dist/lib/format.js.map +1 -0
  251. package/dist/lib/qr-encode.d.ts +44 -0
  252. package/dist/lib/qr-encode.js +338 -0
  253. package/dist/lib/qr-encode.js.map +1 -0
  254. package/dist/rhf/fields.d.ts +241 -0
  255. package/dist/rhf/fields.js +601 -0
  256. package/dist/rhf/fields.js.map +1 -0
  257. package/dist/rhf/form.d.ts +50 -1
  258. package/dist/rhf/line-items.d.ts +31 -0
  259. package/dist/rhf/line-items.js +59 -0
  260. package/dist/rhf/line-items.js.map +1 -0
  261. package/dist/rhf/use-rhf-wizard-step.d.ts +22 -0
  262. package/dist/rhf/use-rhf-wizard-step.js +38 -0
  263. package/dist/rhf/use-rhf-wizard-step.js.map +1 -0
  264. package/dist/rhf.d.ts +63 -1
  265. package/dist/rhf.js +3 -0
  266. package/dist/rhf.js.map +1 -1
  267. package/dist/shell/app-shell.d.ts +99 -2
  268. package/dist/shell/app-shell.js +58 -3
  269. package/dist/shell/app-shell.js.map +1 -1
  270. package/dist/shell/auth-layout.d.ts +74 -0
  271. package/dist/shell/auth-layout.js +97 -0
  272. package/dist/shell/auth-layout.js.map +1 -0
  273. package/dist/shell/top-bar-brand.d.ts +81 -0
  274. package/dist/shell/top-bar-brand.js +47 -0
  275. package/dist/shell/top-bar-brand.js.map +1 -0
  276. package/dist/shell/topbar-action-menu.d.ts +84 -3
  277. package/dist/shell/topbar-action-menu.js +115 -27
  278. package/dist/shell/topbar-action-menu.js.map +1 -1
  279. package/dist/shell.d.ts +58 -2
  280. package/dist/shell.js +2 -0
  281. package/dist/shell.js.map +1 -1
  282. package/dist/wizard/stepper-nav.d.ts +49 -1
  283. package/dist/wizard/wizard-context.d.ts +30 -1
  284. package/dist/wizard/wizard-context.js +23 -2
  285. package/dist/wizard/wizard-context.js.map +1 -1
  286. package/dist/wizard/wizard-step.d.ts +20 -5
  287. package/dist/wizard/wizard-step.js +16 -3
  288. package/dist/wizard/wizard-step.js.map +1 -1
  289. package/dist/wizard.d.ts +51 -3
  290. package/package.json +1 -1
  291. package/src/chart.ts +4 -0
  292. package/src/components/account-settings-labels.ts +190 -0
  293. package/src/components/account-settings.tsx +110 -63
  294. package/src/components/alert-banner.tsx +36 -7
  295. package/src/components/amount-input.tsx +13 -1
  296. package/src/components/authed-image.tsx +190 -0
  297. package/src/components/breadcrumbs.tsx +8 -2
  298. package/src/components/bulk-action-bar.tsx +163 -57
  299. package/src/components/button-group.tsx +129 -4
  300. package/src/components/calendar-heatmap.tsx +599 -0
  301. package/src/components/chip.tsx +8 -4
  302. package/src/components/choice-card.tsx +208 -1
  303. package/src/components/combobox-core.tsx +5 -0
  304. package/src/components/combobox.tsx +31 -9
  305. package/src/components/copy-button.tsx +3 -2
  306. package/src/components/data-table-cells.tsx +119 -0
  307. package/src/components/data-table-filters.ts +77 -0
  308. package/src/components/data-table-labels.ts +23 -0
  309. package/src/components/data-table.tsx +231 -13
  310. package/src/components/date-picker.tsx +19 -3
  311. package/src/components/description-list.tsx +52 -3
  312. package/src/components/entity-combobox.tsx +18 -3
  313. package/src/components/error-boundary.tsx +216 -0
  314. package/src/components/field.tsx +188 -0
  315. package/src/components/floating-panel.tsx +380 -16
  316. package/src/components/form-actions.tsx +191 -0
  317. package/src/components/image-grid.tsx +181 -0
  318. package/src/components/lightbox.tsx +374 -0
  319. package/src/components/line-items.tsx +385 -0
  320. package/src/components/list.tsx +93 -18
  321. package/src/components/loading-state.tsx +47 -0
  322. package/src/components/menu-item.tsx +29 -3
  323. package/src/components/mini-calendar.tsx +317 -57
  324. package/src/components/modal.tsx +61 -3
  325. package/src/components/month-picker.tsx +181 -26
  326. package/src/components/multi-entity-combobox.tsx +19 -3
  327. package/src/components/nav-pills.tsx +152 -0
  328. package/src/components/number-field.tsx +15 -10
  329. package/src/components/number-input.tsx +16 -1
  330. package/src/components/passkeys-setting.tsx +331 -0
  331. package/src/components/picker-sheet.tsx +7 -1
  332. package/src/components/pie-chart-labels.ts +32 -0
  333. package/src/components/pie-chart.tsx +479 -0
  334. package/src/components/progress-bar.tsx +241 -13
  335. package/src/components/qr-code.tsx +83 -0
  336. package/src/components/search-field.tsx +20 -4
  337. package/src/components/series-chart.tsx +21 -0
  338. package/src/components/signed-amount.tsx +286 -0
  339. package/src/components/skeleton.tsx +21 -3
  340. package/src/components/stat-tile.tsx +8 -2
  341. package/src/components/status-dot.tsx +10 -2
  342. package/src/components/table.tsx +186 -23
  343. package/src/components/text-link.tsx +248 -0
  344. package/src/components/text.tsx +9 -1
  345. package/src/components/toggle-group.tsx +106 -13
  346. package/src/components/toggle-legend.tsx +87 -2
  347. package/src/components/tooltip.tsx +17 -3
  348. package/src/components/ui.tsx +572 -44
  349. package/src/components/use-table-state.ts +265 -33
  350. package/src/components/user-avatar.tsx +57 -3
  351. package/src/data-table.ts +10 -0
  352. package/src/feedback/feedback-dialog.tsx +42 -3
  353. package/src/feedback/feedback-thread.tsx +434 -0
  354. package/src/feedback.ts +1 -0
  355. package/src/hooks/use-authed-src.ts +161 -0
  356. package/src/hooks/use-hotkey.ts +172 -0
  357. package/src/hooks/use-search-param-state.ts +187 -0
  358. package/src/i18n/defaults.ts +32 -0
  359. package/src/i18n/kit-labels.tsx +104 -8
  360. package/src/i18n/locales/de.ts +162 -0
  361. package/src/i18n/locales/es.ts +159 -0
  362. package/src/i18n/locales/fr.ts +162 -0
  363. package/src/i18n/locales/hu.ts +160 -0
  364. package/src/i18n/locales/it.ts +160 -0
  365. package/src/i18n/locales/zh.ts +159 -0
  366. package/src/index.ts +64 -0
  367. package/src/lib/clipping.ts +22 -4
  368. package/src/lib/dates.ts +37 -0
  369. package/src/lib/format.ts +325 -0
  370. package/src/lib/qr-encode.ts +420 -0
  371. package/src/rhf/fields.tsx +1054 -0
  372. package/src/rhf/line-items.tsx +130 -0
  373. package/src/rhf/use-rhf-wizard-step.ts +113 -0
  374. package/src/rhf.ts +8 -0
  375. package/src/shell/app-shell.tsx +147 -2
  376. package/src/shell/auth-layout.tsx +190 -0
  377. package/src/shell/top-bar-brand.tsx +76 -0
  378. package/src/shell/topbar-action-menu.tsx +260 -49
  379. package/src/shell.ts +2 -0
  380. package/src/wizard/wizard-context.tsx +50 -1
  381. package/src/wizard/wizard-step.tsx +43 -6
  382. package/tokens.css +12 -0
@@ -1,8 +1,19 @@
1
1
  import { createContext, forwardRef, useCallback, useContext, useEffect, useId, useRef } from "react";
2
- import type { ChangeEvent, ComponentType, InputHTMLAttributes, ReactNode } from "react";
2
+ import type {
3
+ ButtonHTMLAttributes,
4
+ ChangeEvent,
5
+ ComponentType,
6
+ InputHTMLAttributes,
7
+ MouseEvent,
8
+ ReactElement,
9
+ ReactNode,
10
+ Ref,
11
+ } from "react";
3
12
  import { Check, Minus } from "lucide-react";
4
13
  import { cn } from "../lib/cn";
5
14
  import { assignRef, hasMessage, mergeDescribedBy } from "./choice-parts";
15
+ import { useKitLink } from "../i18n/kit-labels";
16
+ import { pickLinkRenderer } from "./text-link";
6
17
 
7
18
  /**
8
19
  * A checkbox or a radio, presented as a bordered card: a title, a line of description,
@@ -218,6 +229,202 @@ export const ChoiceCard = forwardRef<HTMLInputElement, ChoiceCardProps>(function
218
229
  });
219
230
  ChoiceCard.displayName = "ChoiceCard";
220
231
 
232
+ /* ── ActionCard ──────────────────────────────────────────────────────────── */
233
+
234
+ /** The colour of {@link ActionCardProps.meta}. */
235
+ export type ActionCardMetaTone = "muted" | "warning" | "danger" | "info" | "success";
236
+
237
+ const META_TONE: Record<ActionCardMetaTone, string> = {
238
+ muted: "text-[var(--text-muted)]",
239
+ warning: "text-[var(--warning)]",
240
+ danger: "text-[var(--danger)]",
241
+ info: "text-[var(--info)]",
242
+ success: "text-[var(--success)]",
243
+ };
244
+
245
+ /** The colour of {@link ActionCardProps.icon}. `default` is the secondary text colour
246
+ * the icon has always had; `brand` tints it (keksdose's privacy enrolment). */
247
+ export type ActionCardIconTone = "default" | "muted" | "brand" | "warning" | "danger" | "info" | "success";
248
+
249
+ const ICON_TONE: Record<ActionCardIconTone, string> = {
250
+ default: "text-[var(--text-secondary)]",
251
+ ...META_TONE,
252
+ brand: "text-[var(--brand)]",
253
+ };
254
+
255
+ /** What {@link ActionCardProps.renderLink} (and the provider's `linkComponent`) is
256
+ * handed. Spread it onto your router's link — `({ href, ...p }) => <Link to={href} {...p} />`. */
257
+ export interface ActionCardLinkProps {
258
+ href: string;
259
+ /** The card's whole look — keep it. */
260
+ className: string;
261
+ children: ReactNode;
262
+ ref?: Ref<HTMLAnchorElement>;
263
+ onClick?: (event: MouseEvent<HTMLAnchorElement>) => void;
264
+ id?: string;
265
+ [key: `aria-${string}`]: string | boolean | number | undefined;
266
+ [key: `data-${string}`]: unknown;
267
+ }
268
+
269
+ export interface ActionCardProps extends Omit<ButtonHTMLAttributes<HTMLButtonElement>, "title"> {
270
+ /** What the card does — the button's accessible name. */
271
+ title: ReactNode;
272
+ /** A line or two on what it means. Attached with `aria-describedby`. */
273
+ description?: ReactNode;
274
+ /**
275
+ * What it costs, or what else to know — a third line, said as prose and not as fine
276
+ * print (keksdose's custody choice: both options are legitimate and neither is free,
277
+ * so a card listing only benefits would be selling rather than explaining). Attached
278
+ * with `aria-describedby` after the description.
279
+ */
280
+ meta?: ReactNode;
281
+ /** The meta line's colour. Default `muted`; keksdose's cost line is `warning`. */
282
+ metaTone?: ActionCardMetaTone;
283
+ /** A Lucide icon (or any component taking a `className`), shown at the card's start. */
284
+ icon?: ChoiceCardProps["icon"];
285
+ /** The icon's colour. Default `default` (the secondary text colour). */
286
+ iconTone?: ActionCardIconTone;
287
+ /** Classes on the icon, after {@link iconTone} — a size, a tinted tile behind it. */
288
+ iconClassName?: string;
289
+ /**
290
+ * Makes the card a LINK to another page rather than a button that acts: kastlan's
291
+ * section tiles on the group overview (group-overview-page), each an icon, a name and
292
+ * a line on what is there. A real `<a href>`, so a middle click opens it in a new
293
+ * tab. A `disabled` card renders the inert button instead — a link cannot be
294
+ * disabled. The ref then reaches the `<a>`.
295
+ */
296
+ href?: string;
297
+ /** Your router's link for an in-app `href`. Default: the `<UiKitProvider
298
+ * linkComponent>`, then a plain `<a>`. Ignored without `href`. */
299
+ renderLink?: (props: ActionCardLinkProps) => ReactElement;
300
+ }
301
+
302
+ /**
303
+ * A {@link ChoiceCard} that ACTS rather than holds a state: a `<button>` with the
304
+ * card's icon, title and description, plus a `meta` line, that runs `onClick` the
305
+ * moment it is pressed. keksdose's privacy enrolment (privacy-enroll-dialog:345,
306
+ * `CustodyOption`) offers two custody modes this way — picking one IS the next step,
307
+ * so there is no checked state to show and no "Continue" to press after it. With
308
+ * `href` it is the same card as a link (kastlan's overview section tiles).
309
+ *
310
+ * A separate component rather than `ChoiceCard as="button"`: a ChoiceCard is an
311
+ * `<input>` (its ref, its `checked`, its form value), and a button shares none of it.
312
+ * Named by the title alone and described by the rest, so a screen reader hears
313
+ * "Keep the key yourself, button" and then the explanation, not one long name.
314
+ */
315
+ export const ActionCard = forwardRef<HTMLButtonElement, ActionCardProps>(function ActionCard(
316
+ {
317
+ title,
318
+ description,
319
+ meta,
320
+ metaTone = "muted",
321
+ icon: Icon,
322
+ iconTone = "default",
323
+ iconClassName,
324
+ href,
325
+ renderLink,
326
+ className,
327
+ id,
328
+ type = "button",
329
+ ...rest
330
+ },
331
+ ref,
332
+ ) {
333
+ const kitLink = useKitLink();
334
+ const generated = useId();
335
+ const baseId = id ?? generated;
336
+ const titleId = `${baseId}-title`;
337
+ const descriptionId = `${baseId}-description`;
338
+ const metaId = `${baseId}-meta`;
339
+ const showDescription = hasMessage(description);
340
+ const showMeta = hasMessage(meta);
341
+ const look = cn(
342
+ "flex w-full items-start gap-3 rounded-lg border border-[var(--border)] bg-[var(--bg-surface)] p-3 text-start shadow-sm transition-colors",
343
+ "hover:border-[var(--brand)] hover:bg-[var(--bg-hover)]",
344
+ // The outline, as on ChoiceCard: the card is what the eye is on.
345
+ "focus:outline-none focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[var(--brand)]",
346
+ "disabled:cursor-not-allowed disabled:opacity-60 disabled:hover:border-[var(--border)] disabled:hover:bg-[var(--bg-surface)]",
347
+ className,
348
+ );
349
+ const labelledBy = rest["aria-labelledby"] ?? titleId;
350
+ const describedBy = mergeDescribedBy(
351
+ rest["aria-describedby"],
352
+ showDescription && descriptionId,
353
+ showMeta && metaId,
354
+ );
355
+ const body = (
356
+ <>
357
+ {Icon && <Icon aria-hidden className={cn("mt-0.5 size-5 shrink-0", ICON_TONE[iconTone], iconClassName)} />}
358
+ <span className="min-w-0 flex-1 space-y-1">
359
+ <span id={titleId} className="block text-sm font-medium leading-5 text-[var(--text-primary)]">
360
+ {title}
361
+ </span>
362
+ {showDescription && (
363
+ <span id={descriptionId} className="block text-sm text-[var(--text-secondary)]">
364
+ {description}
365
+ </span>
366
+ )}
367
+ {showMeta && (
368
+ <span id={metaId} className={cn("block text-sm", META_TONE[metaTone])}>
369
+ {meta}
370
+ </span>
371
+ )}
372
+ </span>
373
+ </>
374
+ );
375
+
376
+ if (href !== undefined && !rest.disabled) {
377
+ // The button-only attributes stay behind; `aria-*`, `data-*` and handlers ride along.
378
+ const {
379
+ disabled: _disabled,
380
+ form: _form,
381
+ formAction: _formAction,
382
+ name: _name,
383
+ value: _value,
384
+ onClick,
385
+ ...anchorRest
386
+ } = rest;
387
+ const linkProps: ActionCardLinkProps = {
388
+ ...(anchorRest as Partial<ActionCardLinkProps>),
389
+ ref: ref as unknown as Ref<HTMLAnchorElement>,
390
+ id,
391
+ href,
392
+ "aria-labelledby": labelledBy,
393
+ "aria-describedby": describedBy,
394
+ onClick: onClick as unknown as ActionCardLinkProps["onClick"],
395
+ className: look,
396
+ children: body,
397
+ };
398
+ const render = pickLinkRenderer(renderLink, kitLink, href);
399
+ if (render) return <RenderedActionLink render={render} {...linkProps} />;
400
+ const { children: content, ...anchor } = linkProps;
401
+ return <a {...anchor}>{content}</a>;
402
+ }
403
+
404
+ return (
405
+ <button
406
+ ref={ref}
407
+ id={id}
408
+ type={type}
409
+ {...rest}
410
+ aria-labelledby={labelledBy}
411
+ aria-describedby={describedBy}
412
+ className={look}
413
+ >
414
+ {body}
415
+ </button>
416
+ );
417
+ });
418
+ ActionCard.displayName = "ActionCard";
419
+
420
+ /** Calls the link renderer as a component, so a router link's hooks are its own. */
421
+ function RenderedActionLink({
422
+ render,
423
+ ...props
424
+ }: ActionCardLinkProps & { render: (props: ActionCardLinkProps) => ReactElement }) {
425
+ return render(props);
426
+ }
427
+
221
428
  /* ── ChoiceCardGroup ─────────────────────────────────────────────────────── */
222
429
 
223
430
  export interface ChoiceCardOption<T extends string> {
@@ -10,6 +10,7 @@ import type {
10
10
  } from "react";
11
11
  import { Check, Plus } from "lucide-react";
12
12
  import { cn } from "../lib/cn";
13
+ import { CLIPS_ATTRIBUTE } from "../lib/clipping";
13
14
  import { DropdownSearchHeader } from "./dropdown";
14
15
  import { PickerSheet, SHEET_ROW_CLASS } from "./picker-sheet";
15
16
  import { useMediaQuery } from "../hooks/use-media-query";
@@ -636,6 +637,10 @@ export function ComboboxPanel<V extends string | number>({
636
637
  // The rows are provisional while a lookup is in flight — whether or not the
637
638
  // previous query's are still showing.
638
639
  aria-busy={busy || undefined}
640
+ // It scrolls, and says so where jsdom can see it (see `CLIPS_ATTRIBUTE`): a
641
+ // Tooltip in a row then portals out in a test exactly as it does in the
642
+ // browser, instead of joining the option's accessible name (keksdose E8).
643
+ {...{ [CLIPS_ATTRIBUTE]: "" }}
639
644
  className={cn("min-h-0 flex-1 overflow-y-auto py-1", isPhone && "flex-none")}
640
645
  >
641
646
  {results.map((o, i) => {
@@ -113,7 +113,7 @@ function SuggestionList({
113
113
  dir={dir}
114
114
  style={{ top, left: rect.left, width: rect.width }}
115
115
  >
116
- <ul id={id} role="listbox" className={LIST_CLASS} style={{ maxHeight }}>
116
+ <ul id={id} role="listbox" data-clips="" className={LIST_CLASS} style={{ maxHeight }}>
117
117
  {children}
118
118
  </ul>
119
119
  </div>,
@@ -238,9 +238,19 @@ export function Combobox({
238
238
  onBlur,
239
239
  onSubmit,
240
240
  "aria-label": ariaLabel,
241
+ // Off `rest` and onto the <input>, which is the combobox a reader meets: `Field`'s
242
+ // render-prop spreads `{ id, aria-describedby, aria-invalid, aria-required }`, and
243
+ // on the wrapper div the hint and required state described nothing.
244
+ "aria-describedby": ariaDescribedBy,
245
+ "aria-invalid": ariaInvalid,
246
+ "aria-required": ariaRequired,
241
247
  ...rest
242
248
  }: ComboboxProps) {
243
- const field = useComboboxFieldError(error, invalid);
249
+ const field = useComboboxFieldError(
250
+ error,
251
+ invalid || ariaInvalid === true || ariaInvalid === "true",
252
+ ariaDescribedBy,
253
+ );
244
254
  const generated = useId();
245
255
  const fieldId = id ?? generated;
246
256
  // Derived from the GENERATED id, never from `id`: a caller's id is theirs to
@@ -366,9 +376,9 @@ export function Combobox({
366
376
  : null;
367
377
 
368
378
  return (
369
- // `rest` dresses the outer box — a `data-tour` anchor, a test id, a form-level
370
- // `aria-describedby`. Not the NAME: that belongs on the <input> below, which is
371
- // the combobox a reader meets. Spread FIRST, so the field's ARIA and the
379
+ // `rest` dresses the outer box — a `data-tour` anchor, a test id. Not the NAME,
380
+ // description, invalid or required state: those belong on the <input> below,
381
+ // which is the combobox a reader meets. Spread FIRST, so the field's ARIA and the
372
382
  // handlers carrying live #309 and dev#549 cannot be replaced from outside.
373
383
  <div {...rest} ref={wrapperRef} className={cn("relative", className)}>
374
384
  {/* A real <label for> since lenkbank's tests met a field `getByLabelText` could
@@ -407,6 +417,7 @@ export function Combobox({
407
417
  aria-autocomplete="list"
408
418
  aria-invalid={field.isInvalid || undefined}
409
419
  aria-describedby={field.describedBy}
420
+ aria-required={ariaRequired}
410
421
  autoComplete="off"
411
422
  disabled={disabled}
412
423
  autoFocus={autoFocus}
@@ -729,12 +740,22 @@ export function InlineEntityCombobox<V extends string | number, C extends ComboC
729
740
  invalid,
730
741
  error,
731
742
  "aria-label": ariaLabel,
743
+ // Off `rest` and onto the <input>, which is the combobox a reader meets: `Field`'s
744
+ // render-prop spreads `{ id, aria-describedby, aria-invalid, aria-required }`, and
745
+ // on the wrapper div the hint and required state described nothing.
746
+ "aria-describedby": ariaDescribedBy,
747
+ "aria-invalid": ariaInvalid,
748
+ "aria-required": ariaRequired,
732
749
  ...rest
733
750
  }: InlineEntityComboboxProps<V, C>) {
734
751
  // The clear value reads as "nothing selected", whichever one the caller picked —
735
752
  // so from here down `value` is the id or `null`, as it always was.
736
753
  const value: V | null = rawValue == null || rawValue === clearValue ? null : (rawValue as V);
737
- const field = useComboboxFieldError(error, invalid);
754
+ const field = useComboboxFieldError(
755
+ error,
756
+ invalid || ariaInvalid === true || ariaInvalid === "true",
757
+ ariaDescribedBy,
758
+ );
738
759
  const generated = useId();
739
760
  const fieldId = id ?? generated;
740
761
  // See the twin above on why these hang off the generated id.
@@ -853,9 +874,9 @@ export function InlineEntityCombobox<V extends string | number, C extends ComboC
853
874
  };
854
875
 
855
876
  return (
856
- // `rest` dresses the outer box — a `data-tour` anchor, a test id, a form-level
857
- // `aria-describedby`. Not the NAME: that belongs on the <input> below, which is
858
- // the combobox a reader meets. Spread FIRST, so the field's ARIA and the
877
+ // `rest` dresses the outer box — a `data-tour` anchor, a test id. Not the NAME,
878
+ // description, invalid or required state: those belong on the <input> below,
879
+ // which is the combobox a reader meets. Spread FIRST, so the field's ARIA and the
859
880
  // handlers carrying live #309 and dev#549 cannot be replaced from outside.
860
881
  <div {...rest} ref={wrapperRef} className={cn("relative", className)}>
861
882
  {/* A real <label for> — see {@link Combobox}. */}
@@ -889,6 +910,7 @@ export function InlineEntityCombobox<V extends string | number, C extends ComboC
889
910
  aria-autocomplete="list"
890
911
  aria-invalid={field.isInvalid || undefined}
891
912
  aria-describedby={field.describedBy}
913
+ aria-required={ariaRequired}
892
914
  autoComplete="off"
893
915
  disabled={disabled}
894
916
  autoFocus={autoFocus}
@@ -56,7 +56,8 @@ export interface CopyButtonProps
56
56
  stopPropagation?: boolean;
57
57
  /** Icon variant only: where the result tooltip opens. */
58
58
  tooltipSide?: TooltipSide;
59
- /** Pass through to the tooltip — needed inside a scroll container (see Tooltip). */
59
+ /** Pass through to the tooltip. Left unset, the Tooltip decides on its own — it
60
+ * portals inside a clipping container (a DataTable's `data-clips` scroller). */
60
61
  tooltipPortal?: boolean;
61
62
  /** ms until the button returns to idle. Default 2000. */
62
63
  resetAfter?: number;
@@ -93,7 +94,7 @@ export function CopyButton({
93
94
  tone,
94
95
  stopPropagation,
95
96
  tooltipSide = "top",
96
- tooltipPortal = false,
97
+ tooltipPortal,
97
98
  resetAfter = 2000,
98
99
  onCopied,
99
100
  labels,
@@ -0,0 +1,119 @@
1
+ import type { ReactNode } from "react";
2
+ import { Check, Minus, X } from "lucide-react";
3
+ import { cn } from "../lib/cn";
4
+ import { useKitLabels } from "../i18n/kit-labels";
5
+ import { DEFAULT_DATA_TABLE_LABELS, type DataTableLabels } from "./data-table-labels";
6
+ import type { DataTableColumn } from "./data-table";
7
+
8
+ export interface BooleanMarkProps {
9
+ /** `true` → a check, `false` → a cross (or a dash, see `falseAs`), `null` /
10
+ * `undefined` → a dash. */
11
+ value: boolean | null | undefined;
12
+ /**
13
+ * How `false` is drawn. `"cross"` (default) when "no" is news worth seeing — a
14
+ * failed check, a missing document. `"dash"` for a column that is mostly `false` and
15
+ * reads as a list of the `true` ones (kastlan's BoolCell): a column of crosses is
16
+ * noise. The screen-reader text says "No" either way.
17
+ */
18
+ falseAs?: "cross" | "dash";
19
+ /** The spoken words, over `dataTable.booleanTrue` / `booleanFalse` / `booleanUnset`
20
+ * from `<UiKitProvider labels>`. */
21
+ labels?: Partial<Pick<DataTableLabels, "booleanTrue" | "booleanFalse" | "booleanUnset">>;
22
+ className?: string;
23
+ }
24
+
25
+ /**
26
+ * A yes/no value in a table cell: a green check, a muted cross, or a faint dash for
27
+ * "not set" — with the word for a screen reader, since a glyph alone is announced as
28
+ * nothing at all (or as "image").
29
+ *
30
+ * kastlan's `BoolCell` (shared/components/data-table/bool-cell.tsx) drew the icons with
31
+ * no text, so a reader walking the table heard an empty cell for every row; the apps'
32
+ * Yes/No chips were the other answer, and too loud for a column of them.
33
+ */
34
+ export function BooleanMark({ value, falseAs = "cross", labels: labelsProp, className }: BooleanMarkProps) {
35
+ const labels = useKitLabels("dataTable", DEFAULT_DATA_TABLE_LABELS, labelsProp);
36
+ const state = value === true ? "true" : value === false ? "false" : "unset";
37
+ const text =
38
+ state === "true" ? labels.booleanTrue : state === "false" ? labels.booleanFalse : labels.booleanUnset;
39
+ const Icon = state === "true" ? Check : state === "false" && falseAs === "cross" ? X : Minus;
40
+ return (
41
+ <span data-value={state} className={cn("inline-flex items-center align-middle", className)}>
42
+ <Icon
43
+ aria-hidden
44
+ className={cn(
45
+ "size-4",
46
+ state === "true"
47
+ ? "text-[var(--success)]"
48
+ : state === "false" && falseAs === "cross"
49
+ ? "text-[var(--text-muted)]"
50
+ : "text-[var(--text-placeholder)]",
51
+ )}
52
+ />
53
+ <span className="sr-only">{text}</span>
54
+ </span>
55
+ );
56
+ }
57
+
58
+ export interface BooleanColumnOptions<T> {
59
+ key: string;
60
+ header: ReactNode;
61
+ /** The value of this row. */
62
+ value: (row: T) => boolean | null | undefined;
63
+ /** See {@link BooleanMarkProps.falseAs}. */
64
+ falseAs?: "cross" | "dash";
65
+ /** Sortable, `true` first on the first click (a column is sorted to find the yes
66
+ * rows). Default true. */
67
+ sortable?: boolean;
68
+ /**
69
+ * A select filter over the column, with these option labels — pass them translated.
70
+ * Omitted: no filter. The option values are `"true"` / `"false"` (and `"unset"` when
71
+ * `unset` is given), which is also what `filterHref` takes for this column.
72
+ */
73
+ filterOptions?: { true: string; false: string; unset?: string };
74
+ /** Further column settings (`mobileHidden`, `headClassName`, …), merged over. */
75
+ column?: Partial<DataTableColumn<T>>;
76
+ }
77
+
78
+ /**
79
+ * A {@link DataTableColumn} for a yes/no field: {@link BooleanMark} cells,
80
+ * sortable with `true` first, and optionally a Yes/No select filter.
81
+ */
82
+ export function booleanColumn<T>({
83
+ key,
84
+ header,
85
+ value,
86
+ falseAs,
87
+ sortable = true,
88
+ filterOptions,
89
+ column,
90
+ }: BooleanColumnOptions<T>): DataTableColumn<T> {
91
+ const asKey = (row: T) => {
92
+ const v = value(row);
93
+ return v === true ? "true" : v === false ? "false" : "unset";
94
+ };
95
+ return {
96
+ key,
97
+ header,
98
+ cell: (row) => <BooleanMark value={value(row)} falseAs={falseAs} />,
99
+ // 0 for yes, 1 for no, null for unset (which the table sorts last either way).
100
+ sortBy: sortable
101
+ ? (row) => {
102
+ const v = value(row);
103
+ return v == null ? null : v ? 0 : 1;
104
+ }
105
+ : undefined,
106
+ filter: filterOptions
107
+ ? {
108
+ type: "select",
109
+ getValue: asKey,
110
+ options: [
111
+ { value: "true", label: filterOptions.true },
112
+ { value: "false", label: filterOptions.false },
113
+ ...(filterOptions.unset !== undefined ? [{ value: "unset", label: filterOptions.unset }] : []),
114
+ ],
115
+ }
116
+ : undefined,
117
+ ...column,
118
+ };
119
+ }
@@ -174,3 +174,80 @@ export function decodeFilterValueOfType(type: FilterValue["type"], raw: string):
174
174
  export function decodeFilterValue<T>(filter: ColumnFilter<T>, raw: string): FilterValue {
175
175
  return decodeFilterValueOfType(filter.type, raw);
176
176
  }
177
+
178
+ // ---------- Filter builders ----------
179
+ //
180
+ // Terse builders for `DataTableColumn.filter`, lifted from kastlan's column-filters.ts,
181
+ // which every converted list page imports. The one that earns its keep is `selectFilter`:
182
+ // in `serverPagination` mode a select filter MUST declare its options (the table would
183
+ // otherwise derive them from the rows of the page on screen), and the options an app has
184
+ // to hand are usually a translated `{ value: label }` map.
185
+
186
+ /** A free-text filter over `getValue`. */
187
+ export function textFilter<T>(getValue: (row: T) => string): ColumnFilter<T> {
188
+ return { type: "text", getValue };
189
+ }
190
+
191
+ /**
192
+ * A multi-select filter. `options` is an options array, or a `{ value: label }` record
193
+ * (a translated status map) — kept in its key order. Left out, the table derives the
194
+ * options from the rows it holds.
195
+ */
196
+ export function selectFilter<T>(
197
+ getValue: (row: T) => string,
198
+ options?: { value: string; label?: string }[] | Record<string, string>,
199
+ ): ColumnFilter<T> {
200
+ const opts = Array.isArray(options)
201
+ ? options
202
+ : options
203
+ ? Object.entries(options).map(([value, label]) => ({ value, label }))
204
+ : undefined;
205
+ return { type: "select", getValue, options: opts };
206
+ }
207
+
208
+ /** A from/to date filter over an ISO date (or date-time) string. */
209
+ export function dateFilter<T>(getValue: (row: T) => string | null | undefined): ColumnFilter<T> {
210
+ return { type: "date", getValue };
211
+ }
212
+
213
+ /** A min/max (and absolute-value) filter over a number. */
214
+ export function numberFilter<T>(getValue: (row: T) => number | null | undefined): ColumnFilter<T> {
215
+ return { type: "number", getValue };
216
+ }
217
+
218
+ /**
219
+ * A link to a list page with one column filter already applied — in the table's own URL
220
+ * scheme (`f.<column>=<encoded>`), which a table with `urlSync` (or
221
+ * {@link useTableUrlState}) reads on mount. kastlan's `buildListUrl`, which had to
222
+ * restate the `f.` prefix to do it.
223
+ *
224
+ * `value` is a string for a text filter, an array for a select filter, or any
225
+ * {@link FilterValue}. A filter that narrows nothing returns `path` unchanged. A `path`
226
+ * that already carries a query keeps it; a `#fragment` stays at the end.
227
+ *
228
+ * filterHref("/units", "status", ["vacant"]) // "/units?f.status=vacant"
229
+ */
230
+ export function filterHref(
231
+ path: string,
232
+ column: string,
233
+ value: string | string[] | FilterValue,
234
+ /** The target table's `urlSync` prefix, if it has one (`{ prefix: "inv." }` →
235
+ * `inv.f.<column>`). */
236
+ options?: { prefix?: string },
237
+ ): string {
238
+ const state: FilterValue =
239
+ typeof value === "string"
240
+ ? { type: "text", q: value }
241
+ : Array.isArray(value)
242
+ ? { type: "select", values: value }
243
+ : value;
244
+ const encoded = encodeFilterValue(state);
245
+ if (encoded == null) return path;
246
+ const hashAt = path.indexOf("#");
247
+ const base = hashAt >= 0 ? path.slice(0, hashAt) : path;
248
+ const hash = hashAt >= 0 ? path.slice(hashAt) : "";
249
+ const queryAt = base.indexOf("?");
250
+ const params = new URLSearchParams(queryAt >= 0 ? base.slice(queryAt + 1) : "");
251
+ params.set(`${options?.prefix ?? ""}f.${column}`, encoded);
252
+ return `${queryAt >= 0 ? base.slice(0, queryAt) : base}?${params.toString()}${hash}`;
253
+ }
@@ -69,6 +69,22 @@ export interface DataTableLabels {
69
69
  rowCount: (total: number) => string;
70
70
  /** The column-settings rail and its heading: "Columns (4/9)". */
71
71
  columnsCount: (visible: number, total: number) => string;
72
+
73
+ // ---- Empty state, row actions, boolean cells (0.12.0) ----
74
+
75
+ /** The body of a table with no rows and no `empty` prop of its own. A sentence, not
76
+ * "—": a dash in the only row of a table reads as one row with a missing value. */
77
+ empty: string;
78
+ /** The accessible name of the `rowActions` column (its header is visually empty). */
79
+ actions: string;
80
+ /** The `kind: "edit"` row action's name — its tooltip and its swipe label. */
81
+ edit: string;
82
+ /** The `kind: "delete"` row action's name. */
83
+ delete: string;
84
+ /** What {@link BooleanMark} says to a screen reader for `true` / `false` / nothing. */
85
+ booleanTrue: string;
86
+ booleanFalse: string;
87
+ booleanUnset: string;
72
88
  }
73
89
 
74
90
  export const DEFAULT_DATA_TABLE_LABELS: DataTableLabels = {
@@ -120,6 +136,13 @@ export const DEFAULT_DATA_TABLE_LABELS: DataTableLabels = {
120
136
  pageRange: (from, to, total) => `${from}–${to} / ${total}`,
121
137
  rowCount: (total) => `${total}`,
122
138
  columnsCount: (visible, total) => `Columns (${visible}/${total})`,
139
+ empty: "No entries",
140
+ actions: "Actions",
141
+ edit: "Edit",
142
+ delete: "Delete",
143
+ booleanTrue: "Yes",
144
+ booleanFalse: "No",
145
+ booleanUnset: "Not set",
123
146
  };
124
147
 
125
148
  /**