@wealthx/shadcn 1.5.42 → 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 (207) hide show
  1. package/.turbo/turbo-build.log +215 -210
  2. package/CHANGELOG.md +6 -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-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.mjs +4 -4
  102. package/dist/components/ui/color-picker.mjs +2 -2
  103. package/dist/components/ui/contact-alert-dialog/index.mjs +4 -4
  104. package/dist/components/ui/create-contact-modal.mjs +4 -4
  105. package/dist/components/ui/csv-import-modal.mjs +4 -4
  106. package/dist/components/ui/dashboard-expense-categories.js +96 -63
  107. package/dist/components/ui/dashboard-expense-categories.mjs +101 -66
  108. package/dist/components/ui/dashboard-transactions-table.js +37 -44
  109. package/dist/components/ui/dashboard-transactions-table.mjs +45 -52
  110. package/dist/components/ui/data-table.mjs +2 -2
  111. package/dist/components/ui/date-picker.mjs +2 -2
  112. package/dist/components/ui/debt-accordion.mjs +7 -7
  113. package/dist/components/ui/delete-contact-component.mjs +4 -4
  114. package/dist/components/ui/dialog.mjs +3 -3
  115. package/dist/components/ui/document-checklist-template.mjs +2 -2
  116. package/dist/components/ui/expense-bar-chart.js +8 -7
  117. package/dist/components/ui/expense-bar-chart.mjs +5 -5
  118. package/dist/components/ui/expense-categories-bar.js +261 -0
  119. package/dist/components/ui/expense-categories-bar.mjs +12 -0
  120. package/dist/components/ui/expense-work-details.js +8 -7
  121. package/dist/components/ui/expense-work-details.mjs +7 -7
  122. package/dist/components/ui/file-preview-dialog.mjs +4 -4
  123. package/dist/components/ui/financial-cards.mjs +2 -2
  124. package/dist/components/ui/financial-drawers.mjs +2 -2
  125. package/dist/components/ui/financial-sections.mjs +3 -3
  126. package/dist/components/ui/frontend-signup-steps.mjs +2 -2
  127. package/dist/components/ui/income-bar-chart.js +8 -7
  128. package/dist/components/ui/income-bar-chart.mjs +5 -5
  129. package/dist/components/ui/income-sources-card.mjs +1 -1
  130. package/dist/components/ui/income-summary-component.mjs +1 -1
  131. package/dist/components/ui/income-work-details.js +8 -7
  132. package/dist/components/ui/income-work-details.mjs +6 -6
  133. package/dist/components/ui/incoming-outgoings-card.js +2 -2
  134. package/dist/components/ui/incoming-outgoings-card.mjs +3 -3
  135. package/dist/components/ui/interest-rate-section.mjs +1 -1
  136. package/dist/components/ui/kanban-column.mjs +5 -5
  137. package/dist/components/ui/loan-application-cards.mjs +3 -3
  138. package/dist/components/ui/loan-financials.mjs +3 -3
  139. package/dist/components/ui/money-input-with-slider.mjs +2 -2
  140. package/dist/components/ui/opportunity-card.mjs +4 -4
  141. package/dist/components/ui/opportunity-edit-modals.mjs +4 -4
  142. package/dist/components/ui/opportunity-summary-tab.mjs +8 -8
  143. package/dist/components/ui/pagination.mjs +2 -2
  144. package/dist/components/ui/pipeline-board.mjs +6 -6
  145. package/dist/components/ui/pipeline-chart.mjs +2 -2
  146. package/dist/components/ui/pipeline-dialogs.mjs +4 -4
  147. package/dist/components/ui/policy-ai/index.mjs +2 -2
  148. package/dist/components/ui/property-asset-card.mjs +4 -4
  149. package/dist/components/ui/property-cashflow-doughnut-chart.js +3 -3
  150. package/dist/components/ui/property-cashflow-doughnut-chart.mjs +5 -5
  151. package/dist/components/ui/property-debt-equity-doughnut-chart.js +3 -3
  152. package/dist/components/ui/property-debt-equity-doughnut-chart.mjs +5 -5
  153. package/dist/components/ui/property-list-carousel.mjs +2 -2
  154. package/dist/components/ui/property-mobile-estimate-line-chart.js +4 -4
  155. package/dist/components/ui/property-mobile-estimate-line-chart.mjs +5 -5
  156. package/dist/components/ui/property-report-dialog.mjs +5 -5
  157. package/dist/components/ui/resource-center/index.mjs +4 -4
  158. package/dist/components/ui/review-alerts-dialog.mjs +4 -4
  159. package/dist/components/ui/savings-goal-modal.mjs +7 -7
  160. package/dist/components/ui/scenario-drawer.mjs +4 -4
  161. package/dist/components/ui/scenario-list.js +4 -7
  162. package/dist/components/ui/scenario-list.mjs +5 -5
  163. package/dist/components/ui/share-details-dialog.mjs +4 -4
  164. package/dist/components/ui/sidebar-nav.mjs +4 -4
  165. package/dist/components/ui/signup-form-primitives.mjs +2 -2
  166. package/dist/components/ui/stage-timeline.mjs +1 -1
  167. package/dist/components/ui/support-agent/index.mjs +2 -2
  168. package/dist/components/ui/top-three-product.mjs +1 -1
  169. package/dist/components/ui/transactions-expense-categories-doughnut-chart.js +3 -3
  170. package/dist/components/ui/transactions-expense-categories-doughnut-chart.mjs +5 -5
  171. package/dist/components/ui/transactions-income-expense-bar-chart.mjs +5 -5
  172. package/dist/components/ui/transactions-liabilities-breakdown-doughnut-chart.js +4 -4
  173. package/dist/components/ui/transactions-liabilities-breakdown-doughnut-chart.mjs +5 -5
  174. package/dist/components/ui/transactions-summary-block.js +13 -0
  175. package/dist/components/ui/transactions-summary-block.mjs +13 -0
  176. package/dist/index.js +2092 -1935
  177. package/dist/index.mjs +138 -134
  178. package/dist/lib/format-currency.js +54 -0
  179. package/dist/lib/format-currency.mjs +9 -0
  180. package/dist/styles.css +1 -1
  181. package/package.json +6 -1
  182. package/src/component-descriptions/assets-liabilities-side-card.md +19 -0
  183. package/src/component-descriptions/pipeline-chart.md +17 -0
  184. package/src/components/index.tsx +6 -0
  185. package/src/components/ui/assets-liabilities-side-card.tsx +43 -83
  186. package/src/components/ui/borrowing-capacity-atoms.tsx +4 -7
  187. package/src/components/ui/borrowing-capacity-line-chart.tsx +4 -4
  188. package/src/components/ui/cash-balance-line-chart.tsx +123 -42
  189. package/src/components/ui/cashflow-bar-chart.tsx +7 -4
  190. package/src/components/ui/chart-shared.tsx +4 -4
  191. package/src/components/ui/dashboard-expense-categories.tsx +136 -60
  192. package/src/components/ui/dashboard-transactions-table.tsx +42 -28
  193. package/src/components/ui/expense-bar-chart.tsx +32 -19
  194. package/src/components/ui/expense-categories-bar.tsx +178 -0
  195. package/src/components/ui/income-bar-chart.tsx +32 -19
  196. package/src/components/ui/incoming-outgoings-card.tsx +2 -2
  197. package/src/components/ui/property-mobile-estimate-line-chart.tsx +4 -4
  198. package/src/components/ui/scenario-list.tsx +2 -2
  199. package/src/components/ui/transactions-liabilities-breakdown-doughnut-chart.tsx +7 -5
  200. package/src/components/ui/transactions-summary-block.tsx +39 -6
  201. package/src/styles/styles-css.ts +1 -1
  202. package/tsup.config.ts +2 -0
  203. package/dist/{chunk-CEYEK3TI.mjs → chunk-B4R62ID3.mjs} +3 -3
  204. package/dist/{chunk-7LN5OGC2.mjs → chunk-E3VAK4EB.mjs} +3 -3
  205. package/dist/{chunk-EY36WDCF.mjs → chunk-EEZFXE3P.mjs} +3 -3
  206. package/dist/{chunk-T5FRVEJQ.mjs → chunk-JTMN36BK.mjs} +3 -3
  207. /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
