@wealthx/shadcn 1.2.1 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (247) hide show
  1. package/.turbo/turbo-build.log +203 -150
  2. package/CHANGELOG.md +29 -0
  3. package/dist/{chunk-4Y6R4WEC.mjs → chunk-2A5RRQGG.mjs} +9 -22
  4. package/dist/{chunk-TS2ZX2VS.mjs → chunk-2UM72RJ7.mjs} +11 -15
  5. package/dist/{chunk-A56YQQHG.mjs → chunk-3NCUZIFP.mjs} +2 -2
  6. package/dist/chunk-3OYFOX3X.mjs +79 -0
  7. package/dist/{chunk-RP3SQYA3.mjs → chunk-3TTACBDP.mjs} +9 -4
  8. package/dist/chunk-4GAWMKMI.mjs +710 -0
  9. package/dist/{chunk-SYOD63OZ.mjs → chunk-5FQIKDKP.mjs} +6 -6
  10. package/dist/{chunk-K3JYD4IU.mjs → chunk-5IS7G74I.mjs} +11 -4
  11. package/dist/chunk-6AW4KJHE.mjs +235 -0
  12. package/dist/chunk-6CR5N2JW.mjs +302 -0
  13. package/dist/{chunk-XIRTEFKH.mjs → chunk-6DZEXFNB.mjs} +36 -8
  14. package/dist/chunk-6O6KD7CE.mjs +271 -0
  15. package/dist/chunk-7PV3IWCN.mjs +33 -0
  16. package/dist/{chunk-SPJ5KXW7.mjs → chunk-7S5AESZO.mjs} +5 -5
  17. package/dist/{chunk-RYCLWMZ7.mjs → chunk-ABFDMHOR.mjs} +9 -7
  18. package/dist/{chunk-SWGT756Z.mjs → chunk-AMQZRHEZ.mjs} +10 -4
  19. package/dist/{chunk-WOEHFRGB.mjs → chunk-BDYZCBRT.mjs} +4 -4
  20. package/dist/{chunk-WAZD7NFU.mjs → chunk-BKNFWEH2.mjs} +6 -6
  21. package/dist/{chunk-CLIN5525.mjs → chunk-C7CQJNMR.mjs} +1 -1
  22. package/dist/{chunk-D4ILTPOG.mjs → chunk-CFMQP5QS.mjs} +5 -4
  23. package/dist/{chunk-VPBN3WOO.mjs → chunk-DGHAXJBN.mjs} +9 -7
  24. package/dist/chunk-DOEO3CDL.mjs +27 -0
  25. package/dist/{chunk-KUDCQ4FI.mjs → chunk-DUJTAXMH.mjs} +9 -4
  26. package/dist/{chunk-GGM2UYGG.mjs → chunk-EBXQWIYG.mjs} +10 -4
  27. package/dist/{chunk-PMB3A7V3.mjs → chunk-EI5F6FMT.mjs} +1 -1
  28. package/dist/chunk-EWRB4PAD.mjs +468 -0
  29. package/dist/chunk-FAKPBKLT.mjs +253 -0
  30. package/dist/chunk-FNQXOAYJ.mjs +169 -0
  31. package/dist/{chunk-4CX4SBRO.mjs → chunk-GHC7LLUX.mjs} +14 -5
  32. package/dist/chunk-HBZLGDIN.mjs +507 -0
  33. package/dist/{chunk-SIZMLSRU.mjs → chunk-HISNT2MG.mjs} +8 -6
  34. package/dist/{chunk-PR6V5XKM.mjs → chunk-HVY6KCCF.mjs} +7 -4
  35. package/dist/chunk-I3RZS7V2.mjs +136 -0
  36. package/dist/chunk-IAE3F7DR.mjs +1962 -0
  37. package/dist/{chunk-ZRO5JO3H.mjs → chunk-IHMFS7NZ.mjs} +81 -84
  38. package/dist/{chunk-PCPLO5HT.mjs → chunk-IOJRDS6V.mjs} +96 -14
  39. package/dist/{chunk-LHYCMLVA.mjs → chunk-JKGDCQTZ.mjs} +11 -4
  40. package/dist/{chunk-H45TKD34.mjs → chunk-JMHR3YGZ.mjs} +1 -1
  41. package/dist/{chunk-4MN6UQHG.mjs → chunk-K5A5L6T2.mjs} +17 -39
  42. package/dist/{chunk-CSDO6VBW.mjs → chunk-LBMRIB3G.mjs} +10 -10
  43. package/dist/chunk-LV35NGVG.mjs +272 -0
  44. package/dist/{chunk-FZIXGLMV.mjs → chunk-M3FV7LOK.mjs} +5 -12
  45. package/dist/{chunk-FMAXJ2SI.mjs → chunk-MBON7YRJ.mjs} +1 -1
  46. package/dist/chunk-MIZQHHUO.mjs +441 -0
  47. package/dist/chunk-MN5NYQCL.mjs +29 -0
  48. package/dist/chunk-NL3ZO62D.mjs +31 -0
  49. package/dist/{chunk-Q76O3RIQ.mjs → chunk-NMOI6CQD.mjs} +1 -1
  50. package/dist/{chunk-P6AM5V7O.mjs → chunk-OODBHKG7.mjs} +1 -1
  51. package/dist/chunk-PBL4OQV2.mjs +283 -0
  52. package/dist/{chunk-3WMX6KWS.mjs → chunk-PU4YZQXV.mjs} +11 -12
  53. package/dist/chunk-QMY3AZJH.mjs +80 -0
  54. package/dist/{chunk-BL3DXM2X.mjs → chunk-QZ4RE6NA.mjs} +11 -4
  55. package/dist/{chunk-VACKZOMY.mjs → chunk-R3VSPKNP.mjs} +3 -3
  56. package/dist/{chunk-OPNQAVVH.mjs → chunk-RJI6GKVF.mjs} +8 -6
  57. package/dist/{chunk-WG6JGJXB.mjs → chunk-T4BJLT57.mjs} +1 -1
  58. package/dist/chunk-U4NDAF2P.mjs +207 -0
  59. package/dist/{chunk-DOH3EHX7.mjs → chunk-U5X52X37.mjs} +1 -1
  60. package/dist/chunk-UMTOX62O.mjs +415 -0
  61. package/dist/{chunk-7MMXNK3C.mjs → chunk-VLARHE5V.mjs} +8 -6
  62. package/dist/{chunk-2I5S2AMY.mjs → chunk-XREGSKX3.mjs} +2 -2
  63. package/dist/{chunk-JNQORUPP.mjs → chunk-YJG55G2H.mjs} +14 -11
  64. package/dist/chunk-ZC45IGZO.mjs +388 -0
  65. package/dist/components/ui/add-column-modal.js +42 -14
  66. package/dist/components/ui/add-column-modal.mjs +4 -4
  67. package/dist/components/ui/add-lead-modal.js +42 -11
  68. package/dist/components/ui/add-lead-modal.mjs +3 -3
  69. package/dist/components/ui/advisor-card.js +497 -0
  70. package/dist/components/ui/advisor-card.mjs +13 -0
  71. package/dist/components/ui/ai-assistant-drawer.js +11 -10
  72. package/dist/components/ui/ai-assistant-drawer.mjs +3 -3
  73. package/dist/components/ui/alert-dialog.js +2 -2
  74. package/dist/components/ui/alert-dialog.mjs +2 -2
  75. package/dist/components/ui/appointment-action-dialogs.js +1160 -0
  76. package/dist/components/ui/appointment-action-dialogs.mjs +23 -0
  77. package/dist/components/ui/appointment-availability-settings.js +1590 -0
  78. package/dist/components/ui/appointment-availability-settings.mjs +23 -0
  79. package/dist/components/ui/appointment-book-dialog.js +1744 -0
  80. package/dist/components/ui/appointment-book-dialog.mjs +27 -0
  81. package/dist/components/ui/appointment-calendar-view.js +833 -0
  82. package/dist/components/ui/appointment-calendar-view.mjs +14 -0
  83. package/dist/components/ui/appointment-detail-sheet.js +1517 -0
  84. package/dist/components/ui/appointment-detail-sheet.mjs +24 -0
  85. package/dist/components/ui/appointment-gmail-connect.js +467 -0
  86. package/dist/components/ui/appointment-gmail-connect.mjs +14 -0
  87. package/dist/components/ui/appointment-mini-card.js +345 -0
  88. package/dist/components/ui/appointment-mini-card.mjs +11 -0
  89. package/dist/components/ui/appointment-time-slot-picker.js +311 -0
  90. package/dist/components/ui/appointment-time-slot-picker.mjs +13 -0
  91. package/dist/components/ui/appointment-upcoming-card.js +1268 -0
  92. package/dist/components/ui/appointment-upcoming-card.mjs +21 -0
  93. package/dist/components/ui/backoffice-alert-history-chart.js +11 -5
  94. package/dist/components/ui/backoffice-alert-history-chart.mjs +5 -4
  95. package/dist/components/ui/backoffice-alerts-chart.js +786 -0
  96. package/dist/components/ui/backoffice-alerts-chart.mjs +19 -0
  97. package/dist/components/ui/backoffice-connections-chart.js +817 -0
  98. package/dist/components/ui/backoffice-connections-chart.mjs +19 -0
  99. package/dist/components/ui/backoffice-contact-history-chart.js +11 -5
  100. package/dist/components/ui/backoffice-contact-history-chart.mjs +5 -4
  101. package/dist/components/ui/badge.js +6 -6
  102. package/dist/components/ui/badge.mjs +1 -1
  103. package/dist/components/ui/borrowing-capacity-line-chart.js +30 -21
  104. package/dist/components/ui/borrowing-capacity-line-chart.mjs +5 -4
  105. package/dist/components/ui/button.js +2 -2
  106. package/dist/components/ui/button.mjs +1 -1
  107. package/dist/components/ui/calendar.js +2 -2
  108. package/dist/components/ui/calendar.mjs +2 -2
  109. package/dist/components/ui/card.js +1 -1
  110. package/dist/components/ui/card.mjs +1 -1
  111. package/dist/components/ui/cash-balance-line-chart.js +31 -23
  112. package/dist/components/ui/cash-balance-line-chart.mjs +5 -4
  113. package/dist/components/ui/cashflow-bar-chart.js +12 -5
  114. package/dist/components/ui/cashflow-bar-chart.mjs +5 -4
  115. package/dist/components/ui/chip.js +97 -18
  116. package/dist/components/ui/chip.mjs +3 -2
  117. package/dist/components/ui/color-picker.js +547 -0
  118. package/dist/components/ui/color-picker.mjs +24 -0
  119. package/dist/components/ui/data-table.js +182 -129
  120. package/dist/components/ui/data-table.mjs +3 -2
  121. package/dist/components/ui/date-picker.js +48 -27
  122. package/dist/components/ui/date-picker.mjs +4 -3
  123. package/dist/components/ui/dialog.js +37 -9
  124. package/dist/components/ui/dialog.mjs +2 -2
  125. package/dist/components/ui/expense-bar-chart.js +12 -5
  126. package/dist/components/ui/expense-bar-chart.mjs +5 -4
  127. package/dist/components/ui/field.mjs +2 -2
  128. package/dist/components/ui/financial-cards.js +322 -155
  129. package/dist/components/ui/financial-cards.mjs +5 -3
  130. package/dist/components/ui/financial-drawers.js +2 -2
  131. package/dist/components/ui/financial-drawers.mjs +3 -3
  132. package/dist/components/ui/financial-sections.js +14 -10
  133. package/dist/components/ui/financial-sections.mjs +6 -5
  134. package/dist/components/ui/form-primitives.js +4 -4
  135. package/dist/components/ui/form-primitives.mjs +3 -3
  136. package/dist/components/ui/income-bar-chart.js +12 -5
  137. package/dist/components/ui/income-bar-chart.mjs +5 -4
  138. package/dist/components/ui/input-group.js +2 -2
  139. package/dist/components/ui/input-group.mjs +2 -2
  140. package/dist/components/ui/kanban-column.js +52 -44
  141. package/dist/components/ui/kanban-column.mjs +7 -5
  142. package/dist/components/ui/opportunity-card.js +52 -44
  143. package/dist/components/ui/opportunity-card.mjs +6 -4
  144. package/dist/components/ui/opportunity-edit-modals.js +1371 -1267
  145. package/dist/components/ui/opportunity-edit-modals.mjs +10 -10
  146. package/dist/components/ui/opportunity-summary-tab.js +2748 -2161
  147. package/dist/components/ui/opportunity-summary-tab.mjs +16 -16
  148. package/dist/components/ui/page-header.js +92 -0
  149. package/dist/components/ui/page-header.mjs +8 -0
  150. package/dist/components/ui/page-top-bar.js +88 -0
  151. package/dist/components/ui/page-top-bar.mjs +8 -0
  152. package/dist/components/ui/pagination.js +303 -19
  153. package/dist/components/ui/pagination.mjs +11 -4
  154. package/dist/components/ui/pipeline-board.js +209 -195
  155. package/dist/components/ui/pipeline-board.mjs +10 -8
  156. package/dist/components/ui/pipeline-dialogs.js +118 -69
  157. package/dist/components/ui/pipeline-dialogs.mjs +8 -7
  158. package/dist/components/ui/pipeline-primitives.js +6 -6
  159. package/dist/components/ui/pipeline-primitives.mjs +2 -2
  160. package/dist/components/ui/property-cashflow-doughnut-chart.js +14 -12
  161. package/dist/components/ui/property-cashflow-doughnut-chart.mjs +5 -4
  162. package/dist/components/ui/property-debt-equity-doughnut-chart.js +14 -12
  163. package/dist/components/ui/property-debt-equity-doughnut-chart.mjs +5 -4
  164. package/dist/components/ui/property-mobile-estimate-line-chart.js +16 -14
  165. package/dist/components/ui/property-mobile-estimate-line-chart.mjs +5 -4
  166. package/dist/components/ui/sidebar-nav.js +679 -0
  167. package/dist/components/ui/sidebar-nav.mjs +14 -0
  168. package/dist/components/ui/stage-timeline.js +6 -6
  169. package/dist/components/ui/stage-timeline.mjs +3 -3
  170. package/dist/components/ui/stepper.js +283 -0
  171. package/dist/components/ui/stepper.mjs +18 -0
  172. package/dist/components/ui/toggle-group.js +4 -4
  173. package/dist/components/ui/toggle-group.mjs +2 -2
  174. package/dist/components/ui/toggle.js +4 -4
  175. package/dist/components/ui/toggle.mjs +1 -1
  176. package/dist/components/ui/transactions-expense-categories-doughnut-chart.js +18 -16
  177. package/dist/components/ui/transactions-expense-categories-doughnut-chart.mjs +5 -4
  178. package/dist/components/ui/transactions-income-expense-bar-chart.js +28 -12
  179. package/dist/components/ui/transactions-income-expense-bar-chart.mjs +5 -4
  180. package/dist/components/ui/transactions-liabilities-breakdown-doughnut-chart.js +18 -16
  181. package/dist/components/ui/transactions-liabilities-breakdown-doughnut-chart.mjs +5 -4
  182. package/dist/index.js +12927 -8522
  183. package/dist/index.mjs +288 -190
  184. package/dist/lib/typography.js +10 -10
  185. package/dist/lib/typography.mjs +1 -1
  186. package/dist/styles.css +1 -1
  187. package/package.json +86 -1
  188. package/src/components/index.tsx +146 -0
  189. package/src/components/ui/add-column-modal.tsx +7 -7
  190. package/src/components/ui/add-lead-modal.tsx +6 -3
  191. package/src/components/ui/advisor-card.tsx +227 -0
  192. package/src/components/ui/ai-assistant-drawer.tsx +4 -3
  193. package/src/components/ui/appointment-action-dialogs.tsx +297 -0
  194. package/src/components/ui/appointment-availability-settings.tsx +645 -0
  195. package/src/components/ui/appointment-book-dialog.tsx +618 -0
  196. package/src/components/ui/appointment-calendar-view.tsx +510 -0
  197. package/src/components/ui/appointment-detail-sheet.tsx +415 -0
  198. package/src/components/ui/appointment-gmail-connect.tsx +188 -0
  199. package/src/components/ui/appointment-mini-card.tsx +104 -0
  200. package/src/components/ui/appointment-time-slot-picker.tsx +123 -0
  201. package/src/components/ui/appointment-upcoming-card.tsx +635 -0
  202. package/src/components/ui/backoffice-alert-history-chart.tsx +10 -2
  203. package/src/components/ui/backoffice-alerts-chart.tsx +312 -0
  204. package/src/components/ui/backoffice-connections-chart.tsx +339 -0
  205. package/src/components/ui/backoffice-contact-history-chart.tsx +10 -2
  206. package/src/components/ui/badge.tsx +12 -6
  207. package/src/components/ui/borrowing-capacity-line-chart.tsx +4 -11
  208. package/src/components/ui/button.tsx +2 -2
  209. package/src/components/ui/card.tsx +1 -1
  210. package/src/components/ui/cash-balance-line-chart.tsx +4 -23
  211. package/src/components/ui/cashflow-bar-chart.tsx +9 -2
  212. package/src/components/ui/chart-shared.tsx +4 -11
  213. package/src/components/ui/chip.tsx +23 -19
  214. package/src/components/ui/color-picker.tsx +309 -0
  215. package/src/components/ui/data-table.tsx +117 -83
  216. package/src/components/ui/date-picker.tsx +42 -37
  217. package/src/components/ui/dialog.tsx +72 -6
  218. package/src/components/ui/expense-bar-chart.tsx +11 -2
  219. package/src/components/ui/financial-cards.tsx +99 -10
  220. package/src/components/ui/income-bar-chart.tsx +11 -2
  221. package/src/components/ui/opportunity-card.tsx +10 -39
  222. package/src/components/ui/opportunity-edit-modals.tsx +98 -36
  223. package/src/components/ui/opportunity-summary-tab.tsx +548 -232
  224. package/src/components/ui/page-header.tsx +57 -0
  225. package/src/components/ui/page-top-bar.tsx +48 -0
  226. package/src/components/ui/pagination.tsx +171 -22
  227. package/src/components/ui/pipeline-board.tsx +12 -5
  228. package/src/components/ui/property-cashflow-doughnut-chart.tsx +3 -1
  229. package/src/components/ui/property-debt-equity-doughnut-chart.tsx +3 -1
  230. package/src/components/ui/property-mobile-estimate-line-chart.tsx +3 -1
  231. package/src/components/ui/sidebar-nav.tsx +516 -0
  232. package/src/components/ui/stepper.tsx +347 -0
  233. package/src/components/ui/toggle.tsx +4 -4
  234. package/src/components/ui/transactions-expense-categories-doughnut-chart.tsx +3 -1
  235. package/src/components/ui/transactions-income-expense-bar-chart.tsx +12 -9
  236. package/src/components/ui/transactions-liabilities-breakdown-doughnut-chart.tsx +3 -1
  237. package/src/lib/format-currency.ts +44 -0
  238. package/src/lib/format-date.ts +50 -0
  239. package/src/lib/opportunity-constants.ts +12 -0
  240. package/src/lib/typography.ts +11 -11
  241. package/src/styles/globals.css +36 -34
  242. package/src/styles/styles-css.ts +1 -1
  243. package/tsup.config.ts +17 -0
  244. package/dist/chunk-PG6K5XEC.mjs +0 -475
  245. package/dist/chunk-WA6O6EUR.mjs +0 -1885
  246. package/dist/chunk-WNGWBVLV.mjs +0 -148
  247. package/dist/{chunk-LLVQKSU3.mjs → chunk-GD4BJDJR.mjs} +3 -3
