@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
@@ -3,6 +3,19 @@ import type { ComponentPropsWithoutRef, ReactNode } from "react";
3
3
  import { cn } from "../lib/cn";
4
4
  import { DEFAULT_COMMON_LABELS, useKitLabels, useKitLocale } from "../i18n/kit-labels";
5
5
 
6
+ /** The words a progress bar adds of its own. */
7
+ export interface ProgressBarLabels {
8
+ /** Shown after the value when `max={null}`: a quota with no ceiling. */
9
+ unlimited: string;
10
+ /** The `overage` line: how far past `max` the value is. `amount` is formatted. */
11
+ overLimit: (amount: string) => string;
12
+ }
13
+
14
+ export const DEFAULT_PROGRESS_BAR_LABELS: ProgressBarLabels = {
15
+ unlimited: "Unlimited",
16
+ overLimit: (amount) => `${amount} over the limit`,
17
+ };
18
+
6
19
  /**
7
20
  * `income` / `expense` are the money pair (`--money-income` / `--money-expense`), the
8
21
  * names `Chip`, `StatTile` and `Sparkline` already use for them: a share bar of what
@@ -41,12 +54,69 @@ const FILL: Record<ProgressBarTone, string> = {
41
54
 
42
55
  const TRACK_HEIGHT: Record<ProgressBarSize, string> = { sm: "h-1", slim: "h-1.5", md: "h-2", lg: "h-3" };
43
56
 
57
+ // A segment with no tone of its own takes the next categorical chart colour, so a
58
+ // stacked bar of unnamed parts is still a set of tellable parts rather than one fill.
59
+ const SERIES_FILL = [
60
+ "bg-[var(--chart-1)]",
61
+ "bg-[var(--chart-2)]",
62
+ "bg-[var(--chart-3)]",
63
+ "bg-[var(--chart-4)]",
64
+ "bg-[var(--chart-5)]",
65
+ "bg-[var(--chart-6)]",
66
+ "bg-[var(--chart-7)]",
67
+ "bg-[var(--chart-8)]",
68
+ "bg-[var(--chart-9)]",
69
+ ];
70
+
71
+ /** Each segment's fill classes: its own classes, else its tone, else (with no `color`)
72
+ * the next chart colour. "Next" counts the UNCOLOURED segments only — a segment with
73
+ * its own `className`/`tone`/`color` does not use up `--chart-1`, so an "Other" in grey
74
+ * ahead of two plain parts leaves them `--chart-1` and `--chart-2`. */
75
+ function segmentFills(segments: ProgressBarSegment[]): Array<string | undefined> {
76
+ let next = 0;
77
+ return segments.map((seg) => {
78
+ if (seg.className) return seg.className;
79
+ if (seg.tone) return FILL[seg.tone];
80
+ return seg.color ? undefined : SERIES_FILL[next++ % SERIES_FILL.length];
81
+ });
82
+ }
83
+
84
+ /** One part of a stacked bar. See {@link ProgressBarProps.segments}. */
85
+ export interface ProgressBarSegment {
86
+ /** Its size, on the bar's own `min`–`max` scale. */
87
+ value: number;
88
+ /** A kit tone. Left out (and no `color`/`className`), the next `--chart-N` colour. */
89
+ tone?: ProgressBarTone;
90
+ /** Any CSS colour (`var(--token)`), for a palette the tones do not cover. Wins over `tone`. */
91
+ color?: string;
92
+ /** Classes for the segment's fill — keksdose's `STEP_CLASS` ramp. Wins over `tone`. */
93
+ className?: string;
94
+ /** The part's name: said in the bar's `aria-valuetext` and shown in the legend. */
95
+ label?: ReactNode;
96
+ /** A stable React key, when `label` is not a string. */
97
+ key?: string;
98
+ /**
99
+ * List it in the `legend` but leave it out of the bar — its value, its share of the
100
+ * `aria-valuetext`, and the sum. keksdose's cut card (reports/cut-card:132) names
101
+ * EVERY bucket under its bar, the empty ones included, while the bar draws only the
102
+ * ones with a share: pass the empty buckets as `legendOnly` entries.
103
+ */
104
+ legendOnly?: boolean;
105
+ }
106
+
44
107
  export interface ProgressBarProps extends Omit<ComponentPropsWithoutRef<"div">, "children" | "role"> {
45
108
  /** Where it stands. Leave it undefined for an INDETERMINATE bar — work is under way
46
109
  * and nobody knows how much is left (an import that reports no count). */
47
110
  value?: number;
48
111
  min?: number;
49
- max?: number;
112
+ /**
113
+ * The top of the scale (default 100). `null` is a quota with NO ceiling — kastlan's
114
+ * billing usage on an unlimited plan (usage-progress-bar:25): nothing to fill, so no
115
+ * bar is drawn; the label row shows the value and "Unlimited" (`progressBar.unlimited`)
116
+ * as plain text. `formatValue` is then called with `max` = `Infinity`; without it,
117
+ * the value is a plain number in the kit's locale.
118
+ */
119
+ max?: number | null;
50
120
  /**
51
121
  * `progress` (default): a task moving toward completion — `role="progressbar"`.
52
122
  * `meter`: a share of a known whole that is not going anywhere (budget used, disk
@@ -69,6 +139,39 @@ export interface ProgressBarProps extends Omit<ComponentPropsWithoutRef<"div">,
69
139
  tone?: ProgressBarTone;
70
140
  size?: ProgressBarSize;
71
141
  locale?: string;
142
+ /**
143
+ * Draw a METER OF PARTS: one bar, several fills side by side in order, a 2px gap
144
+ * between them — keksdose's discretionary-spending bar (cut-card:133), a whole
145
+ * split into the steps of a scale. Forces `variant="meter"`: parts of a whole are
146
+ * never "busy". `aria-valuenow` is the sum; `aria-valuetext` names every part
147
+ * ("Rent: 40%, Food: 25%"), each part's value through `formatValue` and joined in the
148
+ * kit's locale — the words keksdose's hand-drawn bar had to hide from a reader
149
+ * altogether, leaving the legend as the only way in. The sum is clamped to `max`,
150
+ * and parts past it are cut off at the track's end.
151
+ */
152
+ segments?: ProgressBarSegment[];
153
+ /** With `segments`: a list under the bar naming each part with its swatch and value
154
+ * — the table view of the bar, in text tokens rather than the segment colour. */
155
+ legend?: boolean;
156
+ /**
157
+ * What a legend row shows at its end, in place of the part's formatted value — which
158
+ * it is handed, with the segment and its index. keksdose's cut card shows the share
159
+ * AND a private outflow amount per bucket (cut-card:150), which one figure could not:
160
+ * `legendValue={(seg, share) => <>{share} <Amount … /></>}`.
161
+ */
162
+ legendValue?: (segment: ProgressBarSegment, formatted: string, index: number) => ReactNode;
163
+ /** A muted line under the bar — the plan's reset date, "3 of 5 seats in use". It
164
+ * describes the bar (`aria-describedby`). */
165
+ hint?: ReactNode;
166
+ /**
167
+ * When `value` is past `max`, say by how much, in a danger-coloured line under the bar
168
+ * (kastlan usage-progress-bar:46): `true` prints `progressBar.overLimit` with the
169
+ * excess as a plain number; a function renders the line itself from the excess (a
170
+ * currency, "2 seats"). The bar stays full at `max` either way — colour it with
171
+ * `tone="danger"` if it should read as over. Ignored for `segments` and `max={null}`.
172
+ */
173
+ overage?: boolean | ((over: number) => ReactNode);
174
+ labels?: Partial<ProgressBarLabels>;
72
175
  }
