@wealthx/shadcn 1.1.0 → 1.2.1

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 (300) hide show
  1. package/.turbo/turbo-build.log +235 -154
  2. package/CHANGELOG.md +12 -0
  3. package/dist/{chunk-6OJF6XRN.mjs → chunk-24FUO7TD.mjs} +4 -8
  4. package/dist/{chunk-4AJ5HWHD.mjs → chunk-2I5S2AMY.mjs} +3 -3
  5. package/dist/{chunk-GPRJQ24C.mjs → chunk-34NWQURD.mjs} +2 -2
  6. package/dist/{chunk-MQ72DIBH.mjs → chunk-3GF7OVTP.mjs} +14 -5
  7. package/dist/chunk-3WMX6KWS.mjs +245 -0
  8. package/dist/{chunk-PMKODV6M.mjs → chunk-462HMNO4.mjs} +6 -10
  9. package/dist/chunk-4CX4SBRO.mjs +153 -0
  10. package/dist/chunk-4MN6UQHG.mjs +443 -0
  11. package/dist/chunk-4Y6R4WEC.mjs +250 -0
  12. package/dist/{chunk-BGP2N52Z.mjs → chunk-66MI7Q4B.mjs} +5 -5
  13. package/dist/{chunk-CGOKTPXU.mjs → chunk-6JQFUE5I.mjs} +20 -23
  14. package/dist/{chunk-Z3MK2KKZ.mjs → chunk-7DHU4VGG.mjs} +7 -3
  15. package/dist/chunk-7MMXNK3C.mjs +317 -0
  16. package/dist/{chunk-VZ2NR7L3.mjs → chunk-7PYJD5JI.mjs} +35 -27
  17. package/dist/{chunk-JU2RUWHF.mjs → chunk-7XJHLGUV.mjs} +1 -1
  18. package/dist/{chunk-BMFN37JH.mjs → chunk-7YAU5CY6.mjs} +1 -1
  19. package/dist/chunk-A56YQQHG.mjs +402 -0
  20. package/dist/{chunk-GLW2UO6O.mjs → chunk-BL3DXM2X.mjs} +84 -62
  21. package/dist/{chunk-SLWCCURD.mjs → chunk-CLIN5525.mjs} +8 -4
  22. package/dist/{chunk-3VQNJ235.mjs → chunk-CSDO6VBW.mjs} +7 -0
  23. package/dist/chunk-D4ILTPOG.mjs +293 -0
  24. package/dist/{chunk-HS7TFG7V.mjs → chunk-D6ID6M4V.mjs} +1 -1
  25. package/dist/chunk-DOH3EHX7.mjs +378 -0
  26. package/dist/{chunk-MJIEMGRD.mjs → chunk-EFRENWEJ.mjs} +9 -17
  27. package/dist/{chunk-YBXCIF5Q.mjs → chunk-ERGGHC2V.mjs} +36 -49
  28. package/dist/{chunk-OXQQNQZI.mjs → chunk-FEZKMUCF.mjs} +10 -1
  29. package/dist/{chunk-55CEW76V.mjs → chunk-FH6QVUVZ.mjs} +1 -1
  30. package/dist/chunk-FMAXJ2SI.mjs +71 -0
  31. package/dist/chunk-FZIXGLMV.mjs +173 -0
  32. package/dist/chunk-GGM2UYGG.mjs +273 -0
  33. package/dist/{chunk-DS2AMHN2.mjs → chunk-GYMYRIZP.mjs} +2 -2
  34. package/dist/{chunk-KQDD5MU3.mjs → chunk-H45TKD34.mjs} +5 -5
  35. package/dist/{chunk-BBJBJSXQ.mjs → chunk-J5UICVJS.mjs} +1 -1
  36. package/dist/{chunk-RL772EH7.mjs → chunk-JHJHG4GO.mjs} +4 -12
  37. package/dist/{chunk-RN67642N.mjs → chunk-JNQORUPP.mjs} +49 -42
  38. package/dist/{chunk-5JGQAAQV.mjs → chunk-K3JYD4IU.mjs} +86 -63
  39. package/dist/{chunk-FHNT55I5.mjs → chunk-KUDCQ4FI.mjs} +4 -4
  40. package/dist/{chunk-UEL4RD5P.mjs → chunk-LHYCMLVA.mjs} +82 -68
  41. package/dist/{chunk-NLLKTU4B.mjs → chunk-LLVQKSU3.mjs} +21 -17
  42. package/dist/{chunk-KKHTJNMM.mjs → chunk-MARPPFOJ.mjs} +8 -4
  43. package/dist/{chunk-6AFMNC42.mjs → chunk-N2PT566P.mjs} +15 -11
  44. package/dist/{chunk-YN5SYTOO.mjs → chunk-NQPOYKAQ.mjs} +9 -5
  45. package/dist/{chunk-ZZV5JVNW.mjs → chunk-NSLMILBT.mjs} +3 -7
  46. package/dist/chunk-OGOYQ7BG.mjs +150 -0
  47. package/dist/chunk-OPNQAVVH.mjs +162 -0
  48. package/dist/{chunk-3NQGYJEZ.mjs → chunk-P6AM5V7O.mjs} +10 -18
  49. package/dist/{chunk-CZ3BW5GL.mjs → chunk-P76HMUI6.mjs} +5 -11
  50. package/dist/chunk-PCPLO5HT.mjs +671 -0
  51. package/dist/chunk-PG6K5XEC.mjs +475 -0
  52. package/dist/{chunk-DDPA2XXS.mjs → chunk-PMB3A7V3.mjs} +2 -2
  53. package/dist/chunk-PR6V5XKM.mjs +209 -0
  54. package/dist/{chunk-46OFHMQA.mjs → chunk-Q76O3RIQ.mjs} +10 -6
  55. package/dist/chunk-RGU7HOEC.mjs +140 -0
  56. package/dist/{chunk-JF4PHPD5.mjs → chunk-RGVKLTLH.mjs} +4 -4
  57. package/dist/{chunk-VG6UF6UT.mjs → chunk-RP3SQYA3.mjs} +2 -2
  58. package/dist/chunk-RYCLWMZ7.mjs +162 -0
  59. package/dist/chunk-SIZMLSRU.mjs +162 -0
  60. package/dist/chunk-SPJ5KXW7.mjs +199 -0
  61. package/dist/chunk-SWGT756Z.mjs +210 -0
  62. package/dist/chunk-SYOD63OZ.mjs +225 -0
  63. package/dist/chunk-TS2ZX2VS.mjs +270 -0
  64. package/dist/chunk-UFYSFDER.mjs +42 -0
  65. package/dist/chunk-VACKZOMY.mjs +190 -0
  66. package/dist/chunk-VLQZANBF.mjs +42 -0
  67. package/dist/chunk-VPBN3WOO.mjs +164 -0
  68. package/dist/chunk-WA6O6EUR.mjs +1885 -0
  69. package/dist/{chunk-E3K6O4FZ.mjs → chunk-WAZD7NFU.mjs} +5 -2
  70. package/dist/chunk-WG6JGJXB.mjs +165 -0
  71. package/dist/{chunk-I64K754C.mjs → chunk-WNGWBVLV.mjs} +2 -2
  72. package/dist/{chunk-3U7SD3MS.mjs → chunk-WOEHFRGB.mjs} +3 -3
  73. package/dist/{chunk-DKZRJOMF.mjs → chunk-XIRTEFKH.mjs} +12 -12
  74. package/dist/chunk-Y6DWJSKZ.mjs +79 -0
  75. package/dist/{chunk-CJ46PDXE.mjs → chunk-ZRO5JO3H.mjs} +106 -66
  76. package/dist/{chunk-VYMHBV6D.mjs → chunk-ZU4NV6RG.mjs} +5 -3
  77. package/dist/components/ui/accordion.js +40 -4
  78. package/dist/components/ui/accordion.mjs +2 -2
  79. package/dist/components/ui/add-column-modal.js +789 -0
  80. package/dist/components/ui/add-column-modal.mjs +17 -0
  81. package/dist/components/ui/add-lead-modal.js +647 -0
  82. package/dist/components/ui/add-lead-modal.mjs +16 -0
  83. package/dist/components/ui/ai-assistant-drawer.js +686 -0
  84. package/dist/components/ui/ai-assistant-drawer.mjs +16 -0
  85. package/dist/components/ui/alert-dialog.js +37 -5
  86. package/dist/components/ui/alert-dialog.mjs +4 -4
  87. package/dist/components/ui/alert.js +37 -11
  88. package/dist/components/ui/alert.mjs +2 -2
  89. package/dist/components/ui/avatar.js +36 -8
  90. package/dist/components/ui/avatar.mjs +2 -2
  91. package/dist/components/ui/backoffice-alert-history-chart.js +624 -0
  92. package/dist/components/ui/backoffice-alert-history-chart.mjs +16 -0
  93. package/dist/components/ui/backoffice-contact-history-chart.js +687 -0
  94. package/dist/components/ui/backoffice-contact-history-chart.mjs +16 -0
  95. package/dist/components/ui/badge.js +37 -2
  96. package/dist/components/ui/badge.mjs +2 -2
  97. package/dist/components/ui/borrowing-capacity-line-chart.js +640 -0
  98. package/dist/components/ui/borrowing-capacity-line-chart.mjs +16 -0
  99. package/dist/components/ui/button.js +35 -3
  100. package/dist/components/ui/button.mjs +2 -2
  101. package/dist/components/ui/calendar.js +43 -19
  102. package/dist/components/ui/calendar.mjs +3 -3
  103. package/dist/components/ui/card.js +40 -4
  104. package/dist/components/ui/card.mjs +2 -2
  105. package/dist/components/ui/cash-balance-line-chart.js +628 -0
  106. package/dist/components/ui/cash-balance-line-chart.mjs +16 -0
  107. package/dist/components/ui/cashflow-bar-chart.js +124 -70
  108. package/dist/components/ui/cashflow-bar-chart.mjs +8 -8
  109. package/dist/components/ui/checkbox.js +36 -5
  110. package/dist/components/ui/checkbox.mjs +2 -3
  111. package/dist/components/ui/chip.js +37 -2
  112. package/dist/components/ui/chip.mjs +3 -3
  113. package/dist/components/ui/combobox.js +68 -49
  114. package/dist/components/ui/combobox.mjs +2 -2
  115. package/dist/components/ui/data-table.js +160 -88
  116. package/dist/components/ui/data-table.mjs +10 -11
  117. package/dist/components/ui/date-picker.js +44 -20
  118. package/dist/components/ui/date-picker.mjs +6 -7
  119. package/dist/components/ui/dialog.js +44 -12
  120. package/dist/components/ui/dialog.mjs +4 -4
  121. package/dist/components/ui/drawer.js +46 -10
  122. package/dist/components/ui/drawer.mjs +3 -3
  123. package/dist/components/ui/dropdown-menu.js +40 -16
  124. package/dist/components/ui/dropdown-menu.mjs +3 -3
  125. package/dist/components/ui/empty.js +41 -5
  126. package/dist/components/ui/empty.mjs +2 -2
  127. package/dist/components/ui/expense-bar-chart.js +166 -67
  128. package/dist/components/ui/expense-bar-chart.mjs +8 -8
  129. package/dist/components/ui/field.js +53 -21
  130. package/dist/components/ui/field.mjs +4 -4
  131. package/dist/components/ui/financial-cards.js +1002 -0
  132. package/dist/components/ui/financial-cards.mjs +24 -0
  133. package/dist/components/ui/financial-drawers.js +637 -0
  134. package/dist/components/ui/financial-drawers.mjs +17 -0
  135. package/dist/components/ui/financial-primitives.js +218 -0
  136. package/dist/components/ui/financial-primitives.mjs +22 -0
  137. package/dist/components/ui/financial-sections.js +1422 -0
  138. package/dist/components/ui/financial-sections.mjs +30 -0
  139. package/dist/components/ui/form-primitives.js +682 -0
  140. package/dist/components/ui/form-primitives.mjs +19 -0
  141. package/dist/components/ui/income-bar-chart.js +164 -66
  142. package/dist/components/ui/income-bar-chart.mjs +8 -8
  143. package/dist/components/ui/input-group.js +43 -7
  144. package/dist/components/ui/input-group.mjs +5 -5
  145. package/dist/components/ui/input-otp.js +39 -3
  146. package/dist/components/ui/input-otp.mjs +2 -2
  147. package/dist/components/ui/input.js +34 -2
  148. package/dist/components/ui/input.mjs +2 -2
  149. package/dist/components/ui/kanban-column.js +1143 -0
  150. package/dist/components/ui/kanban-column.mjs +20 -0
  151. package/dist/components/ui/label.js +35 -7
  152. package/dist/components/ui/label.mjs +2 -2
  153. package/dist/components/ui/opportunity-card.js +960 -0
  154. package/dist/components/ui/opportunity-card.mjs +20 -0
  155. package/dist/components/ui/opportunity-edit-modals.js +3360 -0
  156. package/dist/components/ui/opportunity-edit-modals.mjs +37 -0
  157. package/dist/components/ui/opportunity-summary-tab.js +4365 -0
  158. package/dist/components/ui/opportunity-summary-tab.mjs +34 -0
  159. package/dist/components/ui/pagination.js +35 -3
  160. package/dist/components/ui/pagination.mjs +3 -3
  161. package/dist/components/ui/pipeline-alerts.js +103 -0
  162. package/dist/components/ui/pipeline-alerts.mjs +8 -0
  163. package/dist/components/ui/pipeline-board.js +1408 -0
  164. package/dist/components/ui/pipeline-board.mjs +24 -0
  165. package/dist/components/ui/pipeline-chart.js +216 -0
  166. package/dist/components/ui/pipeline-chart.mjs +10 -0
  167. package/dist/components/ui/pipeline-dialogs.js +1183 -0
  168. package/dist/components/ui/pipeline-dialogs.mjs +23 -0
  169. package/dist/components/ui/pipeline-primitives.js +300 -0
  170. package/dist/components/ui/pipeline-primitives.mjs +11 -0
  171. package/dist/components/ui/popover.js +45 -4
  172. package/dist/components/ui/popover.mjs +3 -3
  173. package/dist/components/ui/progress.js +33 -1
  174. package/dist/components/ui/progress.mjs +2 -2
  175. package/dist/components/ui/property-cashflow-doughnut-chart.js +523 -0
  176. package/dist/components/ui/property-cashflow-doughnut-chart.mjs +16 -0
  177. package/dist/components/ui/property-debt-equity-doughnut-chart.js +521 -0
  178. package/dist/components/ui/property-debt-equity-doughnut-chart.mjs +16 -0
  179. package/dist/components/ui/property-mobile-estimate-line-chart.js +683 -0
  180. package/dist/components/ui/property-mobile-estimate-line-chart.mjs +16 -0
  181. package/dist/components/ui/radio-group.js +33 -1
  182. package/dist/components/ui/radio-group.mjs +2 -2
  183. package/dist/components/ui/select.js +66 -26
  184. package/dist/components/ui/select.mjs +3 -3
  185. package/dist/components/ui/separator.js +33 -1
  186. package/dist/components/ui/separator.mjs +2 -2
  187. package/dist/components/ui/sheet.js +37 -9
  188. package/dist/components/ui/sheet.mjs +3 -3
  189. package/dist/components/ui/skeleton.js +33 -1
  190. package/dist/components/ui/skeleton.mjs +2 -2
  191. package/dist/components/ui/slider.js +86 -102
  192. package/dist/components/ui/slider.mjs +2 -2
  193. package/dist/components/ui/spinner.js +33 -1
  194. package/dist/components/ui/spinner.mjs +2 -2
  195. package/dist/components/ui/stage-timeline.js +579 -0
  196. package/dist/components/ui/stage-timeline.mjs +15 -0
  197. package/dist/components/ui/switch.js +37 -4
  198. package/dist/components/ui/switch.mjs +2 -3
  199. package/dist/components/ui/table.js +37 -5
  200. package/dist/components/ui/table.mjs +2 -2
  201. package/dist/components/ui/tabs.js +36 -12
  202. package/dist/components/ui/tabs.mjs +2 -2
  203. package/dist/components/ui/textarea.js +34 -2
  204. package/dist/components/ui/textarea.mjs +2 -2
  205. package/dist/components/ui/toggle-group.js +35 -4
  206. package/dist/components/ui/toggle-group.mjs +3 -4
  207. package/dist/components/ui/toggle.js +35 -4
  208. package/dist/components/ui/toggle.mjs +2 -3
  209. package/dist/components/ui/tooltip.js +51 -22
  210. package/dist/components/ui/tooltip.mjs +3 -3
  211. package/dist/components/ui/transactions-expense-categories-doughnut-chart.js +528 -0
  212. package/dist/components/ui/transactions-expense-categories-doughnut-chart.mjs +16 -0
  213. package/dist/components/ui/transactions-income-expense-bar-chart.js +77 -39
  214. package/dist/components/ui/transactions-income-expense-bar-chart.mjs +8 -8
  215. package/dist/components/ui/transactions-liabilities-breakdown-doughnut-chart.js +528 -0
  216. package/dist/components/ui/transactions-liabilities-breakdown-doughnut-chart.mjs +16 -0
  217. package/dist/index.js +11620 -3832
  218. package/dist/index.mjs +333 -161
  219. package/dist/lib/theme-provider.js +10 -1
  220. package/dist/lib/theme-provider.mjs +1 -1
  221. package/dist/lib/typography.js +8 -0
  222. package/dist/lib/typography.mjs +3 -1
  223. package/dist/lib/utils.js +33 -1
  224. package/dist/lib/utils.mjs +1 -1
  225. package/dist/styles.css +1 -1
  226. package/package.json +140 -5
  227. package/src/components/index.tsx +296 -42
  228. package/src/components/ui/accordion.tsx +6 -3
  229. package/src/components/ui/add-column-modal.tsx +339 -0
  230. package/src/components/ui/add-lead-modal.tsx +290 -0
  231. package/src/components/ui/ai-assistant-drawer.tsx +408 -0
  232. package/src/components/ui/alert-dialog.tsx +80 -54
  233. package/src/components/ui/alert.tsx +28 -28
  234. package/src/components/ui/avatar.tsx +30 -29
  235. package/src/components/ui/backoffice-alert-history-chart.tsx +261 -0
  236. package/src/components/ui/backoffice-contact-history-chart.tsx +326 -0
  237. package/src/components/ui/badge.tsx +17 -15
  238. package/src/components/ui/borrowing-capacity-line-chart.tsx +359 -0
  239. package/src/components/ui/button.tsx +30 -27
  240. package/src/components/ui/calendar.tsx +53 -67
  241. package/src/components/ui/card.tsx +27 -24
  242. package/src/components/ui/cash-balance-line-chart.tsx +304 -0
  243. package/src/components/ui/cashflow-bar-chart.tsx +106 -78
  244. package/src/components/ui/chart-shared.tsx +176 -15
  245. package/src/components/ui/checkbox.tsx +30 -26
  246. package/src/components/ui/combobox.tsx +78 -72
  247. package/src/components/ui/data-table.tsx +160 -99
  248. package/src/components/ui/date-picker.tsx +0 -2
  249. package/src/components/ui/dialog.tsx +70 -60
  250. package/src/components/ui/drawer.tsx +57 -48
  251. package/src/components/ui/dropdown-menu.tsx +90 -82
  252. package/src/components/ui/empty.tsx +31 -27
  253. package/src/components/ui/expense-bar-chart.tsx +85 -66
  254. package/src/components/ui/field.tsx +70 -62
  255. package/src/components/ui/financial-cards.tsx +830 -0
  256. package/src/components/ui/financial-drawers.tsx +339 -0
  257. package/src/components/ui/financial-primitives.tsx +331 -0
  258. package/src/components/ui/financial-sections.tsx +672 -0
  259. package/src/components/ui/form-primitives.tsx +536 -0
  260. package/src/components/ui/income-bar-chart.tsx +81 -61
  261. package/src/components/ui/input-group.tsx +41 -34
  262. package/src/components/ui/input-otp.tsx +29 -24
  263. package/src/components/ui/input.tsx +8 -8
  264. package/src/components/ui/kanban-column.tsx +333 -0
  265. package/src/components/ui/label.tsx +9 -12
  266. package/src/components/ui/opportunity-card.tsx +616 -0
  267. package/src/components/ui/opportunity-edit-modals.tsx +2528 -0
  268. package/src/components/ui/opportunity-summary-tab.tsx +579 -0
  269. package/src/components/ui/pipeline-alerts.tsx +74 -0
  270. package/src/components/ui/pipeline-board.tsx +268 -0
  271. package/src/components/ui/pipeline-chart.tsx +173 -0
  272. package/src/components/ui/pipeline-dialogs.tsx +303 -0
  273. package/src/components/ui/pipeline-primitives.tsx +108 -0
  274. package/src/components/ui/popover.tsx +41 -36
  275. package/src/components/ui/property-cashflow-doughnut-chart.tsx +189 -0
  276. package/src/components/ui/property-debt-equity-doughnut-chart.tsx +186 -0
  277. package/src/components/ui/property-mobile-estimate-line-chart.tsx +395 -0
  278. package/src/components/ui/select.tsx +65 -52
  279. package/src/components/ui/sheet.tsx +55 -52
  280. package/src/components/ui/slider.tsx +54 -77
  281. package/src/components/ui/stage-timeline.tsx +205 -0
  282. package/src/components/ui/switch.tsx +42 -29
  283. package/src/components/ui/table.tsx +28 -28
  284. package/src/components/ui/tabs.tsx +22 -28
  285. package/src/components/ui/textarea.tsx +8 -8
  286. package/src/components/ui/toggle-group.tsx +0 -2
  287. package/src/components/ui/toggle.tsx +13 -15
  288. package/src/components/ui/tooltip.tsx +30 -28
  289. package/src/components/ui/transactions-expense-categories-doughnut-chart.tsx +192 -0
  290. package/src/components/ui/transactions-income-expense-bar-chart.tsx +47 -39
  291. package/src/components/ui/transactions-liabilities-breakdown-doughnut-chart.tsx +192 -0
  292. package/src/lib/theme-provider.tsx +10 -0
  293. package/src/lib/typography.ts +9 -0
  294. package/src/lib/utils.ts +41 -3
  295. package/src/styles/globals.css +371 -124
  296. package/src/styles/styles-css.ts +1 -1
  297. package/tsup.config.ts +27 -0
  298. package/dist/chunk-3EQP72AW.mjs +0 -58
  299. package/dist/chunk-K74JRTJR.mjs +0 -105
  300. package/dist/chunk-V7CNWJT3.mjs +0 -10