@@ -0,0 +1,516 @@
1
+ /**
2
+ * SidebarNav — WealthX Design System
3
+ *
4
+ * Presentational sidebar navigation organism.
5
+ * Used by both Backoffice (Wealth Pro) and Frontend (WealthX app).
6
+ *
7
+ * Background: bg-brand-secondary (tenant dark navy — set via ThemeProvider).
8
+ * Text: text-brand-brand-secondary-foreground (white on dark navy).
9
+ *
10
+ * - All icons must be Lucide icons (LucideIcon type).
11
+ * - Supports collapsible sub-items (accordion).
12
+ * - Collapsed state: icon-only, metrics hidden.
13
+ * - metricsGroups: optional financial summary rows (Frontend sidebar only).
14
+ * - No internal navigation — consumers wire onNavigate / onLogout.
15
+ */
16
+ import * as React from "react";
17
+ import {
18
+ ChevronDown,
19
+ ChevronRight,
20
+ Info,
21
+ LogOut,
22
+ PanelLeftClose,
23
+ PanelLeftOpen,
24
+ } from "lucide-react";
25
+ import type { LucideIcon } from "lucide-react";
26
+ import { cn } from "@/lib/utils";
27
+ import { formatCurrency } from "@/lib/format-currency";
28
+ import { Button } from "./button";
29
+ import {
30
+ Tooltip,
31
+ TooltipContent,
32
+ TooltipProvider,
33
+ TooltipTrigger,
34
+ } from "@/components/ui/tooltip";
35
+
36
+ // ─── Types ─────────────────────────────────────────────────────────────────────
37
+
38
+ export interface SidebarNavSubItem {
39
+ title: string;
40
+ href: string;
41
+ isActive?: boolean;
42
+ }
43
+
44
+ export interface SidebarNavItem {
45
+ /** Lucide icon component */
46
+ icon: LucideIcon;
47
+ title: string;
48
+ href: string;
49
+ isActive?: boolean;
50
+ /** When true, renders an expandable group with subItems */
51
+ isCollapsible?: boolean;
52
+ subItems?: SidebarNavSubItem[];
53
+ }
54
+
55
+ export interface SidebarNavMetricItem {
56
+ name: string;
57
+ /** Numeric value in dollars — formatted as currency automatically */
58
+ value: number;
59
+ /** Net items show +/- prefix and a primary-color underline */
60
+ isNetItem?: boolean;
61
+ /** Optional tooltip text shown via Info icon */
62
+ info?: string;
63
+ }
64
+
65
+ export interface SidebarNavMetricsGroup {
66
+ items: SidebarNavMetricItem[];
67
+ }
68
+
69
+ export interface SidebarNavProps {
70
+ items: SidebarNavItem[];
71
+ /** Display name for the current user */
72
+ userName?: string;
73
+ /** Whether the sidebar is in narrow (icon-only) mode */
74
+ collapsed?: boolean;
75
+ /**
76
+ * Optional logo URL rendered at the top of the sidebar.
77
+ * Hidden when collapsed (unless logoCollapsed is provided).
78
+ * Use a horizontal/landscape logo (max-height 32px).
79
+ * The image is rendered white via CSS filter — use a logo with non-transparent paths.
80
+ */
81
+ logo?: string;
82
+ /**
83
+ * Optional icon-only logo URL shown when the sidebar is collapsed.
84
+ * Use a square icon variant of the logo (rendered at 32×32px, white filter applied).
85
+ */
86
+ logoCollapsed?: string;
87
+ /**
88
+ * Optional financial metric groups rendered between the user section and
89
+ * nav items. Hidden when collapsed. Used by the Frontend (WealthX app) sidebar.
90
+ */
91
+ metricsGroups?: SidebarNavMetricsGroup[];
92
+ onNavigate?: (href: string) => void;
93
+ onLogout?: () => void;
94
+ /** Called when the user clicks the expand/collapse toggle button at the bottom */
95
+ onCollapsedChange?: (collapsed: boolean) => void;
96
+ className?: string;
97
+ }
98
+
99
+ // ─── Helpers ───────────────────────────────────────────────────────────────────
100
+
101
+ function getInitials(name: string): string {
102
+ return name
103
+ .split(" ")
104
+ .filter(Boolean)
105
+ .map((word) => word[0])
106
+ .join("")
107
+ .toUpperCase()
108
+ .slice(0, 2);
109
+ }
110
+
111
+
112
+ function navIconCn(isActive: boolean): string {
113
+ return cn(
114
+ "shrink-0 transition-colors",
115
+ isActive
116
+ ? "text-primary"
117
+ : "text-brand-secondary-foreground/50 group-hover:text-brand-secondary-foreground",
118
+ );
119
+ }
120
+
121
+ // ─── NavTooltip ────────────────────────────────────────────────────────────────
122
+ // Wraps content in a right-side tooltip only when the sidebar is collapsed.
123
+
124
+ interface NavTooltipProps {
125
+ label: string;
126
+ collapsed: boolean;
127
+ children: React.ReactElement;
128
+ }
129
+
130
+ function NavTooltip({ label, collapsed, children }: NavTooltipProps) {
131
+ if (!collapsed) return children;
132
+ return (
133
+ <Tooltip>
134
+ <TooltipTrigger render={children} />
135
+ <TooltipContent side="right">{label}</TooltipContent>
136
+ </Tooltip>
137
+ );
138
+ }
139
+
140
+ // ─── MetricsGroup ──────────────────────────────────────────────────────────────
141
+
142
+ interface MetricsGroupProps {
143
+ group: SidebarNavMetricsGroup;
144
+ }
145
+
146
+ function MetricsGroup({ group }: MetricsGroupProps) {
147
+ return (
148
+ <div className="border-b border-white/15 py-4 px-5 flex flex-col gap-1.5">
149
+ {group.items.map((item) => (
150
+ <div
151
+ key={item.name}
152
+ className="flex items-center justify-between gap-2"
153
+ >
154
+ <div className="flex items-center gap-1 min-w-0">
155
+ <span
156
+ className={cn(
157
+ "text-sm truncate text-brand-secondary-foreground/80",
158
+ item.isNetItem &&
159
+ "font-semibold text-brand-secondary-foreground border-b-2 border-primary pb-px",
160
+ )}
161
+ >
162
+ {item.name}
163
+ </span>
164
+ {item.info && (
165
+ <Info
166
+ size={11}
167
+ strokeWidth={2}
168
+ className="shrink-0 text-brand-secondary-foreground/50"
169
+ />
170
+ )}
171
+ </div>
172
+ <span
173
+ className={cn(
174
+ "text-sm font-semibold tabular-nums shrink-0 text-brand-secondary-foreground",
175
+ item.isNetItem && item.value < 0 && "text-destructive",
176
+ )}
177
+ >
178
+ {formatCurrency(item.value, { showSign: item.isNetItem })}
179
+ </span>
180
+ </div>
181
+ ))}
182
+ </div>
183
+ );
184
+ }
185
+
186
+ // ─── SidebarNavItemView (single link) ──────────────────────────────────────────
187
+
188
+ interface SidebarNavItemViewProps {
189
+ item: SidebarNavItem;
190
+ collapsed: boolean;
191
+ onNavigate?: (href: string) => void;
192
+ }
193
+
194
+ function SidebarNavItemView({
195
+ item,
196
+ collapsed,
197
+ onNavigate,
198
+ }: SidebarNavItemViewProps) {
199
+ const Icon = item.icon;
200
+
201
+ return (
202
+ <NavTooltip label={item.title} collapsed={collapsed}>
203
+ <Button
204
+ type="button"
205
+ variant="ghost"
206
+ onClick={() => onNavigate?.(item.href)}
207
+ className={cn(
208
+ "group h-auto w-full items-center gap-3 py-2.5 text-base font-medium transition-colors",
209
+ "text-brand-secondary-foreground/70 hover:bg-white/10 hover:text-brand-secondary-foreground",
210
+ collapsed
211
+ ? "justify-center px-2"
212
+ : cn(
213
+ "justify-start px-3 border-l-4",
214
+ item.isActive
215
+ ? "bg-white/15 text-brand-secondary-foreground border-primary"
216
+ : "border-transparent",
217
+ ),
218
+ )}
219
+ >
220
+ <Icon
221
+ className={navIconCn(item.isActive ?? false)}
222
+ size={18}
223
+ strokeWidth={1.75}
224
+ />
225
+ {!collapsed && <span className="truncate">{item.title}</span>}
226
+ </Button>
227
+ </NavTooltip>
228
+ );
229
+ }
230
+
231
+ // ─── CollapsibleNavItem (accordion group) ──────────────────────────────────────
232
+
233
+ interface CollapsibleNavItemProps {
234
+ item: SidebarNavItem;
235
+ collapsed: boolean;
236
+ onNavigate?: (href: string) => void;
237
+ }
238
+
239
+ function CollapsibleNavItem({
240
+ item,
241
+ collapsed,
242
+ onNavigate,
243
+ }: CollapsibleNavItemProps) {
244
+ const Icon = item.icon;
245
+ const hasActiveChild = item.subItems?.some((sub) => sub.isActive) ?? false;
246
+ const [open, setOpen] = React.useState(hasActiveChild);
247
+
248
+ React.useEffect(() => {
249
+ if (hasActiveChild) setOpen(true);
250
+ }, [hasActiveChild]);
251
+
252
+ if (collapsed) {
253
+ return (
254
+ <NavTooltip label={item.title} collapsed={collapsed}>
255
+ <Button
256
+ type="button"
257
+ variant="ghost"
258
+ onClick={() => onNavigate?.(item.href)}
259
+ className={cn(
260
+ "group h-auto w-full justify-center px-2 py-2.5 transition-colors",
261
+ "text-brand-secondary-foreground/70 hover:bg-white/10 hover:text-brand-secondary-foreground",
262
+ hasActiveChild && "bg-white/15 text-brand-secondary-foreground",
263
+ )}
264
+ >
265
+ <Icon
266
+ className={navIconCn(hasActiveChild)}
267
+ size={18}
268
+ strokeWidth={1.75}
269
+ />
270
+ </Button>
271
+ </NavTooltip>
272
+ );
273
+ }
274
+
275
+ return (
276
+ <div>
277
+ <Button
278
+ type="button"
279
+ variant="ghost"
280
+ onClick={() => setOpen((prev) => !prev)}
281
+ className={cn(
282
+ "group h-auto w-full justify-start gap-3 px-3 py-2.5 text-base font-medium transition-colors",
283
+ "text-brand-secondary-foreground/70 hover:bg-white/10 hover:text-brand-secondary-foreground",
284
+ "border-l-4 border-transparent",
285
+ hasActiveChild &&
286
+ "bg-white/15 text-brand-secondary-foreground border-primary",
287
+ )}
288
+ >
289
+ <Icon
290
+ className={navIconCn(hasActiveChild)}
291
+ size={18}
292
+ strokeWidth={1.75}
293
+ />
294
+ <span className="flex-1 truncate text-left">{item.title}</span>
295
+ <ChevronDown
296
+ className={cn(
297
+ "ml-auto shrink-0 text-brand-secondary-foreground/40 transition-transform duration-200",
298
+ open && "rotate-180",
299
+ )}
300
+ size={14}
301
+ strokeWidth={2}
302
+ />
303
+ </Button>
304
+
305
+ {open && item.subItems && (
306
+ <div className="ml-9 border-l border-white/15 pl-3">
307
+ {item.subItems.map((sub) => (
308
+ <Button
309
+ key={sub.href}
310
+ type="button"
311
+ variant="ghost"
312
+ onClick={() => onNavigate?.(sub.href)}
313
+ className={cn(
314
+ "h-auto w-full justify-start gap-2 py-1.5 pl-1 text-sm transition-colors",
315
+ "text-brand-secondary-foreground/50 hover:text-brand-secondary-foreground",
316
+ sub.isActive && "text-primary font-medium",
317
+ )}
318
+ >
319
+ <ChevronRight
320
+ size={11}
321
+ strokeWidth={2}
322
+ className={cn(
323
+ "shrink-0",
324
+ sub.isActive
325
+ ? "text-primary"
326
+ : "text-brand-secondary-foreground/30",
327
+ )}
328
+ />
329
+ <span className="truncate">{sub.title}</span>
330
+ </Button>
331
+ ))}
332
+ </div>
333
+ )}
334
+ </div>
335
+ );
336
+ }
337
+
338
+ // ─── SidebarNav (root) ─────────────────────────────────────────────────────────
339
+
340
+ export function SidebarNav({
341
+ items,
342
+ userName = "Anonymous User",
343
+ collapsed = false,
344
+ logo,
345
+ logoCollapsed,
346
+ metricsGroups,
347
+ onNavigate,
348
+ onLogout,
349
+ onCollapsedChange,
350
+ className,
351
+ }: SidebarNavProps) {
352
+ const [userMenuOpen, setUserMenuOpen] = React.useState(false);
353
+
354
+ return (
355
+ <TooltipProvider>
356
+ <nav
357
+ data-slot="sidebar-nav"
358
+ data-collapsed={collapsed}
359
+ className={cn(
360
+ // Force dark-mode CSS variable resolution — sidebar is always dark-backgrounded
361
+ // regardless of system theme, so semantic tokens (destructive, success, etc.)
362
+ // must use their dark-mode values to maintain WCAG contrast.
363
+ "dark flex h-full flex-col bg-brand-secondary text-brand-secondary-foreground",
364
+ "transition-all duration-200 ease-in-out",
365
+ collapsed ? "w-14" : "w-[279px]",
366
+ className,
367
+ )}
368
+ >
369
+ {/* Logo */}
370
+ {!collapsed && logo && (
371
+ <div className="flex items-center border-b border-white/15 px-5 py-4">
372
+ <img
373
+ src={logo}
374
+ alt="Logo"
375
+ className="h-8 w-auto object-contain object-left"
376
+ style={{ filter: "brightness(0) invert(1)" }}
377
+ />
378
+ </div>
379
+ )}
380
+ {collapsed && logoCollapsed && (
381
+ <div className="flex items-center justify-center border-b border-white/15 py-4">
382
+ <img
383
+ src={logoCollapsed}
384
+ alt="Logo"
385
+ className="h-8 w-8 object-contain"
386
+ style={{ filter: "brightness(0) invert(1)" }}
387
+ />
388
+ </div>
389
+ )}
390
+
391
+ {/* User section */}
392
+ <div className="border-b border-white/15">
393
+ <NavTooltip label={userName} collapsed={collapsed}>
394
+ <Button
395
+ type="button"
396
+ variant="ghost"
397
+ onClick={() => !collapsed && setUserMenuOpen((prev) => !prev)}
398
+ className={cn(
399
+ "group h-auto w-full justify-start gap-3 px-5 py-5 text-base transition-colors",
400
+ "text-brand-secondary-foreground hover:bg-white/10",
401
+ collapsed && "justify-center px-2 py-4",
402
+ )}
403
+ >
404
+ <div className="flex h-8 w-8 shrink-0 items-center justify-center font-semibold text-xs bg-primary text-primary-foreground">
405
+ {getInitials(userName)}
406
+ </div>
407
+ {!collapsed && (
408
+ <>
409
+ <span className="flex-1 truncate text-left font-medium text-brand-secondary-foreground">
410
+ {userName}
411
+ </span>
412
+ <ChevronDown
413
+ className={cn(
414
+ "shrink-0 text-brand-secondary-foreground/50 transition-transform duration-200",
415
+ userMenuOpen && "rotate-180",
416
+ )}
417
+ size={16}
418
+ strokeWidth={2}
419
+ />
420
+ </>
421
+ )}
422
+ </Button>
423
+ </NavTooltip>
424
+
425
+ {/* Logout dropdown */}
426
+ {!collapsed && userMenuOpen && (
427
+ <div className="border-t border-white/15 bg-black/20">
428
+ <Button
429
+ type="button"
430
+ variant="ghost"
431
+ onClick={onLogout}
432
+ className={cn(
433
+ "h-auto w-full justify-start gap-3 px-5 py-3 text-base",
434
+ "text-brand-secondary-foreground/70 hover:bg-white/10 hover:text-brand-secondary-foreground transition-colors",
435
+ )}
436
+ >
437
+ <LogOut
438
+ size={16}
439
+ strokeWidth={1.75}
440
+ className="shrink-0 text-destructive"
441
+ />
442
+ <span>Logout</span>
443
+ </Button>
444
+ </div>
445
+ )}
446
+ </div>
447
+
448
+ {/* Financial metrics (Frontend sidebar only, hidden when collapsed) */}
449
+ {!collapsed && !!metricsGroups?.length && (
450
+ <div>
451
+ {metricsGroups.map((group, i) => (
452
+ <MetricsGroup key={i} group={group} />
453
+ ))}
454
+ </div>
455
+ )}
456
+
457
+ {/* Nav items */}
458
+ <div className="flex flex-col overflow-y-auto py-3">
459
+ {items.map((item) =>
460
+ item.isCollapsible ? (
461
+ <CollapsibleNavItem
462
+ key={item.href}
463
+ item={item}
464
+ collapsed={collapsed}
465
+ onNavigate={onNavigate}
466
+ />
467
+ ) : (
468
+ <SidebarNavItemView
469
+ key={item.href}
470
+ item={item}
471
+ collapsed={collapsed}
472
+ onNavigate={onNavigate}
473
+ />
474
+ ),
475
+ )}
476
+ </div>
477
+
478
+ {/* Expand/collapse toggle */}
479
+ {onCollapsedChange && (
480
+ <div className="mt-auto border-t border-white/15 bg-white/8">
481
+ <NavTooltip
482
+ label={collapsed ? "Expand" : "Collapse"}
483
+ collapsed={collapsed}
484
+ >
485
+ <Button
486
+ type="button"
487
+ variant="ghost"
488
+ onClick={() => onCollapsedChange(!collapsed)}
489
+ className={cn(
490
+ "h-auto w-full justify-start gap-3 px-3 py-3 transition-colors",
491
+ "text-brand-secondary-foreground/80 hover:bg-white/10 hover:text-brand-secondary-foreground",
492
+ collapsed && "justify-center px-2",
493
+ )}
494
+ >
495
+ {collapsed ? (
496
+ <PanelLeftOpen
497
+ size={18}
498
+ strokeWidth={1.75}
499
+ className="shrink-0"
500
+ />
501
+ ) : (
502
+ <PanelLeftClose
503
+ size={18}
504
+ strokeWidth={1.75}
505
+ className="shrink-0"
506
+ />
507
+ )}
508
+ {!collapsed && <span className="text-sm">Collapse</span>}
509
+ </Button>
510
+ </NavTooltip>
511
+ </div>
512
+ )}
513
+ </nav>
514
+ </TooltipProvider>
515
+ );
516
+ }