@wealthx/shadcn 1.0.2 → 1.2.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 (300) hide show
  1. package/.turbo/turbo-build.log +235 -138
  2. package/CHANGELOG.md +12 -0
  3. package/README.md +82 -0
  4. package/dist/{chunk-6OJF6XRN.mjs → chunk-24FUO7TD.mjs} +4 -8
  5. package/dist/{chunk-4AJ5HWHD.mjs → chunk-2I5S2AMY.mjs} +3 -3
  6. package/dist/chunk-2SF672SZ.mjs +161 -0
  7. package/dist/{chunk-GPRJQ24C.mjs → chunk-34NWQURD.mjs} +2 -2
  8. package/dist/{chunk-MQ72DIBH.mjs → chunk-3GF7OVTP.mjs} +14 -5
  9. package/dist/chunk-3WMX6KWS.mjs +245 -0
  10. package/dist/{chunk-PMKODV6M.mjs → chunk-462HMNO4.mjs} +6 -10
  11. package/dist/chunk-4CX4SBRO.mjs +153 -0
  12. package/dist/chunk-4MN6UQHG.mjs +443 -0
  13. package/dist/chunk-5QQVZTVZ.mjs +233 -0
  14. package/dist/{chunk-BGP2N52Z.mjs → chunk-66MI7Q4B.mjs} +5 -5
  15. package/dist/chunk-6FCGKSZX.mjs +268 -0
  16. package/dist/{chunk-CGOKTPXU.mjs → chunk-6JQFUE5I.mjs} +20 -23
  17. package/dist/{chunk-Z3MK2KKZ.mjs → chunk-7DHU4VGG.mjs} +7 -3
  18. package/dist/{chunk-VZ2NR7L3.mjs → chunk-7PYJD5JI.mjs} +35 -27
  19. package/dist/{chunk-JU2RUWHF.mjs → chunk-7XJHLGUV.mjs} +1 -1
  20. package/dist/{chunk-BMFN37JH.mjs → chunk-7YAU5CY6.mjs} +1 -1
  21. package/dist/chunk-A56YQQHG.mjs +402 -0
  22. package/dist/chunk-AH52LG6N.mjs +315 -0
  23. package/dist/{chunk-SLWCCURD.mjs → chunk-CLIN5525.mjs} +8 -4
  24. package/dist/{chunk-3VQNJ235.mjs → chunk-CSDO6VBW.mjs} +7 -0
  25. package/dist/chunk-D4ILTPOG.mjs +293 -0
  26. package/dist/{chunk-HS7TFG7V.mjs → chunk-D6ID6M4V.mjs} +1 -1
  27. package/dist/chunk-DOH3EHX7.mjs +378 -0
  28. package/dist/{chunk-MJIEMGRD.mjs → chunk-EFRENWEJ.mjs} +9 -17
  29. package/dist/chunk-ERGGHC2V.mjs +185 -0
  30. package/dist/{chunk-OXQQNQZI.mjs → chunk-FEZKMUCF.mjs} +10 -1
  31. package/dist/{chunk-55CEW76V.mjs → chunk-FH6QVUVZ.mjs} +1 -1
  32. package/dist/chunk-FMAXJ2SI.mjs +71 -0
  33. package/dist/chunk-FZIXGLMV.mjs +173 -0
  34. package/dist/{chunk-DS2AMHN2.mjs → chunk-GYMYRIZP.mjs} +2 -2
  35. package/dist/{chunk-KQDD5MU3.mjs → chunk-H45TKD34.mjs} +5 -5
  36. package/dist/{chunk-BBJBJSXQ.mjs → chunk-J5UICVJS.mjs} +1 -1
  37. package/dist/{chunk-RL772EH7.mjs → chunk-JHJHG4GO.mjs} +4 -12
  38. package/dist/chunk-KMCGSZTX.mjs +177 -0
  39. package/dist/{chunk-FHNT55I5.mjs → chunk-KUDCQ4FI.mjs} +4 -4
  40. package/dist/chunk-LE6YFY6D.mjs +209 -0
  41. package/dist/{chunk-HUVTPUV2.mjs → chunk-LLVQKSU3.mjs} +23 -19
  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-NLCKVHWB.mjs +161 -0
  45. package/dist/{chunk-YN5SYTOO.mjs → chunk-NQPOYKAQ.mjs} +9 -5
  46. package/dist/{chunk-ZZV5JVNW.mjs → chunk-NSLMILBT.mjs} +3 -7
  47. package/dist/chunk-NXA3CZ7A.mjs +248 -0
  48. package/dist/chunk-OGOYQ7BG.mjs +150 -0
  49. package/dist/{chunk-3NQGYJEZ.mjs → chunk-P6AM5V7O.mjs} +10 -18
  50. package/dist/{chunk-CZ3BW5GL.mjs → chunk-P76HMUI6.mjs} +5 -11
  51. package/dist/chunk-PCPLO5HT.mjs +671 -0
  52. package/dist/chunk-PG6K5XEC.mjs +475 -0
  53. package/dist/chunk-PJHPSRYD.mjs +234 -0
  54. package/dist/{chunk-DDPA2XXS.mjs → chunk-PMB3A7V3.mjs} +2 -2
  55. package/dist/chunk-PR6V5XKM.mjs +209 -0
  56. package/dist/{chunk-46OFHMQA.mjs → chunk-Q76O3RIQ.mjs} +10 -6
  57. package/dist/chunk-QVKWW6KE.mjs +272 -0
  58. package/dist/chunk-RGU7HOEC.mjs +140 -0
  59. package/dist/{chunk-JF4PHPD5.mjs → chunk-RGVKLTLH.mjs} +4 -4
  60. package/dist/{chunk-VG6UF6UT.mjs → chunk-RP3SQYA3.mjs} +2 -2
  61. package/dist/chunk-RRBS6D63.mjs +163 -0
  62. package/dist/chunk-SMQ3DG25.mjs +285 -0
  63. package/dist/chunk-SPJ5KXW7.mjs +199 -0
  64. package/dist/chunk-SYOD63OZ.mjs +225 -0
  65. package/dist/chunk-UFYSFDER.mjs +42 -0
  66. package/dist/chunk-VACKZOMY.mjs +190 -0
  67. package/dist/chunk-VLQZANBF.mjs +42 -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-YKPROFLB.mjs +161 -0
  76. package/dist/{chunk-K76E2TQU.mjs → chunk-ZRO5JO3H.mjs} +107 -67
  77. package/dist/{chunk-VYMHBV6D.mjs → chunk-ZU4NV6RG.mjs} +5 -3
  78. package/dist/components/ui/accordion.js +40 -4
  79. package/dist/components/ui/accordion.mjs +2 -2
  80. package/dist/components/ui/add-column-modal.js +789 -0
  81. package/dist/components/ui/add-column-modal.mjs +17 -0
  82. package/dist/components/ui/add-lead-modal.js +647 -0
  83. package/dist/components/ui/add-lead-modal.mjs +16 -0
  84. package/dist/components/ui/ai-assistant-drawer.js +686 -0
  85. package/dist/components/ui/ai-assistant-drawer.mjs +16 -0
  86. package/dist/components/ui/alert-dialog.js +37 -5
  87. package/dist/components/ui/alert-dialog.mjs +4 -4
  88. package/dist/components/ui/alert.js +37 -11
  89. package/dist/components/ui/alert.mjs +2 -2
  90. package/dist/components/ui/avatar.js +36 -8
  91. package/dist/components/ui/avatar.mjs +2 -2
  92. package/dist/components/ui/backoffice-alert-history-chart.js +624 -0
  93. package/dist/components/ui/backoffice-alert-history-chart.mjs +16 -0
  94. package/dist/components/ui/backoffice-contact-history-chart.js +687 -0
  95. package/dist/components/ui/backoffice-contact-history-chart.mjs +16 -0
  96. package/dist/components/ui/badge.js +37 -2
  97. package/dist/components/ui/badge.mjs +2 -2
  98. package/dist/components/ui/borrowing-capacity-line-chart.js +639 -0
  99. package/dist/components/ui/borrowing-capacity-line-chart.mjs +16 -0
  100. package/dist/components/ui/button.js +35 -3
  101. package/dist/components/ui/button.mjs +2 -2
  102. package/dist/components/ui/calendar.js +43 -19
  103. package/dist/components/ui/calendar.mjs +3 -3
  104. package/dist/components/ui/card.js +40 -4
  105. package/dist/components/ui/card.mjs +2 -2
  106. package/dist/components/ui/cash-balance-line-chart.js +627 -0
  107. package/dist/components/ui/cash-balance-line-chart.mjs +16 -0
  108. package/dist/components/ui/cashflow-bar-chart.js +650 -0
  109. package/dist/components/ui/cashflow-bar-chart.mjs +16 -0
  110. package/dist/components/ui/checkbox.js +36 -5
  111. package/dist/components/ui/checkbox.mjs +2 -3
  112. package/dist/components/ui/chip.js +37 -2
  113. package/dist/components/ui/chip.mjs +3 -3
  114. package/dist/components/ui/combobox.js +280 -0
  115. package/dist/components/ui/combobox.mjs +28 -0
  116. package/dist/components/ui/data-table.js +160 -88
  117. package/dist/components/ui/data-table.mjs +10 -11
  118. package/dist/components/ui/date-picker.js +44 -20
  119. package/dist/components/ui/date-picker.mjs +6 -7
  120. package/dist/components/ui/dialog.js +44 -12
  121. package/dist/components/ui/dialog.mjs +4 -4
  122. package/dist/components/ui/drawer.js +46 -10
  123. package/dist/components/ui/drawer.mjs +3 -3
  124. package/dist/components/ui/dropdown-menu.js +40 -16
  125. package/dist/components/ui/dropdown-menu.mjs +3 -3
  126. package/dist/components/ui/empty.js +41 -5
  127. package/dist/components/ui/empty.mjs +2 -2
  128. package/dist/components/ui/expense-bar-chart.js +642 -0
  129. package/dist/components/ui/expense-bar-chart.mjs +16 -0
  130. package/dist/components/ui/field.js +53 -21
  131. package/dist/components/ui/field.mjs +4 -4
  132. package/dist/components/ui/financial-cards.js +1002 -0
  133. package/dist/components/ui/financial-cards.mjs +24 -0
  134. package/dist/components/ui/financial-drawers.js +637 -0
  135. package/dist/components/ui/financial-drawers.mjs +17 -0
  136. package/dist/components/ui/financial-primitives.js +218 -0
  137. package/dist/components/ui/financial-primitives.mjs +22 -0
  138. package/dist/components/ui/financial-sections.js +1422 -0
  139. package/dist/components/ui/financial-sections.mjs +30 -0
  140. package/dist/components/ui/form-primitives.js +682 -0
  141. package/dist/components/ui/form-primitives.mjs +19 -0
  142. package/dist/components/ui/income-bar-chart.js +641 -0
  143. package/dist/components/ui/income-bar-chart.mjs +16 -0
  144. package/dist/components/ui/input-group.js +43 -7
  145. package/dist/components/ui/input-group.mjs +5 -5
  146. package/dist/components/ui/input-otp.js +39 -3
  147. package/dist/components/ui/input-otp.mjs +2 -2
  148. package/dist/components/ui/input.js +34 -2
  149. package/dist/components/ui/input.mjs +2 -2
  150. package/dist/components/ui/kanban-column.js +1143 -0
  151. package/dist/components/ui/kanban-column.mjs +20 -0
  152. package/dist/components/ui/label.js +35 -7
  153. package/dist/components/ui/label.mjs +2 -2
  154. package/dist/components/ui/opportunity-card.js +960 -0
  155. package/dist/components/ui/opportunity-card.mjs +20 -0
  156. package/dist/components/ui/opportunity-edit-modals.js +3360 -0
  157. package/dist/components/ui/opportunity-edit-modals.mjs +37 -0
  158. package/dist/components/ui/opportunity-summary-tab.js +4365 -0
  159. package/dist/components/ui/opportunity-summary-tab.mjs +34 -0
  160. package/dist/components/ui/pagination.js +35 -3
  161. package/dist/components/ui/pagination.mjs +3 -3
  162. package/dist/components/ui/pipeline-alerts.js +103 -0
  163. package/dist/components/ui/pipeline-alerts.mjs +8 -0
  164. package/dist/components/ui/pipeline-board.js +1408 -0
  165. package/dist/components/ui/pipeline-board.mjs +24 -0
  166. package/dist/components/ui/pipeline-chart.js +216 -0
  167. package/dist/components/ui/pipeline-chart.mjs +10 -0
  168. package/dist/components/ui/pipeline-dialogs.js +1183 -0
  169. package/dist/components/ui/pipeline-dialogs.mjs +23 -0
  170. package/dist/components/ui/pipeline-primitives.js +300 -0
  171. package/dist/components/ui/pipeline-primitives.mjs +11 -0
  172. package/dist/components/ui/popover.js +45 -4
  173. package/dist/components/ui/popover.mjs +3 -3
  174. package/dist/components/ui/progress.js +33 -1
  175. package/dist/components/ui/progress.mjs +2 -2
  176. package/dist/components/ui/property-cashflow-doughnut-chart.js +523 -0
  177. package/dist/components/ui/property-cashflow-doughnut-chart.mjs +16 -0
  178. package/dist/components/ui/property-debt-equity-doughnut-chart.js +521 -0
  179. package/dist/components/ui/property-debt-equity-doughnut-chart.mjs +16 -0
  180. package/dist/components/ui/property-mobile-estimate-line-chart.js +682 -0
  181. package/dist/components/ui/property-mobile-estimate-line-chart.mjs +16 -0
  182. package/dist/components/ui/radio-group.js +33 -1
  183. package/dist/components/ui/radio-group.mjs +2 -2
  184. package/dist/components/ui/select.js +66 -26
  185. package/dist/components/ui/select.mjs +3 -3
  186. package/dist/components/ui/separator.js +33 -1
  187. package/dist/components/ui/separator.mjs +2 -2
  188. package/dist/components/ui/sheet.js +37 -9
  189. package/dist/components/ui/sheet.mjs +3 -3
  190. package/dist/components/ui/skeleton.js +33 -1
  191. package/dist/components/ui/skeleton.mjs +2 -2
  192. package/dist/components/ui/slider.js +86 -102
  193. package/dist/components/ui/slider.mjs +2 -2
  194. package/dist/components/ui/spinner.js +33 -1
  195. package/dist/components/ui/spinner.mjs +2 -2
  196. package/dist/components/ui/stage-timeline.js +579 -0
  197. package/dist/components/ui/stage-timeline.mjs +15 -0
  198. package/dist/components/ui/switch.js +37 -4
  199. package/dist/components/ui/switch.mjs +2 -3
  200. package/dist/components/ui/table.js +37 -5
  201. package/dist/components/ui/table.mjs +2 -2
  202. package/dist/components/ui/tabs.js +36 -12
  203. package/dist/components/ui/tabs.mjs +2 -2
  204. package/dist/components/ui/textarea.js +34 -2
  205. package/dist/components/ui/textarea.mjs +2 -2
  206. package/dist/components/ui/toggle-group.js +35 -4
  207. package/dist/components/ui/toggle-group.mjs +3 -4
  208. package/dist/components/ui/toggle.js +35 -4
  209. package/dist/components/ui/toggle.mjs +2 -3
  210. package/dist/components/ui/tooltip.js +51 -22
  211. package/dist/components/ui/tooltip.mjs +3 -3
  212. package/dist/components/ui/transactions-expense-categories-doughnut-chart.js +528 -0
  213. package/dist/components/ui/transactions-expense-categories-doughnut-chart.mjs +16 -0
  214. package/dist/components/ui/transactions-income-expense-bar-chart.js +516 -0
  215. package/dist/components/ui/transactions-income-expense-bar-chart.mjs +16 -0
  216. package/dist/components/ui/transactions-liabilities-breakdown-doughnut-chart.js +528 -0
  217. package/dist/components/ui/transactions-liabilities-breakdown-doughnut-chart.mjs +16 -0
  218. package/dist/index.js +11613 -2868
  219. package/dist/index.mjs +377 -164
  220. package/dist/lib/theme-provider.js +10 -1
  221. package/dist/lib/theme-provider.mjs +1 -1
  222. package/dist/lib/typography.js +8 -0
  223. package/dist/lib/typography.mjs +3 -1
  224. package/dist/lib/utils.js +33 -1
  225. package/dist/lib/utils.mjs +1 -1
  226. package/dist/styles.css +1 -1
  227. package/package.json +169 -6
  228. package/src/components/index.tsx +323 -13
  229. package/src/components/ui/accordion.tsx +6 -3
  230. package/src/components/ui/add-column-modal.tsx +339 -0
  231. package/src/components/ui/add-lead-modal.tsx +290 -0
  232. package/src/components/ui/ai-assistant-drawer.tsx +408 -0
  233. package/src/components/ui/alert-dialog.tsx +80 -54
  234. package/src/components/ui/alert.tsx +28 -28
  235. package/src/components/ui/avatar.tsx +30 -29
  236. package/src/components/ui/backoffice-alert-history-chart.tsx +260 -0
  237. package/src/components/ui/backoffice-contact-history-chart.tsx +325 -0
  238. package/src/components/ui/badge.tsx +17 -15
  239. package/src/components/ui/borrowing-capacity-line-chart.tsx +357 -0
  240. package/src/components/ui/button.tsx +30 -27
  241. package/src/components/ui/calendar.tsx +53 -67
  242. package/src/components/ui/card.tsx +27 -24
  243. package/src/components/ui/cash-balance-line-chart.tsx +302 -0
  244. package/src/components/ui/cashflow-bar-chart.tsx +363 -0
  245. package/src/components/ui/chart-shared.tsx +261 -0
  246. package/src/components/ui/checkbox.tsx +30 -26
  247. package/src/components/ui/combobox.tsx +223 -0
  248. package/src/components/ui/data-table.tsx +160 -99
  249. package/src/components/ui/date-picker.tsx +0 -2
  250. package/src/components/ui/dialog.tsx +70 -60
  251. package/src/components/ui/drawer.tsx +57 -48
  252. package/src/components/ui/dropdown-menu.tsx +90 -82
  253. package/src/components/ui/empty.tsx +31 -27
  254. package/src/components/ui/expense-bar-chart.tsx +296 -0
  255. package/src/components/ui/field.tsx +70 -62
  256. package/src/components/ui/financial-cards.tsx +830 -0
  257. package/src/components/ui/financial-drawers.tsx +339 -0
  258. package/src/components/ui/financial-primitives.tsx +331 -0
  259. package/src/components/ui/financial-sections.tsx +672 -0
  260. package/src/components/ui/form-primitives.tsx +536 -0
  261. package/src/components/ui/income-bar-chart.tsx +297 -0
  262. package/src/components/ui/input-group.tsx +41 -34
  263. package/src/components/ui/input-otp.tsx +29 -24
  264. package/src/components/ui/input.tsx +8 -8
  265. package/src/components/ui/kanban-column.tsx +333 -0
  266. package/src/components/ui/label.tsx +9 -12
  267. package/src/components/ui/opportunity-card.tsx +616 -0
  268. package/src/components/ui/opportunity-edit-modals.tsx +2528 -0
  269. package/src/components/ui/opportunity-summary-tab.tsx +579 -0
  270. package/src/components/ui/pipeline-alerts.tsx +74 -0
  271. package/src/components/ui/pipeline-board.tsx +268 -0
  272. package/src/components/ui/pipeline-chart.tsx +173 -0
  273. package/src/components/ui/pipeline-dialogs.tsx +303 -0
  274. package/src/components/ui/pipeline-primitives.tsx +108 -0
  275. package/src/components/ui/popover.tsx +41 -36
  276. package/src/components/ui/property-cashflow-doughnut-chart.tsx +188 -0
  277. package/src/components/ui/property-debt-equity-doughnut-chart.tsx +185 -0
  278. package/src/components/ui/property-mobile-estimate-line-chart.tsx +393 -0
  279. package/src/components/ui/select.tsx +65 -52
  280. package/src/components/ui/sheet.tsx +55 -52
  281. package/src/components/ui/slider.tsx +54 -77
  282. package/src/components/ui/stage-timeline.tsx +205 -0
  283. package/src/components/ui/switch.tsx +42 -29
  284. package/src/components/ui/table.tsx +28 -28
  285. package/src/components/ui/tabs.tsx +22 -28
  286. package/src/components/ui/textarea.tsx +8 -8
  287. package/src/components/ui/toggle-group.tsx +0 -2
  288. package/src/components/ui/toggle.tsx +13 -15
  289. package/src/components/ui/tooltip.tsx +30 -28
  290. package/src/components/ui/transactions-expense-categories-doughnut-chart.tsx +191 -0
  291. package/src/components/ui/transactions-income-expense-bar-chart.tsx +205 -0
  292. package/src/components/ui/transactions-liabilities-breakdown-doughnut-chart.tsx +191 -0
  293. package/src/lib/theme-provider.tsx +10 -0
  294. package/src/lib/typography.ts +9 -0
  295. package/src/lib/utils.ts +41 -3
  296. package/src/styles/globals.css +371 -124
  297. package/src/styles/styles-css.ts +1 -1
  298. package/tsup.config.ts +32 -0
  299. package/dist/chunk-K74JRTJR.mjs +0 -105
  300. package/dist/chunk-V7CNWJT3.mjs +0 -10
