@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
@@ -0,0 +1,286 @@
1
+ import type { ComponentPropsWithoutRef, ReactNode } from "react";
2
+ import { ArrowDown, ArrowUp, Minus } from "lucide-react";
3
+ import { cn } from "../lib/cn";
4
+ import { formatMoney, formatNumber, formatPercent } from "../lib/format";
5
+ import { useKitLabels, useKitLocale } from "../i18n/kit-labels";
6
+ import { DEFAULT_STAT_TILE_LABELS } from "./stat-tile";
7
+ import type { StatTileLabels } from "./stat-tile";
8
+
9
+ /**
10
+ * The text colours a figure can carry, token-backed — `StatTile`'s tones, as a class
11
+ * any span can take.
12
+ *
13
+ * - `default` — the primary text colour. `muted` — the secondary, quiet one.
14
+ * - `income` / `expense` / `net` — the money trio (CVD-safe teal / amber / violet).
15
+ * - `success` / `warning` / `danger` / `info` — a verdict: a rent rise is the tenant's
16
+ * bad news (kastlan lease-columns), a vote "for" is green (meeting-agenda-tab).
17
+ * - `brand` — the app's accent.
18
+ *
19
+ * Colour is never the only carrier: pair a verdict with an arrow, a word, or a sign.
20
+ */
21
+ export type TextTone =
22
+ | "default"
23
+ | "muted"
24
+ | "brand"
25
+ | "income"
26
+ | "expense"
27
+ | "net"
28
+ | "success"
29
+ | "warning"
30
+ | "danger"
31
+ | "info";
32
+
33
+ const TEXT_TONE: Record<TextTone, string> = {
34
+ default: "text-[var(--text-primary)]",
35
+ muted: "text-[var(--text-muted)]",
36
+ brand: "text-[var(--brand)]",
37
+ income: "text-[var(--money-income)]",
38
+ expense: "text-[var(--money-expense)]",
39
+ net: "text-[var(--money-net)]",
40
+ success: "text-[var(--success)]",
41
+ warning: "text-[var(--warning)]",
42
+ danger: "text-[var(--danger)]",
43
+ info: "text-[var(--info)]",
44
+ };
45
+
46
+ /**
47
+ * The class that colours text in `tone` — for the spans kastlan colours by hand with
48
+ * `text-[var(--danger)]` / `text-emerald-600` (settlement-table, changelog-page,
49
+ * meeting-agenda-tab), so the palette they draw from is the kit's tokens.
50
+ */
51
+ export function toneTextClass(tone: TextTone): string {
52
+ return TEXT_TONE[tone];
53
+ }
54
+
55
+ export interface ToneProps extends ComponentPropsWithoutRef<"span"> {
56
+ tone: TextTone;
57
+ }
58
+
59
+ /** A `<span>` in a {@link TextTone} — `<Tone tone="success">{votesFor}</Tone>`. */
60
+ export function Tone({ tone, className, ...rest }: ToneProps) {
61
+ return <span {...rest} className={cn(TEXT_TONE[tone], className)} />;
62
+ }
63
+
64
+ /* ── Signed amounts ──────────────────────────────────────────────────────── */
65
+
66
+ /** Which way is +1: `up` for a figure where more is good news (income, savings),
67
+ * `down` where less is (a rent the tenant pays, a cost). */
68
+ export type GoodDirection = "up" | "down";
69
+
70
+ /**
71
+ * How a signed figure is coloured:
72
+ * - `signed` (default) — money read by its own sign: above zero `income`, below zero
73
+ * `expense`, zero muted. A balance, a net.
74
+ * - `income` / `expense` / `neutral` — one colour whatever the sign (`neutral` is the
75
+ * text's own colour).
76
+ * - `verdict` — the change judged by {@link SignedAmountProps.goodDirection}: good
77
+ * news `success`, bad news `danger`, zero muted. Setting `goodDirection` implies it.
78
+ */
79
+ export type SignedAmountTone = "signed" | "income" | "expense" | "neutral" | "verdict";
80
+
81
+ /** What a signed figure says to a screen reader, whose readers disagree about a
82
+ * leading "+" (most skip it) and a "−" (some read "dash", some nothing). */
83
+ export interface SignedAmountLabels {
84
+ /** A figure above zero. `amount` is formatted and unsigned. */
85
+ positive: (amount: string) => string;
86
+ /** A figure below zero. `amount` is formatted and unsigned. */
87
+ negative: (amount: string) => string;
88
+ }
89
+
90
+ export const DEFAULT_SIGNED_AMOUNT_LABELS: SignedAmountLabels = {
91
+ positive: (amount) => `plus ${amount}`,
92
+ negative: (amount) => `minus ${amount}`,
93
+ };
94
+
95
+ interface SignedFormatProps {
96
+ /** ISO 4217 code: format as money. */
97
+ currency?: string;
98
+ /** `"percent"` reads `value` as a RATIO (`0.12` → 12 %), as `StatTile`'s delta does. */
99
+ unit?: "value" | "percent";
100
+ /** Compact notation: 12 400 → "12K". */
101
+ compact?: boolean;
102
+ /** Fraction digits, as `formatNumber`'s `digits`. */
103
+ digits?: number | { min?: number; max?: number };
104
+ /** Formats the UNSIGNED magnitude, replacing the `Intl` default outright. The sign
105
+ * is the component's. */
106
+ format?: (magnitude: number) => string;
107
+ locale?: string;
108
+ }
109
+
110
+ function useMagnitudeFormatter({ currency, unit, compact, digits, format, locale }: SignedFormatProps) {
111
+ const kitLocale = useKitLocale(locale);
112
+ return (magnitude: number): string => {
113
+ if (format) return format(magnitude);
114
+ if (unit === "percent") return formatPercent(magnitude, { locale: kitLocale, compact, digits });
115
+ if (currency) return formatMoney(magnitude, currency, { locale: kitLocale, compact, digits });
116
+ return formatNumber(magnitude, { locale: kitLocale, compact, digits });
117
+ };
118
+ }
119
+
120
+ function directionOf(value: number): "up" | "down" | "flat" {
121
+ return value > 0 ? "up" : value < 0 ? "down" : "flat";
122
+ }
123
+
124
+ function signedToneClass(
125
+ tone: SignedAmountTone,
126
+ dir: "up" | "down" | "flat",
127
+ goodDirection: GoodDirection | undefined,
128
+ ): string {
129
+ if (tone === "verdict" || (goodDirection && tone === "signed")) {
130
+ if (dir === "flat" || !goodDirection) return TEXT_TONE.muted;
131
+ return dir === goodDirection ? TEXT_TONE.success : TEXT_TONE.danger;
132
+ }
133
+ if (tone === "signed") return dir === "flat" ? TEXT_TONE.muted : dir === "up" ? TEXT_TONE.income : TEXT_TONE.expense;
134
+ if (tone === "neutral") return "";
135
+ return TEXT_TONE[tone];
136
+ }
137
+
138
+ const ARROW = { up: ArrowUp, down: ArrowDown, flat: Minus } as const;
139
+
140
+ export interface SignedAmountProps
141
+ extends SignedFormatProps,
142
+ Omit<ComponentPropsWithoutRef<"span">, "children"> {
143
+ value: number;
144
+ /** See {@link SignedAmountTone}. Default `signed`, or `verdict` with `goodDirection`. */
145
+ tone?: SignedAmountTone;
146
+ /** Which way is good news — colours by verdict (`success` / `danger`). */
147
+ goodDirection?: GoodDirection;
148
+ /** Print "+" before a positive figure (default `true`). A negative always shows its
149
+ * minus; zero shows neither. */
150
+ showPlus?: boolean;
151
+ /** An arrow before the figure (up / down / a dash at zero) — so the direction does not
152
+ * ride on colour alone. Decorative; the words say the same. */
153
+ arrow?: boolean;
154
+ /** Tag the figure `data-private`, for the host's demo-mode blur. */
155
+ sensitive?: boolean;
156
+ labels?: Partial<SignedAmountLabels>;
157
+ }
158
+
159
+ /**
160
+ * A figure with a sign that matters: a balance, a rent change, a net.
161
+ *
162
+ * kastlan writes it by hand in five places — `{change > 0 ? "+" : ""}{formatCurrency(
163
+ * change)}` in a span coloured by a ternary (lease-columns:190, settlement-table:58,
164
+ * the tenancy `formatPct`) — and each picks its own colours for the same verdict.
165
+ *
166
+ * The visible figure (sign, digits, arrow) is hidden from assistive tech and spoken as
167
+ * words instead ("plus CHF 50.00" / "minus CHF 50.00", `signedAmount.*`): a "+" is
168
+ * skipped by most screen readers, and `Intl`'s "−" is read as "dash" or not at all.
169
+ */
170
+ export function SignedAmount({
171
+ value,
172
+ currency,
173
+ unit,
174
+ compact,
175
+ digits,
176
+ format,
177
+ locale,
178
+ tone = "signed",
179
+ goodDirection,
180
+ showPlus = true,
181
+ arrow = false,
182
+ sensitive = false,
183
+ labels,
184
+ className,
185
+ ...rest
186
+ }: SignedAmountProps) {
187
+ const text = useKitLabels("signedAmount", DEFAULT_SIGNED_AMOUNT_LABELS, labels);
188
+ const fmt = useMagnitudeFormatter({ currency, unit, compact, digits, format, locale });
189
+ const finite = Number.isFinite(value);
190
+ const dir = finite ? directionOf(value) : "flat";
191
+ const amount = finite ? fmt(Math.abs(value)) : "—";
192
+ // U+2212, the typographic minus: the same width as "+", so a column of signed figures
193
+ // lines up under `tabular-nums`.
194
+ const sign = dir === "up" ? (showPlus ? "+" : "") : dir === "down" ? "−" : "";
195
+ const spoken = dir === "up" ? text.positive(amount) : dir === "down" ? text.negative(amount) : amount;
196
+ const Icon = ARROW[dir];
197
+ return (
198
+ <span
199
+ {...rest}
200
+ data-private={sensitive ? "" : undefined}
201
+ data-direction={dir}
202
+ className={cn(
203
+ "relative inline-flex items-center gap-0.5 whitespace-nowrap tabular-nums",
204
+ signedToneClass(tone, dir, goodDirection),
205
+ className,
206
+ )}
207
+ >
208
+ {arrow && <Icon aria-hidden className="size-[1em] shrink-0" />}
209
+ <span aria-hidden>
210
+ {sign}
211
+ {amount}
212
+ </span>
213
+ <span className="sr-only">{spoken}</span>
214
+ </span>
215
+ );
216
+ }
217
+
218
+ export interface DeltaProps extends SignedFormatProps, Omit<ComponentPropsWithoutRef<"span">, "children"> {
219
+ /** The change. Above zero is "up". */
220
+ value: number;
221
+ /** Which way is good news. Unset, the change is stated and not judged (muted); set,
222
+ * it is coloured `success` / `danger` and its sentence says "(better)" / "(worse)". */
223
+ goodDirection?: GoodDirection;
224
+ /** What it is measured against, after it: "vs last month". */
225
+ label?: ReactNode;
226
+ /** Hide the arrow (shown by default, as on a StatTile). */
227
+ arrow?: boolean;
228
+ sensitive?: boolean;
229
+ /** The `statTile` namespace's direction words — a delta says the same as a tile's. */
230
+ labels?: Partial<Pick<StatTileLabels, "increase" | "decrease" | "unchanged" | "better" | "worse">>;
231
+ }
232
+
233
+ /**
234
+ * `StatTile`'s delta on its own: an arrow, the unsigned magnitude, and a sentence for
235
+ * screen readers ("Up 12% (worse)"), coloured only when `goodDirection` judges it —
236
+ * users up and cost up are the same arrow until someone says which is good.
237
+ *
238
+ * For a change shown outside a tile: kastlan's rent-change column
239
+ * (lease-columns:219, a "+1.25%" chip), the breakdown rows of the rent calculator
240
+ * (breakdown-row:5, a trend icon coloured by hand).
241
+ */
242
+ export function Delta({
243
+ value,
244
+ currency,
245
+ unit,
246
+ compact,
247
+ digits,
248
+ format,
249
+ locale,
250
+ goodDirection,
251
+ label,
252
+ arrow = true,
253
+ sensitive = false,
254
+ labels,
255
+ className,
256
+ ...rest
257
+ }: DeltaProps) {
258
+ const text = useKitLabels("statTile", DEFAULT_STAT_TILE_LABELS, labels);
259
+ const fmt = useMagnitudeFormatter({ currency, unit, compact, digits, format, locale });
260
+ if (!Number.isFinite(value)) return null;
261
+ const amount = fmt(Math.abs(value));
262
+ const dir = directionOf(value);
263
+ const Icon = ARROW[dir];
264
+ let spoken = dir === "up" ? text.increase(amount) : dir === "down" ? text.decrease(amount) : text.unchanged;
265
+ let tone = TEXT_TONE.muted;
266
+ if (goodDirection && dir !== "flat") {
267
+ const good = dir === goodDirection;
268
+ spoken = good ? text.better(spoken) : text.worse(spoken);
269
+ tone = good ? TEXT_TONE.success : TEXT_TONE.danger;
270
+ }
271
+ return (
272
+ <span
273
+ {...rest}
274
+ data-direction={dir}
275
+ className={cn("relative inline-flex flex-wrap items-center gap-x-1.5 tabular-nums", className)}
276
+ >
277
+ <span data-private={sensitive ? "" : undefined} className={cn("inline-flex items-center gap-0.5 font-medium", tone)}>
278
+ {arrow && <Icon className="size-[1em] shrink-0" aria-hidden />}
279
+ {/* Flat with an arrow is the dash alone, as on a tile; without one, the zero. */}
280
+ <span aria-hidden>{dir === "flat" && arrow ? null : amount}</span>
281
+ <span className="sr-only">{spoken}</span>
282
+ </span>
283
+ {label != null && <span className="text-[var(--text-muted)]">{label}</span>}
284
+ </span>
285
+ );
286
+ }
@@ -21,6 +21,13 @@ export interface SkeletonProps extends Omit<ComponentPropsWithoutRef<"div">, "ch
21
21
  shape?: SkeletonShape;
