@wealthx/shadcn 1.2.2 → 1.3.1
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.
- package/.turbo/turbo-build.log +193 -149
- package/CHANGELOG.md +28 -0
- package/dist/{chunk-4Y6R4WEC.mjs → chunk-2A5RRQGG.mjs} +9 -22
- package/dist/{chunk-TS2ZX2VS.mjs → chunk-2UM72RJ7.mjs} +11 -15
- package/dist/{chunk-A56YQQHG.mjs → chunk-3NCUZIFP.mjs} +2 -2
- package/dist/chunk-3OYFOX3X.mjs +79 -0
- package/dist/{chunk-RP3SQYA3.mjs → chunk-3TTACBDP.mjs} +9 -4
- package/dist/chunk-4GAWMKMI.mjs +710 -0
- package/dist/{chunk-VGSESELX.mjs → chunk-5FQIKDKP.mjs} +5 -5
- package/dist/{chunk-K3JYD4IU.mjs → chunk-5IS7G74I.mjs} +11 -4
- package/dist/chunk-6AW4KJHE.mjs +235 -0
- package/dist/chunk-6CR5N2JW.mjs +302 -0
- package/dist/{chunk-XIRTEFKH.mjs → chunk-6DZEXFNB.mjs} +36 -8
- package/dist/chunk-6O6KD7CE.mjs +271 -0
- package/dist/chunk-7PV3IWCN.mjs +33 -0
- package/dist/{chunk-SPJ5KXW7.mjs → chunk-7S5AESZO.mjs} +5 -5
- package/dist/{chunk-RYCLWMZ7.mjs → chunk-ABFDMHOR.mjs} +9 -7
- package/dist/{chunk-SWGT756Z.mjs → chunk-AMQZRHEZ.mjs} +10 -4
- package/dist/{chunk-WAZD7NFU.mjs → chunk-BKNFWEH2.mjs} +6 -6
- package/dist/{chunk-CLIN5525.mjs → chunk-C7CQJNMR.mjs} +1 -1
- package/dist/{chunk-D4ILTPOG.mjs → chunk-CFMQP5QS.mjs} +5 -4
- package/dist/{chunk-VPBN3WOO.mjs → chunk-DGHAXJBN.mjs} +9 -7
- package/dist/chunk-DOEO3CDL.mjs +27 -0
- package/dist/{chunk-5MEWU56Z.mjs → chunk-DUJTAXMH.mjs} +11 -6
- package/dist/{chunk-GGM2UYGG.mjs → chunk-EBXQWIYG.mjs} +10 -4
- package/dist/chunk-EWRB4PAD.mjs +468 -0
- package/dist/{chunk-ZSHYDDRB.mjs → chunk-FAKPBKLT.mjs} +6 -2
- package/dist/{chunk-A6AAWBPF.mjs → chunk-GHC7LLUX.mjs} +13 -4
- package/dist/chunk-HBZLGDIN.mjs +507 -0
- package/dist/{chunk-SIZMLSRU.mjs → chunk-HISNT2MG.mjs} +8 -6
- package/dist/{chunk-CGH4DRNG.mjs → chunk-HVY6KCCF.mjs} +10 -7
- package/dist/chunk-I3RZS7V2.mjs +136 -0
- package/dist/chunk-IAE3F7DR.mjs +1962 -0
- package/dist/{chunk-UT4KJR7V.mjs → chunk-IHMFS7NZ.mjs} +35 -74
- package/dist/{chunk-PCPLO5HT.mjs → chunk-IOJRDS6V.mjs} +96 -14
- package/dist/{chunk-LHYCMLVA.mjs → chunk-JKGDCQTZ.mjs} +11 -4
- package/dist/{chunk-H45TKD34.mjs → chunk-JMHR3YGZ.mjs} +1 -1
- package/dist/{chunk-4MN6UQHG.mjs → chunk-K5A5L6T2.mjs} +17 -39
- package/dist/chunk-LV35NGVG.mjs +272 -0
- package/dist/{chunk-FZIXGLMV.mjs → chunk-M3FV7LOK.mjs} +5 -12
- package/dist/{chunk-FMAXJ2SI.mjs → chunk-MBON7YRJ.mjs} +1 -1
- package/dist/chunk-MIZQHHUO.mjs +441 -0
- package/dist/chunk-MLNEWRWV.mjs +449 -0
- package/dist/chunk-MN5NYQCL.mjs +29 -0
- package/dist/chunk-NL3ZO62D.mjs +31 -0
- package/dist/{chunk-Q76O3RIQ.mjs → chunk-NMOI6CQD.mjs} +1 -1
- package/dist/{chunk-P6AM5V7O.mjs → chunk-OODBHKG7.mjs} +1 -1
- package/dist/chunk-PBL4OQV2.mjs +283 -0
- package/dist/{chunk-Y4QFWRNR.mjs → chunk-PU4YZQXV.mjs} +17 -18
- package/dist/chunk-Q2BGOAMG.mjs +202 -0
- package/dist/chunk-QMY3AZJH.mjs +80 -0
- package/dist/{chunk-BL3DXM2X.mjs → chunk-QZ4RE6NA.mjs} +11 -4
- package/dist/{chunk-VACKZOMY.mjs → chunk-R3VSPKNP.mjs} +3 -3
- package/dist/{chunk-OPNQAVVH.mjs → chunk-RJI6GKVF.mjs} +8 -6
- package/dist/{chunk-WG6JGJXB.mjs → chunk-T4BJLT57.mjs} +1 -1
- package/dist/chunk-UMTOX62O.mjs +415 -0
- package/dist/{chunk-7MMXNK3C.mjs → chunk-VLARHE5V.mjs} +8 -6
- package/dist/{chunk-2I5S2AMY.mjs → chunk-XREGSKX3.mjs} +2 -2
- package/dist/{chunk-JNQORUPP.mjs → chunk-YJG55G2H.mjs} +14 -11
- package/dist/components/ui/add-column-modal.js +42 -14
- package/dist/components/ui/add-column-modal.mjs +5 -5
- package/dist/components/ui/add-lead-modal.js +42 -11
- package/dist/components/ui/add-lead-modal.mjs +3 -3
- package/dist/components/ui/advisor-card.js +530 -0
- package/dist/components/ui/advisor-card.mjs +15 -0
- package/dist/components/ui/ai-assistant-drawer.js +11 -10
- package/dist/components/ui/ai-assistant-drawer.mjs +3 -3
- package/dist/components/ui/alert-dialog.js +2 -2
- package/dist/components/ui/alert-dialog.mjs +2 -2
- package/dist/components/ui/appointment-action-dialogs.js +1160 -0
- package/dist/components/ui/appointment-action-dialogs.mjs +23 -0
- package/dist/components/ui/appointment-availability-settings.js +1590 -0
- package/dist/components/ui/appointment-availability-settings.mjs +23 -0
- package/dist/components/ui/appointment-book-dialog.js +1744 -0
- package/dist/components/ui/appointment-book-dialog.mjs +27 -0
- package/dist/components/ui/appointment-calendar-view.js +833 -0
- package/dist/components/ui/appointment-calendar-view.mjs +14 -0
- package/dist/components/ui/appointment-detail-sheet.js +1517 -0
- package/dist/components/ui/appointment-detail-sheet.mjs +24 -0
- package/dist/components/ui/appointment-gmail-connect.js +467 -0
- package/dist/components/ui/appointment-gmail-connect.mjs +14 -0
- package/dist/components/ui/appointment-mini-card.js +345 -0
- package/dist/components/ui/appointment-mini-card.mjs +11 -0
- package/dist/components/ui/appointment-time-slot-picker.js +311 -0
- package/dist/components/ui/appointment-time-slot-picker.mjs +13 -0
- package/dist/components/ui/appointment-upcoming-card.js +1268 -0
- package/dist/components/ui/appointment-upcoming-card.mjs +21 -0
- package/dist/components/ui/backoffice-alert-history-chart.js +11 -5
- package/dist/components/ui/backoffice-alert-history-chart.mjs +5 -4
- package/dist/components/ui/backoffice-alerts-chart.js +786 -0
- package/dist/components/ui/backoffice-alerts-chart.mjs +19 -0
- package/dist/components/ui/backoffice-connections-chart.js +817 -0
- package/dist/components/ui/backoffice-connections-chart.mjs +19 -0
- package/dist/components/ui/backoffice-contact-history-chart.js +11 -5
- package/dist/components/ui/backoffice-contact-history-chart.mjs +5 -4
- package/dist/components/ui/badge.js +6 -6
- package/dist/components/ui/badge.mjs +1 -1
- package/dist/components/ui/borrowing-capacity-line-chart.js +30 -21
- package/dist/components/ui/borrowing-capacity-line-chart.mjs +5 -4
- package/dist/components/ui/button.js +2 -2
- package/dist/components/ui/button.mjs +1 -1
- package/dist/components/ui/calendar.js +2 -2
- package/dist/components/ui/calendar.mjs +2 -2
- package/dist/components/ui/card.js +1 -1
- package/dist/components/ui/card.mjs +1 -1
- package/dist/components/ui/cash-balance-line-chart.js +31 -23
- package/dist/components/ui/cash-balance-line-chart.mjs +5 -4
- package/dist/components/ui/cashflow-bar-chart.js +12 -5
- package/dist/components/ui/cashflow-bar-chart.mjs +5 -4
- package/dist/components/ui/chip.js +97 -18
- package/dist/components/ui/chip.mjs +3 -2
- package/dist/components/ui/color-picker.js +158 -28
- package/dist/components/ui/color-picker.mjs +3 -1
- package/dist/components/ui/data-table.js +140 -119
- package/dist/components/ui/data-table.mjs +3 -2
- package/dist/components/ui/date-picker.js +48 -27
- package/dist/components/ui/date-picker.mjs +4 -3
- package/dist/components/ui/dialog.js +37 -9
- package/dist/components/ui/dialog.mjs +2 -2
- package/dist/components/ui/expense-bar-chart.js +12 -5
- package/dist/components/ui/expense-bar-chart.mjs +5 -4
- package/dist/components/ui/field.mjs +2 -2
- package/dist/components/ui/financial-cards.js +322 -155
- package/dist/components/ui/financial-cards.mjs +5 -3
- package/dist/components/ui/financial-drawers.js +2 -2
- package/dist/components/ui/financial-drawers.mjs +3 -3
- package/dist/components/ui/financial-sections.js +14 -10
- package/dist/components/ui/financial-sections.mjs +6 -5
- package/dist/components/ui/income-bar-chart.js +12 -5
- package/dist/components/ui/income-bar-chart.mjs +5 -4
- package/dist/components/ui/input-group.js +2 -2
- package/dist/components/ui/input-group.mjs +2 -2
- package/dist/components/ui/kanban-column.js +52 -44
- package/dist/components/ui/kanban-column.mjs +7 -5
- package/dist/components/ui/opportunity-card.js +52 -44
- package/dist/components/ui/opportunity-card.mjs +6 -4
- package/dist/components/ui/opportunity-edit-modals.js +1367 -1263
- package/dist/components/ui/opportunity-edit-modals.mjs +8 -8
- package/dist/components/ui/opportunity-summary-tab.js +2744 -2157
- package/dist/components/ui/opportunity-summary-tab.mjs +14 -14
- package/dist/components/ui/page-header.js +92 -0
- package/dist/components/ui/page-header.mjs +8 -0
- package/dist/components/ui/page-top-bar.js +88 -0
- package/dist/components/ui/page-top-bar.mjs +8 -0
- package/dist/components/ui/pagination.js +303 -19
- package/dist/components/ui/pagination.mjs +11 -4
- package/dist/components/ui/pipeline-board.js +205 -191
- package/dist/components/ui/pipeline-board.mjs +9 -7
- package/dist/components/ui/pipeline-dialogs.js +114 -65
- package/dist/components/ui/pipeline-dialogs.mjs +7 -6
- package/dist/components/ui/pipeline-primitives.js +6 -6
- package/dist/components/ui/pipeline-primitives.mjs +2 -2
- package/dist/components/ui/property-cashflow-doughnut-chart.js +14 -12
- package/dist/components/ui/property-cashflow-doughnut-chart.mjs +5 -4
- package/dist/components/ui/property-debt-equity-doughnut-chart.js +14 -12
- package/dist/components/ui/property-debt-equity-doughnut-chart.mjs +5 -4
- package/dist/components/ui/property-mobile-estimate-line-chart.js +16 -14
- package/dist/components/ui/property-mobile-estimate-line-chart.mjs +5 -4
- package/dist/components/ui/sidebar-nav.js +426 -191
- package/dist/components/ui/sidebar-nav.mjs +5 -1
- package/dist/components/ui/stage-timeline.js +6 -6
- package/dist/components/ui/stage-timeline.mjs +3 -3
- package/dist/components/ui/transactions-expense-categories-doughnut-chart.js +18 -16
- package/dist/components/ui/transactions-expense-categories-doughnut-chart.mjs +5 -4
- package/dist/components/ui/transactions-income-expense-bar-chart.js +28 -12
- package/dist/components/ui/transactions-income-expense-bar-chart.mjs +5 -4
- package/dist/components/ui/transactions-liabilities-breakdown-doughnut-chart.js +18 -16
- package/dist/components/ui/transactions-liabilities-breakdown-doughnut-chart.mjs +5 -4
- package/dist/index.js +12258 -8611
- package/dist/index.mjs +258 -190
- package/dist/styles.css +1 -1
- package/package.json +71 -1
- package/src/components/index.tsx +115 -9
- package/src/components/ui/add-column-modal.tsx +7 -7
- package/src/components/ui/add-lead-modal.tsx +6 -3
- package/src/components/ui/advisor-card.tsx +284 -0
- package/src/components/ui/ai-assistant-drawer.tsx +4 -3
- package/src/components/ui/appointment-action-dialogs.tsx +297 -0
- package/src/components/ui/appointment-availability-settings.tsx +645 -0
- package/src/components/ui/appointment-book-dialog.tsx +618 -0
- package/src/components/ui/appointment-calendar-view.tsx +510 -0
- package/src/components/ui/appointment-detail-sheet.tsx +415 -0
- package/src/components/ui/appointment-gmail-connect.tsx +188 -0
- package/src/components/ui/appointment-mini-card.tsx +104 -0
- package/src/components/ui/appointment-time-slot-picker.tsx +123 -0
- package/src/components/ui/appointment-upcoming-card.tsx +635 -0
- package/src/components/ui/backoffice-alert-history-chart.tsx +10 -2
- package/src/components/ui/backoffice-alerts-chart.tsx +312 -0
- package/src/components/ui/backoffice-connections-chart.tsx +339 -0
- package/src/components/ui/backoffice-contact-history-chart.tsx +10 -2
- package/src/components/ui/badge.tsx +12 -6
- package/src/components/ui/borrowing-capacity-line-chart.tsx +4 -11
- package/src/components/ui/button.tsx +2 -2
- package/src/components/ui/card.tsx +1 -1
- package/src/components/ui/cash-balance-line-chart.tsx +4 -23
- package/src/components/ui/cashflow-bar-chart.tsx +9 -2
- package/src/components/ui/chart-shared.tsx +4 -11
- package/src/components/ui/chip.tsx +23 -19
- package/src/components/ui/color-picker.tsx +4 -2
- package/src/components/ui/data-table.tsx +28 -74
- package/src/components/ui/date-picker.tsx +42 -37
- package/src/components/ui/dialog.tsx +72 -6
- package/src/components/ui/expense-bar-chart.tsx +11 -2
- package/src/components/ui/financial-cards.tsx +99 -10
- package/src/components/ui/income-bar-chart.tsx +11 -2
- package/src/components/ui/opportunity-card.tsx +10 -39
- package/src/components/ui/opportunity-edit-modals.tsx +98 -36
- package/src/components/ui/opportunity-summary-tab.tsx +548 -232
- package/src/components/ui/page-header.tsx +57 -0
- package/src/components/ui/page-top-bar.tsx +48 -0
- package/src/components/ui/pagination.tsx +171 -22
- package/src/components/ui/pipeline-board.tsx +12 -5
- package/src/components/ui/property-cashflow-doughnut-chart.tsx +3 -1
- package/src/components/ui/property-debt-equity-doughnut-chart.tsx +3 -1
- package/src/components/ui/property-mobile-estimate-line-chart.tsx +3 -1
- package/src/components/ui/sidebar-nav.tsx +213 -157
- package/src/components/ui/transactions-expense-categories-doughnut-chart.tsx +3 -1
- package/src/components/ui/transactions-income-expense-bar-chart.tsx +12 -9
- package/src/components/ui/transactions-liabilities-breakdown-doughnut-chart.tsx +3 -1
- package/src/lib/format-currency.ts +44 -0
- package/src/lib/format-date.ts +50 -0
- package/src/lib/opportunity-constants.ts +12 -0
- package/src/styles/globals.css +17 -15
- package/src/styles/styles-css.ts +1 -1
- package/tsup.config.ts +14 -0
- package/dist/chunk-S4QRUQNW.mjs +0 -475
- package/dist/chunk-URGMJAE3.mjs +0 -1885
- package/dist/chunk-WNGWBVLV.mjs +0 -148
- package/dist/chunk-ZRSDX6OW.mjs +0 -385
- package/dist/{chunk-LLVQKSU3.mjs → chunk-GD4BJDJR.mjs} +3 -3
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
*
|
|
10
10
|
* - All icons must be Lucide icons (LucideIcon type).
|
|
11
11
|
* - Supports collapsible sub-items (accordion).
|
|
12
|
-
* - Collapsed state: icon-only, metrics
|
|
12
|
+
* - Collapsed state: icon-only, metrics animated out.
|
|
13
13
|
* - metricsGroups: optional financial summary rows (Frontend sidebar only).
|
|
14
14
|
* - No internal navigation — consumers wire onNavigate / onLogout.
|
|
15
15
|
*/
|
|
@@ -23,7 +23,11 @@ import {
|
|
|
23
23
|
PanelLeftOpen,
|
|
24
24
|
} from "lucide-react";
|
|
25
25
|
import type { LucideIcon } from "lucide-react";
|
|
26
|
+
import { Accordion as AccordionPrimitive } from "@base-ui/react/accordion";
|
|
26
27
|
import { cn } from "@/lib/utils";
|
|
28
|
+
import { formatCurrency } from "@/lib/format-currency";
|
|
29
|
+
import { Accordion, AccordionContent, AccordionItem } from "./accordion";
|
|
30
|
+
import { Button } from "./button";
|
|
27
31
|
import {
|
|
28
32
|
Tooltip,
|
|
29
33
|
TooltipContent,
|
|
@@ -106,20 +110,6 @@ function getInitials(name: string): string {
|
|
|
106
110
|
.slice(0, 2);
|
|
107
111
|
}
|
|
108
112
|
|
|
109
|
-
function formatCurrency(value: number, isNetItem = false): string {
|
|
110
|
-
const abs = Math.abs(value);
|
|
111
|
-
const formatted = new Intl.NumberFormat("en-AU", {
|
|
112
|
-
style: "currency",
|
|
113
|
-
currency: "AUD",
|
|
114
|
-
minimumFractionDigits: 0,
|
|
115
|
-
maximumFractionDigits: 0,
|
|
116
|
-
}).format(abs);
|
|
117
|
-
if (!isNetItem) return formatted;
|
|
118
|
-
if (value > 0) return `+${formatted}`;
|
|
119
|
-
if (value < 0) return `-${formatted}`;
|
|
120
|
-
return formatted;
|
|
121
|
-
}
|
|
122
|
-
|
|
123
113
|
function navIconCn(isActive: boolean): string {
|
|
124
114
|
return cn(
|
|
125
115
|
"shrink-0 transition-colors",
|
|
@@ -186,7 +176,7 @@ function MetricsGroup({ group }: MetricsGroupProps) {
|
|
|
186
176
|
item.isNetItem && item.value < 0 && "text-destructive",
|
|
187
177
|
)}
|
|
188
178
|
>
|
|
189
|
-
{formatCurrency(item.value, item.isNetItem)}
|
|
179
|
+
{formatCurrency(item.value, { showSign: item.isNetItem })}
|
|
190
180
|
</span>
|
|
191
181
|
</div>
|
|
192
182
|
))}
|
|
@@ -211,16 +201,17 @@ function SidebarNavItemView({
|
|
|
211
201
|
|
|
212
202
|
return (
|
|
213
203
|
<NavTooltip label={item.title} collapsed={collapsed}>
|
|
214
|
-
<
|
|
204
|
+
<Button
|
|
215
205
|
type="button"
|
|
206
|
+
variant="ghost"
|
|
216
207
|
onClick={() => onNavigate?.(item.href)}
|
|
217
208
|
className={cn(
|
|
218
|
-
"group
|
|
209
|
+
"group h-auto w-full items-center gap-3 py-2.5 text-base font-medium transition-colors",
|
|
219
210
|
"text-brand-secondary-foreground/70 hover:bg-white/10 hover:text-brand-secondary-foreground",
|
|
220
211
|
collapsed
|
|
221
212
|
? "justify-center px-2"
|
|
222
213
|
: cn(
|
|
223
|
-
"px-3 border-l-4",
|
|
214
|
+
"justify-start px-3 border-l-4",
|
|
224
215
|
item.isActive
|
|
225
216
|
? "bg-white/15 text-brand-secondary-foreground border-primary"
|
|
226
217
|
: "border-transparent",
|
|
@@ -229,11 +220,11 @@ function SidebarNavItemView({
|
|
|
229
220
|
>
|
|
230
221
|
<Icon
|
|
231
222
|
className={navIconCn(item.isActive ?? false)}
|
|
232
|
-
size={
|
|
223
|
+
size={24}
|
|
233
224
|
strokeWidth={1.75}
|
|
234
225
|
/>
|
|
235
226
|
{!collapsed && <span className="truncate">{item.title}</span>}
|
|
236
|
-
</
|
|
227
|
+
</Button>
|
|
237
228
|
</NavTooltip>
|
|
238
229
|
);
|
|
239
230
|
}
|
|
@@ -262,83 +253,92 @@ function CollapsibleNavItem({
|
|
|
262
253
|
if (collapsed) {
|
|
263
254
|
return (
|
|
264
255
|
<NavTooltip label={item.title} collapsed={collapsed}>
|
|
265
|
-
<
|
|
256
|
+
<Button
|
|
266
257
|
type="button"
|
|
258
|
+
variant="ghost"
|
|
267
259
|
onClick={() => onNavigate?.(item.href)}
|
|
268
260
|
className={cn(
|
|
269
|
-
"group
|
|
261
|
+
"group h-auto w-full justify-center px-2 py-2.5 transition-colors",
|
|
270
262
|
"text-brand-secondary-foreground/70 hover:bg-white/10 hover:text-brand-secondary-foreground",
|
|
271
263
|
hasActiveChild && "bg-white/15 text-brand-secondary-foreground",
|
|
272
264
|
)}
|
|
273
265
|
>
|
|
274
266
|
<Icon
|
|
275
267
|
className={navIconCn(hasActiveChild)}
|
|
276
|
-
size={
|
|
268
|
+
size={24}
|
|
277
269
|
strokeWidth={1.75}
|
|
278
270
|
/>
|
|
279
|
-
</
|
|
271
|
+
</Button>
|
|
280
272
|
</NavTooltip>
|
|
281
273
|
);
|
|
282
274
|
}
|
|
283
275
|
|
|
284
276
|
return (
|
|
285
|
-
<
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
"
|
|
306
|
-
|
|
307
|
-
)}
|
|
308
|
-
size={14}
|
|
309
|
-
strokeWidth={2}
|
|
310
|
-
/>
|
|
311
|
-
</button>
|
|
312
|
-
|
|
313
|
-
{open && item.subItems && (
|
|
314
|
-
<div className="ml-9 border-l border-white/15 pl-3">
|
|
315
|
-
{item.subItems.map((sub) => (
|
|
316
|
-
<button
|
|
317
|
-
key={sub.href}
|
|
318
|
-
type="button"
|
|
319
|
-
onClick={() => onNavigate?.(sub.href)}
|
|
277
|
+
<Accordion
|
|
278
|
+
value={open ? [item.href] : []}
|
|
279
|
+
onValueChange={(values) => setOpen(values.length > 0)}
|
|
280
|
+
>
|
|
281
|
+
<AccordionItem className="border-none" value={item.href}>
|
|
282
|
+
<AccordionPrimitive.Header className="flex">
|
|
283
|
+
<AccordionPrimitive.Trigger
|
|
284
|
+
className={cn(
|
|
285
|
+
"group flex h-auto w-full items-center justify-start gap-3 px-3 py-2.5 text-base font-medium transition-colors",
|
|
286
|
+
"text-brand-secondary-foreground/70 hover:bg-white/10 hover:text-brand-secondary-foreground",
|
|
287
|
+
"border-l-4 border-transparent",
|
|
288
|
+
hasActiveChild &&
|
|
289
|
+
"bg-white/15 text-brand-secondary-foreground border-primary",
|
|
290
|
+
)}
|
|
291
|
+
>
|
|
292
|
+
<Icon
|
|
293
|
+
className={navIconCn(hasActiveChild)}
|
|
294
|
+
size={24}
|
|
295
|
+
strokeWidth={1.75}
|
|
296
|
+
/>
|
|
297
|
+
<span className="flex-1 truncate text-left">{item.title}</span>
|
|
298
|
+
<ChevronDown
|
|
320
299
|
className={cn(
|
|
321
|
-
"
|
|
322
|
-
"
|
|
323
|
-
sub.isActive && "text-primary font-medium",
|
|
300
|
+
"ml-auto shrink-0 text-brand-secondary-foreground/40 transition-transform duration-200",
|
|
301
|
+
"group-data-[panel-open]:rotate-180",
|
|
324
302
|
)}
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
303
|
+
size={14}
|
|
304
|
+
strokeWidth={2}
|
|
305
|
+
/>
|
|
306
|
+
</AccordionPrimitive.Trigger>
|
|
307
|
+
</AccordionPrimitive.Header>
|
|
308
|
+
|
|
309
|
+
{item.subItems && (
|
|
310
|
+
<AccordionContent className="p-0 text-inherit">
|
|
311
|
+
<div className="ml-9 border-l border-white/15 pl-3">
|
|
312
|
+
{item.subItems.map((sub) => (
|
|
313
|
+
<Button
|
|
314
|
+
key={sub.href}
|
|
315
|
+
type="button"
|
|
316
|
+
variant="ghost"
|
|
317
|
+
onClick={() => onNavigate?.(sub.href)}
|
|
318
|
+
className={cn(
|
|
319
|
+
"h-auto w-full justify-start gap-2 py-1.5 pl-1 text-sm transition-colors",
|
|
320
|
+
"text-brand-secondary-foreground/50 hover:text-brand-secondary-foreground",
|
|
321
|
+
sub.isActive && "text-primary font-medium",
|
|
322
|
+
)}
|
|
323
|
+
>
|
|
324
|
+
<ChevronRight
|
|
325
|
+
size={11}
|
|
326
|
+
strokeWidth={2}
|
|
327
|
+
className={cn(
|
|
328
|
+
"shrink-0",
|
|
329
|
+
sub.isActive
|
|
330
|
+
? "text-primary"
|
|
331
|
+
: "text-brand-secondary-foreground/30",
|
|
332
|
+
)}
|
|
333
|
+
/>
|
|
334
|
+
<span className="truncate">{sub.title}</span>
|
|
335
|
+
</Button>
|
|
336
|
+
))}
|
|
337
|
+
</div>
|
|
338
|
+
</AccordionContent>
|
|
339
|
+
)}
|
|
340
|
+
</AccordionItem>
|
|
341
|
+
</Accordion>
|
|
342
342
|
);
|
|
343
343
|
}
|
|
344
344
|
|
|
@@ -357,6 +357,27 @@ export function SidebarNav({
|
|
|
357
357
|
className,
|
|
358
358
|
}: SidebarNavProps) {
|
|
359
359
|
const [userMenuOpen, setUserMenuOpen] = React.useState(false);
|
|
360
|
+
const navScrollRef = React.useRef<HTMLDivElement>(null);
|
|
361
|
+
const expandedScrollRef = React.useRef(0);
|
|
362
|
+
|
|
363
|
+
React.useEffect(() => {
|
|
364
|
+
if (collapsed) setUserMenuOpen(false);
|
|
365
|
+
}, [collapsed]);
|
|
366
|
+
|
|
367
|
+
// Preserve nav items scroll position across collapse/expand transitions.
|
|
368
|
+
// Cleanup saves scrollTop before the DOM changes; setup restores it when expanding.
|
|
369
|
+
React.useLayoutEffect(() => {
|
|
370
|
+
const nav = navScrollRef.current;
|
|
371
|
+
if (!nav) return;
|
|
372
|
+
if (!collapsed) {
|
|
373
|
+
nav.scrollTop = expandedScrollRef.current;
|
|
374
|
+
}
|
|
375
|
+
return () => {
|
|
376
|
+
if (!collapsed && nav) {
|
|
377
|
+
expandedScrollRef.current = nav.scrollTop;
|
|
378
|
+
}
|
|
379
|
+
};
|
|
380
|
+
}, [collapsed]);
|
|
360
381
|
|
|
361
382
|
return (
|
|
362
383
|
<TooltipProvider>
|
|
@@ -364,100 +385,134 @@ export function SidebarNav({
|
|
|
364
385
|
data-slot="sidebar-nav"
|
|
365
386
|
data-collapsed={collapsed}
|
|
366
387
|
className={cn(
|
|
367
|
-
|
|
388
|
+
// Force dark-mode CSS variable resolution — sidebar is always dark-backgrounded
|
|
389
|
+
// regardless of system theme, so semantic tokens (destructive, success, etc.)
|
|
390
|
+
// must use their dark-mode values to maintain WCAG contrast.
|
|
391
|
+
"dark flex h-full flex-col bg-brand-secondary text-brand-secondary-foreground",
|
|
368
392
|
"transition-all duration-200 ease-in-out",
|
|
369
393
|
collapsed ? "w-14" : "w-[279px]",
|
|
370
394
|
className,
|
|
371
395
|
)}
|
|
372
396
|
>
|
|
373
|
-
{/* Logo */}
|
|
374
|
-
{
|
|
375
|
-
<div className="flex items-center border-b border-white/15
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
397
|
+
{/* Logo — crossfade between full and icon variant */}
|
|
398
|
+
{(logo || logoCollapsed) && (
|
|
399
|
+
<div className="relative flex items-center border-b border-white/15 py-4 overflow-hidden">
|
|
400
|
+
{logo && (
|
|
401
|
+
<img
|
|
402
|
+
src={logo}
|
|
403
|
+
alt="Logo"
|
|
404
|
+
className={cn(
|
|
405
|
+
"h-8 w-auto object-contain object-left px-5 transition-opacity duration-200",
|
|
406
|
+
collapsed ? "opacity-0" : "opacity-100",
|
|
407
|
+
)}
|
|
408
|
+
style={{ filter: "brightness(0) invert(1)" }}
|
|
409
|
+
/>
|
|
410
|
+
)}
|
|
411
|
+
{logoCollapsed && (
|
|
412
|
+
<img
|
|
413
|
+
src={logoCollapsed}
|
|
414
|
+
alt="Logo"
|
|
415
|
+
className={cn(
|
|
416
|
+
"absolute inset-y-0 left-0 right-0 m-auto h-8 w-8 object-contain transition-opacity duration-200",
|
|
417
|
+
collapsed ? "opacity-100" : "opacity-0",
|
|
418
|
+
)}
|
|
419
|
+
style={{ filter: "brightness(0) invert(1)" }}
|
|
420
|
+
/>
|
|
421
|
+
)}
|
|
382
422
|
</div>
|
|
383
423
|
)}
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
424
|
+
|
|
425
|
+
{/* User section — crossfade between expanded and collapsed */}
|
|
426
|
+
<div className="relative border-b border-white/15">
|
|
427
|
+
{/* Expanded — in flow (defines height), no transition (overlay handles it) */}
|
|
428
|
+
<div
|
|
429
|
+
className={cn(
|
|
430
|
+
collapsed ? "opacity-0 pointer-events-none" : "opacity-100",
|
|
431
|
+
)}
|
|
432
|
+
>
|
|
433
|
+
<Accordion
|
|
434
|
+
value={userMenuOpen ? ["user-menu"] : []}
|
|
435
|
+
onValueChange={(values) => setUserMenuOpen(values.length > 0)}
|
|
436
|
+
>
|
|
437
|
+
<AccordionItem className="border-none" value="user-menu">
|
|
438
|
+
<AccordionPrimitive.Header className="flex">
|
|
439
|
+
<AccordionPrimitive.Trigger
|
|
440
|
+
className={cn(
|
|
441
|
+
"group flex h-auto w-full items-center justify-start gap-3 px-5 py-5 text-base transition-colors",
|
|
442
|
+
"text-brand-secondary-foreground hover:bg-white/10",
|
|
443
|
+
)}
|
|
444
|
+
>
|
|
445
|
+
<div className="flex h-8 w-8 shrink-0 items-center justify-center font-semibold text-xs bg-primary text-primary-foreground">
|
|
446
|
+
{getInitials(userName)}
|
|
447
|
+
</div>
|
|
448
|
+
<span className="flex-1 truncate text-left font-medium text-brand-secondary-foreground">
|
|
449
|
+
{userName}
|
|
450
|
+
</span>
|
|
451
|
+
<ChevronDown
|
|
452
|
+
className="ml-auto shrink-0 text-brand-secondary-foreground/50 transition-transform duration-200 group-data-[panel-open]:rotate-180"
|
|
453
|
+
size={16}
|
|
454
|
+
strokeWidth={2}
|
|
455
|
+
/>
|
|
456
|
+
</AccordionPrimitive.Trigger>
|
|
457
|
+
</AccordionPrimitive.Header>
|
|
458
|
+
|
|
459
|
+
<AccordionContent className="p-0 text-inherit">
|
|
460
|
+
<div className="border-t border-white/15 bg-black/20">
|
|
461
|
+
<Button
|
|
462
|
+
type="button"
|
|
463
|
+
variant="ghost"
|
|
464
|
+
onClick={onLogout}
|
|
465
|
+
className={cn(
|
|
466
|
+
"h-auto w-full justify-start gap-3 px-5 py-3 text-base",
|
|
467
|
+
"text-brand-secondary-foreground/70 hover:bg-white/10 hover:text-brand-secondary-foreground transition-colors",
|
|
468
|
+
)}
|
|
469
|
+
>
|
|
470
|
+
<LogOut
|
|
471
|
+
size={16}
|
|
472
|
+
strokeWidth={1.75}
|
|
473
|
+
className="shrink-0 text-destructive"
|
|
474
|
+
/>
|
|
475
|
+
<span>Logout</span>
|
|
476
|
+
</Button>
|
|
477
|
+
</div>
|
|
478
|
+
</AccordionContent>
|
|
479
|
+
</AccordionItem>
|
|
480
|
+
</Accordion>
|
|
392
481
|
</div>
|
|
393
|
-
)}
|
|
394
482
|
|
|
395
|
-
|
|
396
|
-
<div className="border-b border-white/15">
|
|
483
|
+
{/* Collapsed — absolute overlay, centered avatar */}
|
|
397
484
|
<NavTooltip label={userName} collapsed={collapsed}>
|
|
398
|
-
<
|
|
399
|
-
type="button"
|
|
400
|
-
onClick={() => !collapsed && setUserMenuOpen((prev) => !prev)}
|
|
485
|
+
<div
|
|
401
486
|
className={cn(
|
|
402
|
-
"
|
|
403
|
-
"
|
|
404
|
-
collapsed && "justify-center px-2 py-4",
|
|
487
|
+
"absolute inset-0 flex items-center justify-center transition-opacity duration-200",
|
|
488
|
+
collapsed ? "opacity-100" : "opacity-0 pointer-events-none",
|
|
405
489
|
)}
|
|
406
490
|
>
|
|
407
491
|
<div className="flex h-8 w-8 shrink-0 items-center justify-center font-semibold text-xs bg-primary text-primary-foreground">
|
|
408
492
|
{getInitials(userName)}
|
|
409
493
|
</div>
|
|
410
|
-
{!collapsed && (
|
|
411
|
-
<>
|
|
412
|
-
<span className="flex-1 truncate text-left font-medium text-brand-secondary-foreground">
|
|
413
|
-
{userName}
|
|
414
|
-
</span>
|
|
415
|
-
<ChevronDown
|
|
416
|
-
className={cn(
|
|
417
|
-
"shrink-0 text-brand-secondary-foreground/50 transition-transform duration-200",
|
|
418
|
-
userMenuOpen && "rotate-180",
|
|
419
|
-
)}
|
|
420
|
-
size={16}
|
|
421
|
-
strokeWidth={2}
|
|
422
|
-
/>
|
|
423
|
-
</>
|
|
424
|
-
)}
|
|
425
|
-
</button>
|
|
426
|
-
</NavTooltip>
|
|
427
|
-
|
|
428
|
-
{/* Logout dropdown */}
|
|
429
|
-
{!collapsed && userMenuOpen && (
|
|
430
|
-
<div className="border-t border-white/15 bg-black/20">
|
|
431
|
-
<button
|
|
432
|
-
type="button"
|
|
433
|
-
onClick={onLogout}
|
|
434
|
-
className={cn(
|
|
435
|
-
"flex w-full items-center gap-3 px-5 py-3 text-base",
|
|
436
|
-
"text-brand-secondary-foreground/70 hover:bg-white/10 hover:text-brand-secondary-foreground transition-colors",
|
|
437
|
-
)}
|
|
438
|
-
>
|
|
439
|
-
<LogOut
|
|
440
|
-
size={16}
|
|
441
|
-
strokeWidth={1.75}
|
|
442
|
-
className="shrink-0 text-destructive"
|
|
443
|
-
/>
|
|
444
|
-
<span>Logout</span>
|
|
445
|
-
</button>
|
|
446
494
|
</div>
|
|
447
|
-
|
|
495
|
+
</NavTooltip>
|
|
448
496
|
</div>
|
|
449
497
|
|
|
450
|
-
{/* Financial metrics
|
|
451
|
-
{
|
|
452
|
-
<
|
|
453
|
-
{
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
498
|
+
{/* Financial metrics — animated in/out with sidebar collapse to prevent nav items jumping */}
|
|
499
|
+
{!!metricsGroups?.length && (
|
|
500
|
+
<Accordion
|
|
501
|
+
value={!collapsed ? ["metrics"] : []}
|
|
502
|
+
onValueChange={() => {}}
|
|
503
|
+
>
|
|
504
|
+
<AccordionItem className="border-none" value="metrics">
|
|
505
|
+
<AccordionContent className="p-0 text-inherit">
|
|
506
|
+
{metricsGroups.map((group, i) => (
|
|
507
|
+
<MetricsGroup key={i} group={group} />
|
|
508
|
+
))}
|
|
509
|
+
</AccordionContent>
|
|
510
|
+
</AccordionItem>
|
|
511
|
+
</Accordion>
|
|
457
512
|
)}
|
|
458
513
|
|
|
459
514
|
{/* Nav items */}
|
|
460
|
-
<div className="flex flex-col overflow-y-auto py-3">
|
|
515
|
+
<div ref={navScrollRef} className="flex flex-col overflow-y-auto py-3">
|
|
461
516
|
{items.map((item) =>
|
|
462
517
|
item.isCollapsible ? (
|
|
463
518
|
<CollapsibleNavItem
|
|
@@ -484,30 +539,31 @@ export function SidebarNav({
|
|
|
484
539
|
label={collapsed ? "Expand" : "Collapse"}
|
|
485
540
|
collapsed={collapsed}
|
|
486
541
|
>
|
|
487
|
-
<
|
|
542
|
+
<Button
|
|
488
543
|
type="button"
|
|
544
|
+
variant="ghost"
|
|
489
545
|
onClick={() => onCollapsedChange(!collapsed)}
|
|
490
546
|
className={cn(
|
|
491
|
-
"
|
|
547
|
+
"h-12 w-full justify-start gap-3 px-3 py-3 transition-colors",
|
|
492
548
|
"text-brand-secondary-foreground/80 hover:bg-white/10 hover:text-brand-secondary-foreground",
|
|
493
549
|
collapsed && "justify-center px-2",
|
|
494
550
|
)}
|
|
495
551
|
>
|
|
496
552
|
{collapsed ? (
|
|
497
553
|
<PanelLeftOpen
|
|
498
|
-
size={
|
|
554
|
+
size={24}
|
|
499
555
|
strokeWidth={1.75}
|
|
500
556
|
className="shrink-0"
|
|
501
557
|
/>
|
|
502
558
|
) : (
|
|
503
559
|
<PanelLeftClose
|
|
504
|
-
size={
|
|
560
|
+
size={24}
|
|
505
561
|
strokeWidth={1.75}
|
|
506
562
|
className="shrink-0"
|
|
507
563
|
/>
|
|
508
564
|
)}
|
|
509
565
|
{!collapsed && <span className="text-sm">Collapse</span>}
|
|
510
|
-
</
|
|
566
|
+
</Button>
|
|
511
567
|
</NavTooltip>
|
|
512
568
|
</div>
|
|
513
569
|
)}
|
|
@@ -129,7 +129,9 @@ export function TransactionsExpenseCategoriesDoughnutChart({
|
|
|
129
129
|
style={{ maxWidth: width, fontFamily }}
|
|
130
130
|
>
|
|
131
131
|
<CardHeader className="px-3 sm:px-6">
|
|
132
|
-
<CardTitle className="text-
|
|
132
|
+
<CardTitle className="text-xs font-semibold uppercase tracking-wide">
|
|
133
|
+
{title}
|
|
134
|
+
</CardTitle>
|
|
133
135
|
</CardHeader>
|
|
134
136
|
|
|
135
137
|
<CardContent className="px-3 sm:px-6">
|
|
@@ -16,13 +16,20 @@ import { Card, CardContent, CardHeader, CardTitle } from "./card";
|
|
|
16
16
|
import { Empty, EmptyDescription } from "./empty";
|
|
17
17
|
import { Spinner } from "./spinner";
|
|
18
18
|
import { cn } from "@/lib/utils";
|
|
19
|
+
import { formatCurrency } from "@/lib/format-currency";
|
|
19
20
|
import {
|
|
20
21
|
FALLBACK_TICK,
|
|
21
22
|
FALLBACK_PRIMARY,
|
|
22
23
|
FALLBACK_SECONDARY,
|
|
23
24
|
} from "./chart-shared";
|
|
24
25
|
|
|
25
|
-
ChartJS.register(
|
|
26
|
+
ChartJS.register(
|
|
27
|
+
CategoryScale,
|
|
28
|
+
LinearScale,
|
|
29
|
+
BarController,
|
|
30
|
+
BarElement,
|
|
31
|
+
Tooltip,
|
|
32
|
+
);
|
|
26
33
|
|
|
27
34
|
// ---------------------------------------------------------------------------
|
|
28
35
|
// Constants
|
|
@@ -32,12 +39,6 @@ ChartJS.register(CategoryScale, LinearScale, BarController, BarElement, Tooltip)
|
|
|
32
39
|
// Helpers
|
|
33
40
|
// ---------------------------------------------------------------------------
|
|
34
41
|
|
|
35
|
-
function formatDollar(value: number): string {
|
|
36
|
-
return `$${value.toLocaleString(undefined, {
|
|
37
|
-
minimumFractionDigits: 2,
|
|
38
|
-
maximumFractionDigits: 2,
|
|
39
|
-
})}`;
|
|
40
|
-
}
|
|
41
42
|
|
|
42
43
|
// ---------------------------------------------------------------------------
|
|
43
44
|
// Types
|
|
@@ -110,7 +111,7 @@ export function TransactionsIncomeExpenseBarChart({
|
|
|
110
111
|
color: FALLBACK_SECONDARY,
|
|
111
112
|
textAlign: "left",
|
|
112
113
|
// Returns array for multi-line: dollar value on line 1, blank on line 2
|
|
113
|
-
formatter: (v: number) => [
|
|
114
|
+
formatter: (v: number) => [formatCurrency(v, { decimals: 2 }), ""],
|
|
114
115
|
},
|
|
115
116
|
name: {
|
|
116
117
|
anchor: "end",
|
|
@@ -173,7 +174,9 @@ export function TransactionsIncomeExpenseBarChart({
|
|
|
173
174
|
style={{ maxWidth: width, fontFamily }}
|
|
174
175
|
>
|
|
175
176
|
<CardHeader className="px-3 sm:px-6">
|
|
176
|
-
<CardTitle className="text-
|
|
177
|
+
<CardTitle className="text-xs font-semibold uppercase tracking-wide">
|
|
178
|
+
{title}
|
|
179
|
+
</CardTitle>
|
|
177
180
|
</CardHeader>
|
|
178
181
|
|
|
179
182
|
<CardContent className="px-3 sm:px-6">
|
|
@@ -129,7 +129,9 @@ export function TransactionsLiabilitiesBreakdownChart({
|
|
|
129
129
|
style={{ maxWidth: width, fontFamily }}
|
|
130
130
|
>
|
|
131
131
|
<CardHeader className="px-3 sm:px-6">
|
|
132
|
-
<CardTitle className="text-
|
|
132
|
+
<CardTitle className="text-xs font-semibold uppercase tracking-wide">
|
|
133
|
+
{title}
|
|
134
|
+
</CardTitle>
|
|
133
135
|
</CardHeader>
|
|
134
136
|
|
|
135
137
|
<CardContent className="px-3 sm:px-6">
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Currency formatting utilities for the WealthX design system.
|
|
3
|
+
* All formatters default to AUD (en-AU locale).
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Format a number as AUD currency using Intl.NumberFormat.
|
|
8
|
+
*
|
|
9
|
+
* @param value The numeric value to format.
|
|
10
|
+
* @param options Optional formatting overrides.
|
|
11
|
+
* @param options.decimals Decimal places (default: 0).
|
|
12
|
+
* @param options.showSign Prefix with +/− for positive/negative (default: false).
|
|
13
|
+
*/
|
|
14
|
+
export function formatCurrency(
|
|
15
|
+
value: number,
|
|
16
|
+
options?: { decimals?: number; showSign?: boolean },
|
|
17
|
+
): string {
|
|
18
|
+
const { decimals = 0, showSign = false } = options ?? {};
|
|
19
|
+
const abs = Math.abs(value);
|
|
20
|
+
const formatted = new Intl.NumberFormat("en-AU", {
|
|
21
|
+
style: "currency",
|
|
22
|
+
currency: "AUD",
|
|
23
|
+
minimumFractionDigits: decimals,
|
|
24
|
+
maximumFractionDigits: decimals,
|
|
25
|
+
}).format(abs);
|
|
26
|
+
if (!showSign) return value < 0 ? `-${formatted}` : formatted;
|
|
27
|
+
if (value > 0) return `+${formatted}`;
|
|
28
|
+
if (value < 0) return `-${formatted}`;
|
|
29
|
+
return formatted;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Abbreviated currency: $1.2B, $3.5M, $580K, $42.
|
|
34
|
+
* Used primarily for chart axis ticks.
|
|
35
|
+
*/
|
|
36
|
+
export function formatCurrencyAbbrev(value: number): string {
|
|
37
|
+
const abs = Math.abs(value);
|
|
38
|
+
const sign = value < 0 ? "-" : "";
|
|
39
|
+
if (abs >= 1_000_000_000)
|
|
40
|
+
return `${sign}$${(abs / 1_000_000_000).toFixed(1)}B`;
|
|
41
|
+
if (abs >= 1_000_000) return `${sign}$${(abs / 1_000_000).toFixed(1)}M`;
|
|
42
|
+
if (abs >= 1_000) return `${sign}$${(abs / 1_000).toFixed(0)}K`;
|
|
43
|
+
return `${sign}$${abs.toFixed(0)}`;
|
|
44
|
+
}
|