109
  <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>
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,29 +211,41 @@ 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-sm 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
250
  <span className="text-xs text-muted-foreground">
178
251
  {item.pct.toFixed(0)}%
@@ -182,7 +255,11 @@ function CategoryRow({
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,18 @@ export interface DashboardTransactionsTableProps {
58
58
  categoryId: string,
59
59
  applyToFuture: boolean,
60
60
  ) => void;
61
+ /**
62
+ * Show or hide the card header (title + "Account Transaction" tab).
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;
61
73
  }
62
74
 
63
75
  // ─── Category chip ────────────────────────────────────────────────────────────
@@ -76,7 +88,7 @@ function CategoryChip({
76
88
  <Badge
77
89
  asChild
78
90
  variant="secondary"
79
- className="cursor-pointer transition-colors hover:border-primary/40 hover:bg-primary/10 hover:text-primary"
91
+ className="cursor-pointer transition-colors hover:bg-muted"
80
92
  >
81
93
  <button type="button" onClick={onClick}>
82
94
  {label}
@@ -93,12 +105,10 @@ function CategoryChip({
93
105
 
94
106
  function TransactionRow({
95
107
  tx,
96
- isDimmed,
97
108
  canEdit,
98
109
  onChipClick,
99
110
  }: {
100
111
  tx: DashboardTransaction;
101
- isDimmed: boolean;
102
112
  canEdit: boolean;
103
113
  onChipClick?: () => void;
104
114
  }) {
@@ -106,12 +116,7 @@ function TransactionRow({
106
116
  const categoryLabel = tx.editedCategoryName ?? tx.category;
107
117
 
108
118
  return (
109
- <div
110
- className={cn(
111
- "flex items-start justify-between gap-4 border-b border-border py-3 last:border-0 transition-opacity",
112
- isDimmed && "opacity-30",
113
- )}
114
- >
119
+ <div className="flex items-start justify-between gap-4 border-b border-border py-3 last:border-0">
115
120
  <div className="min-w-0 flex-1">
116
121
  <p className="text-xs text-muted-foreground">
117
122
  {formatDateShort(tx.date)}
@@ -161,6 +166,8 @@ export function DashboardTransactionsTable({
161
166
  selectedCategoryId,
162
167
  categories,
163
168
  onCategoryChange,
169
+ showHeader = true,
170
+ showTab = true,
164
171
  }: DashboardTransactionsTableProps) {
165
172
  const isFiltering = selectedCategoryId != null;
166
173
  const canEdit = !!categories?.length;
@@ -177,16 +184,20 @@ export function DashboardTransactionsTable({
177
184
  return (
178
185
  <>
179
186
  <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>
187
+ {showHeader && (
188
+ <CardHeader className="pb-0">
189
+ <CardTitle className="text-xs font-semibold uppercase tracking-wider text-muted-foreground">
190
+ {title}
191
+ </CardTitle>
192
+ {showTab && (
193
+ <div className="mt-2 flex border-b border-border">
194
+ <span className="border-b-2 border-foreground pb-1.5 text-xs font-semibold text-foreground">
195
+ Account Transaction
196
+ </span>
197
+ </div>
198
+ )}
199
+ </CardHeader>
200
+ )}
190
201
 
191
202
  <CardContent className="flex flex-1 flex-col px-4 pb-0 pt-0">
192
203
  {isLoading ? (
@@ -199,15 +210,18 @@ export function DashboardTransactionsTable({
199
210
  </p>
200
211
  ) : (
201
212
  <div className="flex flex-col">
202
- {transactions.map((tx) => (
203
- <TransactionRow
204
- key={tx.id}
205
- tx={tx}
206
- isDimmed={isFiltering && tx.categoryId !== selectedCategoryId}
207
- canEdit={canEdit}
208
- onChipClick={() => setEditingTx(tx)}
209
- />
210
- ))}
213
+ {transactions
214
+ .filter(
215
+ (tx) => !isFiltering || tx.categoryId === selectedCategoryId,
216
+ )
217
+ .map((tx) => (
218
+ <TransactionRow
219
+ key={tx.id}
220
+ tx={tx}
221
+ canEdit={canEdit}
222
+ onChipClick={() => setEditingTx(tx)}
223
+ />
224
+ ))}
211
225
  </div>
212
226
  )}
213
227
 
@@ -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 ? (