73
176
 
74
177
  /**
@@ -91,7 +194,7 @@ export interface ProgressBarProps extends Omit<ComponentPropsWithoutRef<"div">,
91
194
  export function ProgressBar({
92
195
  value,
93
196
  min = 0,
94
- max = 100,
197
+ max: maxProp = 100,
95
198
  variant = "progress",
96
199
  label,
97
200
  showValue = false,
@@ -99,28 +202,105 @@ export function ProgressBar({
99
202
  tone = "brand",
100
203
  size = "md",
101
204
  locale,
205
+ segments,
206
+ legend = false,
207
+ legendValue,
208
+ hint,
209
+ overage,
210
+ labels,
102
211
  className,
103
212
  "aria-label": ariaLabel,
104
213
  "aria-labelledby": ariaLabelledBy,
214
+ "aria-describedby": ariaDescribedBy,
105
215
  ...rest
106
216
  }: ProgressBarProps) {
107
217
  const common = useKitLabels("common", DEFAULT_COMMON_LABELS);
218
+ const text = useKitLabels("progressBar", DEFAULT_PROGRESS_BAR_LABELS, labels);
108
219
  const kitLocale = useKitLocale(locale);
109
220
  const labelId = useId();
221
+ const hintId = useId();
222
+ const hintNode =
223
+ hint != null ? (
224
+ <p id={hintId} data-part="hint" className="mt-1 text-xs text-[var(--text-muted)]">
225
+ {hint}
226
+ </p>
227
+ ) : null;
110
228
 
111
- const meter = variant === "meter";
112
- const current = value === undefined || !Number.isFinite(value) ? (meter ? min : undefined) : value;
113
- const indeterminate = current === undefined;
229
+ if (maxProp === null) {
230
+ // No ceiling: no bar, no role — a meter with no maximum is not one. The value and
231
+ // "Unlimited" are the content, so they are plain visible text.
232
+ const shown =
233
+ value === undefined || !Number.isFinite(value)
234
+ ? undefined
235
+ : formatValue
236
+ ? formatValue(value, Infinity, min)
237
+ : new Intl.NumberFormat(kitLocale).format(value);
238
+ return (
239
+ <div {...rest} className={cn("min-w-0", className)} data-state="unlimited">
240
+ <div className="flex items-baseline gap-2 text-sm">
241
+ {label != null && (
242
+ <span id={labelId} className="min-w-0 text-[var(--text-secondary)]">
243
+ {label}
244
+ </span>
245
+ )}
246
+ <span data-part="unlimited" className="ms-auto shrink-0 tabular-nums text-[var(--text-muted)]">
247
+ {shown !== undefined && `${shown} · `}
248
+ {text.unlimited}
249
+ </span>
250
+ </div>
251
+ {hintNode}
252
+ </div>
253
+ );
254
+ }
255
+ const max = maxProp;
256
+
257
+ const stacked = segments !== undefined;
258
+ const meter = stacked || variant === "meter";
114
259
  const span = max - min;
260
+ const partValue = (v: number) => (Number.isFinite(v) ? Math.max(0, v - min) : 0);
261
+ // Colours are dealt over EVERY segment, legend-only ones included, so an entry keeps
262
+ // its swatch whether or not it is in the bar this time.
263
+ const fills = stacked ? segmentFills(segments) : [];
264
+ const barParts = stacked ? segments.filter((seg) => !seg.legendOnly) : [];
265
+ const partTotal = stacked ? min + barParts.reduce((sum, seg) => sum + partValue(seg.value), 0) : undefined;
266
+ const format = (v: number) =>
267
+ formatValue
268
+ ? formatValue(v, max, min)
269
+ : new Intl.NumberFormat(kitLocale, { style: "percent", maximumFractionDigits: 0 }).format(
270
+ span <= 0 ? 0 : (Math.min(max, Math.max(min, v)) - min) / span,
271
+ );
272
+ const partTexts = stacked
273
+ ? barParts.map((seg) => {
274
+ const text = format(min + partValue(seg.value));
275
+ return typeof seg.label === "string" || typeof seg.label === "number"
276
+ ? common.fieldValue(String(seg.label), text)
277
+ : text;
278
+ })
279
+ : [];
280
+ const current =
281
+ partTotal ?? (value === undefined || !Number.isFinite(value) ? (meter ? min : undefined) : value);
282
+ const indeterminate = current === undefined;
115
283
  const clamped = indeterminate ? undefined : Math.min(max, Math.max(min, current));
116
284
  const fraction = clamped === undefined || span <= 0 ? 0 : (clamped - min) / span;
117
285
 
118
- const valueText =
119
- clamped === undefined
120
- ? undefined
121
- : formatValue
122
- ? formatValue(clamped, max, min)
123
- : new Intl.NumberFormat(kitLocale, { style: "percent", maximumFractionDigits: 0 }).format(fraction);
286
+ const valueText = clamped === undefined ? undefined : format(clamped);
287
+ const ariaValueText =
288
+ stacked && partTexts.length > 0
289
+ ? new Intl.ListFormat(kitLocale, { style: "short", type: "unit" }).format(partTexts)
290
+ : valueText;
291
+
292
+ const over =
293
+ overage && !stacked && value !== undefined && Number.isFinite(value) && value > max ? value - max : 0;
294
+ const overageNode =
295
+ over > 0 ? (
296
+ <p id={`${hintId}-over`} data-part="overage" className="mt-1 text-xs text-[var(--danger)]">
297
+ {typeof overage === "function"
298
+ ? overage(over)
299
+ : text.overLimit(new Intl.NumberFormat(kitLocale).format(over))}
300
+ </p>
301
+ ) : null;
302
+ const describedBy =
303
+ [ariaDescribedBy, hintNode && hintId, overageNode && `${hintId}-over`].filter(Boolean).join(" ") || undefined;
124
304
 
125
305
  const labelledBy = ariaLabelledBy ?? (label != null ? labelId : undefined);
126
306
  const name = labelledBy ? undefined : (ariaLabel ?? (indeterminate ? common.loading : undefined));
@@ -148,16 +328,38 @@ export function ProgressBar({
148
328
  role={meter ? "meter" : "progressbar"}
149
329
  aria-label={name}
150
330
  aria-labelledby={labelledBy}
331
+ aria-describedby={describedBy}
151
332
  aria-valuemin={indeterminate ? undefined : min}
152
333
  aria-valuemax={indeterminate ? undefined : max}
153
334
  aria-valuenow={clamped}
154
- aria-valuetext={valueText}
335
+ aria-valuetext={ariaValueText}
155
336
  aria-busy={!meter && indeterminate ? true : undefined}
156
337
  // `relative` + `overflow-hidden`: the indeterminate segment is positioned in
157
338
  // here and must not paint past the rounded ends.
158
339
  className={cn("relative w-full overflow-hidden rounded-full bg-[var(--bg-active)]", TRACK_HEIGHT[size])}
159
340
  >
160
- {indeterminate ? (
341
+ {stacked ? (
342
+ // The parts in a flex row with a 2px gap, so two neighbouring steps of one
343
+ // hue are parted by the track rather than by their difference in lightness.
344
+ <div className="flex h-full w-full gap-0.5">
345
+ {segments.map((seg, i) =>
346
+ seg.legendOnly ? null : (
347
+ <div
348
+ key={seg.key ?? i}
349
+ data-part="segment"
350
+ className={cn(
351
+ "h-full shrink-0 rounded-full transition-[width] duration-300 motion-reduce:transition-none",
352
+ fills[i],
353
+ )}
354
+ style={{
355
+ width: `${span <= 0 ? 0 : (partValue(seg.value) / span) * 100}%`,
356
+ backgroundColor: seg.className ? undefined : seg.color,
357
+ }}
358
+ />
359
+ ),
360
+ )}
361
+ </div>
362
+ ) : indeterminate ? (
161
363
  <div
162
364
  data-part="fill"
163
365
  className={cn(
@@ -182,6 +384,32 @@ export function ProgressBar({
182
384
  />
183
385
  )}
184
386
  </div>
387
+ {stacked && legend && segments.length > 0 && (
388
+ <ul className="mt-2 space-y-1 text-sm" data-part="legend">
389
+ {segments.map((seg, i) => (
390
+ <li key={seg.key ?? i} className="flex items-center gap-2">
391
+ <span
392
+ aria-hidden
393
+ className={cn(
394
+ "size-2.5 shrink-0 rounded-sm",
395
+ fills[i],
396
+ )}
397
+ style={{ backgroundColor: seg.className ? undefined : seg.color }}
398
+ />
399
+ {seg.label != null && (
400
+ <span className="min-w-0 flex-1 truncate text-[var(--text-secondary)]">{seg.label}</span>
401
+ )}
402
+ <span className="ms-auto shrink-0 text-xs tabular-nums text-[var(--text-muted)]">
403
+ {legendValue
404
+ ? legendValue(seg, format(min + partValue(seg.value)), i)
405
+ : format(min + partValue(seg.value))}
406
+ </span>
407
+ </li>
408
+ ))}
409
+ </ul>
410
+ )}
411
+ {overageNode}
412
+ {hintNode}
185
413
  </div>
186
414
  );
187
415
  }
@@ -0,0 +1,83 @@
1
+ import { useMemo } from "react";
2
+ import type { ComponentPropsWithoutRef } from "react";
3
+ import { cn } from "../lib/cn";
4
+ import { encodeQr } from "../lib/qr-encode";
5
+ import type { QrErrorCorrection } from "../lib/qr-encode";
6
+
7
+ export interface QrCodeProps extends Omit<ComponentPropsWithoutRef<"svg">, "children" | "viewBox"> {
8
+ /** The text to encode (UTF-8) — an `otpauth://` URI, a URL, a pairing code. */
9
+ value: string;
10
+ /** The accessible name. Without it the code is decorative (`aria-hidden`): say what
11
+ * it is for, not what it encodes — nobody wants a URI read out. */
12
+ label?: string;
13
+ /** Default `"M"`, raised automatically where the size allows it. */
14
+ errorCorrection?: QrErrorCorrection;
15
+ /** The quiet zone, in modules. The spec asks for 4; less reads worse. Default 4. */
16
+ margin?: number;
17
+ }
18
+
19
+ /**
20
+ * A QR code as one inline SVG — no canvas, no data URL, no dependency (see
21
+ * `lib/qr-encode.ts` for why the encoder lives in the kit).
22
+ *
23
+ * Sized by CSS (`className="size-48"`); the SVG scales without blurring because every
24
+ * module is a whole unit of the viewBox and `shape-rendering` is `crispEdges`.
25
+ *
26
+ * **Black on white, always — the one literal colour pair in the kit.** A camera looks
27
+ * for the contrast between the modules and their quiet zone; on the dark theme a
28
+ * surface-coloured background makes a code slow to acquire, and an inverted one is not
29
+ * read at all by several authenticator apps. The quiet zone is part of the SVG, so the
30
+ * code carries its own white border wherever it is placed.
31
+ *
32
+ * A value too long for any QR version (≈2.9 kB) renders nothing rather than throwing
33
+ * in render.
34
+ */
35
+ export function QrCode({
36
+ value,
37
+ label,
38
+ errorCorrection = "M",
39
+ margin = 4,
40
+ className,
41
+ ...rest
42
+ }: QrCodeProps) {
43
+ const path = useMemo(() => {
44
+ try {
45
+ const { modules, size } = encodeQr(value, { errorCorrection });
46
+ // One path, one sub-path per horizontal run of dark modules: a few hundred
47
+ // commands rather than one <rect> per module.
48
+ let d = "";
49
+ for (let y = 0; y < size; y++) {
50
+ for (let x = 0; x < size; ) {
51
+ if (!modules[y][x]) {
52
+ x++;
53
+ continue;
54
+ }
55
+ let run = 1;
56
+ while (x + run < size && modules[y][x + run]) run++;
57
+ d += `M${x + margin} ${y + margin}h${run}v1h-${run}z`;
58
+ x += run;
59
+ }
60
+ }
61
+ return { d, extent: size + margin * 2 };
62
+ } catch {
63
+ return null;
64
+ }
65
+ }, [value, errorCorrection, margin]);
66
+
67
+ if (!path) return null;
68
+ return (
69
+ <svg
70
+ {...rest}
71
+ xmlns="http://www.w3.org/2000/svg"
72
+ viewBox={`0 0 ${path.extent} ${path.extent}`}
73
+ shapeRendering="crispEdges"
74
+ role={label ? "img" : undefined}
75
+ aria-label={label}
76
+ aria-hidden={label ? undefined : true}
77
+ className={cn("block", className)}
78
+ >
79
+ <rect width={path.extent} height={path.extent} fill="#ffffff" />
80
+ <path d={path.d} fill="#000000" />
81
+ </svg>
82
+ );
83
+ }
@@ -3,6 +3,7 @@ import type { InputHTMLAttributes } from "react";
3
3
  import { Search, X } from "lucide-react";
