@wealthx/shadcn 1.5.41 → 1.5.43

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 (210) hide show
  1. package/.turbo/turbo-build.log +204 -199
  2. package/CHANGELOG.md +12 -0
  3. package/dist/{chunk-5FHBC6DY.mjs → chunk-33WZ5NCW.mjs} +1 -1
  4. package/dist/{chunk-C35JMOII.mjs → chunk-3G6JUYRE.mjs} +4 -4
  5. package/dist/{chunk-LBXIYS34.mjs → chunk-4PVCJ3JD.mjs} +1 -1
  6. package/dist/{chunk-EHQL64B7.mjs → chunk-4SUXTO2Z.mjs} +4 -4
  7. package/dist/{chunk-BAONSY54.mjs → chunk-5RYH7SOQ.mjs} +1 -1
  8. package/dist/{chunk-3C4DZTGA.mjs → chunk-5XD7A7YC.mjs} +1 -1
  9. package/dist/{chunk-5DAQU3B6.mjs → chunk-66JXT7NY.mjs} +1 -1
  10. package/dist/{chunk-NGKTJRFN.mjs → chunk-6DO4EGT2.mjs} +2 -2
  11. package/dist/{chunk-C7ZTZTEW.mjs → chunk-6XJWL2E5.mjs} +1 -1
  12. package/dist/{chunk-FQUT5XD6.mjs → chunk-A4UP4QFB.mjs} +1 -1
  13. package/dist/{chunk-USIRKDYQ.mjs → chunk-BFCX7ADE.mjs} +1 -1
  14. package/dist/{chunk-XGRSPFFC.mjs → chunk-CQHKU24Z.mjs} +1 -1
  15. package/dist/{chunk-HONTZFLO.mjs → chunk-DP4ER6TJ.mjs} +1 -1
  16. package/dist/{chunk-VLVEZHFE.mjs → chunk-EFSLAMHI.mjs} +4 -4
  17. package/dist/{chunk-FYZBGWYR.mjs → chunk-FVSOFXJQ.mjs} +1 -1
  18. package/dist/{chunk-JUMEIPII.mjs → chunk-G2MOZZPE.mjs} +8 -8
  19. package/dist/{chunk-D3HKFRQO.mjs → chunk-GQWKBESP.mjs} +8 -5
  20. package/dist/{chunk-MD66TGX7.mjs → chunk-GXDKWCMV.mjs} +1 -1
  21. package/dist/{chunk-77L3UPBW.mjs → chunk-H7NOUDU3.mjs} +5 -5
  22. package/dist/{chunk-4LLTZ45R.mjs → chunk-HOXTEU5K.mjs} +8 -7
  23. package/dist/{chunk-ZA37ZWZW.mjs → chunk-IXW77PMI.mjs} +7 -7
  24. package/dist/{chunk-XHZONBL4.mjs → chunk-JLEQU5BO.mjs} +1 -1
  25. package/dist/{chunk-6UKOJLXO.mjs → chunk-JSFWRD7K.mjs} +4 -4
  26. package/dist/{chunk-7PTRHNUV.mjs → chunk-JY3FUGNL.mjs} +1 -1
  27. package/dist/{chunk-3ZU5BH6X.mjs → chunk-KEOAPKJO.mjs} +3 -3
  28. package/dist/{chunk-4QTHK7ML.mjs → chunk-KWYFJQV6.mjs} +1 -1
  29. package/dist/{chunk-FGMDBJCF.mjs → chunk-LDPCSE7J.mjs} +4 -4
  30. package/dist/chunk-LFWNKXZU.mjs +109 -0
  31. package/dist/{chunk-IRZWYTGV.mjs → chunk-M32YSAWL.mjs} +8 -7
  32. package/dist/{chunk-LLAGF6BA.mjs → chunk-MUB2G36A.mjs} +1 -1
  33. package/dist/{chunk-DQNNP6I4.mjs → chunk-NIETQFJQ.mjs} +1 -1
  34. package/dist/{chunk-RUX3OLVZ.mjs → chunk-OTFG57ZF.mjs} +1 -1
  35. package/dist/{chunk-OKIWXOJL.mjs → chunk-OWTW5WAJ.mjs} +1 -1
  36. package/dist/{chunk-WWIWRNBK.mjs → chunk-P7NSCTAW.mjs} +1 -1
  37. package/dist/{chunk-BZWQU52U.mjs → chunk-QZREZL2F.mjs} +1 -1
  38. package/dist/{chunk-E432NK23.mjs → chunk-RBQ4BZUV.mjs} +6 -6
  39. package/dist/{chunk-I2EKKSEF.mjs → chunk-RKBLVNDC.mjs} +4 -7
  40. package/dist/{chunk-LHQACMZY.mjs → chunk-SPPQFW32.mjs} +106 -50
  41. package/dist/{chunk-OSSS56CB.mjs → chunk-SUXJWKRI.mjs} +4 -4
  42. package/dist/{chunk-SCGCGVDN.mjs → chunk-SZXIPE5J.mjs} +1 -1
  43. package/dist/{chunk-GIQGZFP6.mjs → chunk-THOHFAW2.mjs} +28 -12
  44. package/dist/{chunk-VVURVETY.mjs → chunk-TOQRA2TD.mjs} +1 -1
  45. package/dist/{chunk-GYWOD2YI.mjs → chunk-TZSDYQFH.mjs} +4 -4
  46. package/dist/{chunk-S7SBLNX4.mjs → chunk-UB3WG6I4.mjs} +1 -1
  47. package/dist/{chunk-PGJRZHN7.mjs → chunk-UVZ3JWFG.mjs} +1 -1
  48. package/dist/{chunk-UD5UF5OC.mjs → chunk-W7OPFKTZ.mjs} +4 -4
  49. package/dist/{chunk-YEWNFK5S.mjs → chunk-WLXP4OOF.mjs} +5 -5
  50. package/dist/{chunk-ORMC3TV3.mjs → chunk-XYXYTTNW.mjs} +1 -1
  51. package/dist/{chunk-CZOGJC76.mjs → chunk-YACFZWRR.mjs} +7 -7
  52. package/dist/{chunk-UTCW5YUX.mjs → chunk-YPATB6YQ.mjs} +9 -9
  53. package/dist/{chunk-BZGFW6L7.mjs → chunk-YWJAIPUA.mjs} +1 -1
  54. package/dist/{chunk-MHBQJVHE.mjs → chunk-Z65BGSHI.mjs} +5 -5
  55. package/dist/{chunk-PCULNQWA.mjs → chunk-ZGSFRUVI.mjs} +3 -3
  56. package/dist/{chunk-7NQKFPXE.mjs → chunk-ZRYG6ICN.mjs} +1 -1
  57. package/dist/{chunk-ZFKAYRFQ.mjs → chunk-ZUHFYW65.mjs} +1 -1
  58. package/dist/components/ui/about-you-form.mjs +2 -2
  59. package/dist/components/ui/account-list-carousel.mjs +2 -2
  60. package/dist/components/ui/add-column-modal.mjs +4 -4
  61. package/dist/components/ui/add-lead-modal.mjs +4 -4
  62. package/dist/components/ui/advisor-card.mjs +2 -2
  63. package/dist/components/ui/ai-assistant-drawer.mjs +2 -2
  64. package/dist/components/ui/ai-builder/index.mjs +4 -4
  65. package/dist/components/ui/ai-conversations/index.mjs +4 -4
  66. package/dist/components/ui/alert-dialog.mjs +3 -3
  67. package/dist/components/ui/applicant-expenses-section.mjs +1 -1
  68. package/dist/components/ui/appointment-action-dialogs.mjs +5 -5
  69. package/dist/components/ui/appointment-availability-settings.mjs +4 -4
  70. package/dist/components/ui/appointment-book-dialog.mjs +4 -4
  71. package/dist/components/ui/appointment-detail-sheet.mjs +6 -6
  72. package/dist/components/ui/appointment-upcoming-card.mjs +4 -4
  73. package/dist/components/ui/asset-accordion.mjs +7 -7
  74. package/dist/components/ui/assets-liabilities-side-card.js +19 -66
  75. package/dist/components/ui/assets-liabilities-side-card.mjs +22 -69
  76. package/dist/components/ui/backoffice-alert-history-chart.js +1 -1
  77. package/dist/components/ui/backoffice-alert-history-chart.mjs +5 -5
  78. package/dist/components/ui/backoffice-alert-matching-chart.js +1 -1
  79. package/dist/components/ui/backoffice-alert-matching-chart.mjs +5 -5
  80. package/dist/components/ui/backoffice-alerts-chart.js +1 -1
  81. package/dist/components/ui/backoffice-alerts-chart.mjs +5 -5
  82. package/dist/components/ui/backoffice-connections-chart.js +1 -1
  83. package/dist/components/ui/backoffice-connections-chart.mjs +5 -5
  84. package/dist/components/ui/backoffice-contact-history-chart.js +1 -1
  85. package/dist/components/ui/backoffice-contact-history-chart.mjs +5 -5
  86. package/dist/components/ui/backoffice-contact-matching-chart.js +1 -1
  87. package/dist/components/ui/backoffice-contact-matching-chart.mjs +5 -5
  88. package/dist/components/ui/backoffice-signup-steps.mjs +4 -4
  89. package/dist/components/ui/bank-statement-generate-dialog.mjs +4 -4
  90. package/dist/components/ui/bank-statement-pdf-viewer.mjs +4 -4
  91. package/dist/components/ui/borrowing-capacity-atoms.js +3 -6
  92. package/dist/components/ui/borrowing-capacity-atoms.mjs +2 -2
  93. package/dist/components/ui/borrowing-capacity-card.js +5 -5
  94. package/dist/components/ui/borrowing-capacity-card.mjs +6 -6
  95. package/dist/components/ui/borrowing-capacity-line-chart.js +5 -5
  96. package/dist/components/ui/borrowing-capacity-line-chart.mjs +5 -5
  97. package/dist/components/ui/calculator-section.mjs +4 -4
  98. package/dist/components/ui/cash-balance-line-chart.js +102 -46
  99. package/dist/components/ui/cash-balance-line-chart.mjs +5 -5
  100. package/dist/components/ui/cashflow-bar-chart.js +7 -4
  101. package/dist/components/ui/cashflow-bar-chart.mjs +5 -5
  102. package/dist/components/ui/category-edit-dialog.mjs +4 -4
  103. package/dist/components/ui/color-picker.mjs +2 -2
  104. package/dist/components/ui/contact-alert-dialog/index.mjs +4 -4
  105. package/dist/components/ui/create-contact-modal.mjs +4 -4
  106. package/dist/components/ui/csv-import-modal.mjs +4 -4
  107. package/dist/components/ui/dashboard-expense-categories.js +96 -63
  108. package/dist/components/ui/dashboard-expense-categories.mjs +101 -66
  109. package/dist/components/ui/dashboard-transactions-table.js +37 -44
  110. package/dist/components/ui/dashboard-transactions-table.mjs +45 -52
  111. package/dist/components/ui/data-table.mjs +2 -2
  112. package/dist/components/ui/date-picker.mjs +2 -2
  113. package/dist/components/ui/debt-accordion.mjs +7 -7
  114. package/dist/components/ui/delete-contact-component.mjs +4 -4
  115. package/dist/components/ui/dialog.mjs +3 -3
  116. package/dist/components/ui/document-checklist-template.mjs +2 -2
  117. package/dist/components/ui/expense-bar-chart.js +8 -7
  118. package/dist/components/ui/expense-bar-chart.mjs +5 -5
  119. package/dist/components/ui/expense-categories-bar.js +261 -0
  120. package/dist/components/ui/expense-categories-bar.mjs +12 -0
  121. package/dist/components/ui/expense-work-details.js +8 -7
  122. package/dist/components/ui/expense-work-details.mjs +7 -7
  123. package/dist/components/ui/file-preview-dialog.mjs +4 -4
  124. package/dist/components/ui/financial-cards.mjs +2 -2
  125. package/dist/components/ui/financial-drawers.mjs +2 -2
  126. package/dist/components/ui/financial-sections.mjs +3 -3
  127. package/dist/components/ui/frontend-signup-steps.mjs +2 -2
  128. package/dist/components/ui/income-bar-chart.js +8 -7
  129. package/dist/components/ui/income-bar-chart.mjs +5 -5
  130. package/dist/components/ui/income-sources-card.mjs +1 -1
  131. package/dist/components/ui/income-summary-component.mjs +1 -1
  132. package/dist/components/ui/income-work-details.js +8 -7
  133. package/dist/components/ui/income-work-details.mjs +6 -6
  134. package/dist/components/ui/incoming-outgoings-card.js +2 -2
  135. package/dist/components/ui/incoming-outgoings-card.mjs +3 -3
  136. package/dist/components/ui/interest-rate-section.mjs +1 -1
  137. package/dist/components/ui/kanban-column.mjs +5 -5
  138. package/dist/components/ui/loan-application-cards.mjs +3 -3
  139. package/dist/components/ui/loan-financials.mjs +3 -3
  140. package/dist/components/ui/money-input-with-slider.mjs +2 -2
  141. package/dist/components/ui/opportunity-card.mjs +4 -4
  142. package/dist/components/ui/opportunity-edit-modals.mjs +4 -4
  143. package/dist/components/ui/opportunity-summary-tab.mjs +8 -8
  144. package/dist/components/ui/pagination.mjs +2 -2
  145. package/dist/components/ui/pipeline-board.mjs +6 -6
  146. package/dist/components/ui/pipeline-chart.mjs +2 -2
  147. package/dist/components/ui/pipeline-dialogs.mjs +4 -4
  148. package/dist/components/ui/policy-ai/index.mjs +2 -2
  149. package/dist/components/ui/property-asset-card.mjs +4 -4
  150. package/dist/components/ui/property-cashflow-doughnut-chart.js +3 -3
  151. package/dist/components/ui/property-cashflow-doughnut-chart.mjs +5 -5
  152. package/dist/components/ui/property-debt-equity-doughnut-chart.js +3 -3
  153. package/dist/components/ui/property-debt-equity-doughnut-chart.mjs +5 -5
  154. package/dist/components/ui/property-list-carousel.mjs +2 -2
  155. package/dist/components/ui/property-mobile-estimate-line-chart.js +4 -4
  156. package/dist/components/ui/property-mobile-estimate-line-chart.mjs +5 -5
  157. package/dist/components/ui/property-report-dialog.mjs +5 -5
  158. package/dist/components/ui/resource-center/index.mjs +4 -4
  159. package/dist/components/ui/review-alerts-dialog.mjs +4 -4
  160. package/dist/components/ui/savings-goal-modal.mjs +7 -7
  161. package/dist/components/ui/scenario-drawer.mjs +4 -4
  162. package/dist/components/ui/scenario-list.js +4 -7
  163. package/dist/components/ui/scenario-list.mjs +5 -5
  164. package/dist/components/ui/share-details-dialog.mjs +4 -4
  165. package/dist/components/ui/sidebar-nav.mjs +4 -4
  166. package/dist/components/ui/signup-form-primitives.mjs +2 -2
  167. package/dist/components/ui/stage-timeline.mjs +1 -1
  168. package/dist/components/ui/support-agent/index.js +27 -11
  169. package/dist/components/ui/support-agent/index.mjs +3 -3
  170. package/dist/components/ui/top-three-product.mjs +1 -1
  171. package/dist/components/ui/transactions-expense-categories-doughnut-chart.js +3 -3
  172. package/dist/components/ui/transactions-expense-categories-doughnut-chart.mjs +5 -5
  173. package/dist/components/ui/transactions-income-expense-bar-chart.mjs +5 -5
  174. package/dist/components/ui/transactions-liabilities-breakdown-doughnut-chart.js +4 -4
  175. package/dist/components/ui/transactions-liabilities-breakdown-doughnut-chart.mjs +5 -5
  176. package/dist/components/ui/transactions-summary-block.js +13 -0
  177. package/dist/components/ui/transactions-summary-block.mjs +13 -0
  178. package/dist/index.js +2119 -1946
  179. package/dist/index.mjs +139 -135
  180. package/dist/lib/format-currency.js +54 -0
  181. package/dist/lib/format-currency.mjs +9 -0
  182. package/dist/styles.css +1 -1
  183. package/package.json +6 -1
  184. package/src/component-descriptions/assets-liabilities-side-card.md +19 -0
  185. package/src/component-descriptions/pipeline-chart.md +17 -0
  186. package/src/components/index.tsx +6 -0
  187. package/src/components/ui/assets-liabilities-side-card.tsx +43 -83
  188. package/src/components/ui/borrowing-capacity-atoms.tsx +4 -7
  189. package/src/components/ui/borrowing-capacity-line-chart.tsx +4 -4
  190. package/src/components/ui/cash-balance-line-chart.tsx +123 -42
  191. package/src/components/ui/cashflow-bar-chart.tsx +7 -4
  192. package/src/components/ui/chart-shared.tsx +4 -4
  193. package/src/components/ui/dashboard-expense-categories.tsx +136 -60
  194. package/src/components/ui/dashboard-transactions-table.tsx +42 -28
  195. package/src/components/ui/expense-bar-chart.tsx +32 -19
  196. package/src/components/ui/expense-categories-bar.tsx +178 -0
  197. package/src/components/ui/income-bar-chart.tsx +32 -19
  198. package/src/components/ui/incoming-outgoings-card.tsx +2 -2
  199. package/src/components/ui/property-mobile-estimate-line-chart.tsx +4 -4
  200. package/src/components/ui/scenario-list.tsx +2 -2
  201. package/src/components/ui/support-agent/support-agent-panel.tsx +40 -11
  202. package/src/components/ui/transactions-liabilities-breakdown-doughnut-chart.tsx +7 -5
  203. package/src/components/ui/transactions-summary-block.tsx +39 -6
  204. package/src/styles/styles-css.ts +1 -1
  205. package/tsup.config.ts +2 -0
  206. package/dist/{chunk-CEYEK3TI.mjs → chunk-B4R62ID3.mjs} +3 -3
  207. package/dist/{chunk-7LN5OGC2.mjs → chunk-E3VAK4EB.mjs} +3 -3
  208. package/dist/{chunk-EY36WDCF.mjs → chunk-EEZFXE3P.mjs} +3 -3
  209. package/dist/{chunk-T5FRVEJQ.mjs → chunk-JTMN36BK.mjs} +3 -3
  210. /package/dist/{chunk-MN5NYQCL.mjs → chunk-XQDTFNVL.mjs} +0 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wealthx/shadcn",
