@wealthx/shadcn 1.5.42 → 1.5.44

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 (209) hide show
  1. package/.turbo/turbo-build.log +207 -202
  2. package/CHANGELOG.md +12 -0
  3. package/dist/{chunk-5FHBC6DY.mjs → chunk-33WZ5NCW.mjs} +1 -1
  4. package/dist/{chunk-LBXIYS34.mjs → chunk-4PVCJ3JD.mjs} +1 -1
  5. package/dist/{chunk-EHQL64B7.mjs → chunk-4SUXTO2Z.mjs} +4 -4
  6. package/dist/{chunk-BAONSY54.mjs → chunk-5RYH7SOQ.mjs} +1 -1
  7. package/dist/{chunk-3C4DZTGA.mjs → chunk-5XD7A7YC.mjs} +1 -1
  8. package/dist/{chunk-5DAQU3B6.mjs → chunk-66JXT7NY.mjs} +1 -1
  9. package/dist/{chunk-NGKTJRFN.mjs → chunk-6DO4EGT2.mjs} +2 -2
  10. package/dist/{chunk-C7ZTZTEW.mjs → chunk-6XJWL2E5.mjs} +1 -1
  11. package/dist/{chunk-C35JMOII.mjs → chunk-7RMXM5O6.mjs} +5 -5
  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-VVURVETY.mjs → chunk-TOQRA2TD.mjs} +1 -1
  44. package/dist/{chunk-GYWOD2YI.mjs → chunk-TZSDYQFH.mjs} +4 -4
  45. package/dist/{chunk-S7SBLNX4.mjs → chunk-UB3WG6I4.mjs} +1 -1
  46. package/dist/{chunk-PGJRZHN7.mjs → chunk-UVZ3JWFG.mjs} +1 -1
  47. package/dist/{chunk-UD5UF5OC.mjs → chunk-W7OPFKTZ.mjs} +4 -4
  48. package/dist/{chunk-YEWNFK5S.mjs → chunk-WLXP4OOF.mjs} +5 -5
  49. package/dist/{chunk-ORMC3TV3.mjs → chunk-XYXYTTNW.mjs} +1 -1
  50. package/dist/{chunk-CZOGJC76.mjs → chunk-YACFZWRR.mjs} +7 -7
  51. package/dist/{chunk-UTCW5YUX.mjs → chunk-YPATB6YQ.mjs} +9 -9
  52. package/dist/{chunk-BZGFW6L7.mjs → chunk-YWJAIPUA.mjs} +1 -1
  53. package/dist/{chunk-MHBQJVHE.mjs → chunk-Z65BGSHI.mjs} +5 -5
  54. package/dist/{chunk-PCULNQWA.mjs → chunk-ZGSFRUVI.mjs} +3 -3
  55. package/dist/{chunk-7NQKFPXE.mjs → chunk-ZRYG6ICN.mjs} +1 -1
  56. package/dist/{chunk-ZFKAYRFQ.mjs → chunk-ZUHFYW65.mjs} +1 -1
  57. package/dist/components/ui/about-you-form.mjs +2 -2
  58. package/dist/components/ui/account-list-carousel.mjs +2 -2
  59. package/dist/components/ui/add-column-modal.mjs +4 -4
  60. package/dist/components/ui/add-lead-modal.mjs +4 -4
  61. package/dist/components/ui/advisor-card.mjs +2 -2
  62. package/dist/components/ui/ai-assistant-drawer.mjs +2 -2
  63. package/dist/components/ui/ai-builder/index.mjs +4 -4
  64. package/dist/components/ui/ai-conversations/index.mjs +4 -4
  65. package/dist/components/ui/alert-dialog.mjs +3 -3
  66. package/dist/components/ui/applicant-expenses-section.mjs +1 -1
  67. package/dist/components/ui/appointment-action-dialogs.mjs +5 -5
  68. package/dist/components/ui/appointment-availability-settings.mjs +4 -4
  69. package/dist/components/ui/appointment-book-dialog.mjs +4 -4
  70. package/dist/components/ui/appointment-detail-sheet.mjs +6 -6
  71. package/dist/components/ui/appointment-upcoming-card.mjs +4 -4
  72. package/dist/components/ui/asset-accordion.mjs +7 -7
  73. package/dist/components/ui/assets-liabilities-side-card.js +19 -66
  74. package/dist/components/ui/assets-liabilities-side-card.mjs +22 -69
  75. package/dist/components/ui/backoffice-alert-history-chart.js +1 -1
  76. package/dist/components/ui/backoffice-alert-history-chart.mjs +5 -5
  77. package/dist/components/ui/backoffice-alert-matching-chart.js +1 -1
  78. package/dist/components/ui/backoffice-alert-matching-chart.mjs +5 -5
  79. package/dist/components/ui/backoffice-alerts-chart.js +1 -1
  80. package/dist/components/ui/backoffice-alerts-chart.mjs +5 -5
  81. package/dist/components/ui/backoffice-connections-chart.js +1 -1
  82. package/dist/components/ui/backoffice-connections-chart.mjs +5 -5
  83. package/dist/components/ui/backoffice-contact-history-chart.js +1 -1
  84. package/dist/components/ui/backoffice-contact-history-chart.mjs +5 -5
  85. package/dist/components/ui/backoffice-contact-matching-chart.js +1 -1
  86. package/dist/components/ui/backoffice-contact-matching-chart.mjs +5 -5
  87. package/dist/components/ui/backoffice-signup-steps.mjs +4 -4
  88. package/dist/components/ui/bank-statement-generate-dialog.mjs +4 -4
  89. package/dist/components/ui/bank-statement-pdf-viewer.mjs +4 -4
  90. package/dist/components/ui/borrowing-capacity-atoms.js +3 -6
  91. package/dist/components/ui/borrowing-capacity-atoms.mjs +2 -2
  92. package/dist/components/ui/borrowing-capacity-card.js +5 -5
  93. package/dist/components/ui/borrowing-capacity-card.mjs +6 -6
  94. package/dist/components/ui/borrowing-capacity-line-chart.js +5 -5
  95. package/dist/components/ui/borrowing-capacity-line-chart.mjs +5 -5
  96. package/dist/components/ui/calculator-section.mjs +4 -4
  97. package/dist/components/ui/cash-balance-line-chart.js +102 -46
  98. package/dist/components/ui/cash-balance-line-chart.mjs +5 -5
  99. package/dist/components/ui/cashflow-bar-chart.js +7 -4
  100. package/dist/components/ui/cashflow-bar-chart.mjs +5 -5
  101. package/dist/components/ui/category-edit-dialog.js +1 -1
  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 +99 -66
  108. package/dist/components/ui/dashboard-expense-categories.mjs +104 -69
  109. package/dist/components/ui/dashboard-transactions-table.js +19 -9
  110. package/dist/components/ui/dashboard-transactions-table.mjs +26 -16
  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.mjs +2 -2
  169. package/dist/components/ui/top-three-product.mjs +1 -1
  170. package/dist/components/ui/transactions-expense-categories-doughnut-chart.js +3 -3
  171. package/dist/components/ui/transactions-expense-categories-doughnut-chart.mjs +5 -5
  172. package/dist/components/ui/transactions-income-expense-bar-chart.mjs +5 -5
  173. package/dist/components/ui/transactions-liabilities-breakdown-doughnut-chart.js +4 -4
  174. package/dist/components/ui/transactions-liabilities-breakdown-doughnut-chart.mjs +5 -5
  175. package/dist/components/ui/transactions-summary-block.js +13 -0
  176. package/dist/components/ui/transactions-summary-block.mjs +13 -0
  177. package/dist/index.js +2092 -1935
  178. package/dist/index.mjs +138 -134
  179. package/dist/lib/format-currency.js +54 -0
  180. package/dist/lib/format-currency.mjs +9 -0
  181. package/dist/styles.css +1 -1
  182. package/package.json +6 -1
  183. package/src/component-descriptions/assets-liabilities-side-card.md +19 -0
  184. package/src/component-descriptions/pipeline-chart.md +17 -0
  185. package/src/components/index.tsx +6 -0
  186. package/src/components/ui/assets-liabilities-side-card.tsx +43 -83
  187. package/src/components/ui/borrowing-capacity-atoms.tsx +4 -7
  188. package/src/components/ui/borrowing-capacity-line-chart.tsx +4 -4
  189. package/src/components/ui/cash-balance-line-chart.tsx +123 -42
  190. package/src/components/ui/cashflow-bar-chart.tsx +7 -4
  191. package/src/components/ui/category-edit-dialog.tsx +1 -1
  192. package/src/components/ui/chart-shared.tsx +4 -4
  193. package/src/components/ui/dashboard-expense-categories.tsx +139 -63
  194. package/src/components/ui/dashboard-transactions-table.tsx +51 -15
  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/transactions-liabilities-breakdown-doughnut-chart.tsx +7 -5
  202. package/src/components/ui/transactions-summary-block.tsx +39 -6
  203. package/src/styles/styles-css.ts +1 -1
  204. package/tsup.config.ts +2 -0
  205. package/dist/{chunk-CEYEK3TI.mjs → chunk-B4R62ID3.mjs} +3 -3
  206. package/dist/{chunk-7LN5OGC2.mjs → chunk-E3VAK4EB.mjs} +3 -3
  207. package/dist/{chunk-EY36WDCF.mjs → chunk-EEZFXE3P.mjs} +3 -3
  208. package/dist/{chunk-T5FRVEJQ.mjs → chunk-JTMN36BK.mjs} +3 -3
  209. /package/dist/{chunk-MN5NYQCL.mjs → chunk-XQDTFNVL.mjs} +0 -0