4
4
  import { cn } from "../lib/cn";
5
5
  import { FIELD_BASE } from "./ui";
6
+ import { DEFAULT_COMMON_LABELS, useKitLabels } from "../i18n/kit-labels";
6
7
 
7
8
  interface SearchFieldOwnProps
8
9
  extends Omit<
@@ -23,10 +24,22 @@ interface SearchFieldOwnProps
23
24
  * absent, so a consumer can migrate one call site at a time.
24
25
  */
25
26
  label?: string;
26
- /** Names the clear button. Omit it and no clear button is rendered — which is a
27
- * decision, not a default: a filter you can type into and not untype is the
28
- * complaint that produced the button on three of these four screens. */
27
+ /** Names the clear button. Default: the catalogue's `common.clear`, in the
28
+ * provider's language. */
29
29
  clearLabel?: string;
30
+ /**
31
+ * Draw the "×" that empties the field once there is something in it. Default
32
+ * `true`.
33
+ *
34
+ * It used to appear only when a `clearLabel` was passed, and every kastlan call
35
+ * site passed one — lease-list-page.tsx:91, trial-balance-page.tsx:~196,
36
+ * account-ledger-page.tsx:~131 and data-table/resource-list-page.tsx:256 each
37
+ * spelled `clearLabel={t("table.clear")}` — so the opt-in was boilerplate: a filter
38
+ * you can type into and not untype is the complaint that produced the button in
39
+ * the first place, and the catalogue already names it. `false` is for a field
40
+ * whose clearing lives elsewhere (a "Reset filters" beside it).
41
+ */
42
+ clearable?: boolean;
30
43
  /**
31
44
  * `"field"` (default): the bordered filter box that sits on a page.
32
45
  *
@@ -96,7 +109,8 @@ export const SearchField = forwardRef<HTMLInputElement, SearchFieldProps>(functi
96
109
  value,
97
110
  onChange,
98
111
  label,
99
- clearLabel,
112
+ clearLabel: clearLabelProp,
113
+ clearable = true,
100
114
  className,
101
115
  inputClassName,
102
116
  placeholder,
@@ -107,6 +121,8 @@ export const SearchField = forwardRef<HTMLInputElement, SearchFieldProps>(functi
107
121
  ref,
108
122
  ) {
109
123
  const innerRef = useRef<HTMLInputElement>(null);
124
+ const common = useKitLabels("common", DEFAULT_COMMON_LABELS);
125
+ const clearLabel = clearable ? (clearLabelProp ?? common.clear) : undefined;
110
126
  // Whichever spelling the caller used. It is the accessible name AND the placeholder
111
127
  // fallback, so a box named only by `aria-label` still says what it is before anyone
112
128
  // has typed in it — the same deal `label` has always had.
@@ -129,6 +129,18 @@ export interface SeriesChartSeries {
129
129
  * `{ r }` sets its radius. Default: recharts' dot. Bars have none, and ignore it.
130
130
  */
