@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.
- package/.turbo/turbo-build.log +203 -150
- package/CHANGELOG.md +29 -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-SYOD63OZ.mjs → chunk-5FQIKDKP.mjs} +6 -6
- 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-WOEHFRGB.mjs → chunk-BDYZCBRT.mjs} +4 -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-KUDCQ4FI.mjs → chunk-DUJTAXMH.mjs} +9 -4
- package/dist/{chunk-GGM2UYGG.mjs → chunk-EBXQWIYG.mjs} +10 -4
- package/dist/{chunk-PMB3A7V3.mjs → chunk-EI5F6FMT.mjs} +1 -1
- package/dist/chunk-EWRB4PAD.mjs +468 -0
- package/dist/chunk-FAKPBKLT.mjs +253 -0
- package/dist/chunk-FNQXOAYJ.mjs +169 -0
- package/dist/{chunk-4CX4SBRO.mjs → chunk-GHC7LLUX.mjs} +14 -5
- package/dist/chunk-HBZLGDIN.mjs +507 -0
- package/dist/{chunk-SIZMLSRU.mjs → chunk-HISNT2MG.mjs} +8 -6
- package/dist/{chunk-PR6V5XKM.mjs → chunk-HVY6KCCF.mjs} +7 -4
- package/dist/chunk-I3RZS7V2.mjs +136 -0
- package/dist/chunk-IAE3F7DR.mjs +1962 -0
- package/dist/{chunk-ZRO5JO3H.mjs → chunk-IHMFS7NZ.mjs} +81 -84
- 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-CSDO6VBW.mjs → chunk-LBMRIB3G.mjs} +10 -10
- 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-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-3WMX6KWS.mjs → chunk-PU4YZQXV.mjs} +11 -12
- 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-U4NDAF2P.mjs +207 -0
- package/dist/{chunk-DOH3EHX7.mjs → chunk-U5X52X37.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/chunk-ZC45IGZO.mjs +388 -0
- package/dist/components/ui/add-column-modal.js +42 -14
- package/dist/components/ui/add-column-modal.mjs +4 -4
- 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 +497 -0
- package/dist/components/ui/advisor-card.mjs +13 -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 +547 -0
- package/dist/components/ui/color-picker.mjs +24 -0
- package/dist/components/ui/data-table.js +182 -129
- 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/form-primitives.js +4 -4
- package/dist/components/ui/form-primitives.mjs +3 -3
- 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 +1371 -1267
- package/dist/components/ui/opportunity-edit-modals.mjs +10 -10
- package/dist/components/ui/opportunity-summary-tab.js +2748 -2161
- package/dist/components/ui/opportunity-summary-tab.mjs +16 -16
- 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 +209 -195
- package/dist/components/ui/pipeline-board.mjs +10 -8
- package/dist/components/ui/pipeline-dialogs.js +118 -69
- package/dist/components/ui/pipeline-dialogs.mjs +8 -7
- 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 +679 -0
- package/dist/components/ui/sidebar-nav.mjs +14 -0
- package/dist/components/ui/stage-timeline.js +6 -6
- package/dist/components/ui/stage-timeline.mjs +3 -3
- package/dist/components/ui/stepper.js +283 -0
- package/dist/components/ui/stepper.mjs +18 -0
- package/dist/components/ui/toggle-group.js +4 -4
- package/dist/components/ui/toggle-group.mjs +2 -2
- package/dist/components/ui/toggle.js +4 -4
- package/dist/components/ui/toggle.mjs +1 -1
- 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 +12927 -8522
- package/dist/index.mjs +288 -190
- package/dist/lib/typography.js +10 -10
- package/dist/lib/typography.mjs +1 -1
- package/dist/styles.css +1 -1
- package/package.json +86 -1
- package/src/components/index.tsx +146 -0
- 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 +227 -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 +309 -0
- package/src/components/ui/data-table.tsx +117 -83
- 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 +516 -0
- package/src/components/ui/stepper.tsx +347 -0
- package/src/components/ui/toggle.tsx +4 -4
- 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/lib/typography.ts +11 -11
- package/src/styles/globals.css +36 -34
- package/src/styles/styles-css.ts +1 -1
- package/tsup.config.ts +17 -0
- package/dist/chunk-PG6K5XEC.mjs +0 -475
- package/dist/chunk-WA6O6EUR.mjs +0 -1885
- package/dist/chunk-WNGWBVLV.mjs +0 -148
- package/dist/{chunk-LLVQKSU3.mjs → chunk-GD4BJDJR.mjs} +3 -3
|
@@ -25,6 +25,7 @@ import {
|
|
|
25
25
|
ChartLegendItem,
|
|
26
26
|
formatAbbrev,
|
|
27
27
|
} from "./chart-shared";
|
|
28
|
+
import { formatCurrency } from "@/lib/format-currency";
|
|
28
29
|
|
|
29
30
|
ChartJS.register(
|
|
30
31
|
CategoryScale,
|
|
@@ -123,14 +124,6 @@ const DASH_PATTERN: number[] = [6, 4];
|
|
|
123
124
|
// Helpers
|
|
124
125
|
// ---------------------------------------------------------------------------
|
|
125
126
|
|
|
126
|
-
function formatCurrencyFull(dollars: number): string {
|
|
127
|
-
return new Intl.NumberFormat("en-AU", {
|
|
128
|
-
style: "currency",
|
|
129
|
-
currency: "AUD",
|
|
130
|
-
maximumFractionDigits: 0,
|
|
131
|
-
}).format(dollars);
|
|
132
|
-
}
|
|
133
|
-
|
|
134
127
|
/** "2025-01" or "2025-01-15" → "Jan '25" for x-axis ticks */
|
|
135
128
|
function formatDateLabel(iso: string): string {
|
|
136
129
|
const d = new Date(`${iso.slice(0, 7)}-01T00:00:00`);
|
|
@@ -246,7 +239,7 @@ export function BorrowingCapacityLineChart({
|
|
|
246
239
|
},
|
|
247
240
|
label: (ctx) => {
|
|
248
241
|
const dollars = ctx.parsed.y ?? 0;
|
|
249
|
-
return ` ${ctx.dataset.label}: ${
|
|
242
|
+
return ` ${ctx.dataset.label}: ${formatCurrency(dollars)}`;
|
|
250
243
|
},
|
|
251
244
|
},
|
|
252
245
|
},
|
|
@@ -319,12 +312,12 @@ export function BorrowingCapacityLineChart({
|
|
|
319
312
|
>
|
|
320
313
|
<CardHeader className="px-3 sm:px-6">
|
|
321
314
|
<div className="flex items-start justify-between gap-4">
|
|
322
|
-
<CardTitle className="text-
|
|
315
|
+
<CardTitle className="text-xs font-semibold uppercase tracking-wide">
|
|
323
316
|
{title}
|
|
324
317
|
</CardTitle>
|
|
325
318
|
{kpiValue != null && (
|
|
326
319
|
<span className="shrink-0 text-xl font-bold tabular-nums">
|
|
327
|
-
{
|
|
320
|
+
{formatCurrency(kpiValue / 100)}
|
|
328
321
|
</span>
|
|
329
322
|
)}
|
|
330
323
|
</div>
|
|
@@ -23,13 +23,13 @@ const buttonVariants = cva(
|
|
|
23
23
|
destructive:
|
|
24
24
|
"bg-destructive text-destructive-foreground shadow-xs hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:bg-destructive/60 dark:focus-visible:ring-destructive/40",
|
|
25
25
|
outline:
|
|
26
|
-
"border border-input bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50",
|
|
26
|
+
"border border-input bg-background shadow-xs hover:bg-accent hover:text-accent-foreground focus-visible:ring-border/50 dark:bg-input/30 dark:border-input dark:hover:bg-input/50",
|
|
27
27
|
"outline-primary":
|
|
28
28
|
"border border-primary text-foreground bg-transparent shadow-xs hover:bg-primary/5 focus-visible:ring-primary/50",
|
|
29
29
|
"outline-secondary":
|
|
30
30
|
"border border-brand-secondary text-brand-secondary bg-transparent shadow-xs hover:bg-brand-secondary/10 focus-visible:ring-brand-secondary/30",
|
|
31
31
|
ghost:
|
|
32
|
-
"hover:bg-accent hover:text-accent-foreground hover:shadow-xs dark:hover:bg-accent/50",
|
|
32
|
+
"hover:bg-accent hover:text-accent-foreground hover:shadow-xs focus-visible:ring-border/50 dark:hover:bg-accent/50",
|
|
33
33
|
link: "text-primary underline-offset-4 hover:underline",
|
|
34
34
|
},
|
|
35
35
|
size: {
|
|
@@ -23,7 +23,7 @@ function CardHeader({ className, ...props }: CardHeaderProps): ReactElement {
|
|
|
23
23
|
return (
|
|
24
24
|
<div
|
|
25
25
|
className={cn(
|
|
26
|
-
"@container/card-header grid auto-rows-min grid-rows-[auto_auto] items-
|
|
26
|
+
"@container/card-header grid auto-rows-min grid-rows-[auto_auto] items-center gap-2 px-6 has-data-[slot=card-action]:grid-cols-[1fr_auto] [.border-b]:pb-6",
|
|
27
27
|
className,
|
|
28
28
|
)}
|
|
29
29
|
data-slot="card-header"
|
|
@@ -21,7 +21,9 @@ import {
|
|
|
21
21
|
FALLBACK_PRIMARY,
|
|
22
22
|
FALLBACK_BG,
|
|
23
23
|
ChartLegendItem,
|
|
24
|
+
formatAbbrev,
|
|
24
25
|
} from "./chart-shared";
|
|
26
|
+
import { formatCurrency } from "@/lib/format-currency";
|
|
25
27
|
|
|
26
28
|
ChartJS.register(
|
|
27
29
|
CategoryScale,
|
|
@@ -36,27 +38,6 @@ ChartJS.register(
|
|
|
36
38
|
// Helpers
|
|
37
39
|
// ---------------------------------------------------------------------------
|
|
38
40
|
|
|
39
|
-
/** Abbreviate a dollar value: $1.2K, $4.5M, $1.1B */
|
|
40
|
-
function formatAbbrev(value: number): string {
|
|
41
|
-
const abs = Math.abs(value);
|
|
42
|
-
const sign = value < 0 ? "-" : "";
|
|
43
|
-
if (abs >= 1_000_000_000)
|
|
44
|
-
return `${sign}$${(abs / 1_000_000_000).toFixed(1)}B`;
|
|
45
|
-
if (abs >= 1_000_000) return `${sign}$${(abs / 1_000_000).toFixed(1)}M`;
|
|
46
|
-
if (abs >= 1_000) return `${sign}$${(abs / 1_000).toFixed(1)}K`;
|
|
47
|
-
return `${sign}$${abs.toFixed(0)}`;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
/** Format a balance value for prominent display: $12,345.67 */
|
|
51
|
-
function formatBalanceFull(value: number): string {
|
|
52
|
-
const abs = Math.abs(value);
|
|
53
|
-
const sign = value < 0 ? "-" : "";
|
|
54
|
-
return `${sign}$${abs.toLocaleString(undefined, {
|
|
55
|
-
minimumFractionDigits: 2,
|
|
56
|
-
maximumFractionDigits: 2,
|
|
57
|
-
})}`;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
41
|
/** Format ISO date string to "MMM dd" label e.g. "Jan 15" */
|
|
61
42
|
function formatDateLabel(iso: string): string {
|
|
62
43
|
const d = new Date(iso);
|
|
@@ -244,12 +225,12 @@ export function CashBalanceLineChart({
|
|
|
244
225
|
>
|
|
245
226
|
<CardHeader className="px-3 sm:px-6">
|
|
246
227
|
<div className="flex flex-col gap-0.5">
|
|
247
|
-
<CardTitle className="text-
|
|
228
|
+
<CardTitle className="text-xs font-semibold uppercase tracking-wide">
|
|
248
229
|
{title}
|
|
249
230
|
</CardTitle>
|
|
250
231
|
{showBalanceValue && latestValue !== null && (
|
|
251
232
|
<p className="text-2xl font-bold tabular-nums leading-tight">
|
|
252
|
-
{
|
|
233
|
+
{formatCurrency(latestValue, { decimals: 2 })}
|
|
253
234
|
</p>
|
|
254
235
|
)}
|
|
255
236
|
</div>
|
|
@@ -25,7 +25,14 @@ import {
|
|
|
25
25
|
ChartPeriodButton,
|
|
26
26
|
} from "./chart-shared";
|
|
27
27
|
|
|
28
|
-
ChartJS.register(
|
|
28
|
+
ChartJS.register(
|
|
29
|
+
CategoryScale,
|
|
30
|
+
LinearScale,
|
|
31
|
+
BarController,
|
|
32
|
+
BarElement,
|
|
33
|
+
Tooltip,
|
|
34
|
+
Legend,
|
|
35
|
+
);
|
|
29
36
|
|
|
30
37
|
// ---------------------------------------------------------------------------
|
|
31
38
|
// Types
|
|
@@ -313,7 +320,7 @@ export function CashflowBarChart({
|
|
|
313
320
|
style={{ maxWidth: width, fontFamily }}
|
|
314
321
|
>
|
|
315
322
|
<CardHeader className="px-3 sm:px-6">
|
|
316
|
-
<CardTitle className="text-
|
|
323
|
+
<CardTitle className="text-xs font-semibold uppercase tracking-wide">
|
|
317
324
|
{title}
|
|
318
325
|
</CardTitle>
|
|
319
326
|
{showPeriodSelector && (
|
|
@@ -90,18 +90,11 @@ export const SEVERITY_COLORS = {
|
|
|
90
90
|
} as const;
|
|
91
91
|
|
|
92
92
|
/**
|
|
93
|
-
*
|
|
94
|
-
*
|
|
93
|
+
* Re-export abbreviated currency formatter from shared lib.
|
|
94
|
+
* Kept as `formatAbbrev` for backward compatibility with existing chart consumers.
|
|
95
95
|
*/
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
const sign = value < 0 ? "-" : "";
|
|
99
|
-
if (abs >= 1_000_000_000)
|
|
100
|
-
return `${sign}$${(abs / 1_000_000_000).toFixed(1)}B`;
|
|
101
|
-
if (abs >= 1_000_000) return `${sign}$${(abs / 1_000_000).toFixed(1)}M`;
|
|
102
|
-
if (abs >= 1_000) return `${sign}$${(abs / 1_000).toFixed(0)}K`;
|
|
103
|
-
return `${sign}$${abs.toFixed(0)}`;
|
|
104
|
-
}
|
|
96
|
+
import { formatCurrencyAbbrev as formatAbbrev } from "@/lib/format-currency";
|
|
97
|
+
export { formatAbbrev };
|
|
105
98
|
|
|
106
99
|
// ---------------------------------------------------------------------------
|
|
107
100
|
// Tooltip date format
|
|
@@ -7,19 +7,19 @@
|
|
|
7
7
|
// <Chip>Label</Chip>
|
|
8
8
|
// <Chip onRemove={() => handleRemove(id)}>Label</Chip>
|
|
9
9
|
// <Chip variant="outline" disabled onRemove={...}>Label</Chip>
|
|
10
|
-
import { type ReactElement } from "react"
|
|
11
|
-
import * as React from "react"
|
|
12
|
-
import { X } from "lucide-react"
|
|
13
|
-
import
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
10
|
+
import { type ReactElement } from "react";
|
|
11
|
+
import * as React from "react";
|
|
12
|
+
import { X } from "lucide-react";
|
|
13
|
+
import { Button } from "./button";
|
|
14
|
+
import type { VariantProps } from "class-variance-authority";
|
|
15
|
+
import { cn } from "@/lib/utils";
|
|
16
|
+
import { badgeVariants } from "@/components/ui/badge";
|
|
16
17
|
|
|
17
18
|
export interface ChipProps
|
|
18
|
-
extends React.ComponentProps<"span">,
|
|
19
|
-
VariantProps<typeof badgeVariants> {
|
|
19
|
+
extends React.ComponentProps<"span">, VariantProps<typeof badgeVariants> {
|
|
20
20
|
/** When provided, renders a dismiss (×) button inside the chip. */
|
|
21
|
-
onRemove?: () => void
|
|
22
|
-
disabled?: boolean
|
|
21
|
+
onRemove?: () => void;
|
|
22
|
+
disabled?: boolean;
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
function Chip({
|
|
@@ -38,28 +38,32 @@ function Chip({
|
|
|
38
38
|
// extra right padding only when dismiss button is present
|
|
39
39
|
onRemove && "pr-1",
|
|
40
40
|
disabled && "pointer-events-none opacity-50",
|
|
41
|
-
className
|
|
41
|
+
className,
|
|
42
42
|
)}
|
|
43
43
|
data-slot="chip"
|
|
44
44
|
data-variant={variant}
|
|
45
45
|
{...props}
|
|
46
46
|
>
|
|
47
47
|
{children}
|
|
48
|
-
{onRemove ?
|
|
48
|
+
{onRemove ? (
|
|
49
|
+
<Button
|
|
50
|
+
type="button"
|
|
51
|
+
variant="ghost"
|
|
52
|
+
size="icon"
|
|
49
53
|
aria-label="Remove"
|
|
50
|
-
className="ml-0.5
|
|
54
|
+
className="ml-0.5 size-4 shrink-0 rounded-full p-0.5 opacity-60 hover:opacity-100 disabled:pointer-events-none"
|
|
51
55
|
data-slot="chip-remove"
|
|
52
56
|
disabled={disabled}
|
|
53
57
|
onClick={(e) => {
|
|
54
|
-
e.stopPropagation()
|
|
55
|
-
onRemove()
|
|
58
|
+
e.stopPropagation();
|
|
59
|
+
onRemove();
|
|
56
60
|
}}
|
|
57
|
-
type="button"
|
|
58
61
|
>
|
|
59
62
|
<X className="size-3" />
|
|
60
|
-
</
|
|
63
|
+
</Button>
|
|
64
|
+
) : null}
|
|
61
65
|
</span>
|
|
62
|
-
)
|
|
66
|
+
);
|
|
63
67
|
}
|
|
64
68
|
|
|
65
|
-
export { Chip }
|
|
69
|
+
export { Chip };
|
|
@@ -0,0 +1,309 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { cn } from "@/lib/utils";
|
|
3
|
+
import {
|
|
4
|
+
Popover,
|
|
5
|
+
PopoverContent,
|
|
6
|
+
PopoverTrigger,
|
|
7
|
+
} from "@/components/ui/popover";
|
|
8
|
+
import { Input } from "@/components/ui/input";
|
|
9
|
+
import { Button } from "@/components/ui/button";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* ColorPicker — WealthX Design System
|
|
13
|
+
*
|
|
14
|
+
* Popover-based color picker with preset swatches and a hex input field.
|
|
15
|
+
* Designed for tenant white-label brand color configuration.
|
|
16
|
+
*
|
|
17
|
+
* Figma: https://www.figma.com/design/9V9F0NGVsif8LGmEhVjOcT/Design-System---shadcn
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
// ---------------------------------------------------------------------------
|
|
21
|
+
// Preset palettes
|
|
22
|
+
// ---------------------------------------------------------------------------
|
|
23
|
+
|
|
24
|
+
export const COLOR_PICKER_PRESETS = [
|
|
25
|
+
// Blues
|
|
26
|
+
"#1E40AF",
|
|
27
|
+
"#2563EB",
|
|
28
|
+
"#3B82F6",
|
|
29
|
+
"#60A5FA",
|
|
30
|
+
// Purples
|
|
31
|
+
"#7C3AED",
|
|
32
|
+
"#8B5CF6",
|
|
33
|
+
"#A78BFA",
|
|
34
|
+
"#C4B5FD",
|
|
35
|
+
// Greens
|
|
36
|
+
"#15803D",
|
|
37
|
+
"#16A34A",
|
|
38
|
+
"#22C55E",
|
|
39
|
+
"#4ADE80",
|
|
40
|
+
// Reds
|
|
41
|
+
"#B91C1C",
|
|
42
|
+
"#DC2626",
|
|
43
|
+
"#EF4444",
|
|
44
|
+
"#F87171",
|
|
45
|
+
// Oranges
|
|
46
|
+
"#C2410C",
|
|
47
|
+
"#EA580C",
|
|
48
|
+
"#F97316",
|
|
49
|
+
"#FB923C",
|
|
50
|
+
// Teals
|
|
51
|
+
"#0F766E",
|
|
52
|
+
"#0D9488",
|
|
53
|
+
"#14B8A6",
|
|
54
|
+
"#2DD4BF",
|
|
55
|
+
// Neutrals
|
|
56
|
+
"#111827",
|
|
57
|
+
"#374151",
|
|
58
|
+
"#6B7280",
|
|
59
|
+
"#D1D5DB",
|
|
60
|
+
];
|
|
61
|
+
|
|
62
|
+
// ---------------------------------------------------------------------------
|
|
63
|
+
// Utility
|
|
64
|
+
// ---------------------------------------------------------------------------
|
|
65
|
+
|
|
66
|
+
/** Returns true if the string is a valid 6-digit or 3-digit hex color. */
|
|
67
|
+
function isValidHex(value: string): boolean {
|
|
68
|
+
return /^#([0-9A-Fa-f]{3}|[0-9A-Fa-f]{6})$/.test(value);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** Ensures value always has a leading `#`. */
|
|
72
|
+
function normalizeHex(value: string): string {
|
|
73
|
+
const stripped = value.replace(/^#/, "");
|
|
74
|
+
return `#${stripped}`;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// ---------------------------------------------------------------------------
|
|
78
|
+
// ColorSwatch
|
|
79
|
+
// ---------------------------------------------------------------------------
|
|
80
|
+
|
|
81
|
+
interface ColorSwatchProps {
|
|
82
|
+
color: string;
|
|
83
|
+
selected?: boolean;
|
|
84
|
+
size?: "sm" | "md";
|
|
85
|
+
onClick?: (color: string) => void;
|
|
86
|
+
className?: string;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function ColorSwatch({
|
|
90
|
+
color,
|
|
91
|
+
selected,
|
|
92
|
+
size = "md",
|
|
93
|
+
onClick,
|
|
94
|
+
className,
|
|
95
|
+
}: ColorSwatchProps) {
|
|
96
|
+
return (
|
|
97
|
+
<Button
|
|
98
|
+
type="button"
|
|
99
|
+
variant="ghost"
|
|
100
|
+
title={color}
|
|
101
|
+
aria-label={`Select color ${color}`}
|
|
102
|
+
aria-pressed={selected}
|
|
103
|
+
onClick={() => onClick?.(color)}
|
|
104
|
+
className={cn(
|
|
105
|
+
"relative shrink-0 p-0 transition-all outline-none shadow-[inset_0_0_0_1px_rgba(0,0,0,0.12)]",
|
|
106
|
+
"focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1",
|
|
107
|
+
size === "md" ? "size-7" : "size-5",
|
|
108
|
+
selected &&
|
|
109
|
+
"ring-2 ring-foreground ring-offset-1 ring-offset-background",
|
|
110
|
+
className,
|
|
111
|
+
)}
|
|
112
|
+
style={{ backgroundColor: color }}
|
|
113
|
+
/>
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// ---------------------------------------------------------------------------
|
|
118
|
+
// ColorPickerContent (inner panel shown in popover)
|
|
119
|
+
// ---------------------------------------------------------------------------
|
|
120
|
+
|
|
121
|
+
interface ColorPickerContentProps {
|
|
122
|
+
value: string;
|
|
123
|
+
onChange: (color: string) => void;
|
|
124
|
+
presets?: string[];
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function ColorPickerContent({
|
|
128
|
+
value,
|
|
129
|
+
onChange,
|
|
130
|
+
presets = COLOR_PICKER_PRESETS,
|
|
131
|
+
}: ColorPickerContentProps) {
|
|
132
|
+
const [hexInput, setHexInput] = React.useState(value);
|
|
133
|
+
|
|
134
|
+
React.useEffect(() => {
|
|
135
|
+
setHexInput(value);
|
|
136
|
+
}, [value]);
|
|
137
|
+
|
|
138
|
+
function handleHexInputChange(e: React.ChangeEvent<HTMLInputElement>) {
|
|
139
|
+
const raw = e.target.value;
|
|
140
|
+
setHexInput(raw);
|
|
141
|
+
const normalized = normalizeHex(raw);
|
|
142
|
+
if (isValidHex(normalized)) {
|
|
143
|
+
onChange(normalized);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function handleHexBlur() {
|
|
148
|
+
const normalized = normalizeHex(hexInput);
|
|
149
|
+
if (isValidHex(normalized)) {
|
|
150
|
+
setHexInput(normalized);
|
|
151
|
+
if (normalized !== value) onChange(normalized);
|
|
152
|
+
} else {
|
|
153
|
+
setHexInput(value);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const normalizedInput = normalizeHex(hexInput);
|
|
158
|
+
const isValid = isValidHex(normalizedInput);
|
|
159
|
+
|
|
160
|
+
return (
|
|
161
|
+
<div data-slot="color-picker-content" className="flex flex-col gap-4">
|
|
162
|
+
<div>
|
|
163
|
+
<p className="mb-2 text-xs font-medium text-muted-foreground">
|
|
164
|
+
Presets
|
|
165
|
+
</p>
|
|
166
|
+
<div className="grid grid-cols-7 gap-1.5">
|
|
167
|
+
{presets.map((color) => (
|
|
168
|
+
<ColorSwatch
|
|
169
|
+
key={color}
|
|
170
|
+
color={color}
|
|
171
|
+
selected={value.toLowerCase() === color.toLowerCase()}
|
|
172
|
+
onClick={onChange}
|
|
173
|
+
/>
|
|
174
|
+
))}
|
|
175
|
+
</div>
|
|
176
|
+
</div>
|
|
177
|
+
|
|
178
|
+
<div>
|
|
179
|
+
<p className="mb-2 text-xs font-medium text-muted-foreground">
|
|
180
|
+
Custom hex
|
|
181
|
+
</p>
|
|
182
|
+
<div className="flex items-center gap-2">
|
|
183
|
+
<label
|
|
184
|
+
className="relative size-9 shrink-0 cursor-pointer border border-border"
|
|
185
|
+
title="Open color picker"
|
|
186
|
+
style={{
|
|
187
|
+
backgroundColor: isValid ? normalizedInput : value,
|
|
188
|
+
}}
|
|
189
|
+
>
|
|
190
|
+
<input
|
|
191
|
+
type="color"
|
|
192
|
+
aria-label="Native color picker"
|
|
193
|
+
value={isValid ? normalizedInput : value}
|
|
194
|
+
onChange={(e) => {
|
|
195
|
+
setHexInput(e.target.value);
|
|
196
|
+
onChange(e.target.value);
|
|
197
|
+
}}
|
|
198
|
+
className="absolute inset-0 h-full w-full cursor-pointer opacity-0"
|
|
199
|
+
/>
|
|
200
|
+
</label>
|
|
201
|
+
<Input
|
|
202
|
+
aria-label="Hex color value"
|
|
203
|
+
placeholder="#000000"
|
|
204
|
+
value={hexInput}
|
|
205
|
+
onChange={handleHexInputChange}
|
|
206
|
+
onBlur={handleHexBlur}
|
|
207
|
+
maxLength={7}
|
|
208
|
+
className="h-9 flex-1 font-mono text-sm uppercase"
|
|
209
|
+
/>
|
|
210
|
+
</div>
|
|
211
|
+
</div>
|
|
212
|
+
</div>
|
|
213
|
+
);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// ---------------------------------------------------------------------------
|
|
217
|
+
// ColorPicker (popover trigger + content)
|
|
218
|
+
// ---------------------------------------------------------------------------
|
|
219
|
+
|
|
220
|
+
interface ColorPickerProps {
|
|
221
|
+
/** The current color value (hex string, e.g. `"#3B82F6"`). */
|
|
222
|
+
value?: string;
|
|
223
|
+
/** Default value when uncontrolled. */
|
|
224
|
+
defaultValue?: string;
|
|
225
|
+
/** Called whenever the selected color changes. */
|
|
226
|
+
onChange?: (color: string) => void;
|
|
227
|
+
/** Preset colors shown in the swatch grid. Defaults to `COLOR_PICKER_PRESETS`. */
|
|
228
|
+
presets?: string[];
|
|
229
|
+
/** Disable the picker. */
|
|
230
|
+
disabled?: boolean;
|
|
231
|
+
/** Optional label shown above the trigger. */
|
|
232
|
+
label?: string;
|
|
233
|
+
className?: string;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
function ColorPicker({
|
|
237
|
+
value: controlledValue,
|
|
238
|
+
defaultValue = "#3B82F6",
|
|
239
|
+
onChange,
|
|
240
|
+
presets,
|
|
241
|
+
disabled,
|
|
242
|
+
label,
|
|
243
|
+
className,
|
|
244
|
+
}: ColorPickerProps) {
|
|
245
|
+
const isControlled = controlledValue !== undefined;
|
|
246
|
+
const [internalValue, setInternalValue] = React.useState(defaultValue);
|
|
247
|
+
const color = isControlled ? controlledValue : internalValue;
|
|
248
|
+
const [open, setOpen] = React.useState(false);
|
|
249
|
+
|
|
250
|
+
React.useEffect(() => {
|
|
251
|
+
if (disabled) setOpen(false);
|
|
252
|
+
}, [disabled]);
|
|
253
|
+
|
|
254
|
+
function handleChange(newColor: string) {
|
|
255
|
+
if (!isControlled) setInternalValue(newColor);
|
|
256
|
+
onChange?.(newColor);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
return (
|
|
260
|
+
<div
|
|
261
|
+
data-slot="color-picker"
|
|
262
|
+
className={cn("inline-flex flex-col gap-1.5 font-sans", className)}
|
|
263
|
+
>
|
|
264
|
+
{label && (
|
|
265
|
+
<span className="text-sm font-medium text-foreground">{label}</span>
|
|
266
|
+
)}
|
|
267
|
+
<Popover open={open} onOpenChange={setOpen}>
|
|
268
|
+
<PopoverTrigger
|
|
269
|
+
disabled={disabled}
|
|
270
|
+
className={cn(
|
|
271
|
+
"flex h-9 min-w-[140px] cursor-pointer items-center gap-2.5 border border-input bg-background px-3 text-sm outline-none transition-colors",
|
|
272
|
+
"hover:border-ring",
|
|
273
|
+
"focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1",
|
|
274
|
+
"disabled:cursor-not-allowed disabled:opacity-50",
|
|
275
|
+
)}
|
|
276
|
+
>
|
|
277
|
+
<span
|
|
278
|
+
aria-hidden
|
|
279
|
+
className="size-5 shrink-0 border border-border/50"
|
|
280
|
+
style={{ backgroundColor: color }}
|
|
281
|
+
/>
|
|
282
|
+
<span className="font-mono text-xs uppercase tracking-wide">
|
|
283
|
+
{color}
|
|
284
|
+
</span>
|
|
285
|
+
</PopoverTrigger>
|
|
286
|
+
<PopoverContent
|
|
287
|
+
className="w-[220px] p-4"
|
|
288
|
+
align="start"
|
|
289
|
+
data-shadcn-scope
|
|
290
|
+
>
|
|
291
|
+
<ColorPickerContent
|
|
292
|
+
value={color}
|
|
293
|
+
onChange={handleChange}
|
|
294
|
+
presets={presets}
|
|
295
|
+
/>
|
|
296
|
+
</PopoverContent>
|
|
297
|
+
</Popover>
|
|
298
|
+
</div>
|
|
299
|
+
);
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
export {
|
|
303
|
+
ColorPicker,
|
|
304
|
+
ColorPickerContent,
|
|
305
|
+
ColorSwatch,
|
|
306
|
+
isValidHex,
|
|
307
|
+
normalizeHex,
|
|
308
|
+
};
|
|
309
|
+
export type { ColorPickerProps, ColorPickerContentProps, ColorSwatchProps };
|