@@ -0,0 +1,191 @@
1
+ import React, { useMemo } from "react";
2
+ import {
3
+ Chart as ChartJS,
4
+ ArcElement,
5
+ Tooltip,
6
+ type ChartOptions,
7
+ type ChartData,
8
+ } from "chart.js";
9
+ import { Chart } from "react-chartjs-2";
10
+ import { useThemeVars } from "@/lib/theme-provider";
11
+ import { Card, CardContent, CardHeader, CardTitle } from "./card";
12
+ import { Empty, EmptyDescription } from "./empty";
13
+ import { Skeleton } from "./skeleton";
14
+ import { cn } from "@/lib/utils";
15
+ import {
16
+ formatAbbrev,
17
+ hexToRgba,
18
+ DATASET_ALPHAS,
19
+ FALLBACK_PRIMARY,
20
+ DoughnutLegendRow,
21
+ } from "./chart-shared";
22
+
23
+ ChartJS.register(ArcElement, Tooltip);
24
+
25
+ // ---------------------------------------------------------------------------
26
+ // Types
27
+ // ---------------------------------------------------------------------------
28
+
29
+ export interface ExpenseCategorySegment {
30
+ label: string;
31
+ /** Spend amount in dollars */
32
+ value: number;
33
+ }
34
+
35
+ export interface TransactionsExpenseCategoriesDoughnutChartProps {
36
+ segments?: ExpenseCategorySegment[] | null;
37
+ title?: string;
38
+ showLegend?: boolean;
39
+ /** Chart canvas height in pixels */
40
+ height?: number;
41
+ /** Card max-width in pixels or CSS string */
42
+ width?: number | string;
43
+ className?: string;
44
+ /** Show skeleton loading state instead of the chart */
45
+ isLoading?: boolean;
46
+ }
47
+
48
+ // ---------------------------------------------------------------------------
49
+ // ---------------------------------------------------------------------------
50
+ // Component
51
+ // ---------------------------------------------------------------------------
52
+
53
+ export function TransactionsExpenseCategoriesDoughnutChart({
54
+ segments,
55
+ title = "Expense Categories",
56
+ showLegend = true,
57
+ height = 200,
58
+ width = "100%",
59
+ className,
60
+ isLoading = false,
61
+ }: TransactionsExpenseCategoriesDoughnutChartProps) {
62
+ const themeVars = useThemeVars();
63
+ const brandPrimary: string =
64
+ (themeVars["--theme-primary"] as string | undefined) || FALLBACK_PRIMARY;
65
+ const fontFamily: string =
66
+ (themeVars["--font-sans"] as string | undefined) || "Figtree, sans-serif";
67
+
68
+ const hasData = !!segments?.length && segments.some((s) => s.value > 0);
69
+
70
+ const total = useMemo(
71
+ () => segments?.reduce((sum, s) => sum + s.value, 0) ?? 0,
72
+ [segments],
73
+ );
74
+
75
+ const colors = useMemo(
76
+ () =>
77
+ (segments ?? []).map((_, i) =>
78
+ hexToRgba(brandPrimary, DATASET_ALPHAS[i % DATASET_ALPHAS.length]),
79
+ ),
80
+ [segments, brandPrimary],
81
+ );
82
+
83
+ const chartData = useMemo<ChartData<"doughnut">>(
84
+ () => ({
85
+ labels: segments?.map((s) => s.label) ?? [],
86
+ datasets: [
87
+ {
88
+ data: segments?.map((s) => s.value) ?? [],
89
+ backgroundColor: colors,
90
+ borderWidth: 0,
91
+ hoverOffset: 4,
92
+ },
93
+ ],
94
+ }),
95
+ [segments, colors],
96
+ );
97
+
98
+ const options = useMemo<ChartOptions<"doughnut">>(
99
+ () => ({
100
+ responsive: true,
101
+ maintainAspectRatio: false,
102
+ cutout: "68%",
103
+ animation: { duration: 600, easing: "easeOutQuart" },
104
+ plugins: {
105
+ legend: { display: false },
106
+ tooltip: {
107
+ padding: 10,
108
+ cornerRadius: 0,
109
+ titleFont: { size: 11, weight: 600 },
110
+ bodyFont: { size: 12, weight: 500 },
111
+ callbacks: {
112
+ label: (ctx) => {
113
+ const val = ctx.raw as number;
114
+ const pct =
115
+ total > 0 ? `${((val / total) * 100).toFixed(1)}%` : "0%";
116
+ return ` ${ctx.label}: ${formatAbbrev(val)} (${pct})`;
117
+ },
118
+ },
119
+ },
120
+ },
121
+ }),
122
+ [total],
123
+ );
124
+
125
+ return (
126
+ <Card
127
+ className={cn("w-full py-4 sm:py-6 gap-2", className)}
128
+ style={{ maxWidth: width, fontFamily }}
129
+ >
130
+ <CardHeader className="px-3 sm:px-6">
131
+ <CardTitle className="text-sm sm:text-base">{title}</CardTitle>
132
+ </CardHeader>
133
+
134
+ <CardContent className="px-3 sm:px-6">
135
+ {isLoading ? (
136
+ <Skeleton style={{ height, width: "100%" }} />
137
+ ) : !hasData ? (
138
+ <Empty className="flex-none p-4" style={{ height }}>
139
+ <EmptyDescription>No data available</EmptyDescription>
140
+ </Empty>
141
+ ) : (
142
+ <div className="flex flex-col gap-4">
143
+ <div style={{ height, width: "100%", position: "relative" }}>
144
+ <Chart
145
+ key={brandPrimary}
146
+ type="doughnut"
147
+ data={chartData}
148
+ options={options}
149
+ aria-label={title}
150
+ />
151
+ <div
152
+ style={{
153
+ position: "absolute",
154
+ top: "50%",
155
+ left: "50%",
156
+ transform: "translate(-50%, -50%)",
157
+ textAlign: "center",
158
+ pointerEvents: "none",
159
+ }}
160
+ >
161
+ <div className="text-base font-semibold leading-tight">
162
+ {formatAbbrev(total)}
163
+ </div>
164
+ <div className="text-[11px] text-muted-foreground leading-none mt-1">
165
+ Total
166
+ </div>
167
+ </div>
168
+ </div>
169
+ {showLegend && (
170
+ <div className="flex flex-col gap-2">
171
+ {segments!.map((s, i) => (
172
+ <DoughnutLegendRow
173
+ key={s.label}
174
+ label={s.label}
175
+ color={colors[i]}
176
+ value={s.value}
177
+ percent={
178
+ total > 0
179
+ ? `${((s.value / total) * 100).toFixed(1)}%`
180
+ : "0%"
181
+ }
182
+ />
183
+ ))}
184
+ </div>
185
+ )}
186
+ </div>
187
+ )}
188
+ </CardContent>
189
+ </Card>
190
+ );
191
+ }
@@ -0,0 +1,205 @@
1
+ import React, { useMemo } from "react";
2
+ import {
3
+ Chart as ChartJS,
4
+ CategoryScale,
5
+ LinearScale,
6
+ BarElement,
7
+ Tooltip,
8
+ type ChartOptions,
9
+ type ChartData,
10
+ } from "chart.js";
11
+ import ChartDataLabels from "chartjs-plugin-datalabels";
12
+ import { Chart } from "react-chartjs-2";
13
+ import { useThemeVars } from "@/lib/theme-provider";
14
+ import { Card, CardContent, CardHeader, CardTitle } from "./card";
15
+ import { Empty, EmptyDescription } from "./empty";
16
+ import { Spinner } from "./spinner";
17
+ import { cn } from "@/lib/utils";
18
+ import {
19
+ FALLBACK_TICK,
20
+ FALLBACK_PRIMARY,
21
+ FALLBACK_SECONDARY,
22
+ } from "./chart-shared";
23
+
24
+ ChartJS.register(CategoryScale, LinearScale, BarElement, Tooltip);
25
+
26
+ // ---------------------------------------------------------------------------
27
+ // Constants
28
+ // ---------------------------------------------------------------------------
29
+
30
+ // ---------------------------------------------------------------------------
31
+ // Helpers
32
+ // ---------------------------------------------------------------------------
33
+
34
+ function formatDollar(value: number): string {
35
+ return `$${value.toLocaleString(undefined, {
36
+ minimumFractionDigits: 2,
37
+ maximumFractionDigits: 2,
38
+ })}`;
39
+ }
40
+
41
+ // ---------------------------------------------------------------------------
42
+ // Types
43
+ // ---------------------------------------------------------------------------
44
+
45
+ export interface TransactionsIncomeExpenseBarChartProps {
46
+ /** Total income amount (positive number) */
47
+ totalIncome: number | null;
48
+ /** Total expense amount (negative or positive — absolute value is used) */
49
+ totalExpense: number | null;
50
+ title?: string;
51
+ /** Chart canvas height in pixels */
52
+ height?: number;
53
+ /** Width of the card */
54
+ width?: number | string;
55
+ className?: string;
56
+ /** Show skeleton loading state instead of the chart */
57
+ isLoading?: boolean;
58
+ }
59
+
60
+ // ---------------------------------------------------------------------------
61
+ // Component
62
+ // ---------------------------------------------------------------------------
63
+
64
+ export function TransactionsIncomeExpenseBarChart({
65
+ totalIncome,
66
+ totalExpense,
67
+ title = "Transactions — Income vs Expense",
68
+ height = 120,
69
+ width = "100%",
70
+ className,
71
+ isLoading = false,
72
+ }: TransactionsIncomeExpenseBarChartProps) {
73
+ const themeVars = useThemeVars();
74
+ const brandPrimary =
75
+ (themeVars["--theme-primary"] as string | undefined) || FALLBACK_PRIMARY;
76
+ const brandSecondary =
77
+ (themeVars["--theme-secondary"] as string | undefined) ||
78
+ FALLBACK_SECONDARY;
79
+ const fontFamily: string =
80
+ (themeVars["--font-sans"] as string | undefined) || "Figtree, sans-serif";
81
+
82
+ const hasData = totalIncome != null && totalExpense != null;
83
+ const incomeVal = totalIncome ?? 0;
84
+ const expenseVal = Math.abs(totalExpense ?? 0);
85
+ const maxVal = Math.max(incomeVal, expenseVal);
86
+
87
+ const chartData = useMemo<ChartData<"bar">>(() => {
88
+ if (!hasData) return { labels: [], datasets: [] };
89
+ return {
90
+ labels: ["Incoming", "Outgoing"],
91
+ datasets: [
92
+ {
93
+ barThickness: 40,
94
+ backgroundColor: [brandPrimary, brandSecondary],
95
+ hoverBackgroundColor: [brandPrimary, brandSecondary],
96
+ borderWidth: 0,
97
+ borderRadius: 0,
98
+ borderSkipped: false,
99
+ data: [incomeVal, expenseVal],
100
+ // chartjs-plugin-datalabels config — typed via plugin module augmentation
101
+ datalabels: {
102
+ labels: {
103
+ value: {
104
+ anchor: "end",
105
+ align: "end",
106
+ offset: 10,
107
+ clamp: false,
108
+ font: { weight: "bold", size: 14 },
109
+ color: FALLBACK_SECONDARY,
110
+ textAlign: "left",
111
+ // Returns array for multi-line: dollar value on line 1, blank on line 2
112
+ formatter: (v: number) => [formatDollar(v), ""],
113
+ },
114
+ name: {
115
+ anchor: "end",
116
+ align: "end",
117
+ offset: 10,
118
+ clamp: false,
119
+ font: { size: 12 },
120
+ color: FALLBACK_TICK,
121
+ textAlign: "left",
122
+ // Returns array for multi-line: blank on line 1, bar label on line 2
123
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
124
+ formatter: (_: number, ctx: any) => [
125
+ "",
126
+ String(ctx.chart.data.labels?.[ctx.dataIndex] ?? ""),
127
+ ],
128
+ },
129
+ },
130
+ } as unknown as never,
131
+ },
132
+ ],
133
+ };
134
+ }, [hasData, incomeVal, expenseVal, brandPrimary, brandSecondary]);
135
+
136
+ const options = useMemo<ChartOptions<"bar">>(
137
+ () => ({
138
+ indexAxis: "y",
139
+ responsive: true,
140
+ maintainAspectRatio: false,
141
+ animation: { duration: 800, easing: "easeOutQuart" },
142
+ layout: {
143
+ // Right padding reserves space for the datalabels rendered outside the bar area
144
+ padding: { right: 180, left: 0, top: 10, bottom: 10 },
145
+ },
146
+ plugins: {
147
+ legend: { display: false },
148
+ tooltip: { enabled: false },
149
+ },
150
+ scales: {
151
+ y: {
152
+ display: true,
153
+ grid: { display: false },
154
+ border: { display: false },
155
+ ticks: { display: false },
156
+ },
157
+ x: {
158
+ display: true,
159
+ suggestedMax: maxVal * 1.3,
160
+ grid: { display: false },
161
+ border: { display: false },
162
+ ticks: { display: false },
163
+ },
164
+ },
165
+ }),
166
+ [maxVal],
167
+ );
168
+
169
+ return (
170
+ <Card
171
+ className={cn("w-full py-4 sm:py-6 gap-2", className)}
172
+ style={{ maxWidth: width, fontFamily }}
173
+ >
174
+ <CardHeader className="px-3 sm:px-6">
175
+ <CardTitle className="text-sm sm:text-base">{title}</CardTitle>
176
+ </CardHeader>
177
+
178
+ <CardContent className="px-3 sm:px-6">
179
+ {isLoading ? (
180
+ <div
181
+ className="flex items-center justify-center text-muted-foreground"
182
+ style={{ height, width: "100%" }}
183
+ >
184
+ <Spinner size="lg" />
185
+ </div>
186
+ ) : !hasData ? (
187
+ <Empty className="flex-none p-4" style={{ height }}>
188
+ <EmptyDescription>No data available</EmptyDescription>
189
+ </Empty>
190
+ ) : (
191
+ <div style={{ height, width: "100%", position: "relative" }}>
192
+ <Chart
193
+ key={`${brandPrimary}__${brandSecondary}`}
194
+ type="bar"
195
+ data={chartData}
196
+ options={options}
197
+ plugins={[ChartDataLabels]}
198
+ aria-label={title}
199
+ />
200
+ </div>
201
+ )}
202
+ </CardContent>
203
+ </Card>
204
+ );
205
+ }
@@ -0,0 +1,191 @@
1
+ import React, { useMemo } from "react";
2
+ import {
3
+ Chart as ChartJS,
4
+ ArcElement,
5
+ Tooltip,
6
+ type ChartOptions,
7
+ type ChartData,
8
+ } from "chart.js";
9
+ import { Chart } from "react-chartjs-2";
10
+ import { useThemeVars } from "@/lib/theme-provider";
11
+ import { Card, CardContent, CardHeader, CardTitle } from "./card";
12
+ import { Empty, EmptyDescription } from "./empty";
13
+ import { Skeleton } from "./skeleton";
14
+ import { cn } from "@/lib/utils";
15
+ import {
16
+ formatAbbrev,
17
+ hexToRgba,
18
+ DATASET_ALPHAS,
19
+ FALLBACK_PRIMARY,
20
+ DoughnutLegendRow,
21
+ } from "./chart-shared";
22
+
23
+ ChartJS.register(ArcElement, Tooltip);
24
+
25
+ // ---------------------------------------------------------------------------
26
+ // Types
27
+ // ---------------------------------------------------------------------------
28
+
29
+ export interface LiabilitySegment {
30
+ label: string;
31
+ /** Value in dollars */
32
+ value: number;
33
+ }
34
+
35
+ export interface TransactionsLiabilitiesBreakdownChartProps {
36
+ segments?: LiabilitySegment[] | null;
37
+ title?: string;
38
+ showLegend?: boolean;
39
+ /** Chart canvas height in pixels */
40
+ height?: number;
41
+ /** Card max-width in pixels or CSS string */
42
+ width?: number | string;
43
+ className?: string;
44
+ /** Show skeleton loading state instead of the chart */
45
+ isLoading?: boolean;
46
+ }
47
+
48
+ // ---------------------------------------------------------------------------
49
+ // ---------------------------------------------------------------------------
50
+ // Component
51
+ // ---------------------------------------------------------------------------
52
+
53
+ export function TransactionsLiabilitiesBreakdownChart({
54
+ segments,
55
+ title = "Liabilities Breakdown",
56
+ showLegend = true,
57
+ height = 200,
58
+ width = "100%",
59
+ className,
60
+ isLoading = false,
61
+ }: TransactionsLiabilitiesBreakdownChartProps) {
62
+ const themeVars = useThemeVars();
63
+ const brandPrimary: string =
64
+ (themeVars["--theme-primary"] as string | undefined) || FALLBACK_PRIMARY;
65
+ const fontFamily: string =
66
+ (themeVars["--font-sans"] as string | undefined) || "Figtree, sans-serif";
67
+
68
+ const hasData = !!segments?.length && segments.some((s) => s.value > 0);
69
+
70
+ const total = useMemo(
71
+ () => segments?.reduce((sum, s) => sum + s.value, 0) ?? 0,
72
+ [segments],
73
+ );
74
+
75
+ const colors = useMemo(
76
+ () =>
77
+ (segments ?? []).map((_, i) =>
78
+ hexToRgba(brandPrimary, DATASET_ALPHAS[i % DATASET_ALPHAS.length]),
79
+ ),
80
+ [segments, brandPrimary],
81
+ );
82
+
83
+ const chartData = useMemo<ChartData<"doughnut">>(
84
+ () => ({
85
+ labels: segments?.map((s) => s.label) ?? [],
86
+ datasets: [
87
+ {
88
+ data: segments?.map((s) => s.value) ?? [],
89
+ backgroundColor: colors,
90
+ borderWidth: 0,
91
+ hoverOffset: 4,
92
+ },
93
+ ],
94
+ }),
95
+ [segments, colors],
96
+ );
97
+
98
+ const options = useMemo<ChartOptions<"doughnut">>(
99
+ () => ({
100
+ responsive: true,
101
+ maintainAspectRatio: false,
102
+ cutout: "68%",
103
+ animation: { duration: 600, easing: "easeOutQuart" },
104
+ plugins: {
105
+ legend: { display: false },
106
+ tooltip: {
107
+ padding: 10,
108
+ cornerRadius: 0,
109
+ titleFont: { size: 11, weight: 600 },
110
+ bodyFont: { size: 12, weight: 500 },
111
+ callbacks: {
112
+ label: (ctx) => {
113
+ const val = ctx.raw as number;
114
+ const pct =
115
+ total > 0 ? `${((val / total) * 100).toFixed(1)}%` : "0%";
116
+ return ` ${ctx.label}: ${formatAbbrev(val)} (${pct})`;
117
+ },
118
+ },
119
+ },
120
+ },
121
+ }),
122
+ [total],
123
+ );
124
+
125
+ return (
126
+ <Card
127
+ className={cn("w-full py-4 sm:py-6 gap-2", className)}
128
+ style={{ maxWidth: width, fontFamily }}
129
+ >
130
+ <CardHeader className="px-3 sm:px-6">
131
+ <CardTitle className="text-sm sm:text-base">{title}</CardTitle>
132
+ </CardHeader>
133
+
134
+ <CardContent className="px-3 sm:px-6">
135
+ {isLoading ? (
136
+ <Skeleton style={{ height, width: "100%" }} />
137
+ ) : !hasData ? (
138
+ <Empty className="flex-none p-4" style={{ height }}>
139
+ <EmptyDescription>No data available</EmptyDescription>
140
+ </Empty>
141
+ ) : (
142
+ <div className="flex flex-col gap-4">
143
+ <div style={{ height, width: "100%", position: "relative" }}>
144
+ <Chart
145
+ key={brandPrimary}
146
+ type="doughnut"
147
+ data={chartData}
148
+ options={options}
149
+ aria-label={title}
150
+ />
151
+ <div
152
+ style={{
153
+ position: "absolute",
154
+ top: "50%",
155
+ left: "50%",
156
+ transform: "translate(-50%, -50%)",
157
+ textAlign: "center",
158
+ pointerEvents: "none",
159
+ }}
160
+ >
161
+ <div className="text-base font-semibold leading-tight">
162
+ {formatAbbrev(total)}
163
+ </div>
164
+ <div className="text-[11px] text-muted-foreground leading-none mt-1">
165
+ Total
166
+ </div>
167
+ </div>
168
+ </div>
169
+ {showLegend && (
170
+ <div className="flex flex-col gap-2">
171
+ {segments!.map((s, i) => (
172
+ <DoughnutLegendRow
173
+ key={s.label}
174
+ label={s.label}
175
+ color={colors[i]}
176
+ value={s.value}
177
+ percent={
178
+ total > 0
179
+ ? `${((s.value / total) * 100).toFixed(1)}%`
180
+ : "0%"
181
+ }
182
+ />
183
+ ))}
184
+ </div>
185
+ )}
186
+ </div>
187
+ )}
188
+ </CardContent>
189
+ </Card>
190
+ );
191
+ }
@@ -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
  }