131
131
  activeDot?: boolean | { r?: number };
132
+ /**
133
+ * The shortest a bar of this series is drawn, in px — recharts' `minPointSize`. A
134
+ * zero or a value too small to see keeps a visible stub, so an empty slot still reads
135
+ * as a slot with a value rather than a gap: keksdose's weekday pattern, where a
136
+ * Sunday with no spending drew a 2% stub by hand. Only the DRAWING changes: the axis
137
+ * is fitted to the values, and the tooltip, the keyboard stops and every accessible
138
+ * name still say 0. A hole (no value in the row) stays a hole. Overrides the chart's
139
+ * {@link SeriesChartProps.minBarLength}; `0` turns it off for this series. Meant for
140
+ * unstacked bars — in a `stack` the stub is drawn over the layer above it. Lines and
141
+ * areas ignore it.
142
+ */
143
+ minBarLength?: number;
132
144
  }
133
145
 
134
146
  /** What a series draws. See {@link SeriesChartSeries.type}. */
@@ -507,6 +519,10 @@ export interface SeriesChartProps {
507
519
  locale?: string;
508
520
  /** Onto the chart's root (`ChartContainer`) — e.g. an app's own grid ink. */
509
521
  className?: string;
522
+ /** The shortest bar drawn, in px, for every bar series that does not set its own
523
+ * {@link SeriesChartSeries.minBarLength}. Drawing only: domain, tooltip and names
524
+ * keep the real value. Default: none — a zero draws nothing. */
525
+ minBarLength?: number;
510
526
  }
