@eifi1/ui-kit 0.4.0 → 0.5.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 (410) hide show
  1. package/LICENSE +32 -0
  2. package/README.md +498 -57
  3. package/dist/chart.d.ts +10 -0
  4. package/dist/chart.js +10 -0
  5. package/dist/chart.js.map +1 -0
  6. package/dist/components/account-settings.d.ts +11 -0
  7. package/dist/components/account-settings.js +19 -12
  8. package/dist/components/account-settings.js.map +1 -1
  9. package/dist/components/alert-banner.d.ts +10 -6
  10. package/dist/components/alert-banner.js +20 -16
  11. package/dist/components/alert-banner.js.map +1 -1
  12. package/dist/components/amount-input.d.ts +30 -4
  13. package/dist/components/amount-input.js +36 -14
  14. package/dist/components/amount-input.js.map +1 -1
  15. package/dist/components/calculator.d.ts +24 -7
  16. package/dist/components/calculator.js +37 -20
  17. package/dist/components/calculator.js.map +1 -1
  18. package/dist/components/chart-zoom.d.ts +156 -0
  19. package/dist/components/chart-zoom.js +262 -0
  20. package/dist/components/chart-zoom.js.map +1 -0
  21. package/dist/components/chart.d.ts +28 -3
  22. package/dist/components/chart.js +71 -32
  23. package/dist/components/chart.js.map +1 -1
  24. package/dist/components/checkbox.d.ts +51 -0
  25. package/dist/components/checkbox.js +123 -0
  26. package/dist/components/checkbox.js.map +1 -0
  27. package/dist/components/chip.d.ts +128 -0
  28. package/dist/components/chip.js +357 -0
  29. package/dist/components/chip.js.map +1 -0
  30. package/dist/components/choice-parts.d.ts +24 -0
  31. package/dist/components/choice-parts.js +18 -0
  32. package/dist/components/choice-parts.js.map +1 -0
  33. package/dist/components/combobox-core.d.ts +52 -9
  34. package/dist/components/combobox-core.js +91 -20
  35. package/dist/components/combobox-core.js.map +1 -1
  36. package/dist/components/combobox.d.ts +43 -33
  37. package/dist/components/combobox.js +409 -340
  38. package/dist/components/combobox.js.map +1 -1
  39. package/dist/components/currency-select.d.ts +48 -8
  40. package/dist/components/currency-select.js +107 -65
  41. package/dist/components/currency-select.js.map +1 -1
  42. package/dist/components/data-table-filter-popover.d.ts +15 -3
  43. package/dist/components/data-table-filter-popover.js +23 -17
  44. package/dist/components/data-table-filter-popover.js.map +1 -1
  45. package/dist/components/data-table-filters.d.ts +1 -1
  46. package/dist/components/data-table-filters.js +3 -2
  47. package/dist/components/data-table-filters.js.map +1 -1
  48. package/dist/components/data-table-labels.d.ts +53 -2
  49. package/dist/components/data-table-labels.js +31 -2
  50. package/dist/components/data-table-labels.js.map +1 -1
  51. package/dist/components/data-table-pagination.d.ts +3 -1
  52. package/dist/components/data-table-pagination.js +16 -10
  53. package/dist/components/data-table-pagination.js.map +1 -1
  54. package/dist/components/data-table-sort.js +1 -0
  55. package/dist/components/data-table-sort.js.map +1 -1
  56. package/dist/components/data-table.d.ts +1 -1
  57. package/dist/components/data-table.js +201 -85
  58. package/dist/components/data-table.js.map +1 -1
  59. package/dist/components/date-picker.d.ts +25 -9
  60. package/dist/components/date-picker.js +232 -94
  61. package/dist/components/date-picker.js.map +1 -1
  62. package/dist/components/dropdown.d.ts +96 -20
  63. package/dist/components/dropdown.js +77 -33
  64. package/dist/components/dropdown.js.map +1 -1
  65. package/dist/components/entity-combobox.d.ts +5 -4
  66. package/dist/components/entity-combobox.js +107 -76
  67. package/dist/components/entity-combobox.js.map +1 -1
  68. package/dist/components/facing-pair.d.ts +36 -0
  69. package/dist/components/facing-pair.js +40 -0
  70. package/dist/components/facing-pair.js.map +1 -0
  71. package/dist/components/field-sync.d.ts +164 -0
  72. package/dist/components/field-sync.js +264 -0
  73. package/dist/components/field-sync.js.map +1 -0
  74. package/dist/components/file-dropzone.d.ts +38 -6
  75. package/dist/components/file-dropzone.js +34 -9
  76. package/dist/components/file-dropzone.js.map +1 -1
  77. package/dist/components/full-bleed-dialog.d.ts +37 -26
  78. package/dist/components/full-bleed-dialog.js +20 -5
  79. package/dist/components/full-bleed-dialog.js.map +1 -1
  80. package/dist/components/grouped-picker.d.ts +21 -10
  81. package/dist/components/grouped-picker.js +66 -52
  82. package/dist/components/grouped-picker.js.map +1 -1
  83. package/dist/components/hover-menu.d.ts +20 -4
  84. package/dist/components/hover-menu.js +15 -4
  85. package/dist/components/hover-menu.js.map +1 -1
  86. package/dist/components/mini-calendar.d.ts +102 -14
  87. package/dist/components/mini-calendar.js +256 -70
  88. package/dist/components/mini-calendar.js.map +1 -1
  89. package/dist/components/modal.d.ts +9 -3
  90. package/dist/components/modal.js +34 -45
  91. package/dist/components/modal.js.map +1 -1
  92. package/dist/components/month-picker.d.ts +79 -0
  93. package/dist/components/month-picker.js +342 -0
  94. package/dist/components/month-picker.js.map +1 -0
  95. package/dist/components/multi-entity-combobox.d.ts +7 -6
  96. package/dist/components/multi-entity-combobox.js +112 -72
  97. package/dist/components/multi-entity-combobox.js.map +1 -1
  98. package/dist/components/multi-select.d.ts +13 -11
  99. package/dist/components/multi-select.js +161 -84
  100. package/dist/components/multi-select.js.map +1 -1
  101. package/dist/components/number-field.d.ts +92 -0
  102. package/dist/components/number-field.js +111 -0
  103. package/dist/components/number-field.js.map +1 -0
  104. package/dist/components/number-input.d.ts +28 -2
  105. package/dist/components/number-input.js +12 -2
  106. package/dist/components/number-input.js.map +1 -1
  107. package/dist/components/numpad-sheet.d.ts +32 -4
  108. package/dist/components/numpad-sheet.js +30 -13
  109. package/dist/components/numpad-sheet.js.map +1 -1
  110. package/dist/components/page-contents.d.ts +90 -0
  111. package/dist/components/page-contents.js +170 -0
  112. package/dist/components/page-contents.js.map +1 -0
  113. package/dist/components/password-strength.d.ts +106 -0
  114. package/dist/components/password-strength.js +132 -0
  115. package/dist/components/password-strength.js.map +1 -0
  116. package/dist/components/picker-sheet.d.ts +23 -15
  117. package/dist/components/picker-sheet.js +34 -14
  118. package/dist/components/picker-sheet.js.map +1 -1
  119. package/dist/components/popover.d.ts +62 -4
  120. package/dist/components/popover.js +52 -2
  121. package/dist/components/popover.js.map +1 -1
  122. package/dist/components/search-field.d.ts +25 -7
  123. package/dist/components/search-field.js +7 -5
  124. package/dist/components/search-field.js.map +1 -1
  125. package/dist/components/series-chart-labels.d.ts +14 -0
  126. package/dist/components/series-chart-labels.js +11 -0
  127. package/dist/components/series-chart-labels.js.map +1 -0
  128. package/dist/components/series-chart.d.ts +207 -0
  129. package/dist/components/series-chart.js +223 -0
  130. package/dist/components/series-chart.js.map +1 -0
  131. package/dist/components/settings-fields.d.ts +14 -4
  132. package/dist/components/settings-fields.js +19 -8
  133. package/dist/components/settings-fields.js.map +1 -1
  134. package/dist/components/signature-pad.d.ts +119 -0
  135. package/dist/components/signature-pad.js +405 -0
  136. package/dist/components/signature-pad.js.map +1 -0
  137. package/dist/components/slider.d.ts +80 -0
  138. package/dist/components/slider.js +155 -0
  139. package/dist/components/slider.js.map +1 -0
  140. package/dist/components/sparkline.d.ts +93 -0
  141. package/dist/components/sparkline.js +205 -0
  142. package/dist/components/sparkline.js.map +1 -0
  143. package/dist/components/stat-tile.d.ts +151 -0
  144. package/dist/components/stat-tile.js +236 -0
  145. package/dist/components/stat-tile.js.map +1 -0
  146. package/dist/components/swipeable-row.d.ts +25 -2
  147. package/dist/components/swipeable-row.js +31 -0
  148. package/dist/components/swipeable-row.js.map +1 -1
  149. package/dist/components/switch.d.ts +60 -0
  150. package/dist/components/switch.js +81 -0
  151. package/dist/components/switch.js.map +1 -0
  152. package/dist/components/time-input.d.ts +58 -0
  153. package/dist/components/time-input.js +50 -0
  154. package/dist/components/time-input.js.map +1 -0
  155. package/dist/components/toggle-group.d.ts +18 -3
  156. package/dist/components/toggle-group.js +10 -6
  157. package/dist/components/toggle-group.js.map +1 -1
  158. package/dist/components/toggle-legend.d.ts +115 -0
  159. package/dist/components/toggle-legend.js +114 -0
  160. package/dist/components/toggle-legend.js.map +1 -0
  161. package/dist/components/tooltip.d.ts +40 -11
  162. package/dist/components/tooltip.js +77 -20
  163. package/dist/components/tooltip.js.map +1 -1
  164. package/dist/components/treemap.d.ts +132 -0
  165. package/dist/components/treemap.js +179 -0
  166. package/dist/components/treemap.js.map +1 -0
  167. package/dist/components/ui.d.ts +159 -46
  168. package/dist/components/ui.js +220 -115
  169. package/dist/components/ui.js.map +1 -1
  170. package/dist/components/use-mobile-reveal.js +1 -0
  171. package/dist/components/use-mobile-reveal.js.map +1 -1
  172. package/dist/components/use-table-state.d.ts +1 -1
  173. package/dist/components/use-table-state.js +38 -17
  174. package/dist/components/use-table-state.js.map +1 -1
  175. package/dist/components/user-avatar.d.ts +15 -4
  176. package/dist/components/user-avatar.js +5 -4
  177. package/dist/components/user-avatar.js.map +1 -1
  178. package/dist/components/wizard-stepper.js +3 -2
  179. package/dist/components/wizard-stepper.js.map +1 -1
  180. package/dist/{data-table-filters-aXyf0Xub.d.ts → data-table-filters-DM1auUfH.d.ts} +27 -5
  181. package/dist/data-table.d.ts +7 -0
  182. package/dist/data-table.js +12 -0
  183. package/dist/data-table.js.map +1 -0
  184. package/dist/feedback/feedback-attachment.js +12 -11
  185. package/dist/feedback/feedback-attachment.js.map +1 -1
  186. package/dist/feedback/feedback-dialog.js +2 -1
  187. package/dist/feedback/feedback-dialog.js.map +1 -1
  188. package/dist/feedback/feedback-inbox.d.ts +33 -7
  189. package/dist/feedback/feedback-inbox.js +49 -37
  190. package/dist/feedback/feedback-inbox.js.map +1 -1
  191. package/dist/feedback.d.ts +5 -0
  192. package/dist/feedback.js +5 -0
  193. package/dist/feedback.js.map +1 -0
  194. package/dist/hooks/use-anchored-panel.js +1 -0
  195. package/dist/hooks/use-anchored-panel.js.map +1 -1
  196. package/dist/hooks/use-anchored-rect.js +1 -0
  197. package/dist/hooks/use-anchored-rect.js.map +1 -1
  198. package/dist/hooks/use-announce.d.ts +46 -0
  199. package/dist/hooks/use-announce.js +37 -0
  200. package/dist/hooks/use-announce.js.map +1 -0
  201. package/dist/hooks/use-body-scroll-lock.js +1 -0
  202. package/dist/hooks/use-body-scroll-lock.js.map +1 -1
  203. package/dist/hooks/use-close-transition.js +1 -0
  204. package/dist/hooks/use-close-transition.js.map +1 -1
  205. package/dist/hooks/use-dismiss.js +1 -0
  206. package/dist/hooks/use-dismiss.js.map +1 -1
  207. package/dist/hooks/use-focus-trap.d.ts +16 -0
  208. package/dist/hooks/use-focus-trap.js +67 -0
  209. package/dist/hooks/use-focus-trap.js.map +1 -0
  210. package/dist/hooks/use-media-query.js +1 -0
  211. package/dist/hooks/use-media-query.js.map +1 -1
  212. package/dist/hooks/use-overlay-history.js +27 -1
  213. package/dist/hooks/use-overlay-history.js.map +1 -1
  214. package/dist/hooks/use-row-swipe.js +1 -0
  215. package/dist/hooks/use-row-swipe.js.map +1 -1
  216. package/dist/i18n/defaults.d.ts +31 -0
  217. package/dist/i18n/defaults.js +63 -0
  218. package/dist/i18n/defaults.js.map +1 -0
  219. package/dist/i18n/kit-labels.d.ts +250 -0
  220. package/dist/i18n/kit-labels.js +184 -0
  221. package/dist/i18n/kit-labels.js.map +1 -0
  222. package/dist/index.d.ts +47 -22
  223. package/dist/index.js +28 -0
  224. package/dist/index.js.map +1 -1
  225. package/dist/lib/calc.js +1 -0
  226. package/dist/lib/calc.js.map +1 -1
  227. package/dist/lib/cn.js +1 -0
  228. package/dist/lib/cn.js.map +1 -1
  229. package/dist/lib/dates.js +1 -0
  230. package/dist/lib/dates.js.map +1 -1
  231. package/dist/lib/logger.js +1 -0
  232. package/dist/lib/logger.js.map +1 -1
  233. package/dist/lib/safe-storage.js +1 -0
  234. package/dist/lib/safe-storage.js.map +1 -1
  235. package/dist/search/command-palette.d.ts +11 -1
  236. package/dist/search/command-palette.js +21 -15
  237. package/dist/search/command-palette.js.map +1 -1
  238. package/dist/search.d.ts +2 -0
  239. package/dist/search.js +3 -0
  240. package/dist/search.js.map +1 -0
  241. package/dist/shell/app-shell.d.ts +33 -4
  242. package/dist/shell/app-shell.js +275 -78
  243. package/dist/shell/app-shell.js.map +1 -1
  244. package/dist/shell/option-switcher-menu.js +3 -2
  245. package/dist/shell/option-switcher-menu.js.map +1 -1
  246. package/dist/shell/role-switcher.d.ts +6 -0
  247. package/dist/shell/role-switcher.js +7 -4
  248. package/dist/shell/role-switcher.js.map +1 -1
  249. package/dist/shell/top-bar.d.ts +15 -7
  250. package/dist/shell/top-bar.js +3 -5
  251. package/dist/shell/top-bar.js.map +1 -1
  252. package/dist/shell/topbar-action-menu.js +3 -2
  253. package/dist/shell/topbar-action-menu.js.map +1 -1
  254. package/dist/shell/topbar-controls.d.ts +5 -2
  255. package/dist/shell/topbar-controls.js +20 -15
  256. package/dist/shell/topbar-controls.js.map +1 -1
  257. package/dist/shell.d.ts +13 -0
  258. package/dist/shell.js +8 -0
  259. package/dist/shell.js.map +1 -0
  260. package/dist/theme/chart-palette.d.ts +19 -12
  261. package/dist/theme/chart-palette.js +24 -30
  262. package/dist/theme/chart-palette.js.map +1 -1
  263. package/dist/theme/color.d.ts +78 -0
  264. package/dist/theme/color.js +148 -0
  265. package/dist/theme/color.js.map +1 -0
  266. package/dist/theme/palette-derive.d.ts +140 -0
  267. package/dist/theme/palette-derive.js +376 -0
  268. package/dist/theme/palette-derive.js.map +1 -0
  269. package/dist/theme/palette-presets.d.ts +27 -5
  270. package/dist/theme/palette-presets.js +231 -33
  271. package/dist/theme/palette-presets.js.map +1 -1
  272. package/dist/theme/palette-store.js +1 -0
  273. package/dist/theme/palette-store.js.map +1 -1
  274. package/dist/theme/theme-store.js +1 -0
  275. package/dist/theme/theme-store.js.map +1 -1
  276. package/dist/tour/tour.d.ts +2 -1
  277. package/dist/tour/tour.js +15 -9
  278. package/dist/tour/tour.js.map +1 -1
  279. package/dist/tour.d.ts +2 -0
  280. package/dist/tour.js +3 -0
  281. package/dist/tour.js.map +1 -0
  282. package/dist/wizard/stepper-nav.js +10 -8
  283. package/dist/wizard/stepper-nav.js.map +1 -1
  284. package/dist/wizard/types.d.ts +18 -2
  285. package/dist/wizard/types.js +4 -1
  286. package/dist/wizard/types.js.map +1 -1
  287. package/dist/wizard/use-wizard.js +12 -4
  288. package/dist/wizard/use-wizard.js.map +1 -1
  289. package/dist/wizard/validation.js +1 -0
  290. package/dist/wizard/validation.js.map +1 -1
  291. package/dist/wizard/wizard-context.js +1 -0
  292. package/dist/wizard/wizard-context.js.map +1 -1
  293. package/dist/wizard/wizard-step.js +1 -0
  294. package/dist/wizard/wizard-step.js.map +1 -1
  295. package/dist/wizard/wizard-summary.js +5 -3
  296. package/dist/wizard/wizard-summary.js.map +1 -1
  297. package/dist/wizard.d.ts +8 -0
  298. package/dist/wizard.js +9 -0
  299. package/dist/wizard.js.map +1 -0
  300. package/package.json +67 -14
  301. package/src/chart.ts +22 -0
  302. package/src/components/account-settings.tsx +275 -0
  303. package/src/components/alert-banner.tsx +75 -0
  304. package/src/components/amount-input.tsx +471 -0
  305. package/src/components/calculator.tsx +252 -0
  306. package/src/components/chart-zoom.tsx +499 -0
  307. package/src/components/chart.tsx +363 -0
  308. package/src/components/checkbox.tsx +222 -0
  309. package/src/components/chip.tsx +570 -0
  310. package/src/components/choice-parts.ts +33 -0
  311. package/src/components/combobox-core.tsx +540 -0
  312. package/src/components/combobox.tsx +1026 -0
  313. package/src/components/currency-select.tsx +269 -0
  314. package/src/components/data-table-filter-popover.tsx +252 -0
  315. package/src/components/data-table-filters.ts +176 -0
  316. package/src/components/data-table-labels.ts +190 -0
  317. package/src/components/data-table-pagination.tsx +126 -0
  318. package/src/components/data-table-sort.ts +70 -0
  319. package/src/components/data-table.tsx +1740 -0
  320. package/src/components/date-picker.tsx +631 -0
  321. package/src/components/dropdown.tsx +341 -0
  322. package/src/components/entity-combobox.tsx +215 -0
  323. package/src/components/facing-pair.ts +86 -0
  324. package/src/components/field-sync.tsx +472 -0
  325. package/src/components/file-dropzone.tsx +181 -0
  326. package/src/components/full-bleed-dialog.tsx +179 -0
  327. package/src/components/grouped-picker.tsx +157 -0
  328. package/src/components/hover-menu.tsx +183 -0
  329. package/src/components/mini-calendar.tsx +554 -0
  330. package/src/components/modal.tsx +287 -0
  331. package/src/components/month-picker.tsx +549 -0
  332. package/src/components/multi-entity-combobox.tsx +226 -0
  333. package/src/components/multi-select.tsx +303 -0
  334. package/src/components/number-field.tsx +225 -0
  335. package/src/components/number-input.tsx +211 -0
  336. package/src/components/numpad-sheet.tsx +264 -0
  337. package/src/components/page-contents.tsx +289 -0
  338. package/src/components/password-strength.tsx +258 -0
  339. package/src/components/picker-sheet.tsx +251 -0
  340. package/src/components/popover.tsx +195 -0
  341. package/src/components/search-field.tsx +146 -0
  342. package/src/components/series-chart-labels.ts +26 -0
  343. package/src/components/series-chart.tsx +481 -0
  344. package/src/components/settings-fields.tsx +68 -0
  345. package/src/components/signature-pad.tsx +630 -0
  346. package/src/components/slider.tsx +313 -0
  347. package/src/components/sparkline.tsx +329 -0
  348. package/src/components/stat-tile.tsx +448 -0
  349. package/src/components/swipeable-row.tsx +271 -0
  350. package/src/components/switch.tsx +170 -0
  351. package/src/components/time-input.tsx +107 -0
  352. package/src/components/toggle-group.tsx +153 -0
  353. package/src/components/toggle-legend.tsx +229 -0
  354. package/src/components/tooltip.tsx +477 -0
  355. package/src/components/treemap.tsx +390 -0
  356. package/src/components/ui.tsx +1284 -0
  357. package/src/components/use-mobile-reveal.ts +54 -0
  358. package/src/components/use-table-state.ts +295 -0
  359. package/src/components/user-avatar.tsx +61 -0
  360. package/src/components/wizard-stepper.tsx +37 -0
  361. package/src/data-table.ts +19 -0
  362. package/src/feedback/feedback-attachment.tsx +246 -0
  363. package/src/feedback/feedback-dialog.tsx +209 -0
  364. package/src/feedback/feedback-inbox.tsx +615 -0
  365. package/src/feedback.ts +8 -0
  366. package/src/hooks/use-anchored-panel.ts +169 -0
  367. package/src/hooks/use-anchored-rect.ts +78 -0
  368. package/src/hooks/use-announce.ts +85 -0
  369. package/src/hooks/use-body-scroll-lock.ts +94 -0
  370. package/src/hooks/use-close-transition.ts +99 -0
  371. package/src/hooks/use-dismiss.ts +77 -0
  372. package/src/hooks/use-focus-trap.ts +160 -0
  373. package/src/hooks/use-media-query.ts +23 -0
  374. package/src/hooks/use-overlay-history.ts +345 -0
  375. package/src/hooks/use-row-swipe.ts +212 -0
  376. package/src/i18n/defaults.ts +68 -0
  377. package/src/i18n/kit-labels.tsx +447 -0
  378. package/src/index.ts +169 -0
  379. package/src/lib/calc.ts +309 -0
  380. package/src/lib/cn.ts +5 -0
  381. package/src/lib/dates.ts +187 -0
  382. package/src/lib/logger.ts +73 -0
  383. package/src/lib/safe-storage.ts +37 -0
  384. package/src/search/command-palette.tsx +342 -0
  385. package/src/search.ts +3 -0
  386. package/src/shell/app-shell.tsx +772 -0
  387. package/src/shell/option-switcher-menu.tsx +72 -0
  388. package/src/shell/role-switcher.tsx +46 -0
  389. package/src/shell/top-bar.tsx +39 -0
  390. package/src/shell/topbar-action-menu.tsx +91 -0
  391. package/src/shell/topbar-controls.tsx +229 -0
  392. package/src/shell.ts +10 -0
  393. package/src/theme/chart-palette.ts +145 -0
  394. package/src/theme/color.ts +266 -0
  395. package/src/theme/palette-derive.ts +681 -0
  396. package/src/theme/palette-presets.ts +760 -0
  397. package/src/theme/palette-store.ts +94 -0
  398. package/src/theme/theme-store.ts +145 -0
  399. package/src/tour/tour.tsx +586 -0
  400. package/src/tour.ts +3 -0
  401. package/src/vite-env.d.ts +26 -0
  402. package/src/wizard/stepper-nav.tsx +221 -0
  403. package/src/wizard/types.ts +178 -0
  404. package/src/wizard/use-wizard.ts +345 -0
  405. package/src/wizard/validation.ts +37 -0
  406. package/src/wizard/wizard-context.tsx +29 -0
  407. package/src/wizard/wizard-step.tsx +14 -0
  408. package/src/wizard/wizard-summary.tsx +69 -0
  409. package/src/wizard.ts +14 -0
  410. package/tokens.css +222 -20
