@wealthx/shadcn 1.0.2 → 1.1.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 (36) hide show
  1. package/.turbo/turbo-build.log +154 -138
  2. package/CHANGELOG.md +6 -0
  3. package/README.md +82 -0
  4. package/dist/chunk-3EQP72AW.mjs +58 -0
  5. package/dist/chunk-5JGQAAQV.mjs +212 -0
  6. package/dist/chunk-GLW2UO6O.mjs +212 -0
  7. package/dist/chunk-RN67642N.mjs +171 -0
  8. package/dist/chunk-UEL4RD5P.mjs +272 -0
  9. package/dist/chunk-YBXCIF5Q.mjs +198 -0
  10. package/dist/components/ui/cashflow-bar-chart.js +596 -0
  11. package/dist/components/ui/cashflow-bar-chart.mjs +16 -0
  12. package/dist/components/ui/combobox.js +261 -0
  13. package/dist/components/ui/combobox.mjs +28 -0
  14. package/dist/components/ui/data-table.mjs +3 -3
  15. package/dist/components/ui/expense-bar-chart.js +543 -0
  16. package/dist/components/ui/expense-bar-chart.mjs +16 -0
  17. package/dist/components/ui/field.mjs +2 -2
  18. package/dist/components/ui/income-bar-chart.js +543 -0
  19. package/dist/components/ui/income-bar-chart.mjs +16 -0
  20. package/dist/components/ui/transactions-income-expense-bar-chart.js +478 -0
  21. package/dist/components/ui/transactions-income-expense-bar-chart.mjs +16 -0
  22. package/dist/index.js +1685 -725
  23. package/dist/index.mjs +152 -111
  24. package/dist/styles.css +1 -1
  25. package/package.json +30 -2
  26. package/src/components/index.tsx +56 -0
  27. package/src/components/ui/cashflow-bar-chart.tsx +336 -0
  28. package/src/components/ui/chart-shared.tsx +100 -0
  29. package/src/components/ui/combobox.tsx +217 -0
  30. package/src/components/ui/expense-bar-chart.tsx +278 -0
  31. package/src/components/ui/income-bar-chart.tsx +278 -0
  32. package/src/components/ui/transactions-income-expense-bar-chart.tsx +198 -0
  33. package/src/styles/styles-css.ts +1 -1
  34. package/tsup.config.ts +5 -0
  35. package/dist/{chunk-K76E2TQU.mjs → chunk-CJ46PDXE.mjs} +5 -5
  36. package/dist/{chunk-HUVTPUV2.mjs → chunk-NLLKTU4B.mjs} +3 -3