@@ -1,5 +1,5 @@
1
1
  import React, { useState } from "react";
2
- import { ChevronDown, ChevronRight } from "lucide-react";
2
+ import { ChevronDown, ChevronRight, ListFilter } from "lucide-react";
3
3
  import { Card, CardContent, CardHeader, CardTitle } from "./card";
4
4
  import { Spinner } from "./spinner";
5
5
  import { cn } from "@/lib/utils";
@@ -38,16 +38,42 @@ export interface DashboardExpenseCategoriesProps {
38
38
  selectedCategoryId?: string | null;
39
39
  /** Called with the category ID to filter by, or null to clear */
40
40
  onCategorySelect?: (id: string | null) => void;
41
+ /**
42
+ * Color scheme for progress bars and active states.
43
+ * Use "secondary" for the Expense tab to distinguish from Income (primary).
44
+ * Defaults to "primary".
45
+ */
46
+ colorScheme?: "primary" | "secondary";
41
47
  }
42
48
 
49
+ // ─── Constants ────────────────────────────────────────────────────────────────
50
+
51
+ const SECONDARY_ACTIVE_BG =
52
+ "color-mix(in oklch, var(--brand-secondary) 8%, transparent)";
53
+ const SECONDARY_ACTIVE_HOVER_BG =
54
+ "color-mix(in oklch, var(--brand-secondary) 12%, transparent)";
55
+
43
56
  // ─── Progress bar ─────────────────────────────────────────────────────────────