22
22
  /** For `line`: this many lines, the last one shorter — a paragraph's outline. */
23
23
  lines?: number;
24
+ /**
25
+ * Say what is loading, once: wraps the placeholder in a `role="status"` region with
26
+ * this as its (visually hidden) text — kastlan's activity feed (activity-feed:52)
27
+ * built exactly that by hand around `<Skeleton lines={5} />`. The placeholder itself
28
+ * stays hidden. Left out, nothing is announced, as before.
29
+ */
30
+ label?: string;
24
31
  }
25
32
 
26
33
  const SHAPE: Record<SkeletonShape, string> = {
@@ -37,10 +44,21 @@ const SHAPE: Record<SkeletonShape, string> = {
37
44
  *
38
45
  * ALWAYS `aria-hidden`. A placeholder is not content, and a reader that met twelve
39
46
  * unnamed grey boxes would have learned nothing. Say "loading" once, where it
40
- * belongs: `aria-busy` on the region being filled, or a `Spinner` / sr-only line
41
- * beside the skeletons.
47
+ * belongs: `aria-busy` on the region being filled, a `Spinner` / sr-only line
48
+ * beside the skeletons, or this component's own `label`.
42
49
  */
43
- export function Skeleton({ shape = "line", lines, className, ...rest }: SkeletonProps) {
50
+ export function Skeleton({ label, ...props }: SkeletonProps) {
51
+ if (!label) return <SkeletonFill {...props} />;
52
+ return (
53
+ // `relative`: the containing block for the sr-only text (sr-only-containment.test).
54
+ <div role="status" className="relative" data-skeleton-status="">
55
+ <span className="sr-only">{label}</span>
56
+ <SkeletonFill {...props} />
57
+ </div>
58
+ );
59
+ }
60
+
61
+ function SkeletonFill({ shape = "line", lines, className, ...rest }: Omit<SkeletonProps, "label">) {
44
62
  if (shape === "line" && lines !== undefined && lines > 1) {
45
63
  return (
46
64
  <div {...rest} aria-hidden className={cn("flex flex-col gap-2", className)} data-skeleton="lines">
@@ -2,7 +2,8 @@ import { isValidElement, useId, useLayoutEffect, useState } from "react";
2
2
  import type { ComponentPropsWithoutRef, CSSProperties, ReactElement, ReactNode } from "react";
3
3
  import { ArrowDown, ArrowUp, Minus } from "lucide-react";
4
4
  import { cn } from "../lib/cn";
5
- import { useKitLabels, useKitLocale } from "../i18n/kit-labels";
5
+ import { useKitLabels, useKitLink, useKitLocale } from "../i18n/kit-labels";
6
+ import { pickLinkRenderer, RenderedKitLink } from "./text-link";
6
7
  import { Card, FieldHint } from "./ui";
7
8
  import { Sparkline } from "./sparkline";
8
9
  import { Tooltip } from "./tooltip";
@@ -219,7 +220,8 @@ export interface StatTileProps
219
220
  /** Makes the tile a link. See {@link renderLink} for a router. */
220
221
  href?: string;
221
222
  /** Renders the link for `href` — pass your router's `<Link>` here, since a plain
222
- * `<a>` reloads a single-page app. Default: `<a>`. */
223
+ * `<a>` reloads a single-page app. Default: the `<UiKitProvider linkComponent>`
224
+ * (for an in-app `href`), then `<a>`. */
223
225
  renderLink?: (props: StatTileLinkProps) => ReactElement;
224
226
  /** Makes the tile a button (ignored when `href` is given). */
225
227
  onClick?: () => void;
@@ -297,6 +299,7 @@ export function StatTile({
297
299
  ...rest
298
300
  }: StatTileProps) {
299
301
  const text = useKitLabels("statTile", DEFAULT_STAT_TILE_LABELS, labels);
302
+ const kitLink = useKitLink();
300
303
  const [labelEl, setLabelEl] = useState<HTMLElement | null>(null);
301
304
  const labelCut = useIsTruncated(truncateLabel ? labelEl : null);
302
305
  const kitLocale = useKitLocale(locale);
@@ -334,8 +337,11 @@ export function StatTile({
334
337
  "aria-describedby": describedBy,
335
338
  children: label,
336
339
  };
340
+ const kit = pickLinkRenderer<StatTileLinkProps>(undefined, kitLink, href);
337
341
  labelNode = renderLink ? (
338
342
  renderLink(linkProps)
343
+ ) : kit ? (
344
+ <RenderedKitLink render={kit} props={linkProps} />
339
345
  ) : (
340
346
  <a href={href} className={stretched} aria-describedby={describedBy}>
341
347
  {label}
@@ -1,14 +1,17 @@
1
1
  import type { ComponentPropsWithoutRef, ReactNode } from "react";
2
2
  import { cn } from "../lib/cn";
3
3
  import type { ProgressBarTone } from "./progress-bar";
4
+ import type { ChipHue } from "./chip";
4
5
 
5
6
  /**
6
7
  * The tones are `ProgressBar`'s, name for name — which are `Chip`'s, less nothing — so a
7
8
  * dot in a legend, the bar it explains and the badge beside it can be given the same
8
9
  * word and agree. The money pair is here for the same reason it is there: a legend
9
- * swatch for income is money, not "success".
10
+ * swatch for income is money, not "success". Chip's categorical hues are here too
11
+ * (0.11): a unit-status dot in a combobox (kastlan lease-unit-step.tsx:35, "rented"
12
+ * is blue) must match the Chip that states the same status elsewhere.
10
13
  */
11
- export type StatusDotTone = ProgressBarTone;
14
+ export type StatusDotTone = ProgressBarTone | ChipHue;
12
15
 
13
16
  /** `sm` 8px (keksdose's unread dot on the support trigger), `md` 10px (the avatar's
14
17
  * unread dot), `lg` 12px (kastlan's occupancy legend swatch). */
@@ -23,6 +26,11 @@ const FILL: Record<StatusDotTone, string> = {
23
26
  info: "bg-[var(--info)]",
24
27
  income: "bg-[var(--money-income)]",
25
28
  expense: "bg-[var(--money-expense)]",
29
+ blue: "bg-[var(--hue-blue)]",
30
+ indigo: "bg-[var(--hue-indigo)]",
31
+ purple: "bg-[var(--hue-purple)]",
32
+ teal: "bg-[var(--hue-teal)]",
33
+ orange: "bg-[var(--hue-orange)]",
26
34
  };
27
35
 
28
36
  const SIZE: Record<StatusDotSize, string> = { sm: "size-2", md: "size-2.5", lg: "size-3" };