3
- "version": "1.5.41",
3
+ "version": "1.5.43",
4
4
  "main": "./dist/index.js",
5
5
  "module": "./dist/index.mjs",
6
6
  "types": "./src/index.ts",
@@ -364,6 +364,11 @@
364
364
  "import": "./dist/components/ui/expense-bar-chart.mjs",
365
365
  "require": "./dist/components/ui/expense-bar-chart.js"
366
366
  },
367
+ "./expense-categories-bar": {
368
+ "types": "./src/components/ui/expense-categories-bar.tsx",
369
+ "import": "./dist/components/ui/expense-categories-bar.mjs",
370
+ "require": "./dist/components/ui/expense-categories-bar.js"
371
+ },
367
372
  "./income-bar-chart": {
368
373
  "types": "./src/components/ui/income-bar-chart.tsx",
369
374
  "import": "./dist/components/ui/income-bar-chart.mjs",
@@ -0,0 +1,19 @@
1
+ # AssetsLiabilitiesSideCard
2
+
3
+ ## 2026-06-05 — Refactor to 2×2 grid layout matching frontend
4
+
5
+ **Prompted by:** frontend parity review
6
+
7
+ ### What changed
8
+ - Replaced vertical list layout (Net Position hero → Total Assets with sub-rows → Total Liabilities) with a 2×2 grid of metric cells
9
+ - Four cells: Total Liabilities, Total Cash Assets, Net Position, Total Property Value — matching the order in the live frontend
10
+ - Each cell now uses `bg-muted p-4` as background (replacing inline card border)
11
+ - Negative values (Total Liabilities) colored with `text-brand-secondary`; positive values with `text-primary`
12
+ - Removed `MetricRow` and `SubRow` helper components (no longer needed)
13
+ - Removed derived `totalAssets` field (Total Assets row was removed from design)
14
+
15
+ ### Why
16
+ - Frontend app uses a compact 2×2 grid; Storybook was showing a different list-based layout, causing design drift
17
+
18
+ ### Affected tokens / files
19
+ - `packages/shadcn/src/components/ui/assets-liabilities-side-card.tsx`
@@ -0,0 +1,17 @@
1
+ # PipelineChart
2
+
3
+ ## 2026-05-19 — Single-color segments with gap separators
4
+
5
+ **Prompted by:** Thinh Tu Duc
6
+
7
+ ### What changed
8
+ - All segments now share a single primary color instead of 5 cycling opacity shades
9
+ - 1px background-color gaps between segments provide visual separation
10
+ - Legend dots follow the same color logic (primary fallback, or explicit `stage.color`)
11
+
12
+ ### Why
13
+ - The previous 5-opacity fallback palette repeated colors when there were more than 5 stages, creating misleading visual groupings
14
+ - A single primary color is tenant-adaptive and works for any number of stages
15
+
16
+ ### Affected tokens / files
17
+ - `packages/shadcn/src/components/ui/pipeline-chart.tsx` — replaced `FALLBACK_COLORS` with `SEGMENT_COLOR = "var(--primary)"`, added `gap-px bg-background` to bar container
@@ -712,6 +712,12 @@ export type {
712
712
  ExpenseGranularity,
713
713
  } from "./ui/expense-bar-chart";
714
714
 
715
+ export { ExpenseCategoriesBar } from "./ui/expense-categories-bar";
716
+ export type {
717
+ ExpenseCategoriesBarProps,
718
+ ExpenseCategoryPart,
719
+ } from "./ui/expense-categories-bar";
720
+
715
721
  export {
716
722
  Field,
717
723
  FieldLabel,
@@ -27,36 +27,13 @@ export interface AssetsLiabilitiesSideCardProps {
27
27
  className?: string;
28
28
  }
29
29
 
30
- // ─── Metric row ──────────────────────────────────────────────────────────────
30
+ // ─── Internal types ───────────────────────────────────────────────────────────
31
31
 
32
- function MetricRow({
33
- label,
34
- value,
35
- valueClass,
36
- }: {
32
+ interface MetricCell {
37
33
  label: string;
38
- value: string;
39
- valueClass?: string;
40
- }) {
41
- return (
42
- <div className="flex items-center justify-between gap-2">
43
- <span className="text-sm font-medium text-foreground">{label}</span>
44
- <span className={cn("text-base font-semibold shrink-0", valueClass)}>
45
- {value}
46
- </span>
47
- </div>
48
- );
49
- }
50
-
51
- // ─── Sub-row ─────────────────────────────────────────────────────────────────
52
-
53
- function SubRow({ label, value }: { label: string; value: string }) {
54
- return (
55
- <div className="flex items-center justify-between gap-2 pl-3 text-sm">
56
- <span className="text-muted-foreground">{label}</span>
57
- <span className="font-medium text-foreground">{value}</span>
58
- </div>
59
- );
34
+ value: number;
35
+ /** When true, value is prefixed with "-" and coloured with brand-secondary */
36
+ negative?: boolean;
60
37
  }
61
38
 
62
39
  // ─── Component ────────────────────────────────────────────────────────────────
@@ -70,12 +47,16 @@ export function AssetsLiabilitiesSideCard({
70
47
  tooltipText = "Current snapshot of your financial position. These values are not affected by the date range filter.",
71
48
  className,
72
49
  }: AssetsLiabilitiesSideCardProps) {
73
- const totalAssets = assets + propertyValue;
74
- const netPositive = netPosition >= 0;
50
+ const cells: MetricCell[] = [
51
+ { label: "Total Liabilities", value: liabilities, negative: true },
52
+ { label: "Total Cash Assets", value: assets },
53
+ { label: "Net Position", value: netPosition },
54
+ { label: "Total Property Value", value: propertyValue },
55
+ ];
75
56
 
76
57
  return (
77
58
  <Card className={cn("flex flex-col", className)}>
78
- <CardHeader className="pb-2">
59
+ <CardHeader className="pb-3">
79
60
  <div className="flex items-center gap-1">
80
61
  <CardTitle className="text-xs font-semibold uppercase tracking-wider text-muted-foreground">
81
62
  {title}
@@ -97,60 +78,39 @@ export function AssetsLiabilitiesSideCard({
97
78
  </div>
98
79
  </CardHeader>
99
80
 
100
- <CardContent className="flex flex-1 flex-col gap-4 pb-5">
101
- {/* Hero: Net Position */}
102
- <div className="flex items-center justify-between gap-2">
103
- <span className="text-sm font-medium text-foreground">
104
- Net Position
105
- </span>
106
- <TooltipProvider>
107
- <Tooltip>
108
- <TooltipTrigger asChild>
109
- <span
110
- className={cn(
111
- "cursor-default text-xl font-bold leading-tight shrink-0",
112
- netPositive ? "text-foreground" : "text-destructive",
113
- )}
114
- >
115
- {netPositive ? "" : "-"}
116
- {formatCurrencyAbbrev(Math.abs(netPosition))}
117
- </span>
118
- </TooltipTrigger>
119
- <TooltipContent>
120
- {netPositive ? "" : "-"}$
121
- {Math.abs(netPosition).toLocaleString()}
122
- </TooltipContent>
123
- </Tooltip>
124
- </TooltipProvider>
125
- </div>
126
-
127
- {/* Divider */}
128
- <div className="border-t border-border" />
81
+ <CardContent className="pb-5">
82
+ {/* 2×2 grid matches frontend layout */}
83
+ <div className="grid grid-cols-2 gap-1.5">
84
+ {cells.map((cell) => {
85
+ const displayValue = `${cell.negative ? "-" : ""}${formatCurrencyAbbrev(Math.abs(cell.value))}`;
86
+ const fullValue = `${cell.negative ? "-" : ""}$${Math.abs(cell.value).toLocaleString()}`;
129
87
 
130
- {/* Total Assets */}
131
- <div className="flex flex-col gap-2">
132
- <MetricRow
133
- label="Total Assets"
134
- value={formatCurrencyAbbrev(totalAssets)}
135
- valueClass="text-xl"
136
- />
137
- <div className="flex flex-col gap-1.5">
138
- <SubRow label="Cash Assets" value={formatCurrencyAbbrev(assets)} />
139
- <SubRow
140
- label="Property Value"
141
- value={formatCurrencyAbbrev(propertyValue)}
142
- />
143
- </div>
88
+ return (
89
+ <div key={cell.label} className="bg-muted p-4">
90
+ <p className="text-xs text-muted-foreground font-medium mb-1.5">
91
+ {cell.label}
92
+ </p>
93
+ <TooltipProvider>
94
+ <Tooltip>
95
+ <TooltipTrigger asChild>
96
+ <span
97
+ className={cn(
98
+ "text-lg font-bold cursor-default",
99
+ cell.negative
100
+ ? "text-brand-secondary"
101
+ : "text-primary",
102
+ )}
103
+ >
104
+ {displayValue}
105
+ </span>
106
+ </TooltipTrigger>
107
+ <TooltipContent>{fullValue}</TooltipContent>
108
+ </Tooltip>
109
+ </TooltipProvider>
110
+ </div>
111
+ );
112
+ })}
144
113
  </div>
145
-
146
- <div className="border-t border-border" />
147
-
148
- {/* Total Liabilities */}
149
- <MetricRow
150
- label="Total Liabilities"
151
- value={`-${formatCurrencyAbbrev(liabilities)}`}
152
- valueClass="text-xl text-destructive"
153
- />
154
114
  </CardContent>
155
115
  </Card>
156
116
  );
@@ -196,7 +196,7 @@ export function LoanToValueRatio({
196
196
  */
197
197
 
198
198
  export interface AddScenarioButtonProps {
199
- /** Button label (default: "Compare Loan Options") */
199
+ /** Button label (default: "Add New Scenario") */
200
200
  title?: string;
201
201
  /** Disables the button */
202
202
  disabled?: boolean;
@@ -206,20 +206,17 @@ export interface AddScenarioButtonProps {
206
206
  }
207
207
 
208
208
  export function AddScenarioButton({
209
- title = "Compare Loan Options",
209
+ title = "Add New Scenario",
210
210
  disabled = false,
211
211
  onClick,
212
212
  className,
213
213
  }: AddScenarioButtonProps) {
214
214
  return (
215
215
  <Button
216
- variant="outline-secondary"
216
+ variant="outline"
217
217
  disabled={disabled}
218
218
  onClick={onClick}
219
- className={cn(
220
- "w-full justify-between border-dashed text-base",
221
- className,
222
- )}
219
+ className={cn("w-full justify-between", className)}
223
220
  >
224
221
  <span>{title}</span>
225
222
  <Plus size={18} className="shrink-0" />
@@ -259,7 +259,7 @@ export function BorrowingCapacityLineChart({
259
259
  maxRotation: 0,
260
260
  minRotation: 0,
261
261
  color: FALLBACK_TICK,
262
- font: { size: 10, family: fontFamily },
262
+ font: { size: 12, family: fontFamily },
263
263
  callback: function (_, index) {
264
264
  const iso = labels[index];
265
265
  return iso ? formatDateLabel(iso) : "";
@@ -275,7 +275,7 @@ export function BorrowingCapacityLineChart({
275
275
  maxTicksLimit: 6,
276
276
  padding: 8,
277
277
  color: FALLBACK_TICK,
278
- font: { size: 10, family: fontFamily },
278
+ font: { size: 12, family: fontFamily },
279
279
  callback: (v) => formatAbbrev(Number(v)),
280
280
  },
281
281
  },
@@ -314,11 +314,11 @@ export function BorrowingCapacityLineChart({
314
314
  >
315
315
  <CardHeader className="px-3 sm:px-6">
316
316
  <div className="flex items-start justify-between gap-4">
317
- <CardTitle className="text-xs font-semibold uppercase tracking-wide">
317
+ <CardTitle className="text-xs font-semibold uppercase tracking-wider text-muted-foreground">
318
318
  {title}
319
319
  </CardTitle>
320
320
  {kpiValue != null && (
321
- <span className="shrink-0 text-xl font-bold tabular-nums">
321
+ <span className="shrink-0 text-lg font-bold tabular-nums">
322
322
  {formatCurrency(kpiValue / 100)}
323
323
  </span>
324
324
  )}
@@ -60,6 +60,17 @@ export interface CashBalanceDataPoint {
60
60
  y: number;
61
61
  }
62
62
 
63
+ /**
64
+ * Optional secondary line datasets rendered on a right-hand Y-axis.
65
+ * Useful for overlaying income / expense lines against the balance.
66
+ */
67
+ export interface ExtraLineDataset {
68
+ label: string;
69
+ data: CashBalanceDataPoint[];
70
+ color: string;
71
+ dashed?: boolean;
72
+ }
73
+
63
74
  export interface CashBalanceLineChartProps {
64
75
  chartData?: CashBalanceDataPoint[] | null;
65
76
  title?: string;
@@ -80,6 +91,11 @@ export interface CashBalanceLineChartProps {
80
91
  showLegend?: boolean;
81
92
  /** Legend placement relative to the chart */
82
93
  legendPosition?: "top" | "bottom";
94
+ /**
95
+ * Optional secondary line datasets (e.g. income / expense) rendered on a
96
+ * right-hand Y-axis so their scale stays independent from the balance line.
97
+ */
98
+ extraDatasets?: ExtraLineDataset[];
83
99
  }
84
100
 
85
101
  // ---------------------------------------------------------------------------
@@ -98,6 +114,7 @@ export function CashBalanceLineChart({
98
114
  showBalanceValue = false,
99
115
  showLegend = false,
100
116
  legendPosition = "bottom",
117
+ extraDatasets,
101
118
  }: CashBalanceLineChartProps) {
102
119
  const themeVars = useThemeVars();
103
120
  const brandPrimary =
@@ -118,29 +135,54 @@ export function CashBalanceLineChart({
118
135
  return indices;
119
136
  }, [hasData, chartData]);
120
137
 
138
+ const hasExtra = Array.isArray(extraDatasets) && extraDatasets.length > 0;
139
+
121
140
  const data = useMemo<ChartData<"line">>(() => {
122
141
  if (!hasData) return { labels: [], datasets: [] };
123
- return {
124
- labels: chartData!.map((p) => p.x), // raw ISO — used for tooltip + tick lookup
125
- datasets: [
126
- {
127
- label: title,
128
- data: chartData!.map((p) => p.y),
142
+
143
+ const mainDs = {
144
+ label: title,
145
+ data: chartData!.map((p) => p.y),
146
+ fill: false,
147
+ borderColor: brandPrimary,
148
+ backgroundColor: "transparent",
149
+ borderWidth: 2.5,
150
+ borderDash: [],
151
+ tension: 0.4,
152
+ pointRadius: 0,
153
+ pointHoverRadius: 6,
154
+ pointHoverBackgroundColor: FALLBACK_BG,
155
+ pointHoverBorderColor: brandPrimary,
156
+ pointHoverBorderWidth: 3,
157
+ pointHitRadius: 10,
158
+ yAxisID: "y",
159
+ };
160
+
161
+ const extraDs = hasExtra
162
+ ? extraDatasets!.map((ds) => ({
163
+ label: ds.label,
164
+ data: ds.data.map((p) => p.y),
129
165
  fill: false,
130
- borderColor: brandPrimary,
166
+ borderColor: ds.color,
131
167
  backgroundColor: "transparent",
132
- borderWidth: 2.5,
168
+ borderWidth: 1.5,
169
+ borderDash: ds.dashed ? ([5, 4] as number[]) : ([] as number[]),
133
170
  tension: 0.4,
134
171
  pointRadius: 0,
135
- pointHoverRadius: 6,
172
+ pointHoverRadius: 4,
136
173
  pointHoverBackgroundColor: FALLBACK_BG,
137
- pointHoverBorderColor: brandPrimary,
138
- pointHoverBorderWidth: 3,
174
+ pointHoverBorderColor: ds.color,
175
+ pointHoverBorderWidth: 2,
139
176
  pointHitRadius: 10,
140
- },
141
- ],
177
+ yAxisID: "y2",
178
+ }))
179
+ : [];
180
+
181
+ return {
182
+ labels: chartData!.map((p) => p.x),
183
+ datasets: [mainDs, ...extraDs],
142
184
  };
143
- }, [hasData, chartData, brandPrimary, title]);
185
+ }, [hasData, chartData, brandPrimary, title, hasExtra, extraDatasets]);
144
186
 
145
187
  const options = useMemo<ChartOptions<"line">>(
146
188
  () => ({
@@ -153,18 +195,19 @@ export function CashBalanceLineChart({
153
195
  enabled: true,
154
196
  mode: "index",
155
197
  intersect: false,
156
- displayColors: false,
198
+ displayColors: hasExtra,
157
199
  padding: 12,
158
200
  cornerRadius: 0,
159
201
  titleFont: { size: 11, weight: "600", family: fontFamily },
160
- bodyFont: { size: 13, weight: "700", family: fontFamily },
202
+ bodyFont: { size: 12, weight: "500", family: fontFamily },
161
203
  callbacks: {
162
204
  title: (items) => {
163
205
  const iso = items[0]?.label;
164
206
  return iso ? formatDateTooltip(iso) : "";
165
207
  },
166
208
  label: (ctx) => {
167
- return formatCurrency(ctx.parsed.y, { decimals: 2 });
209
+ const value = formatCurrency(ctx.parsed.y, { decimals: 2 });
210
+ return hasExtra ? ` ${ctx.dataset.label}: ${value}` : value;
168
211
  },
169
212
  },
170
213
  },
@@ -181,7 +224,7 @@ export function CashBalanceLineChart({
181
224
  maxRotation: 0,
182
225
  minRotation: 0,
183
226
  color: FALLBACK_TICK,
184
- font: { size: 10, family: fontFamily },
227
+ font: { size: 12, family: fontFamily },
185
228
  callback: function (_, index) {
186
229
  if (!tickIndices.has(index) || !chartData) return "";
187
230
  return formatDateLabel(chartData[index].x);
@@ -198,13 +241,27 @@ export function CashBalanceLineChart({
198
241
  maxTicksLimit: 5,
199
242
  padding: 8,
200
243
  color: FALLBACK_TICK,
201
- font: { size: 10, family: fontFamily },
244
+ font: { size: 12, family: fontFamily },
245
+ callback: (v) => formatAbbrev(Number(v)),
246
+ },
247
+ },
248
+ y2: {
249
+ display: hasExtra && showYAxis,
250
+ position: "right" as const,
251
+ beginAtZero: true,
252
+ grid: { display: false },
253
+ border: { display: false },
254
+ ticks: {
255
+ maxTicksLimit: 5,
256
+ padding: 8,
257
+ color: FALLBACK_TICK,
258
+ font: { size: 12, family: fontFamily },
202
259
  callback: (v) => formatAbbrev(Number(v)),
203
260
  },
204
261
  },
205
262
  },
206
263
  }),
207
- [tickIndices, chartData, showXAxis, showYAxis, fontFamily],
264
+ [tickIndices, chartData, showXAxis, showYAxis, fontFamily, hasExtra],
208
265
  );
209
266
 
210
267
  const latestValue = hasData ? chartData![chartData!.length - 1].y : null;
@@ -214,18 +271,22 @@ export function CashBalanceLineChart({
214
271
  className={cn("w-full py-4 sm:py-6 gap-2", className)}
215
272
  style={{ maxWidth: width, fontFamily }}
216
273
  >
217
- <CardHeader className="px-3 sm:px-6">
218
- <div className="flex flex-col gap-0.5">
219
- <CardTitle className="text-xs font-semibold uppercase tracking-wide">
220
- {title}
221
- </CardTitle>
222
- {showBalanceValue && latestValue !== null && (
223
- <p className="text-2xl font-bold tabular-nums leading-tight">
224
- {formatCurrency(latestValue, { decimals: 2 })}
225
- </p>
226
- )}
227
- </div>
228
- </CardHeader>
274
+ {(title || (showBalanceValue && latestValue !== null)) && (
275
+ <CardHeader className="px-3 sm:px-6">
276
+ <div className="flex flex-col gap-0.5">
277
+ {title && (
278
+ <CardTitle className="text-xs font-semibold uppercase tracking-wider text-muted-foreground">
279
+ {title}
280
+ </CardTitle>
281
+ )}
282
+ {showBalanceValue && latestValue !== null && (
283
+ <p className="text-lg font-bold tabular-nums leading-tight">
284
+ {formatCurrency(latestValue, { decimals: 2 })}
285
+ </p>
286
+ )}
287
+ </div>
288
+ </CardHeader>
289
+ )}
229
290
 
230
291
  <CardContent className="px-3 sm:px-6">
231
292
  {isLoading ? (
@@ -243,11 +304,21 @@ export function CashBalanceLineChart({
243
304
  <div className="flex flex-col gap-2">
244
305
  {showLegend && legendPosition === "top" && (
245
306
  <div className="flex flex-wrap items-center gap-x-4 gap-y-1.5">
246
- <ChartLegendItem
247
- label={title}
248
- color={brandPrimary}
249
- lineStyle="solid"
250
- />
307
+ {title && (
308
+ <ChartLegendItem
309
+ label={title}
310
+ color={brandPrimary}
311
+ lineStyle="solid"
312
+ />
313
+ )}
314
+ {extraDatasets?.map((ds) => (
315
+ <ChartLegendItem
316
+ key={ds.label}
317
+ label={ds.label}
318
+ color={ds.color}
319
+ lineStyle={ds.dashed ? "dashed" : "solid"}
320
+ />
321
+ ))}
251
322
  </div>
252
323
  )}
253
324
  <div style={{ height, width: "100%", position: "relative" }}>
@@ -261,11 +332,21 @@ export function CashBalanceLineChart({
261
332
  </div>
262
333
  {showLegend && legendPosition === "bottom" && (
263
334
  <div className="flex flex-wrap items-center gap-x-4 gap-y-1.5">
264
- <ChartLegendItem
265
- label={title}
266
- color={brandPrimary}
267
- lineStyle="solid"
268
- />
335
+ {title && (
336
+ <ChartLegendItem
337
+ label={title}
338
+ color={brandPrimary}
339
+ lineStyle="solid"
340
+ />
341
+ )}
342
+ {extraDatasets?.map((ds) => (
343
+ <ChartLegendItem
344
+ key={ds.label}
345
+ label={ds.label}
346
+ color={ds.color}
347
+ lineStyle={ds.dashed ? "dashed" : "solid"}
348
+ />
349
+ ))}
269
350
  </div>
270
351
  )}
271
352
  </div>
@@ -295,14 +295,17 @@ export function CashflowBarChart({
295
295
  display: showXAxis,
296
296
  grid: { display: false },
297
297
  border: { display: false },
298
- ticks: { font: { size: 10 }, color: FALLBACK_TICK },
298
+ ticks: {
299
+ font: { size: 12, family: fontFamily },
300
+ color: FALLBACK_TICK,
301
+ },
299
302
  },
300
303
  y: {
301
304
  display: showYAxis,
302
305
  grid: { display: false },
303
306
  border: { display: false },
304
307
  ticks: {
305
- font: { size: 10 },
308
+ font: { size: 12, family: fontFamily },
306
309
  color: FALLBACK_TICK,
307
310
  maxTicksLimit: 5,
308
311
  padding: 8,
@@ -311,7 +314,7 @@ export function CashflowBarChart({
311
314
  },
312
315
  },
313
316
  }),
314
- [showXAxis, showYAxis, sliced],
317
+ [showXAxis, showYAxis, sliced, fontFamily],
315
318
  );
316
319
 
317
320
  return (
@@ -320,7 +323,7 @@ export function CashflowBarChart({
320
323
  style={{ maxWidth: width, fontFamily }}
321
324
  >
322
325
  <CardHeader className="px-3 sm:px-6">
323
- <CardTitle className="text-xs font-semibold uppercase tracking-wide">
326
+ <CardTitle className="text-xs font-semibold uppercase tracking-wider text-muted-foreground">
324
327
  {title}
325
328
  </CardTitle>
326
329
  {showPeriodSelector && (
@@ -190,7 +190,7 @@ export function ChartLegendItem({
190
190
  }}
191
191
  />
192
192
  )}
193
- <span className="text-[11px] text-muted-foreground leading-none">
193
+ <span className="text-sm text-muted-foreground leading-none">
194
194
  {label}
195
195
  </span>
196
196
  </div>
@@ -224,15 +224,15 @@ export function DoughnutLegendRow({
224
224
  flexShrink: 0,
225
225
  }}
226
226
  />
227
- <span className="text-[11px] text-muted-foreground leading-none truncate">
227
+ <span className="text-sm text-muted-foreground leading-none truncate">
228
228
  {label}
229
229
  </span>
230
230
  </div>
231
231
  <div className="flex items-center gap-2 shrink-0">
232
- <span className="text-[11px] font-medium leading-none">
232
+ <span className="text-sm font-medium leading-none">
233
233
  {formatAbbrev(value)}
234
234
  </span>
235
- <span className="text-[11px] text-muted-foreground leading-none w-10 text-right">
235
+ <span className="text-sm text-muted-foreground leading-none w-12 text-right">
236
236
  {percent}
237
237
  </span>
238
238
  </div>