44
57
 
45
- function ProgressBar({ pct, className }: { pct: number; className?: string }) {
58
+ function ProgressBar({
59
+ pct,
60
+ className,
61
+ colorScheme = "primary",
62
+ }: {
63
+ pct: number;
64
+ className?: string;
65
+ colorScheme?: "primary" | "secondary";
66
+ }) {
46
67
  return (
47
68
  <div className={cn("h-1.5 w-full overflow-hidden bg-muted", className)}>
48
69
  <div
49
70
  className="h-full bg-primary transition-all"
50
- style={{ width: `${Math.min(100, Math.max(0, pct))}%` }}
71
+ style={{
72
+ width: `${Math.min(100, Math.max(0, pct))}%`,
73
+ ...(colorScheme === "secondary" && {
74
+ backgroundColor: "var(--brand-secondary)",
75
+ }),
76
+ }}
51
77
  aria-hidden="true"
52
78
  />
53
79
  </div>
@@ -59,36 +85,60 @@ function ProgressBar({ pct, className }: { pct: number; className?: string }) {
59
85
  function SubCategoryRow({
60
86
  sub,
61
87
  isSelected,
88
+ colorScheme = "primary",
62
89
  onClick,
63
90
  }: {
64
91
  sub: ExpenseSubCategory;
65
92
  isSelected: boolean;
93
+ colorScheme?: "primary" | "secondary";
66
94
  onClick: () => void;
67
95
  }) {
96
+ const isSecondary = colorScheme === "secondary";
68
97
  return (
69
- <button
70
- type="button"
71
- onClick={onClick}
98
+ <div
72
99
  className={cn(
73
- "flex w-full flex-col gap-1 py-1.5 pl-8 text-left transition-colors",
74
- isSelected ? "bg-primary/5" : "hover:bg-muted/50",
100
+ "flex w-full flex-col gap-1 py-1.5 pl-8",
101
+ isSelected && !isSecondary && "bg-primary/5",
75
102
  )}
103
+ style={
104
+ isSelected && isSecondary
105
+ ? { backgroundColor: SECONDARY_ACTIVE_BG }
106
+ : undefined
107
+ }
76
108
  >
77
- <div className="flex items-center justify-between gap-2 text-xs">
78
- <span
79
- className={cn(
80
- "truncate",
81
- isSelected ? "font-medium text-primary" : "text-muted-foreground",
82
- )}
83
- >
84
- {sub.name}
85
- </span>
109
+ <div className="flex items-center justify-between gap-2 text-sm">
110
+ <div className="flex min-w-0 items-center gap-1">
111
+ <span
112
+ className={cn(
113
+ "truncate text-muted-foreground",
114
+ isSelected && "font-medium",
115
+ )}
116
+ >
117
+ {sub.name}
118
+ </span>
119
+ <button
120
+ type="button"
121
+ onClick={onClick}
122
+ aria-label={`Filter by ${sub.name}`}
123
+ aria-pressed={isSelected}
124
+ className={cn(
125
+ "shrink-0 transition-colors",
126
+ isSelected
127
+ ? isSecondary
128
+ ? "text-[var(--brand-secondary)]"
129
+ : "text-primary"
130
+ : "text-muted-foreground/50 hover:text-muted-foreground",
131
+ )}
132
+ >
133
+ <ListFilter size={12} />
134
+ </button>
135
+ </div>
86
136
  <span className="shrink-0 font-medium text-foreground">
87
137
  {formatCurrency(sub.amount)}
88
138
  </span>
89
139
  </div>
90
- <ProgressBar pct={sub.pct} />
91
- </button>
140
+ <ProgressBar pct={sub.pct} colorScheme={colorScheme} />
141
+ </div>
92
142
  );
93
143
  }
94
144
 
@@ -97,48 +147,59 @@ function SubCategoryRow({
97
147
  function CategoryRow({
98
148
  item,
99
149
  selectedCategoryId,
150
+ colorScheme = "primary",
100
151
  onFilterClick,
101
152
  }: {
102
153
  item: ExpenseCategoryItem;
103
154
  selectedCategoryId?: string | null;
155
+ colorScheme?: "primary" | "secondary";
104
156
  onFilterClick: (id: string | null) => void;
105
157
  }) {
106
158
  const [expanded, setExpanded] = useState(false);
107
- const hasChildren = (item.subCategories?.length ?? 0) > 0;
159
+ const [hovered, setHovered] = useState(false);
108
160
 
161
+ const hasChildren = (item.subCategories?.length ?? 0) > 0;
109
162
  const isSelected = selectedCategoryId === item.id;
110
- const isChildSelected = hasChildren
111
- ? (item.subCategories?.some((s) => s.id === selectedCategoryId) ?? false)
112
- : false;
163
+ const isChildSelected =
164
+ hasChildren &&
165
+ (item.subCategories?.some((s) => s.id === selectedCategoryId) ?? false);
113
166
  const isActive = isSelected || isChildSelected;
167
+ const isSecondary = colorScheme === "secondary";
168
+
169
+ // Secondary scheme active background is set via inline style (color-mix not
170
+ // expressible as a Tailwind arbitrary value here). Hover darkens the tint.
171
+ const wrapperStyle =
172
+ isSecondary && isActive
173
+ ? {
174
+ backgroundColor: hovered
175
+ ? SECONDARY_ACTIVE_HOVER_BG
176
+ : SECONDARY_ACTIVE_BG,
177
+ }
178
+ : undefined;
114
179
 
115
180
  return (
116
181
  <div
117
182
  className={cn(
118
183
  "border-b border-border last:border-0 transition-colors",
119
- isActive && "bg-primary/5",
184
+ isActive && !isSecondary && "bg-primary/5",
120
185
  )}
186
+ style={wrapperStyle}
121
187
  >
122
- {/* Main row — full click = filter */}
188
+ {/* Main row — click expands/collapses; filter icon inside triggers filter */}
123
189
  <button
124
190
  type="button"
125
191
  className={cn(
126
- "flex w-full items-start gap-3 py-3 text-left hover:bg-muted/40 transition-colors",
127
- isActive && "hover:bg-primary/10",
192
+ "flex w-full items-start gap-3 py-3 text-left transition-colors",
193
+ !isSecondary && "hover:bg-muted/40",
194
+ !isSecondary && isActive && "hover:bg-primary/10",
128
195
  )}
129
- onClick={() => onFilterClick(isSelected ? null : item.id)}
130
- aria-pressed={isSelected}
196
+ onMouseEnter={() => setHovered(true)}
197
+ onMouseLeave={() => setHovered(false)}
198
+ onClick={() => hasChildren && setExpanded((v) => !v)}
199
+ aria-expanded={hasChildren ? expanded : undefined}
131
200
  >
132
- {/* Expand chevron — stops propagation, only expands */}
133
- <span
134
- className="mt-0.5 shrink-0 text-muted-foreground"
135
- onClick={(e) => {
136
- if (!hasChildren) return;
137
- e.stopPropagation();
138
- setExpanded((v) => !v);
139
- }}
140
- aria-label={expanded ? "Collapse" : "Expand"}
141
- >
201
+ {/* Expand chevron */}
202
+ <span className="mt-0.5 shrink-0 text-muted-foreground">
142
203
  {hasChildren ? (
143
204
  expanded ? (
144
205
  <ChevronDown size={14} />
@@ -150,39 +211,55 @@ function CategoryRow({
150
211
  )}
151
212
  </span>
152
213
 
153
- {/* Icon */}
214
+ {/* Domain icon */}
154
215
  {item.icon && (
155
- <span
156
- className={cn(
157
- "mt-0.5 shrink-0",
158
- isActive ? "text-primary" : "text-muted-foreground",
159
- )}
160
- >
216
+ <span className="mt-0.5 shrink-0 text-muted-foreground">
161
217
  {item.icon}
162
218
  </span>
163
219
  )}
164
220
 
165
- {/* Name + progress */}
221
+ {/* Name + filter icon + amounts */}
166
222
  <div className="min-w-0 flex-1">
167
223
  <div className="flex items-center justify-between gap-2">
168
- <span
169
- className={cn(
170
- "truncate text-sm font-medium",
171
- isActive ? "text-primary" : "text-foreground",
172
- )}
173
- >
174
- {item.name}
175
- </span>
224
+ <div className="flex min-w-0 items-center gap-1">
225
+ <span className="truncate text-base font-medium text-foreground">
226
+ {item.name}
227
+ </span>
228
+ {/* Filter icon — only this triggers the category filter */}
229
+ <button
230
+ type="button"
231
+ onClick={(e) => {
232
+ e.stopPropagation();
233
+ onFilterClick(isSelected ? null : item.id);
234
+ }}
235
+ aria-label={`Filter by ${item.name}`}
236
+ aria-pressed={isSelected}
237
+ className={cn(
238
+ "shrink-0 transition-colors",
239
+ isActive
240
+ ? isSecondary
241
+ ? "text-[var(--brand-secondary)]"
242
+ : "text-primary"
243
+ : "text-muted-foreground/50 hover:text-muted-foreground",
244
+ )}
245
+ >
246
+ <ListFilter size={13} />
247
+ </button>
248
+ </div>
176
249
  <div className="flex shrink-0 items-center gap-2">
177
- <span className="text-xs text-muted-foreground">
250
+ <span className="text-sm text-muted-foreground">
178
251
  {item.pct.toFixed(0)}%
179
252
  </span>
180
- <span className="text-sm font-semibold text-foreground">
253
+ <span className="text-base font-semibold text-foreground">
181
254
  {formatCurrency(item.amount)}
182
255
  </span>
183
256
  </div>
184
257
  </div>
185
- <ProgressBar pct={item.pct} className="mt-1.5" />
258
+ <ProgressBar
259
+ pct={item.pct}
260
+ className="mt-1.5"
261
+ colorScheme={colorScheme}
262
+ />
186
263
  </div>
187
264
  </button>
188
265
 
@@ -194,6 +271,7 @@ function CategoryRow({
194
271
  key={sub.id}
195
272
  sub={sub}
196
273
  isSelected={selectedCategoryId === sub.id}
274
+ colorScheme={colorScheme}
197
275
  onClick={() =>
198
276
  onFilterClick(selectedCategoryId === sub.id ? null : sub.id)
199
277
  }
@@ -215,11 +293,8 @@ export function DashboardExpenseCategories({
215
293
  className,
216
294
  selectedCategoryId,
217
295
  onCategorySelect,
296
+ colorScheme = "primary",
218
297
  }: DashboardExpenseCategoriesProps) {
219
- const handleFilterClick = (id: string | null) => {
220
- onCategorySelect?.(id);
221
- };
222
-
223
298
  return (
224
299
  <Card className={cn("flex flex-col", className)}>
225
300
  <CardHeader className="pb-2">
@@ -251,7 +326,8 @@ export function DashboardExpenseCategories({
251
326
  key={item.id}
252
327
  item={item}
253
328
  selectedCategoryId={selectedCategoryId}
254
- onFilterClick={handleFilterClick}
329
+ colorScheme={colorScheme}
330
+ onFilterClick={(id) => onCategorySelect?.(id)}
255
331
  />
256
332
  ))}
257
333
  </div>
@@ -58,6 +58,24 @@ export interface DashboardTransactionsTableProps {
58
58
  categoryId: string,
59
59
  applyToFuture: boolean,
60
60
  ) => void;
61
+ /**
62
+ * Show or hide the card header (title + tab row).
63
+ * Set to `false` when the surrounding layout already provides context.
64
+ * Defaults to `true`.
65
+ */
66
+ showHeader?: boolean;
67
+ /**
68
+ * Show or hide the "Account Transaction" underlined tab below the title.
69
+ * Set to `false` when the surrounding layout already provides tab context.
70
+ * Defaults to `true`.
71
+ */
72
+ showTab?: boolean;
73
+ /**
74
+ * Colour scheme for the editable category chip hover state.
75
+ * - `"primary"` (default) — green hover
76
+ * - `"secondary"` — dark navy hover; use when the surrounding context uses the secondary palette (e.g. expense view)
77
+ */
78
+ colorScheme?: "primary" | "secondary";
61
79
  }
62
80
 
63
81
  // ─── Category chip ────────────────────────────────────────────────────────────
@@ -65,10 +83,12 @@ export interface DashboardTransactionsTableProps {
65
83
  function CategoryChip({
66
84
  label,
67
85
  canEdit,
86
+ colorScheme = "primary",
68
87
  onClick,
69
88
  }: {
70
89
  label: string;
71
90
  canEdit: boolean;
91
+ colorScheme?: "primary" | "secondary";
72
92
  onClick?: () => void;
73
93
  }) {
74
94
  if (canEdit) {
@@ -76,7 +96,12 @@ function CategoryChip({
76
96
  <Badge
77
97
  asChild
78
98
  variant="secondary"
79
- className="cursor-pointer transition-colors hover:border-primary/40 hover:bg-primary/10 hover:text-primary"
99
+ className={cn(
100
+ "cursor-pointer transition-colors",
101
+ colorScheme === "secondary"
102
+ ? "hover:bg-brand-secondary/10 hover:text-[var(--brand-secondary)]"
103
+ : "hover:border-primary/40 hover:bg-primary/10 hover:text-primary",
104
+ )}
80
105
  >
81
106
  <button type="button" onClick={onClick}>
82
107
  {label}
@@ -95,11 +120,13 @@ function TransactionRow({
95
120
  tx,
96
121
  isDimmed,
97
122
  canEdit,
123
+ colorScheme = "primary",
98
124
  onChipClick,
99
125
  }: {
100
126
  tx: DashboardTransaction;
101
127
  isDimmed: boolean;
102
128
  canEdit: boolean;
129
+ colorScheme?: "primary" | "secondary";
103
130
  onChipClick?: () => void;
104
131
  }) {
105
132
  const isCredit = tx.amount >= 0;
@@ -113,14 +140,14 @@ function TransactionRow({
113
140
  )}
114
141
  >
115
142
  <div className="min-w-0 flex-1">
116
- <p className="text-xs text-muted-foreground">
143
+ <p className="text-sm text-muted-foreground">
117
144
  {formatDateShort(tx.date)}
118
145
  </p>
119
- <p className="mt-0.5 truncate text-sm font-medium text-foreground">
146
+ <p className="mt-0.5 truncate text-base font-medium text-foreground">
120
147
  {tx.description}
121
148
  </p>
122
149
  {tx.merchant && (
123
- <p className="mt-0.5 truncate text-xs text-muted-foreground">
150
+ <p className="mt-0.5 truncate text-sm text-muted-foreground">
124
151
  {tx.merchant}
125
152
  </p>
126
153
  )}
@@ -129,6 +156,7 @@ function TransactionRow({
129
156
  <CategoryChip
130
157
  label={categoryLabel}
131
158
  canEdit={canEdit}
159
+ colorScheme={colorScheme}
132
160
  onClick={onChipClick}
133
161
  />
134
162
  </div>
@@ -137,7 +165,7 @@ function TransactionRow({
137
165
 
138
166
  <span
139
167
  className={cn(
140
- "shrink-0 text-sm font-semibold",
168
+ "shrink-0 text-base font-semibold",
141
169
  isCredit ? "text-success" : "text-foreground",
142
170
  )}
143
171
  >
@@ -161,6 +189,9 @@ export function DashboardTransactionsTable({
161
189
  selectedCategoryId,
162
190
  categories,
163
191
  onCategoryChange,
192
+ showHeader = true,
193
+ showTab = true,
194
+ colorScheme = "primary",
164
195
  }: DashboardTransactionsTableProps) {
165
196
  const isFiltering = selectedCategoryId != null;
166
197
  const canEdit = !!categories?.length;
@@ -177,16 +208,20 @@ export function DashboardTransactionsTable({
177
208
  return (
178
209
  <>
179
210
  <Card className={cn("flex flex-col", className)}>
180
- <CardHeader className="pb-0">
181
- <CardTitle className="text-xs font-semibold uppercase tracking-wider text-muted-foreground">
182
- {title}
183
- </CardTitle>
184
- <div className="mt-2 flex border-b border-border">
185
- <span className="border-b-2 border-foreground pb-1.5 text-xs font-semibold text-foreground">
186
- Account Transaction
187
- </span>
188
- </div>
189
- </CardHeader>
211
+ {showHeader && (
212
+ <CardHeader className="pb-0">
213
+ <CardTitle className="text-xs font-semibold uppercase tracking-wider text-muted-foreground">
214
+ {title}
215
+ </CardTitle>
216
+ {showTab && (
217
+ <div className="mt-2 flex border-b border-border">
218
+ <span className="border-b-2 border-foreground pb-1.5 text-xs font-semibold text-foreground">
219
+ Account Transaction
220
+ </span>
221
+ </div>
222
+ )}
223
+ </CardHeader>
224
+ )}
190
225
 
191
226
  <CardContent className="flex flex-1 flex-col px-4 pb-0 pt-0">
192
227
  {isLoading ? (
@@ -205,6 +240,7 @@ export function DashboardTransactionsTable({
205
240
  tx={tx}
206
241
  isDimmed={isFiltering && tx.categoryId !== selectedCategoryId}
207
242
  canEdit={canEdit}
243
+ colorScheme={colorScheme}
208
244
  onChipClick={() => setEditingTx(tx)}
209
245
  />
210
246
  ))}
@@ -88,6 +88,12 @@ export interface ExpenseBarChartProps {
88
88
  className?: string;
89
89
  /** Show skeleton loading state instead of the chart */
90
90
  isLoading?: boolean;
91
+ /**
92
+ * Show the internal period selector buttons (3M / 6M / 12M).
93
+ * Set to `false` when the chart is driven by an external period control.
94
+ * Defaults to `true`.
95
+ */
96
+ showPeriodSelector?: boolean;
91
97
  }
92
98
 
93
99
  // ---------------------------------------------------------------------------
@@ -108,6 +114,7 @@ export function ExpenseBarChart({
108
114
  width = "100%",
109
115
  className,
110
116
  isLoading = false,
117
+ showPeriodSelector = true,
111
118
  }: ExpenseBarChartProps) {
112
119
  const periods = CHART_PERIODS[granularity];
113
120
  const [period, setPeriod] = useState<ExpensePeriod>(defaultPeriod);
@@ -215,7 +222,7 @@ export function ExpenseBarChart({
215
222
  stacked: true,
216
223
  grid: { display: false },
217
224
  border: { display: false },
218
- ticks: { font: { size: 10 }, color: FALLBACK_TICK },
225
+ ticks: { font: { size: 12 }, color: FALLBACK_TICK },
219
226
  },
220
227
  y: {
221
228
  display: showYAxis,
@@ -223,7 +230,7 @@ export function ExpenseBarChart({
223
230
  grid: { display: false },
224
231
  border: { display: false },
225
232
  ticks: {
226
- font: { size: 10 },
233
+ font: { size: 12 },
227
234
  color: FALLBACK_TICK,
228
235
  maxTicksLimit: 5,
229
236
  padding: 8,
@@ -240,23 +247,29 @@ export function ExpenseBarChart({
240
247
  className={cn("w-full py-4 sm:py-6 gap-2", className)}
241
248
  style={{ maxWidth: width, fontFamily }}
242
249
  >
243
- <CardHeader className="px-3 sm:px-6">
244
- <CardTitle className="text-xs font-semibold uppercase tracking-wide">
245
- {title}
246
- </CardTitle>
247
- <CardAction>
248
- <div className="flex gap-0.5 sm:gap-1">
249
- {periods.map((p) => (
250
- <ChartPeriodButton
251
- key={p}
252
- period={p}
253
- active={period === p}
254
- onClick={() => setPeriod(p)}
255
- />
256
- ))}
257
- </div>
258
- </CardAction>
259
- </CardHeader>
250
+ {(title || showPeriodSelector) && (
251
+ <CardHeader className="px-3 sm:px-6">
252
+ {title && (
253
+ <CardTitle className="text-xs font-semibold uppercase tracking-wide">
254
+ {title}
255
+ </CardTitle>
256
+ )}
257
+ {showPeriodSelector && (
258
+ <CardAction>
259
+ <div className="flex gap-0.5 sm:gap-1">
260
+ {periods.map((p) => (
261
+ <ChartPeriodButton
262
+ key={p}
263
+ period={p}
264
+ active={period === p}
265
+ onClick={() => setPeriod(p)}
266
+ />
267
+ ))}
268
+ </div>
269
+ </CardAction>
270
+ )}
271
+ </CardHeader>
272
+ )}
260
273
 
261
274
  <CardContent className="px-3 sm:px-6">
262
275
  {isLoading ? (