@@ -0,0 +1,390 @@
1
+ // A tile chart ("treemap"): one rectangle per node, its AREA the node's share of what
2
+ // is drawn. Lifted out of keksdose's reports, where five cards drew it (spending by
3
+ // group and by category, net worth by account and by type, allocation, payees, food
4
+ // groups) — every domain word is gone and every string it could print is a prop.
5
+ //
6
+ // It uses the kit's own chart shell (`ChartContainer`, `ChartTooltipContent`) and the
7
+ // kit's own ink rule (`textOn`), so it re-skins with the palette like every other
8
+ // chart. The one thing it cannot take from a CSS var is the tile colour: the label ink
9
+ // is MEASURED against the fill, and a `var(--chart-3)` string measures as nothing — see
10
+ // `useChartTokenHex` below for how the concrete hex is found instead.
11
+ import { useSyncExternalStore } from "react";
12
+ import type { ComponentProps, ComponentPropsWithoutRef, KeyboardEvent, ReactNode } from "react";
13
+ import { Treemap as RechartsTreemap } from "recharts";
14
+ import { ChartContainer, ChartTooltip, ChartTooltipContent, type ChartConfig } from "./chart";
15
+ import { PALETTE_HEX, textOn } from "../theme/chart-palette";
16
+ import { parseHex } from "../theme/color";
17
+
18
+ /**
19
+ * One tile.
20
+ *
21
+ * `value`, not keksdose's `total`: a treemap's areas are not always money (keksdose
22
+ * already sizes one by transaction COUNT), and `value` is the word every other chart
23
+ * in recharts uses for the number a mark is sized by. Migrating is a rename at the
24
+ * mapping step — `{ id, name, value: row.total }`.
25
+ */
26
+ export interface TreemapNode {
27
+ /** Stable key, handed back by {@link TreemapProps.onNodeClick} and
28
+ * {@link TreemapProps.nodeNote}. Names can collide; ids must not. */
29
+ id: string;
30
+ name: string;
31
+ /** The tile's area. A node whose value is not a positive finite number is NOT
32
+ * drawn — an area cannot be negative, and recharts lays a negative size out as
33
+ * nonsense rather than refusing it. Saying so is the caller's job: a group that
34
+ * vanishes silently between two views of the same data is a bug report. */
35
+ value: number;
36
+ /** Overrides the by-index palette colour for this tile — e.g. to paint every child
37
+ * in its parent group's colour, so the groups stay legible in one picture. A
38
+ * concrete `#rrggbb` keeps the label ink measured; anything else (a CSS var) gets
39
+ * `currentColor` ink, because there is nothing to measure. */
40
+ fill?: string;
41
+ }
42
+
43
+ /**
44
+ * Everything a `<div>` takes reaches the chart's root (`ChartContainer`), so a test id
45
+ * or a `data-tour` anchor can find it. `children` is the chart's own.
46
+ */
47
+ export interface TreemapProps extends Omit<ComponentPropsWithoutRef<"div">, "children"> {
48
+ data: TreemapNode[];
49
+ /**
50
+ * Formats a node's value in the tooltip. Handed the node's SHARE of what is drawn
51
+ * (0–1) as a second argument, because a tile is read for how big it is relative to
52
+ * the rest, and "€412.30" alone makes the reader estimate that ratio off the areas.
53
+ * The share is `undefined` when the drawn total is not positive.
54
+ *
55
+ * Default: the bare number through `Intl.NumberFormat` in the runtime's locale. A
56
+ * host that has a currency or a locale passes this — the kit knows neither.
57
+ */
58
+ valueFormatter?: (value: number, share: number | undefined) => ReactNode;
59
+ /** Chart height in px. Default 320. */
60
+ height?: number;
61
+ /** A tile was activated — by click, or by Enter/Space on a focused tile. Setting
62
+ * this makes every tile a focusable `role="button"` named by its node name. */
63
+ onNodeClick?: (id: string, name: string) => void;
64
+ /**
65
+ * The node names are the user's own data (payees, not categories): the tile labels
66
+ * and the whole tooltip bubble get `data-private`, the same attribute the kit's
67
+ * `Tooltip redact` and `ChartTooltipContent`'s value cell carry, so the host's one
68
+ * privacy rule reaches them. Off by default — a category name is a label of the app.
69
+ */
70
+ redactNames?: boolean;
71
+ /**
72
+ * A second, smaller line under a tile's label — one extra figure per node that the
73
+ * name cannot carry (a year-on-year `+12%`, `new`). Return `undefined` for a node
74
+ * with nothing to add. Never tagged `data-private`: a ratio reveals no amount.
75
+ *
76
+ * ON THE TILE rather than in the tooltip, for two reasons. The tooltip identifies a
77
+ * node by name or by raw value, so a per-node note would be keyed on an amount (two
78
+ * nodes of equal size would swap notes). And a tooltip is a pointer affordance: a
79
+ * phone never hovers, so a figure that only exists on hover does not exist there.
80
+ */
81
+ nodeNote?: (id: string) => string | undefined;
82
+ /**
83
+ * Draw at most this many tiles (the first N drawable nodes, in the order given —
84
+ * sort first). Unbounded by default. A tile under ~44x20px has no room for a label,
85
+ * and a map made of unlabelled rectangles answers nothing; around 24 is where the
86
+ * smallest tile of a skewed series reaches that floor on a phone-width card. The
87
+ * caller owns the "N more not shown" line, since it knows how many it passed.
88
+ */
89
+ maxTiles?: number;
90
+ /**
91
+ * The categorical ramp tiles take by index, as concrete hex. Default: the page's
92
+ * live `--chart-1…9` tokens (see `useChartTokenHex`). Pass one when the host
93
+ * already holds the active palette — e.g. `createPaletteStore`'s `useChartHex()` —
94
+ * so a caller colouring a legend beside the map is guaranteed the same list.
95
+ */
96
+ colors?: readonly string[];
97
+ }
98
+
99
+ // ── The categorical ramp, as concrete hex ──────────────────────────────────
100
+ //
101
+ // The label ink is measured against the tile's fill, so the fill has to be a colour
102
+ // and not a reference to one. keksdose read it from its palette STORE, which is an
103
+ // app object the kit cannot import. What the kit does own is the token layer: whether
104
+ // a palette arrives through `applyTokenSet` (inline properties on <html>) or through
105
+ // tokens.css (`:root` / `.dark`), the resolved `--chart-N` on <html> is the colour
106
+ // every other chart on the page is being painted with. So that is what is read, and
107
+ // re-read whenever <html>'s class or style changes — which is exactly when a theme
108
+ // toggle or a palette switch lands.
109
+
110
+ const CHART_TOKENS = 9;
111
+
112
+ /** The resolved ramp, joined into ONE string so `useSyncExternalStore` compares a
113
+ * primitive: a fresh array per read would never be `Object.is`-equal to the last and
114
+ * would re-render forever. */
115
+ function readChartTokens(): string {
116
+ const root = document.documentElement;
117
+ const fallback = root.classList.contains("dark") ? PALETTE_HEX.dark : PALETTE_HEX.light;
118
+ const style = getComputedStyle(root);
119
+ return Array.from({ length: CHART_TOKENS }, (_, i) => {
120
+ const v = style.getPropertyValue(`--chart-${i + 1}`).trim();
121
+ // An unresolved token (a host that never loaded tokens.css, a test DOM) falls
122
+ // back to the built-in ramp for the current theme rather than to no colour.
123
+ return parseHex(v) ? v : fallback[i];
124
+ }).join(",");
125
+ }
126
+
127
+ function subscribeChartTokens(onChange: () => void): () => void {
128
+ const observer = new MutationObserver(onChange);
129
+ observer.observe(document.documentElement, {
130
+ attributes: true,
131
+ attributeFilter: ["class", "style", "data-theme"],
132
+ });
133
+ return () => observer.disconnect();
134
+ }
135
+
136
+ const SERVER_TOKENS = PALETTE_HEX.light.join(",");
137
+
138
+ function useChartTokenHex(): string[] {
139
+ const joined = useSyncExternalStore(subscribeChartTokens, readChartTokens, () => SERVER_TOKENS);
140
+ return joined.split(",");
141
+ }
142
+
143
+ // ── Tile ──────────────────────────────────────────────────────────────────
144
+
145
+ const LABEL_FONT_SIZE = 12;
146
+ /** The optional second line, a step down from the name so the tile reads name-first. */
147
+ const NOTE_FONT_SIZE = 11;
148
+
149
+ /**
150
+ * Fit a label to its tile: the text itself when it fits, an ellipsised head when it
151
+ * does not, `null` when not even three characters would. Without this a long name ran
152
+ * on across its neighbours and collided with THEIR labels. 0.58em is the average
153
+ * advance of a UI sans at this weight; it only needs to be close, since the tile keeps
154
+ * 6px of padding either side. The full name stays in the tooltip.
155
+ */
156
+ export function fitLabel(name: string, boxWidth: number, fontSize: number): string | null {
157
+ const max = Math.floor((boxWidth - 12) / (fontSize * 0.58));
158
+ if (max < 3) return null;
159
+ return name.length <= max ? name : `${name.slice(0, max - 1).trimEnd()}…`;
160
+ }
161
+
162
+ /** What recharts hands a `content` element for each node, plus the three options the
163
+ * chart forwards. Everything optional because recharts clones the element it was
164
+ * given and fills the geometry in — the element as written carries only the options. */
165
+ export interface TreemapCellProps {
166
+ depth?: number;
167
+ x?: number;
168
+ y?: number;
169
+ width?: number;
170
+ height?: number;
171
+ name?: string;
172
+ fill?: string;
173
+ id?: string;
174
+ onNodeClick?: (id: string, name: string) => void;
175
+ redactNames?: boolean;
176
+ nodeNote?: (id: string) => string | undefined;
177
+ }
178
+
179
+ /**
180
+ * One tile. Exported so a caller drawing its own recharts `<Treemap>` gets the same
181
+ * label rules.
182
+ *
183
+ * recharts' default cell inherits the chart-level `stroke`, an SVG presentation
184
+ * attribute that lands on the `<svg>` root and cascades into the label — so the glyphs
185
+ * were painted with a 1px outline ON TOP of themselves, which reads as blur. This cell
186
+ * draws its own label with a halo in the TILE colour painted BEHIND the fill
187
+ * (`paint-order: stroke`), which defeats any inherited stroke and keeps the text solid
188
+ * where it overlaps a lighter or darker part of its own tile.
189
+ */
190
+ export function TreemapCell({
191
+ depth,
192
+ x = 0,
193
+ y = 0,
194
+ width = 0,
195
+ height = 0,
196
+ name,
197
+ fill,
198
+ id,
199
+ onNodeClick,
200
+ redactNames,
201
+ nodeNote,
202
+ }: TreemapCellProps) {
203
+ // Depth 0 is recharts' synthetic root, which spans the whole chart.
204
+ if (!depth) return null;
205
+ // Whole pixels: a rect on a half pixel renders its edge across two half-lit
206
+ // columns, and the text baseline inherits that blur.
207
+ const px = Math.round(x);
208
+ const py = Math.round(y);
209
+ const pw = Math.round(width);
210
+ const ph = Math.round(height);
211
+ const tileFill = fill ?? "var(--chart-1)";
212
+ const ink = textOn(tileFill);
213
+ // Below ~44x20 there is no room for a readable word, so no label at all rather than
214
+ // a one-letter stub bleeding over the tile edge.
215
+ const label = pw >= 44 && ph >= 20 ? fitLabel(name ?? "", pw, LABEL_FONT_SIZE) : null;
216
+ // Only under a label that is itself drawn — a stray "+12%" on an unnamed rectangle
217
+ // names nothing — and only where a second baseline fits: the note's baseline sits
218
+ // 29px down and an 11px glyph hangs ~2px below it, so 36 leaves the same ~5px under
219
+ // the note that the label keeps under itself at the 20px floor.
220
+ const rawNote = id != null && nodeNote ? nodeNote(id) : undefined;
221
+ const note =
222
+ label != null && rawNote != null && ph >= 36 ? fitLabel(rawNote, pw, NOTE_FONT_SIZE) : null;
223
+ const activate = id != null && onNodeClick ? () => onNodeClick(id, name ?? "") : undefined;
224
+ const onKeyDown = activate
225
+ ? (e: KeyboardEvent<SVGGElement>) => {
226
+ if (e.key !== "Enter" && e.key !== " ") return;
227
+ e.preventDefault();
228
+ activate();
229
+ }
230
+ : undefined;
231
+ return (
232
+ <g
233
+ onClick={activate}
234
+ onKeyDown={onKeyDown}
235
+ // A clickable tile is a button to a keyboard and a screen reader too; the
236
+ // drilldown it opens was mouse-only in the app this came from. Named by the node
237
+ // name, which is also why the group carries `data-private` under `redactNames`.
238
+ role={activate ? "button" : undefined}
239
+ tabIndex={activate ? 0 : undefined}
240
+ aria-label={activate ? name : undefined}
241
+ data-private={activate && redactNames ? "" : undefined}
242
+ cursor={activate ? "pointer" : undefined}
243
+ // CSS beats a presentation attribute, so this recolours the rect's own stroke
244
+ // for as long as the tile has keyboard focus.
245
+ className={activate ? "outline-none [&:focus-visible>rect]:stroke-[var(--brand)]" : undefined}
246
+ >
247
+ {/* The 2px gutter is the card showing through, not a white grid — on a dark
248
+ surface white lines glare harder than the tiles. */}
249
+ <rect
250
+ x={px}
251
+ y={py}
252
+ width={pw}
253
+ height={ph}
254
+ fill={tileFill}
255
+ stroke="var(--bg-surface)"
256
+ strokeWidth={2}
257
+ rx={3}
258
+ />
259
+ {label != null && (
260
+ <text
261
+ // A plain attribute, so the host's `[data-private]` rule reaches an SVG
262
+ // <text> exactly as it reaches a table cell.
263
+ data-private={redactNames ? "" : undefined}
264
+ x={px + 6}
265
+ y={py + 15}
266
+ fill={ink}
267
+ stroke={tileFill}
268
+ strokeWidth={2.5}
269
+ strokeLinejoin="round"
270
+ paintOrder="stroke"
271
+ fontSize={LABEL_FONT_SIZE}
272
+ fontWeight={600}
273
+ style={{ pointerEvents: "none" }}
274
+ >
275
+ {label}
276
+ </text>
277
+ )}
278
+ {note != null && (
279
+ <text
280
+ x={px + 6}
281
+ y={py + 29}
282
+ fill={ink}
283
+ fillOpacity={0.85}
284
+ stroke={tileFill}
285
+ strokeWidth={2.5}
286
+ strokeLinejoin="round"
287
+ paintOrder="stroke"
288
+ fontSize={NOTE_FONT_SIZE}
289
+ fontWeight={500}
290
+ style={{ pointerEvents: "none" }}
291
+ >
292
+ {note}
293
+ </text>
294
+ )}
295
+ </g>
296
+ );
297
+ }
298
+
299
+ // ── Chart ─────────────────────────────────────────────────────────────────
300
+
301
+ // Nodes carry their own `fill`, and `ChartTooltipContent` falls back to it for the
302
+ // swatch, so no series config is needed.
303
+ const TREEMAP_CONFIG: ChartConfig = {};
304
+
305
+ /**
306
+ * The shared tooltip with the whole bubble tagged `data-private`, for names that are
307
+ * the user's own data.
308
+ *
309
+ * The bubble, not just the name: `ChartTooltipContent` prints the name either from
310
+ * `item.name` or from a config `label`, and only the config route could be tagged — but
311
+ * a config entry keyed by a payee name is not a CSS identifier, and `ChartStyle` would
312
+ * (rightly) refuse it. The bubble holds the name and the value, and the value cell is
313
+ * already `data-private`, so tagging the wrapper hides nothing extra.
314
+ *
315
+ * recharts CLONES the `content` element to inject `active`/`payload`, so this has to BE
316
+ * that element — a `<div>` wrapped around `<ChartTooltipContent />` in the JSX would
317
+ * take those props itself and hand the tooltip nothing.
318
+ */
319
+ function RedactedTooltipContent(props: ComponentProps<typeof ChartTooltipContent>) {
320
+ return (
321
+ <div data-private>
322
+ <ChartTooltipContent {...props} />
323
+ </div>
324
+ );
325
+ }
326
+
327
+ const DEFAULT_NUMBER = new Intl.NumberFormat();
328
+
329
+ /**
330
+ * Tile chart: one rectangle per {@link TreemapNode}, sized by `value`, coloured by
331
+ * index from the chart ramp (or by the node's own `fill`), labelled in whichever of the
332
+ * kit's two inks measures better against it. Hover shows the name and the formatted
333
+ * value; `onNodeClick` turns the tiles into buttons.
334
+ *
335
+ * Renders nothing when no node has a positive value — an empty box is a hole in the
336
+ * card, and the caller's own sentence is the better answer there.
337
+ *
338
+ * Needs `recharts`, like everything behind `@eifi1/ui-kit/chart`.
339
+ */
340
+ export function Treemap({
341
+ data,
342
+ valueFormatter,
343
+ height = 320,
344
+ onNodeClick,
345
+ redactNames = false,
346
+ nodeNote,
347
+ maxTiles,
348
+ colors,
349
+ style,
350
+ ...rest
351
+ }: TreemapProps) {
352
+ const tokenHex = useChartTokenHex();
353
+ const ramp = colors && colors.length > 0 ? colors : tokenHex;
354
+ // Coloured by index into `data` AS GIVEN, before anything is dropped: a caller
355
+ // painting a legend or a second map from the same list must land on the same hue
356
+ // for the same row, and filtering first would shift every colour after a gap.
357
+ const nodes = data
358
+ .map((d, idx) => ({
359
+ id: d.id,
360
+ name: d.name,
361
+ size: d.value,
362
+ fill: d.fill ?? ramp[idx % ramp.length],
363
+ }))
364
+ .filter((n) => Number.isFinite(n.size) && n.size > 0)
365
+ .slice(0, maxTiles ?? undefined);
366
+ if (nodes.length === 0) return null;
367
+
368
+ // The share is of what is DRAWN, so it always reads as "of this picture".
369
+ const total = nodes.reduce((acc, n) => acc + n.size, 0);
370
+ const format = (v: number): ReactNode => {
371
+ const share = total > 0 ? v / total : undefined;
372
+ return valueFormatter ? valueFormatter(v, share) : DEFAULT_NUMBER.format(v);
373
+ };
374
+ const Content = redactNames ? RedactedTooltipContent : ChartTooltipContent;
375
+ return (
376
+ <ChartContainer {...rest} config={TREEMAP_CONFIG} style={{ ...style, height }}>
377
+ {/* No chart-level `stroke`: recharts spreads it onto the <svg> root, from where
378
+ it inherits into every label (see TreemapCell). Each cell strokes its own rect. */}
379
+ <RechartsTreemap
380
+ data={nodes}
381
+ dataKey="size"
382
+ fill="var(--chart-1)"
383
+ isAnimationActive={false}
384
+ content={<TreemapCell onNodeClick={onNodeClick} redactNames={redactNames} nodeNote={nodeNote} />}
385
+ >
386
+ <ChartTooltip content={<Content hideLabel valueFormatter={format} />} />
387
+ </RechartsTreemap>
388
+ </ChartContainer>
389
+ );
390
+ }