@@ -0,0 +1,192 @@
1
+ import React, { useMemo } from "react";
2
+ import {
3
+ Chart as ChartJS,
4
+ ArcElement,
5
+ DoughnutController,
6
+ Tooltip,
7
+ type ChartOptions,
8
+ type ChartData,
9
+ } from "chart.js";
10
+ import { Chart } from "react-chartjs-2";
11
+ import { useThemeVars } from "@/lib/theme-provider";
12
+ import { Card, CardContent, CardHeader, CardTitle } from "./card";
13
+ import { Empty, EmptyDescription } from "./empty";
14
+ import { Skeleton } from "./skeleton";
15
+ import { cn } from "@/lib/utils";
16
+ import {
17
+ formatAbbrev,
18
+ hexToRgba,
19
+ DATASET_ALPHAS,
20
+ FALLBACK_PRIMARY,
21
+ DoughnutLegendRow,
22
+ } from "./chart-shared";
23
+
24
+ ChartJS.register(ArcElement, DoughnutController, Tooltip);
25
+
26
+ // ---------------------------------------------------------------------------
27
+ // Types
28
+ // ---------------------------------------------------------------------------
29
+
30
+ export interface ExpenseCategorySegment {
31
+ label: string;
32
+ /** Spend amount in dollars */
33
+ value: number;
34
+ }
35
+
36
+ export interface TransactionsExpenseCategoriesDoughnutChartProps {
37
+ segments?: ExpenseCategorySegment[] | null;
38
+ title?: string;
39
+ showLegend?: boolean;
40
+ /** Chart canvas height in pixels */
41
+ height?: number;
42
+ /** Card max-width in pixels or CSS string */
43
+ width?: number | string;
44
+ className?: string;
45
+ /** Show skeleton loading state instead of the chart */
46
+ isLoading?: boolean;
47
+ }
48
+
49
+ // ---------------------------------------------------------------------------
50
+ // ---------------------------------------------------------------------------
51
+ // Component
52
+ // ---------------------------------------------------------------------------
53
+
54
+ export function TransactionsExpenseCategoriesDoughnutChart({
55
+ segments,
56
+ title = "Expense Categories",
57
+ showLegend = true,
58
+ height = 200,
59
+ width = "100%",
60
+ className,
61
+ isLoading = false,
62
+ }: TransactionsExpenseCategoriesDoughnutChartProps) {
63
+ const themeVars = useThemeVars();
64
+ const brandPrimary: string =
65
+ (themeVars["--theme-primary"] as string | undefined) || FALLBACK_PRIMARY;
66
+ const fontFamily: string =
67
+ (themeVars["--font-sans"] as string | undefined) || "Figtree, sans-serif";
68
+
69
+ const hasData = !!segments?.length && segments.some((s) => s.value > 0);
70
+
71
+ const total = useMemo(
72
+ () => segments?.reduce((sum, s) => sum + s.value, 0) ?? 0,
73
+ [segments],
74
+ );
75
+
76
+ const colors = useMemo(
77
+ () =>
78
+ (segments ?? []).map((_, i) =>
79
+ hexToRgba(brandPrimary, DATASET_ALPHAS[i % DATASET_ALPHAS.length]),
80
+ ),
81
+ [segments, brandPrimary],
82
+ );
83
+
84
+ const chartData = useMemo<ChartData<"doughnut">>(
85
+ () => ({
86
+ labels: segments?.map((s) => s.label) ?? [],
87
+ datasets: [
88
+ {
89
+ data: segments?.map((s) => s.value) ?? [],
90
+ backgroundColor: colors,
91
+ borderWidth: 0,
92
+ hoverOffset: 4,
93
+ },
94
+ ],
95
+ }),
96
+ [segments, colors],
97
+ );
98
+
99
+ const options = useMemo<ChartOptions<"doughnut">>(
100
+ () => ({
101
+ responsive: true,
102
+ maintainAspectRatio: false,
103
+ cutout: "68%",
104
+ animation: { duration: 600, easing: "easeOutQuart" },
105
+ plugins: {
106
+ legend: { display: false },
107
+ tooltip: {
108
+ padding: 10,
109
+ cornerRadius: 0,
110
+ titleFont: { size: 11, weight: 600 },
111
+ bodyFont: { size: 12, weight: 500 },
112
+ callbacks: {
113
+ label: (ctx) => {
114
+ const val = ctx.raw as number;
115
+ const pct =
116
+ total > 0 ? `${((val / total) * 100).toFixed(1)}%` : "0%";
117
+ return ` ${ctx.label}: ${formatAbbrev(val)} (${pct})`;
118
+ },
119
+ },
120
+ },
121
+ },
122
+ }),
123
+ [total],
124
+ );
125
+
126
+ return (
127
+ <Card
128
+ className={cn("w-full py-4 sm:py-6 gap-2", className)}
129
+ style={{ maxWidth: width, fontFamily }}
130
+ >
131
+ <CardHeader className="px-3 sm:px-6">
132
+ <CardTitle className="text-sm sm:text-base">{title}</CardTitle>
133
+ </CardHeader>
134
+
135
+ <CardContent className="px-3 sm:px-6">
136
+ {isLoading ? (
137
+ <Skeleton style={{ height, width: "100%" }} />
138
+ ) : !hasData ? (
139
+ <Empty className="flex-none p-4" style={{ height }}>
140
+ <EmptyDescription>No data available</EmptyDescription>
141
+ </Empty>
142
+ ) : (
143
+ <div className="flex flex-col gap-4">
144
+ <div style={{ height, width: "100%", position: "relative" }}>
145
+ <Chart
146
+ key={brandPrimary}
147
+ type="doughnut"
148
+ data={chartData}
149
+ options={options}
150
+ aria-label={title}
151
+ />
152
+ <div
153
+ style={{
154
+ position: "absolute",
155
+ top: "50%",
156
+ left: "50%",
157
+ transform: "translate(-50%, -50%)",
158
+ textAlign: "center",
159
+ pointerEvents: "none",
160
+ }}
161
+ >
162
+ <div className="text-base font-semibold leading-tight">
163
+ {formatAbbrev(total)}
164
+ </div>
165
+ <div className="text-[11px] text-muted-foreground leading-none mt-1">
166
+ Total
167
+ </div>
168
+ </div>
169
+ </div>
170
+ {showLegend && (
171
+ <div className="flex flex-col gap-2">
172
+ {segments!.map((s, i) => (
173
+ <DoughnutLegendRow
174
+ key={s.label}
175
+ label={s.label}
176
+ color={colors[i]}
177
+ value={s.value}
178
+ percent={
179
+ total > 0
180
+ ? `${((s.value / total) * 100).toFixed(1)}%`
181
+ : "0%"
182
+ }
183
+ />
184
+ ))}
185
+ </div>
186
+ )}
187
+ </div>
188
+ )}
189
+ </CardContent>
190
+ </Card>
191
+ );
192
+ }
@@ -3,6 +3,7 @@ import {
3
3
  Chart as ChartJS,
4
4
  CategoryScale,
5
5
  LinearScale,
6
+ BarController,
6
7
  BarElement,
7
8
  Tooltip,
8
9
  type ChartOptions,
@@ -15,19 +16,18 @@ import { Card, CardContent, CardHeader, CardTitle } from "./card";
15
16
  import { Empty, EmptyDescription } from "./empty";
16
17
  import { Spinner } from "./spinner";
17
18
  import { cn } from "@/lib/utils";
18
- import { FALLBACK_TICK } from "./chart-shared";
19
+ import {
20
+ FALLBACK_TICK,
21
+ FALLBACK_PRIMARY,
22
+ FALLBACK_SECONDARY,
23
+ } from "./chart-shared";
19
24
 
20
- ChartJS.register(CategoryScale, LinearScale, BarElement, Tooltip);
25
+ ChartJS.register(CategoryScale, LinearScale, BarController, BarElement, Tooltip);
21
26
 
22
27
  // ---------------------------------------------------------------------------
23
28
  // Constants
24
29
  // ---------------------------------------------------------------------------
25
30
 
26
- const FALLBACK_PRIMARY = "#33FF99";
27
- const FALLBACK_SECONDARY = "#162029";
28
- /** Dark neutral used for dollar value labels — not a brand color. */
29
- const VALUE_LABEL_COLOR = "#162029";
30
-
31
31
  // ---------------------------------------------------------------------------
32
32
  // Helpers
33
33
  // ---------------------------------------------------------------------------
@@ -75,7 +75,10 @@ export function TransactionsIncomeExpenseBarChart({
75
75
  const brandPrimary =
76
76
  (themeVars["--theme-primary"] as string | undefined) || FALLBACK_PRIMARY;
77
77
  const brandSecondary =
78
- (themeVars["--theme-secondary"] as string | undefined) || FALLBACK_SECONDARY;
78
+ (themeVars["--theme-secondary"] as string | undefined) ||
79
+ FALLBACK_SECONDARY;
80
+ const fontFamily: string =
81
+ (themeVars["--font-sans"] as string | undefined) || "Figtree, sans-serif";
79
82
 
80
83
  const hasData = totalIncome != null && totalExpense != null;
81
84
  const incomeVal = totalIncome ?? 0;
@@ -104,7 +107,7 @@ export function TransactionsIncomeExpenseBarChart({
104
107
  offset: 10,
105
108
  clamp: false,
106
109
  font: { weight: "bold", size: 14 },
107
- color: VALUE_LABEL_COLOR,
110
+ color: FALLBACK_SECONDARY,
108
111
  textAlign: "left",
109
112
  // Returns array for multi-line: dollar value on line 1, blank on line 2
110
113
  formatter: (v: number) => [formatDollar(v), ""],
@@ -119,8 +122,10 @@ export function TransactionsIncomeExpenseBarChart({
119
122
  textAlign: "left",
120
123
  // Returns array for multi-line: blank on line 1, bar label on line 2
121
124
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
122
- formatter: (_: number, ctx: any) =>
123
- ["", String(ctx.chart.data.labels?.[ctx.dataIndex] ?? "")],
125
+ formatter: (_: number, ctx: any) => [
126
+ "",
127
+ String(ctx.chart.data.labels?.[ctx.dataIndex] ?? ""),
128
+ ],
124
129
  },
125
130
  },
126
131
  } as unknown as never,
@@ -129,40 +134,43 @@ export function TransactionsIncomeExpenseBarChart({
129
134
  };
130
135
  }, [hasData, incomeVal, expenseVal, brandPrimary, brandSecondary]);
131
136
 
132
- const options = useMemo<ChartOptions<"bar">>(() => ({
133
- indexAxis: "y",
134
- responsive: true,
135
- maintainAspectRatio: false,
136
- animation: { duration: 800, easing: "easeOutQuart" },
137
- layout: {
138
- // Right padding reserves space for the datalabels rendered outside the bar area
139
- padding: { right: 180, left: 0, top: 10, bottom: 10 },
140
- },
141
- plugins: {
142
- legend: { display: false },
143
- tooltip: { enabled: false },
144
- },
145
- scales: {
146
- y: {
147
- display: true,
148
- grid: { display: false },
149
- border: { display: false },
150
- ticks: { display: false },
137
+ const options = useMemo<ChartOptions<"bar">>(
138
+ () => ({
139
+ indexAxis: "y",
140
+ responsive: true,
141
+ maintainAspectRatio: false,
142
+ animation: { duration: 800, easing: "easeOutQuart" },
143
+ layout: {
144
+ // Right padding reserves space for the datalabels rendered outside the bar area
145
+ padding: { right: 180, left: 0, top: 10, bottom: 10 },
146
+ },
147
+ plugins: {
148
+ legend: { display: false },
149
+ tooltip: { enabled: false },
151
150
  },
152
- x: {
153
- display: true,
154
- suggestedMax: maxVal * 1.3,
155
- grid: { display: false },
156
- border: { display: false },
157
- ticks: { display: false },
151
+ scales: {
152
+ y: {
153
+ display: true,
154
+ grid: { display: false },
155
+ border: { display: false },
156
+ ticks: { display: false },
157
+ },
158
+ x: {
159
+ display: true,
160
+ suggestedMax: maxVal * 1.3,
161
+ grid: { display: false },
162
+ border: { display: false },
163
+ ticks: { display: false },
164
+ },
158
165
  },
159
- },
160
- }), [maxVal]);
166
+ }),
167
+ [maxVal],
168
+ );
161
169
 
162
170
  return (
163
171
  <Card
164
172
  className={cn("w-full py-4 sm:py-6 gap-2", className)}
165
- style={{ maxWidth: width }}
173
+ style={{ maxWidth: width, fontFamily }}
166
174
  >
167
175
  <CardHeader className="px-3 sm:px-6">
168
176
  <CardTitle className="text-sm sm:text-base">{title}</CardTitle>
@@ -0,0 +1,192 @@
1
+ import React, { useMemo } from "react";
2
+ import {
3
+ Chart as ChartJS,
4
+ ArcElement,
5
+ DoughnutController,
6
+ Tooltip,
7
+ type ChartOptions,
8
+ type ChartData,
9
+ } from "chart.js";
10
+ import { Chart } from "react-chartjs-2";
11
+ import { useThemeVars } from "@/lib/theme-provider";
12
+ import { Card, CardContent, CardHeader, CardTitle } from "./card";
13
+ import { Empty, EmptyDescription } from "./empty";
14
+ import { Skeleton } from "./skeleton";
15
+ import { cn } from "@/lib/utils";
16
+ import {
17
+ formatAbbrev,
18
+ hexToRgba,
19
+ DATASET_ALPHAS,
20
+ FALLBACK_PRIMARY,
21
+ DoughnutLegendRow,
22
+ } from "./chart-shared";
23
+
24
+ ChartJS.register(ArcElement, DoughnutController, Tooltip);
25
+
26
+ // ---------------------------------------------------------------------------
27
+ // Types
28
+ // ---------------------------------------------------------------------------
29
+
30
+ export interface LiabilitySegment {
31
+ label: string;
32
+ /** Value in dollars */
33
+ value: number;
34
+ }
35
+
36
+ export interface TransactionsLiabilitiesBreakdownChartProps {
37
+ segments?: LiabilitySegment[] | null;
38
+ title?: string;
39
+ showLegend?: boolean;
40
+ /** Chart canvas height in pixels */
41
+ height?: number;
42
+ /** Card max-width in pixels or CSS string */
43
+ width?: number | string;
44
+ className?: string;
45
+ /** Show skeleton loading state instead of the chart */
46
+ isLoading?: boolean;
47
+ }
48
+
49
+ // ---------------------------------------------------------------------------
50
+ // ---------------------------------------------------------------------------
51
+ // Component
52
+ // ---------------------------------------------------------------------------
53
+
54
+ export function TransactionsLiabilitiesBreakdownChart({
55
+ segments,
56
+ title = "Liabilities Breakdown",
57
+ showLegend = true,
58
+ height = 200,
59
+ width = "100%",
60
+ className,
61
+ isLoading = false,
62
+ }: TransactionsLiabilitiesBreakdownChartProps) {
63
+ const themeVars = useThemeVars();
64
+ const brandPrimary: string =
65
+ (themeVars["--theme-primary"] as string | undefined) || FALLBACK_PRIMARY;
66
+ const fontFamily: string =
67
+ (themeVars["--font-sans"] as string | undefined) || "Figtree, sans-serif";
68
+
69
+ const hasData = !!segments?.length && segments.some((s) => s.value > 0);
70
+
71
+ const total = useMemo(
72
+ () => segments?.reduce((sum, s) => sum + s.value, 0) ?? 0,
73
+ [segments],
74
+ );
75
+
76
+ const colors = useMemo(
77
+ () =>
78
+ (segments ?? []).map((_, i) =>
79
+ hexToRgba(brandPrimary, DATASET_ALPHAS[i % DATASET_ALPHAS.length]),
80
+ ),
81
+ [segments, brandPrimary],
82
+ );
83
+
84
+ const chartData = useMemo<ChartData<"doughnut">>(
85
+ () => ({
86
+ labels: segments?.map((s) => s.label) ?? [],
87
+ datasets: [
88
+ {
89
+ data: segments?.map((s) => s.value) ?? [],
90
+ backgroundColor: colors,
91
+ borderWidth: 0,
92
+ hoverOffset: 4,
93
+ },
94
+ ],
95
+ }),
96
+ [segments, colors],
97
+ );
98
+
99
+ const options = useMemo<ChartOptions<"doughnut">>(
100
+ () => ({
101
+ responsive: true,
102
+ maintainAspectRatio: false,
103
+ cutout: "68%",
104
+ animation: { duration: 600, easing: "easeOutQuart" },
105
+ plugins: {
106
+ legend: { display: false },
107
+ tooltip: {
108
+ padding: 10,
109
+ cornerRadius: 0,
110
+ titleFont: { size: 11, weight: 600 },
111
+ bodyFont: { size: 12, weight: 500 },
112
+ callbacks: {
113
+ label: (ctx) => {
114
+ const val = ctx.raw as number;
115
+ const pct =
116
+ total > 0 ? `${((val / total) * 100).toFixed(1)}%` : "0%";
117
+ return ` ${ctx.label}: ${formatAbbrev(val)} (${pct})`;
118
+ },
119
+ },
120
+ },
121
+ },
122
+ }),
123
+ [total],
124
+ );
125
+
126
+ return (
127
+ <Card
128
+ className={cn("w-full py-4 sm:py-6 gap-2", className)}
129
+ style={{ maxWidth: width, fontFamily }}
130
+ >
131
+ <CardHeader className="px-3 sm:px-6">
132
+ <CardTitle className="text-sm sm:text-base">{title}</CardTitle>
133
+ </CardHeader>
134
+
135
+ <CardContent className="px-3 sm:px-6">
136
+ {isLoading ? (
137
+ <Skeleton style={{ height, width: "100%" }} />
138
+ ) : !hasData ? (
139
+ <Empty className="flex-none p-4" style={{ height }}>
140
+ <EmptyDescription>No data available</EmptyDescription>
141
+ </Empty>
142
+ ) : (
143
+ <div className="flex flex-col gap-4">
144
+ <div style={{ height, width: "100%", position: "relative" }}>
145
+ <Chart
146
+ key={brandPrimary}
147
+ type="doughnut"
148
+ data={chartData}
149
+ options={options}
150
+ aria-label={title}
151
+ />
152
+ <div
153
+ style={{
154
+ position: "absolute",
155
+ top: "50%",
156
+ left: "50%",
157
+ transform: "translate(-50%, -50%)",
158
+ textAlign: "center",
159
+ pointerEvents: "none",
160
+ }}
161
+ >
162
+ <div className="text-base font-semibold leading-tight">
163
+ {formatAbbrev(total)}
164
+ </div>
165
+ <div className="text-[11px] text-muted-foreground leading-none mt-1">
166
+ Total
167
+ </div>
168
+ </div>
169
+ </div>
170
+ {showLegend && (
171
+ <div className="flex flex-col gap-2">
172
+ {segments!.map((s, i) => (
173
+ <DoughnutLegendRow
174
+ key={s.label}
175
+ label={s.label}
176
+ color={colors[i]}
177
+ value={s.value}
178
+ percent={
179
+ total > 0
180
+ ? `${((s.value / total) * 100).toFixed(1)}%`
181
+ : "0%"
182
+ }
183
+ />
184
+ ))}
185
+ </div>
186
+ )}
187
+ </div>
188
+ )}
189
+ </CardContent>
190
+ </Card>
191
+ );
192
+ }
@@ -93,6 +93,16 @@ export function ThemeProvider({
93
93
  // Legacy compat (used by existing WealthX apps)
94
94
  "--theme-primary": primary,
95
95
  "--theme-secondary": secondary,
96
+
97
+ // Pipeline stage palette — 5 opacity levels of primary, tenant-adaptive.
98
+ // Defined here (not :root) so they react to tenant theme switches.
99
+ "--color-stage-1": primaryOklch,
100
+ "--color-stage-2": `color-mix(in oklch, ${primaryOklch} 80%, transparent)`,
101
+ "--color-stage-3": `color-mix(in oklch, ${primaryOklch} 60%, transparent)`,
102
+ "--color-stage-4": `color-mix(in oklch, ${primaryOklch} 40%, transparent)`,
103
+ "--color-stage-5": `color-mix(in oklch, ${primaryOklch} 20%, transparent)`,
104
+ // WCAG-computed contrast text for any primary-shaded background
105
+ "--color-stage-fg": primaryFgOklch,
96
106
  };
97
107
  }, [primary, secondary, fontFamily, injectCssVariables]);
98
108
 
@@ -12,6 +12,7 @@
12
12
  // .text-h1 … .text-h6
13
13
  // .text-body-large, .text-body-medium, .text-body-small
14
14
  // .text-label-large, .text-label-medium, .text-label-small
15
+ // .text-button, .text-button-xs
15
16
  // .text-caption, .text-overline, .text-code
16
17
 
17
18
  export const FONT_FAMILY_SANS =
@@ -65,6 +66,12 @@ export const TYPOGRAPHY_LABEL = {
65
66
  small: style(600, "0.75rem", "1rem", { letterSpacing: "0.03125rem" }),
66
67
  } as const;
67
68
 
69
+ /** Button — interactive control text (weight 500 / Medium per Figma) */
70
+ export const TYPOGRAPHY_BUTTON = {
71
+ default: style(500, "0.875rem", "1.25rem"),
72
+ xs: style(500, "0.75rem", "1.25rem"),
73
+ } as const;
74
+
68
75
  /** Utility — caption, overline, code */
69
76
  export const TYPOGRAPHY_UTILITY = {
70
77
  caption: style(400, "0.75rem", "1rem"),
@@ -80,6 +87,7 @@ export const TYPOGRAPHY = {
80
87
  heading: TYPOGRAPHY_HEADING,
81
88
  body: TYPOGRAPHY_BODY,
82
89
  label: TYPOGRAPHY_LABEL,
90
+ button: TYPOGRAPHY_BUTTON,
83
91
  utility: TYPOGRAPHY_UTILITY,
84
92
  } as const;
85
93
 
@@ -119,6 +127,7 @@ export function getTypographyCssVars(
119
127
  ["", TYPOGRAPHY_HEADING as unknown as Record<string, TypographyStyle>],
120
128
  ["body", TYPOGRAPHY_BODY as unknown as Record<string, TypographyStyle>],
121
129
  ["label", TYPOGRAPHY_LABEL as unknown as Record<string, TypographyStyle>],
130
+ ["button", TYPOGRAPHY_BUTTON as unknown as Record<string, TypographyStyle>],
122
131
  ["", TYPOGRAPHY_UTILITY as unknown as Record<string, TypographyStyle>],
123
132
  ];
124
133
 
package/src/lib/utils.ts CHANGED
@@ -1,6 +1,44 @@
1
- import { clsx, type ClassValue } from "clsx"
2
- import { twMerge } from "tailwind-merge"
1
+ import { clsx, type ClassValue } from "clsx";
2
+ import { extendTailwindMerge } from "tailwind-merge";
3
+
4
+ /**
5
+ * Custom twMerge that recognises WealthX typography tokens as font-size
6
+ * utilities so they don't conflict with text-color utilities
7
+ * (e.g. text-button vs text-primary-foreground).
8
+ */
9
+ const twMerge = extendTailwindMerge({
10
+ extend: {
11
+ classGroups: {
12
+ "font-size": [
13
+ {
14
+ text: [
15
+ "display-large",
16
+ "display-medium",
17
+ "display-small",
18
+ "h1",
19
+ "h2",
20
+ "h3",
21
+ "h4",
22
+ "h5",
23
+ "h6",
24
+ "body-large",
25
+ "body-medium",
26
+ "body-small",
27
+ "label-large",
28
+ "label-medium",
29
+ "label-small",
30
+ "button",
31
+ "button-xs",
32
+ "caption",
33
+ "overline",
34
+ "code",
35
+ ],
36
+ },
37
+ ],
38
+ },
39
+ },
40
+ });
3
41
 
4
42
  export function cn(...inputs: ClassValue[]): string {
5
- return twMerge(clsx(inputs))
43
+ return twMerge(clsx(inputs));
6
44
  }