@@ -0,0 +1,212 @@
1
+ import {
2
+ CHART_PERIODS,
3
+ CHART_SLICE_COUNT,
4
+ ChartLegendItem,
5
+ ChartPeriodButton,
6
+ DATASET_ALPHAS,
7
+ FALLBACK_TICK,
8
+ formatTooltipDate,
9
+ hexToRgba
10
+ } from "./chunk-3EQP72AW.mjs";
11
+ import {
12
+ Skeleton
13
+ } from "./chunk-HS7TFG7V.mjs";
14
+ import {
15
+ Empty,
16
+ EmptyDescription
17
+ } from "./chunk-YN5SYTOO.mjs";
18
+ import {
19
+ Card,
20
+ CardAction,
21
+ CardContent,
22
+ CardHeader,
23
+ CardTitle
24
+ } from "./chunk-SLWCCURD.mjs";
25
+ import {
26
+ useThemeVars
27
+ } from "./chunk-OXQQNQZI.mjs";
28
+ import {
29
+ cn
30
+ } from "./chunk-V7CNWJT3.mjs";
31
+ import {
32
+ __spreadProps,
33
+ __spreadValues
34
+ } from "./chunk-FWCSY2DS.mjs";
35
+
36
+ // src/components/ui/expense-bar-chart.tsx
37
+ import { useEffect, useMemo, useRef, useState } from "react";
38
+ import {
39
+ Chart as ChartJS,
40
+ CategoryScale,
41
+ LinearScale,
42
+ BarElement,
43
+ Tooltip,
44
+ Legend
45
+ } from "chart.js";
46
+ import { Chart } from "react-chartjs-2";
47
+ import { jsx, jsxs } from "react/jsx-runtime";
48
+ ChartJS.register(CategoryScale, LinearScale, BarElement, Tooltip, Legend);
49
+ var FALLBACK_SECONDARY = "#162029";
50
+ function ExpenseBarChart({
51
+ expenseData,
52
+ title = "Expenses",
53
+ showLegend = true,
54
+ showXAxis = true,
55
+ showYAxis = true,
56
+ legendPosition = "top",
57
+ defaultPeriod = 6,
58
+ granularity = "monthly",
59
+ height = 280,
60
+ width = "100%",
61
+ className,
62
+ isLoading = false
63
+ }) {
64
+ const periods = CHART_PERIODS[granularity];
65
+ const [period, setPeriod] = useState(defaultPeriod);
66
+ const isFirstRender = useRef(true);
67
+ useEffect(() => {
68
+ if (isFirstRender.current) {
69
+ isFirstRender.current = false;
70
+ return;
71
+ }
72
+ setPeriod(CHART_PERIODS[granularity][0]);
73
+ }, [granularity]);
74
+ const themeVars = useThemeVars();
75
+ const brandSecondary = themeVars["--theme-secondary"] || FALLBACK_SECONDARY;
76
+ const sliced = useMemo(() => {
77
+ var _a, _b;
78
+ if (!((_a = expenseData == null ? void 0 : expenseData.months) == null ? void 0 : _a.length) || !expenseData.datasets.length) return null;
79
+ const count = Math.min(CHART_SLICE_COUNT[granularity][period], expenseData.months.length);
80
+ const start = expenseData.months.length - count;
81
+ return {
82
+ months: expenseData.months.slice(start),
83
+ dates: (_b = expenseData.dates) == null ? void 0 : _b.slice(start),
84
+ datasets: expenseData.datasets.map((ds) => __spreadProps(__spreadValues({}, ds), {
85
+ data: ds.data.slice(start)
86
+ }))
87
+ };
88
+ }, [expenseData, period, granularity]);
89
+ const datasetColors = useMemo(
90
+ () => {
91
+ var _a;
92
+ return (_a = sliced == null ? void 0 : sliced.datasets.map(
93
+ (_, i) => hexToRgba(brandSecondary, DATASET_ALPHAS[i % DATASET_ALPHAS.length])
94
+ )) != null ? _a : [];
95
+ },
96
+ [sliced, brandSecondary]
97
+ );
98
+ const chartData = useMemo(() => {
99
+ if (!sliced) return { labels: [], datasets: [] };
100
+ return {
101
+ labels: sliced.months,
102
+ datasets: sliced.datasets.map((ds, i) => ({
103
+ label: ds.label,
104
+ data: ds.data,
105
+ backgroundColor: datasetColors[i],
106
+ hoverBackgroundColor: hexToRgba(
107
+ brandSecondary,
108
+ Math.min(DATASET_ALPHAS[i % DATASET_ALPHAS.length] + 0.15, 1)
109
+ ),
110
+ borderWidth: 0,
111
+ borderRadius: 0,
112
+ borderSkipped: false,
113
+ barPercentage: 0.75,
114
+ categoryPercentage: 0.7,
115
+ stack: "expense"
116
+ }))
117
+ };
118
+ }, [sliced, datasetColors, brandSecondary]);
119
+ const options = useMemo(() => ({
120
+ responsive: true,
121
+ maintainAspectRatio: false,
122
+ animation: { duration: 800, easing: "easeOutQuart" },
123
+ layout: { padding: 0 },
124
+ plugins: {
125
+ legend: { display: false },
126
+ tooltip: {
127
+ mode: "index",
128
+ intersect: false,
129
+ padding: 12,
130
+ cornerRadius: 0,
131
+ titleFont: { size: 11, weight: "600" },
132
+ bodyFont: { size: 12, weight: "500" },
133
+ callbacks: {
134
+ title: (tooltipItems) => {
135
+ var _a, _b, _c, _d;
136
+ const idx = (_a = tooltipItems[0]) == null ? void 0 : _a.dataIndex;
137
+ if (idx != null && ((_b = sliced == null ? void 0 : sliced.dates) == null ? void 0 : _b[idx])) {
138
+ return formatTooltipDate(sliced.dates[idx], granularity);
139
+ }
140
+ return (_d = (_c = tooltipItems[0]) == null ? void 0 : _c.label) != null ? _d : "";
141
+ },
142
+ label: (ctx) => {
143
+ const val = ctx.raw;
144
+ if (val === 0) return null;
145
+ return ` ${ctx.dataset.label}: $${val.toLocaleString()}`;
146
+ }
147
+ }
148
+ }
149
+ },
150
+ scales: {
151
+ x: {
152
+ display: showXAxis,
153
+ stacked: true,
154
+ grid: { display: false },
155
+ border: { display: false },
156
+ ticks: { font: { size: 10 }, color: FALLBACK_TICK }
157
+ },
158
+ y: {
159
+ display: showYAxis,
160
+ stacked: true,
161
+ grid: { display: false },
162
+ border: { display: false },
163
+ ticks: {
164
+ font: { size: 10 },
165
+ color: FALLBACK_TICK,
166
+ maxTicksLimit: 5,
167
+ padding: 8,
168
+ callback: (v) => `$${Number(v).toLocaleString()}`
169
+ }
170
+ }
171
+ }
172
+ }), [showXAxis, showYAxis, sliced, granularity]);
173
+ return /* @__PURE__ */ jsxs(
174
+ Card,
175
+ {
176
+ className: cn("w-full py-4 sm:py-6 gap-2", className),
177
+ style: { maxWidth: width },
178
+ children: [
179
+ /* @__PURE__ */ jsxs(CardHeader, { className: "px-3 sm:px-6", children: [
180
+ /* @__PURE__ */ jsx(CardTitle, { className: "text-sm sm:text-base", children: title }),
181
+ /* @__PURE__ */ jsx(CardAction, { children: /* @__PURE__ */ jsx("div", { className: "flex gap-0.5 sm:gap-1", children: periods.map((p) => /* @__PURE__ */ jsx(
182
+ ChartPeriodButton,
183
+ {
184
+ period: p,
185
+ active: period === p,
186
+ onClick: () => setPeriod(p)
187
+ },
188
+ p
189
+ )) }) })
190
+ ] }),
191
+ /* @__PURE__ */ jsx(CardContent, { className: "px-3 sm:px-6", children: isLoading ? /* @__PURE__ */ jsx(Skeleton, { style: { height, width: "100%" } }) : !sliced ? /* @__PURE__ */ jsx(Empty, { className: "flex-none p-4", style: { height }, children: /* @__PURE__ */ jsx(EmptyDescription, { children: "No data available" }) }) : /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-2", children: [
192
+ showLegend && legendPosition === "top" && /* @__PURE__ */ jsx("div", { className: "flex flex-wrap gap-x-3 gap-y-1.5 pb-2", children: sliced.datasets.map((ds, i) => /* @__PURE__ */ jsx(ChartLegendItem, { label: ds.label, color: datasetColors[i] }, ds.label)) }),
193
+ /* @__PURE__ */ jsx("div", { style: { height, width: "100%", position: "relative" }, children: /* @__PURE__ */ jsx(
194
+ Chart,
195
+ {
196
+ type: "bar",
197
+ data: chartData,
198
+ options,
199
+ "aria-label": title
200
+ },
201
+ brandSecondary
202
+ ) }),
203
+ showLegend && legendPosition === "bottom" && /* @__PURE__ */ jsx("div", { className: "flex flex-wrap gap-x-3 gap-y-1.5 pt-2", children: sliced.datasets.map((ds, i) => /* @__PURE__ */ jsx(ChartLegendItem, { label: ds.label, color: datasetColors[i] }, ds.label)) })
204
+ ] }) })
205
+ ]
206
+ }
207
+ );
208
+ }
209
+
210
+ export {
211
+ ExpenseBarChart
212
+ };
@@ -0,0 +1,212 @@
1
+ import {
2
+ CHART_PERIODS,
3
+ CHART_SLICE_COUNT,
4
+ ChartLegendItem,
5
+ ChartPeriodButton,
6
+ DATASET_ALPHAS,
7
+ FALLBACK_TICK,
8
+ formatTooltipDate,
9
+ hexToRgba
10
+ } from "./chunk-3EQP72AW.mjs";
11
+ import {
12
+ Skeleton
13
+ } from "./chunk-HS7TFG7V.mjs";
14
+ import {
15
+ Empty,
16
+ EmptyDescription
17
+ } from "./chunk-YN5SYTOO.mjs";
18
+ import {
19
+ Card,
20
+ CardAction,
21
+ CardContent,
22
+ CardHeader,
23
+ CardTitle
24
+ } from "./chunk-SLWCCURD.mjs";
25
+ import {
26
+ useThemeVars
27
+ } from "./chunk-OXQQNQZI.mjs";
28
+ import {
29
+ cn
30
+ } from "./chunk-V7CNWJT3.mjs";
31
+ import {
32
+ __spreadProps,
33
+ __spreadValues
34
+ } from "./chunk-FWCSY2DS.mjs";
35
+
36
+ // src/components/ui/income-bar-chart.tsx
37
+ import { useEffect, useMemo, useRef, useState } from "react";
38
+ import {
39
+ Chart as ChartJS,
40
+ CategoryScale,
41
+ LinearScale,
42
+ BarElement,
43
+ Tooltip,
44
+ Legend
45
+ } from "chart.js";
46
+ import { Chart } from "react-chartjs-2";
47
+ import { jsx, jsxs } from "react/jsx-runtime";
48
+ ChartJS.register(CategoryScale, LinearScale, BarElement, Tooltip, Legend);
49
+ var FALLBACK_PRIMARY = "#33FF99";
50
+ function IncomeBarChart({
51
+ incomeData,
52
+ title = "Income",
53
+ showLegend = true,
54
+ showXAxis = true,
55
+ showYAxis = true,
56
+ legendPosition = "top",
57
+ defaultPeriod = 6,
58
+ granularity = "monthly",
59
+ height = 280,
60
+ width = "100%",
61
+ className,
62
+ isLoading = false
63
+ }) {
64
+ const periods = CHART_PERIODS[granularity];
65
+ const [period, setPeriod] = useState(defaultPeriod);
66
+ const isFirstRender = useRef(true);
67
+ useEffect(() => {
68
+ if (isFirstRender.current) {
69
+ isFirstRender.current = false;
70
+ return;
71
+ }
72
+ setPeriod(CHART_PERIODS[granularity][0]);
73
+ }, [granularity]);
74
+ const themeVars = useThemeVars();
75
+ const brandPrimary = themeVars["--theme-primary"] || FALLBACK_PRIMARY;
76
+ const sliced = useMemo(() => {
77
+ var _a, _b;
78
+ if (!((_a = incomeData == null ? void 0 : incomeData.months) == null ? void 0 : _a.length) || !incomeData.datasets.length) return null;
79
+ const count = Math.min(CHART_SLICE_COUNT[granularity][period], incomeData.months.length);
80
+ const start = incomeData.months.length - count;
81
+ return {
82
+ months: incomeData.months.slice(start),
83
+ dates: (_b = incomeData.dates) == null ? void 0 : _b.slice(start),
84
+ datasets: incomeData.datasets.map((ds) => __spreadProps(__spreadValues({}, ds), {
85
+ data: ds.data.slice(start)
86
+ }))
87
+ };
88
+ }, [incomeData, period, granularity]);
89
+ const datasetColors = useMemo(
90
+ () => {
91
+ var _a;
92
+ return (_a = sliced == null ? void 0 : sliced.datasets.map(
93
+ (_, i) => hexToRgba(brandPrimary, DATASET_ALPHAS[i % DATASET_ALPHAS.length])
94
+ )) != null ? _a : [];
95
+ },
96
+ [sliced, brandPrimary]
97
+ );
98
+ const chartData = useMemo(() => {
99
+ if (!sliced) return { labels: [], datasets: [] };
100
+ return {
101
+ labels: sliced.months,
102
+ datasets: sliced.datasets.map((ds, i) => ({
103
+ label: ds.label,
104
+ data: ds.data,
105
+ backgroundColor: datasetColors[i],
106
+ hoverBackgroundColor: hexToRgba(
107
+ brandPrimary,
108
+ Math.min(DATASET_ALPHAS[i % DATASET_ALPHAS.length] + 0.15, 1)
109
+ ),
110
+ borderWidth: 0,
111
+ borderRadius: 0,
112
+ borderSkipped: false,
113
+ barPercentage: 0.75,
114
+ categoryPercentage: 0.7,
115
+ stack: "income"
116
+ }))
117
+ };
118
+ }, [sliced, datasetColors, brandPrimary]);
119
+ const options = useMemo(() => ({
120
+ responsive: true,
121
+ maintainAspectRatio: false,
122
+ animation: { duration: 800, easing: "easeOutQuart" },
123
+ layout: { padding: 0 },
124
+ plugins: {
125
+ legend: { display: false },
126
+ tooltip: {
127
+ mode: "index",
128
+ intersect: false,
129
+ padding: 12,
130
+ cornerRadius: 0,
131
+ titleFont: { size: 11, weight: "600" },
132
+ bodyFont: { size: 12, weight: "500" },
133
+ callbacks: {
134
+ title: (tooltipItems) => {
135
+ var _a, _b, _c, _d;
136
+ const idx = (_a = tooltipItems[0]) == null ? void 0 : _a.dataIndex;
137
+ if (idx != null && ((_b = sliced == null ? void 0 : sliced.dates) == null ? void 0 : _b[idx])) {
138
+ return formatTooltipDate(sliced.dates[idx], granularity);
139
+ }
140
+ return (_d = (_c = tooltipItems[0]) == null ? void 0 : _c.label) != null ? _d : "";
141
+ },
142
+ label: (ctx) => {
143
+ const val = ctx.raw;
144
+ if (val === 0) return null;
145
+ return ` ${ctx.dataset.label}: $${val.toLocaleString()}`;
146
+ }
147
+ }
148
+ }
149
+ },
150
+ scales: {
151
+ x: {
152
+ display: showXAxis,
153
+ stacked: true,
154
+ grid: { display: false },
155
+ border: { display: false },
156
+ ticks: { font: { size: 10 }, color: FALLBACK_TICK }
157
+ },
158
+ y: {
159
+ display: showYAxis,
160
+ stacked: true,
161
+ grid: { display: false },
162
+ border: { display: false },
163
+ ticks: {
164
+ font: { size: 10 },
165
+ color: FALLBACK_TICK,
166
+ maxTicksLimit: 5,
167
+ padding: 8,
168
+ callback: (v) => `$${Number(v).toLocaleString()}`
169
+ }
170
+ }
171
+ }
172
+ }), [showXAxis, showYAxis, sliced, granularity]);
173
+ return /* @__PURE__ */ jsxs(
174
+ Card,
175
+ {
176
+ className: cn("w-full py-4 sm:py-6 gap-2", className),
177
+ style: { maxWidth: width },
178
+ children: [
179
+ /* @__PURE__ */ jsxs(CardHeader, { className: "px-3 sm:px-6", children: [
180
+ /* @__PURE__ */ jsx(CardTitle, { className: "text-sm sm:text-base", children: title }),
181
+ /* @__PURE__ */ jsx(CardAction, { children: /* @__PURE__ */ jsx("div", { className: "flex gap-0.5 sm:gap-1", children: periods.map((p) => /* @__PURE__ */ jsx(
182
+ ChartPeriodButton,
183
+ {
184
+ period: p,
185
+ active: period === p,
186
+ onClick: () => setPeriod(p)
187
+ },
188
+ p
189
+ )) }) })
190
+ ] }),
191
+ /* @__PURE__ */ jsx(CardContent, { className: "px-3 sm:px-6", children: isLoading ? /* @__PURE__ */ jsx(Skeleton, { style: { height, width: "100%" } }) : !sliced ? /* @__PURE__ */ jsx(Empty, { className: "flex-none p-4", style: { height }, children: /* @__PURE__ */ jsx(EmptyDescription, { children: "No data available" }) }) : /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-2", children: [
192
+ showLegend && legendPosition === "top" && /* @__PURE__ */ jsx("div", { className: "flex flex-wrap gap-x-3 gap-y-1.5 pb-2", children: sliced.datasets.map((ds, i) => /* @__PURE__ */ jsx(ChartLegendItem, { label: ds.label, color: datasetColors[i] }, ds.label)) }),
193
+ /* @__PURE__ */ jsx("div", { style: { height, width: "100%", position: "relative" }, children: /* @__PURE__ */ jsx(
194
+ Chart,
195
+ {
196
+ type: "bar",
197
+ data: chartData,
198
+ options,
199
+ "aria-label": title
200
+ },
201
+ brandPrimary
202
+ ) }),
203
+ showLegend && legendPosition === "bottom" && /* @__PURE__ */ jsx("div", { className: "flex flex-wrap gap-x-3 gap-y-1.5 pt-2", children: sliced.datasets.map((ds, i) => /* @__PURE__ */ jsx(ChartLegendItem, { label: ds.label, color: datasetColors[i] }, ds.label)) })
204
+ ] }) })
205
+ ]
206
+ }
207
+ );
208
+ }
209
+
210
+ export {
211
+ IncomeBarChart
212
+ };
@@ -0,0 +1,171 @@
1
+ import {
2
+ Spinner
3
+ } from "./chunk-BMFN37JH.mjs";
4
+ import {
5
+ FALLBACK_TICK
6
+ } from "./chunk-3EQP72AW.mjs";
7
+ import {
8
+ Empty,
9
+ EmptyDescription
10
+ } from "./chunk-YN5SYTOO.mjs";
11
+ import {
12
+ Card,
13
+ CardContent,
14
+ CardHeader,
15
+ CardTitle
16
+ } from "./chunk-SLWCCURD.mjs";
17
+ import {
18
+ useThemeVars
19
+ } from "./chunk-OXQQNQZI.mjs";
20
+ import {
21
+ cn
22
+ } from "./chunk-V7CNWJT3.mjs";
23
+
24
+ // src/components/ui/transactions-income-expense-bar-chart.tsx
25
+ import { useMemo } from "react";
26
+ import {
27
+ Chart as ChartJS,
28
+ CategoryScale,
29
+ LinearScale,
30
+ BarElement,
31
+ Tooltip
32
+ } from "chart.js";
33
+ import ChartDataLabels from "chartjs-plugin-datalabels";
34
+ import { Chart } from "react-chartjs-2";
35
+ import { jsx, jsxs } from "react/jsx-runtime";
36
+ ChartJS.register(CategoryScale, LinearScale, BarElement, Tooltip);
37
+ var FALLBACK_PRIMARY = "#33FF99";
38
+ var FALLBACK_SECONDARY = "#162029";
39
+ var VALUE_LABEL_COLOR = "#162029";
40
+ function formatDollar(value) {
41
+ return `$${value.toLocaleString(void 0, {
42
+ minimumFractionDigits: 2,
43
+ maximumFractionDigits: 2
44
+ })}`;
45
+ }
46
+ function TransactionsIncomeExpenseBarChart({
47
+ totalIncome,
48
+ totalExpense,
49
+ title = "Transactions \u2014 Income vs Expense",
50
+ height = 120,
51
+ width = "100%",
52
+ className,
53
+ isLoading = false
54
+ }) {
55
+ const themeVars = useThemeVars();
56
+ const brandPrimary = themeVars["--theme-primary"] || FALLBACK_PRIMARY;
57
+ const brandSecondary = themeVars["--theme-secondary"] || FALLBACK_SECONDARY;
58
+ const hasData = totalIncome != null && totalExpense != null;
59
+ const incomeVal = totalIncome != null ? totalIncome : 0;
60
+ const expenseVal = Math.abs(totalExpense != null ? totalExpense : 0);
61
+ const maxVal = Math.max(incomeVal, expenseVal);
62
+ const chartData = useMemo(() => {
63
+ if (!hasData) return { labels: [], datasets: [] };
64
+ return {
65
+ labels: ["Incoming", "Outgoing"],
66
+ datasets: [
67
+ {
68
+ barThickness: 40,
69
+ backgroundColor: [brandPrimary, brandSecondary],
70
+ hoverBackgroundColor: [brandPrimary, brandSecondary],
71
+ borderWidth: 0,
72
+ borderRadius: 0,
73
+ borderSkipped: false,
74
+ data: [incomeVal, expenseVal],
75
+ // chartjs-plugin-datalabels config — typed via plugin module augmentation
76
+ datalabels: {
77
+ labels: {
78
+ value: {
79
+ anchor: "end",
80
+ align: "end",
81
+ offset: 10,
82
+ clamp: false,
83
+ font: { weight: "bold", size: 14 },
84
+ color: VALUE_LABEL_COLOR,
85
+ textAlign: "left",
86
+ // Returns array for multi-line: dollar value on line 1, blank on line 2
87
+ formatter: (v) => [formatDollar(v), ""]
88
+ },
89
+ name: {
90
+ anchor: "end",
91
+ align: "end",
92
+ offset: 10,
93
+ clamp: false,
94
+ font: { size: 12 },
95
+ color: FALLBACK_TICK,
96
+ textAlign: "left",
97
+ // Returns array for multi-line: blank on line 1, bar label on line 2
98
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
99
+ formatter: (_, ctx) => {
100
+ var _a, _b;
101
+ return ["", String((_b = (_a = ctx.chart.data.labels) == null ? void 0 : _a[ctx.dataIndex]) != null ? _b : "")];
102
+ }
103
+ }
104
+ }
105
+ }
106
+ }
107
+ ]
108
+ };
109
+ }, [hasData, incomeVal, expenseVal, brandPrimary, brandSecondary]);
110
+ const options = useMemo(() => ({
111
+ indexAxis: "y",
112
+ responsive: true,
113
+ maintainAspectRatio: false,
114
+ animation: { duration: 800, easing: "easeOutQuart" },
115
+ layout: {
116
+ // Right padding reserves space for the datalabels rendered outside the bar area
117
+ padding: { right: 180, left: 0, top: 10, bottom: 10 }
118
+ },
119
+ plugins: {
120
+ legend: { display: false },
121
+ tooltip: { enabled: false }
122
+ },
123
+ scales: {
124
+ y: {
125
+ display: true,
126
+ grid: { display: false },
127
+ border: { display: false },
128
+ ticks: { display: false }
129
+ },
130
+ x: {
131
+ display: true,
132
+ suggestedMax: maxVal * 1.3,
133
+ grid: { display: false },
134
+ border: { display: false },
135
+ ticks: { display: false }
136
+ }
137
+ }
138
+ }), [maxVal]);
139
+ return /* @__PURE__ */ jsxs(
140
+ Card,
141
+ {
142
+ className: cn("w-full py-4 sm:py-6 gap-2", className),
143
+ style: { maxWidth: width },
144
+ children: [
145
+ /* @__PURE__ */ jsx(CardHeader, { className: "px-3 sm:px-6", children: /* @__PURE__ */ jsx(CardTitle, { className: "text-sm sm:text-base", children: title }) }),
146
+ /* @__PURE__ */ jsx(CardContent, { className: "px-3 sm:px-6", children: isLoading ? /* @__PURE__ */ jsx(
147
+ "div",
148
+ {
149
+ className: "flex items-center justify-center text-muted-foreground",
150
+ style: { height, width: "100%" },
151
+ children: /* @__PURE__ */ jsx(Spinner, { size: "lg" })
152
+ }
153
+ ) : !hasData ? /* @__PURE__ */ jsx(Empty, { className: "flex-none p-4", style: { height }, children: /* @__PURE__ */ jsx(EmptyDescription, { children: "No data available" }) }) : /* @__PURE__ */ jsx("div", { style: { height, width: "100%", position: "relative" }, children: /* @__PURE__ */ jsx(
154
+ Chart,
155
+ {
156
+ type: "bar",
157
+ data: chartData,
158
+ options,
159
+ plugins: [ChartDataLabels],
160
+ "aria-label": title
161
+ },
162
+ `${brandPrimary}__${brandSecondary}`
163
+ ) }) })
164
+ ]
165
+ }
166
+ );
167
+ }
168
+
169
+ export {
170
+ TransactionsIncomeExpenseBarChart
171
+ };