511
527
 
512
528
  /** How much of an axis band the rotated title takes, in px. Reserved rather than
@@ -1011,6 +1027,7 @@ function SeriesPlot({
1011
1027
  labels: labelsProp,
1012
1028
  locale: localeProp,
1013
1029
  className,
1030
+ minBarLength,
1014
1031
  }: PlotProps) {
1015
1032
  const labels = useKitLabels("seriesChart", DEFAULT_SERIES_CHART_LABELS, labelsProp);
1016
1033
  const locale = useKitLocale(localeProp);
@@ -1418,6 +1435,7 @@ function SeriesPlot({
1418
1435
  const curve = entry.step ? "stepAfter" : (entry.curve ?? "monotone");
1419
1436
  const width = entry.strokeWidth ?? (entry.step ? STEP_WIDTH : LINE_WIDTH);
1420
1437
  if (type === "bar") {
1438
+ const stub = entry.minBarLength ?? minBarLength;
1421
1439
  return (
1422
1440
  <Bar
1423
1441
  key={entry.key}
@@ -1427,6 +1445,9 @@ function SeriesPlot({
1427
1445
  fill={color}
1428
1446
  fillOpacity={entry.fillOpacity}
1429
1447
  radius={entry.stack === undefined ? BAR_RADIUS : 0}
1448
+ // Read off the row rather than recharts' `value`: in a stack that is the
1449
+ // layer's running top, which a hole has too — and a hole gets no stub.
1450
+ {...(stub ? { minPointSize: (_value: unknown, index: number) => (finite(plotted[index]?.[entry.key]) === undefined ? 0 : stub) } : {})}
1430
1451
  // Only the focused stop's series swaps its shape, and only for as long
1431
1452
  // as it has focus: every other bar keeps recharts' own.
1432
1453
  {...(focusedStop?.key === entry.key ? { shape: focusedBarShape(focusedStop.index) } : {})}