@alpic-ai/ui 1.155.0 → 1.157.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/dist/components/bar-chart.d.mts +3 -1
- package/dist/components/bar-chart.mjs +17 -9
- package/dist/components/form.d.mts +1 -1
- package/dist/components/skeleton.d.mts +1 -1
- package/dist/components/stat.d.mts +7 -2
- package/dist/components/stat.mjs +68 -61
- package/package.json +9 -9
- package/src/components/bar-chart.tsx +30 -19
- package/src/components/stat.tsx +72 -44
|
@@ -13,6 +13,8 @@ interface BarChartProps {
|
|
|
13
13
|
valueLabels?: boolean;
|
|
14
14
|
height?: number;
|
|
15
15
|
yAxisWidth?: number;
|
|
16
|
+
bare?: boolean;
|
|
17
|
+
barCategoryGap?: number | string;
|
|
16
18
|
palette?: ChartPaletteName;
|
|
17
19
|
referenceLine?: {
|
|
18
20
|
y: number;
|
|
@@ -27,6 +29,6 @@ interface BarChartProps {
|
|
|
27
29
|
labelFormatter?: (label: string | number) => string;
|
|
28
30
|
className?: string;
|
|
29
31
|
}
|
|
30
|
-
declare function BarChart({ data, index, series, variant, legend, legendAlign, valueLabels, height, yAxisWidth, palette, referenceLine, markers, texture, partialLastBar, loading, valueFormatter, labelFormatter, className }: BarChartProps): React$1.JSX.Element;
|
|
32
|
+
declare function BarChart({ data, index, series, variant, legend, legendAlign, valueLabels, height, yAxisWidth, bare, barCategoryGap, palette, referenceLine, markers, texture, partialLastBar, loading, valueFormatter, labelFormatter, className }: BarChartProps): React$1.JSX.Element;
|
|
31
33
|
//#endregion
|
|
32
34
|
export { BarChart, BarChartProps };
|
|
@@ -12,7 +12,7 @@ import { Bar, BarChart as BarChart$1, CartesianGrid, Cell, LabelList, Responsive
|
|
|
12
12
|
//#region src/components/bar-chart.tsx
|
|
13
13
|
const BAR_RADIUS = 4;
|
|
14
14
|
const MAX_BAR_SIZE = 48;
|
|
15
|
-
function BarChart({ data, index, series, variant = "stacked", legend = false, legendAlign = "left", valueLabels = false, height = 200, yAxisWidth = 48, palette, referenceLine, markers, texture = false, partialLastBar = false, loading = false, valueFormatter = (value) => value.toLocaleString("en-US"), labelFormatter, className }) {
|
|
15
|
+
function BarChart({ data, index, series, variant = "stacked", legend = false, legendAlign = "left", valueLabels = false, height = 200, yAxisWidth = 48, bare = false, barCategoryGap, palette, referenceLine, markers, texture = false, partialLastBar = false, loading = false, valueFormatter = (value) => value.toLocaleString("en-US"), labelFormatter, className }) {
|
|
16
16
|
const { palette: paletteColors, theme } = useChartContext(palette);
|
|
17
17
|
const reactId = React$1.useId().replace(/:/g, "");
|
|
18
18
|
const resolved = resolveSeries(series, paletteColors, theme);
|
|
@@ -39,7 +39,12 @@ function BarChart({ data, index, series, variant = "stacked", legend = false, le
|
|
|
39
39
|
rendered,
|
|
40
40
|
stacked
|
|
41
41
|
]);
|
|
42
|
-
const margin = {
|
|
42
|
+
const margin = bare ? {
|
|
43
|
+
top: 0,
|
|
44
|
+
right: 0,
|
|
45
|
+
bottom: 0,
|
|
46
|
+
left: 0
|
|
47
|
+
} : {
|
|
43
48
|
top: markers?.length || valueLabels ? 18 : 8,
|
|
44
49
|
right: 8,
|
|
45
50
|
bottom: 2,
|
|
@@ -100,7 +105,7 @@ function BarChart({ data, index, series, variant = "stacked", legend = false, le
|
|
|
100
105
|
data,
|
|
101
106
|
stackOffset: variant === "expand" ? "expand" : "none",
|
|
102
107
|
margin,
|
|
103
|
-
barCategoryGap: stacked ? "20%" : "16%",
|
|
108
|
+
barCategoryGap: barCategoryGap ?? (stacked ? "20%" : "16%"),
|
|
104
109
|
children: [
|
|
105
110
|
/* @__PURE__ */ jsxs("defs", { children: [!stacked && rendered.map((entry, slot) => /* @__PURE__ */ jsxs("linearGradient", {
|
|
106
111
|
id: `bar-${reactId}-${slot}`,
|
|
@@ -138,7 +143,7 @@ function BarChart({ data, index, series, variant = "stacked", legend = false, le
|
|
|
138
143
|
strokeOpacity: .65
|
|
139
144
|
})]
|
|
140
145
|
})] }),
|
|
141
|
-
/* @__PURE__ */ jsx(CartesianGrid, {
|
|
146
|
+
!bare && /* @__PURE__ */ jsx(CartesianGrid, {
|
|
142
147
|
vertical: false,
|
|
143
148
|
stroke: theme.grid,
|
|
144
149
|
strokeDasharray: "2 4"
|
|
@@ -146,17 +151,20 @@ function BarChart({ data, index, series, variant = "stacked", legend = false, le
|
|
|
146
151
|
/* @__PURE__ */ jsx(XAxis, {
|
|
147
152
|
dataKey: index,
|
|
148
153
|
...axis,
|
|
154
|
+
hide: bare,
|
|
155
|
+
height: bare ? 0 : void 0,
|
|
149
156
|
tick: makeXAxisTick(theme),
|
|
150
157
|
interval: "preserveStartEnd",
|
|
151
158
|
minTickGap: 56
|
|
152
159
|
}),
|
|
153
160
|
/* @__PURE__ */ jsx(YAxis, {
|
|
154
161
|
...axis,
|
|
155
|
-
|
|
162
|
+
hide: bare,
|
|
163
|
+
width: bare ? 0 : yAxisWidth,
|
|
156
164
|
domain: referenceLine && variant !== "expand" ? [0, Math.ceil(Math.max(numericMax, referenceLine.y) * 1.15)] : void 0,
|
|
157
165
|
tickFormatter: (value) => variant === "expand" ? `${Math.round(value * 100)}%` : valueFormatter(value)
|
|
158
166
|
}),
|
|
159
|
-
/* @__PURE__ */ jsx(Tooltip, {
|
|
167
|
+
!bare && /* @__PURE__ */ jsx(Tooltip, {
|
|
160
168
|
offset: 12,
|
|
161
169
|
allowEscapeViewBox: {
|
|
162
170
|
x: false,
|
|
@@ -172,7 +180,7 @@ function BarChart({ data, index, series, variant = "stacked", legend = false, le
|
|
|
172
180
|
showTotal: withTotal
|
|
173
181
|
})
|
|
174
182
|
}),
|
|
175
|
-
renderReferenceLine({
|
|
183
|
+
!bare && renderReferenceLine({
|
|
176
184
|
referenceLine,
|
|
177
185
|
numericMax,
|
|
178
186
|
theme
|
|
@@ -215,11 +223,11 @@ function BarChart({ data, index, series, variant = "stacked", legend = false, le
|
|
|
215
223
|
content: renderValueLabel(entry.color)
|
|
216
224
|
})]
|
|
217
225
|
}, entry.key)),
|
|
218
|
-
renderMarkers(markers, theme)
|
|
226
|
+
!bare && renderMarkers(markers, theme)
|
|
219
227
|
]
|
|
220
228
|
})
|
|
221
229
|
})
|
|
222
|
-
}), legend && !isEmpty && /* @__PURE__ */ jsx(ChartLegend, {
|
|
230
|
+
}), legend && !bare && !isEmpty && /* @__PURE__ */ jsx(ChartLegend, {
|
|
223
231
|
items: legendItems,
|
|
224
232
|
align: legendAlign,
|
|
225
233
|
insetLeft: yAxisWidth
|
|
@@ -5,7 +5,7 @@ import * as React$1 from "react";
|
|
|
5
5
|
import { Slot } from "@radix-ui/react-slot";
|
|
6
6
|
import { ControllerProps, FieldPath, FieldValues } from "react-hook-form";
|
|
7
7
|
//#region src/components/form.d.ts
|
|
8
|
-
declare const Form: <TFieldValues extends FieldValues, TContext = any, TTransformedValues = TFieldValues>({ children, watch, getValues, getFieldState, setError, clearErrors, setValue, setValues, trigger, formState, resetField, reset, handleSubmit, unregister, control, register, setFocus, subscribe }: import("react-hook-form").FormProviderProps<TFieldValues, TContext, TTransformedValues>) => React$1.JSX.Element;
|
|
8
|
+
declare const Form: <TFieldValues extends FieldValues, TContext = any, TTransformedValues = TFieldValues>({ children, watch, getValues, getFieldState, setError, clearErrors, setValue, setValues, trigger, formState, resetField, reset, resetDefaultValues, handleSubmit, unregister, control, register, setFocus, subscribe }: import("react-hook-form").FormProviderProps<TFieldValues, TContext, TTransformedValues>) => React$1.JSX.Element;
|
|
9
9
|
declare const FormField: <TFieldValues extends FieldValues = FieldValues, TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>>({ ...props }: ControllerProps<TFieldValues, TName>) => React$1.JSX.Element;
|
|
10
10
|
declare const useFormField: () => {
|
|
11
11
|
invalid: boolean;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { VariantProps } from "class-variance-authority";
|
|
2
2
|
//#region src/components/skeleton.d.ts
|
|
3
3
|
declare const skeletonVariants: (props?: ({
|
|
4
|
-
shape?: "
|
|
4
|
+
shape?: "circle" | "rectangle" | null | undefined;
|
|
5
5
|
} & import("class-variance-authority/types").ClassProp) | undefined) => string;
|
|
6
6
|
interface SkeletonProps extends React.ComponentProps<"div">, VariantProps<typeof skeletonVariants> {}
|
|
7
7
|
declare function Skeleton({ className, shape, ...props }: SkeletonProps): import("react").JSX.Element;
|
|
@@ -8,6 +8,7 @@ interface StatDelta {
|
|
|
8
8
|
direction: "up" | "down";
|
|
9
9
|
invert?: boolean;
|
|
10
10
|
label?: React$1.ReactNode;
|
|
11
|
+
indicator?: "arrow" | "sign";
|
|
11
12
|
}
|
|
12
13
|
interface StatProps extends React$1.ComponentProps<"div"> {
|
|
13
14
|
value: React$1.ReactNode;
|
|
@@ -17,7 +18,11 @@ interface StatProps extends React$1.ComponentProps<"div"> {
|
|
|
17
18
|
value: number;
|
|
18
19
|
}>;
|
|
19
20
|
semantic?: "error" | "warning" | "success";
|
|
21
|
+
compact?: boolean;
|
|
20
22
|
}
|
|
21
|
-
declare function Stat({ value, unit, delta, sparkline, semantic, className, ...props }: StatProps): React$1.JSX.Element;
|
|
23
|
+
declare function Stat({ value, unit, delta, sparkline, semantic, compact, className, ...props }: StatProps): React$1.JSX.Element;
|
|
24
|
+
declare function StatSparkline({ sparkline, semantic, className }: Pick<StatProps, "sparkline" | "semantic"> & {
|
|
25
|
+
className?: string;
|
|
26
|
+
}): React$1.JSX.Element | null;
|
|
22
27
|
//#endregion
|
|
23
|
-
export { Stat, StatDelta, StatProps, statDeltaVariants };
|
|
28
|
+
export { Stat, StatDelta, StatProps, StatSparkline, statDeltaVariants };
|
package/dist/components/stat.mjs
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { cn } from "../lib/cn.mjs";
|
|
3
3
|
import { useChartContext } from "./chart-container.mjs";
|
|
4
4
|
import { ArrowDown, ArrowUp } from "lucide-react";
|
|
5
|
-
import { jsx, jsxs } from "react/jsx-runtime";
|
|
5
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
6
6
|
import { cva } from "class-variance-authority";
|
|
7
7
|
import * as React$1 from "react";
|
|
8
8
|
import { Area, AreaChart, ResponsiveContainer } from "recharts";
|
|
@@ -26,85 +26,92 @@ const SEMANTIC_KEY = {
|
|
|
26
26
|
warning: "warning",
|
|
27
27
|
success: "success"
|
|
28
28
|
};
|
|
29
|
-
function Stat({ value, unit, delta, sparkline, semantic, className, ...props }) {
|
|
30
|
-
const { palette, theme } = useChartContext();
|
|
31
|
-
const gradientId = React$1.useId().replace(/:/g, "");
|
|
32
|
-
const sparkData = React$1.useMemo(() => toSparkData(sparkline), [sparkline]);
|
|
33
|
-
const hasSpark = sparkData.some((point) => point.value > 0);
|
|
34
|
-
const sparkColor = semantic ? theme[SEMANTIC_KEY[semantic]] : palette[0];
|
|
29
|
+
function Stat({ value, unit, delta, sparkline, semantic, compact = false, className, ...props }) {
|
|
35
30
|
const sentiment = delta && (delta.invert ? delta.direction === "down" : delta.direction === "up") ? "positive" : "negative";
|
|
36
31
|
return /* @__PURE__ */ jsxs("div", {
|
|
37
32
|
"data-slot": "stat",
|
|
38
33
|
className: cn("flex min-w-0 flex-col gap-2.5", className),
|
|
39
34
|
...props,
|
|
40
35
|
children: [/* @__PURE__ */ jsxs("div", {
|
|
41
|
-
className: "flex min-w-0 items-baseline gap-x-2.5",
|
|
36
|
+
className: cn("flex min-w-0 items-baseline gap-x-2.5", compact && "gap-x-1.5"),
|
|
42
37
|
children: [
|
|
43
38
|
/* @__PURE__ */ jsx("span", {
|
|
44
|
-
className: "
|
|
39
|
+
className: cn("min-w-0 font-bold leading-none tracking-tight tabular-nums text-foreground", compact ? "type-display-xs whitespace-nowrap" : "type-display-sm truncate"),
|
|
45
40
|
children: value
|
|
46
41
|
}),
|
|
47
42
|
unit && /* @__PURE__ */ jsx("span", {
|
|
48
43
|
className: "font-mono text-[11px] leading-none text-quaternary-foreground mb-0.5",
|
|
49
44
|
children: unit
|
|
50
45
|
}),
|
|
51
|
-
delta && /* @__PURE__ */
|
|
46
|
+
delta && /* @__PURE__ */ jsx(DeltaPill, {
|
|
52
47
|
sentiment,
|
|
53
|
-
className: "mb-0.5",
|
|
54
|
-
children: [delta.direction === "up" ? /* @__PURE__ */ jsx(ArrowUp, { className: "size-3.5" }) : /* @__PURE__ */ jsx(ArrowDown, { className: "size-3.5" }), delta.label ?? `${delta.value}%`]
|
|
48
|
+
className: cn("mb-0.5 shrink-0", compact && "px-1.5"),
|
|
49
|
+
children: delta.indicator === "sign" ? /* @__PURE__ */ jsxs(Fragment, { children: [delta.direction === "up" ? "+" : "-", delta.label ?? `${delta.value}%`] }) : /* @__PURE__ */ jsxs(Fragment, { children: [delta.direction === "up" ? /* @__PURE__ */ jsx(ArrowUp, { className: "size-3.5" }) : /* @__PURE__ */ jsx(ArrowDown, { className: "size-3.5" }), delta.label ?? `${delta.value}%`] })
|
|
55
50
|
})
|
|
56
51
|
]
|
|
57
|
-
}),
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
52
|
+
}), /* @__PURE__ */ jsx(StatSparkline, {
|
|
53
|
+
sparkline,
|
|
54
|
+
semantic
|
|
55
|
+
})]
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
function StatSparkline({ sparkline, semantic, className }) {
|
|
59
|
+
const { palette, theme } = useChartContext();
|
|
60
|
+
const gradientId = React$1.useId().replace(/:/g, "");
|
|
61
|
+
const sparkData = React$1.useMemo(() => toSparkData(sparkline), [sparkline]);
|
|
62
|
+
const hasSpark = sparkData.some((point) => point.value > 0);
|
|
63
|
+
const sparkColor = semantic ? theme[SEMANTIC_KEY[semantic]] : palette[0];
|
|
64
|
+
if (!hasSpark) return null;
|
|
65
|
+
return /* @__PURE__ */ jsx("div", {
|
|
66
|
+
className: cn("pointer-events-none h-9 w-full", className),
|
|
67
|
+
children: /* @__PURE__ */ jsx(ResponsiveContainer, {
|
|
68
|
+
width: "100%",
|
|
69
|
+
height: "100%",
|
|
70
|
+
initialDimension: {
|
|
71
|
+
width: 0,
|
|
72
|
+
height: 36
|
|
73
|
+
},
|
|
74
|
+
children: /* @__PURE__ */ jsxs(AreaChart, {
|
|
75
|
+
data: sparkData,
|
|
76
|
+
margin: {
|
|
77
|
+
top: 4,
|
|
78
|
+
right: 2,
|
|
79
|
+
bottom: 0,
|
|
80
|
+
left: 2
|
|
65
81
|
},
|
|
66
|
-
children: /* @__PURE__ */ jsxs(
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
children: [/* @__PURE__ */ jsx("stop", {
|
|
81
|
-
offset: "0%",
|
|
82
|
-
stopColor: sparkColor,
|
|
83
|
-
stopOpacity: .4
|
|
84
|
-
}), /* @__PURE__ */ jsx("stop", {
|
|
85
|
-
offset: "100%",
|
|
86
|
-
stopColor: sparkColor,
|
|
87
|
-
stopOpacity: 0
|
|
88
|
-
})]
|
|
89
|
-
}) }), /* @__PURE__ */ jsx(Area, {
|
|
90
|
-
type: "monotone",
|
|
91
|
-
dataKey: "value",
|
|
92
|
-
stroke: sparkColor,
|
|
93
|
-
strokeWidth: 1.6,
|
|
94
|
-
fill: `url(#${gradientId})`,
|
|
95
|
-
isAnimationActive: false,
|
|
96
|
-
dot: ({ cx, cy, index }) => {
|
|
97
|
-
return index === sparkData.length - 1 && cx != null && cy != null ? /* @__PURE__ */ jsx("circle", {
|
|
98
|
-
cx,
|
|
99
|
-
cy,
|
|
100
|
-
r: 2.5,
|
|
101
|
-
fill: sparkColor
|
|
102
|
-
}, index) : /* @__PURE__ */ jsx("g", {}, index);
|
|
103
|
-
}
|
|
82
|
+
children: [/* @__PURE__ */ jsx("defs", { children: /* @__PURE__ */ jsxs("linearGradient", {
|
|
83
|
+
id: gradientId,
|
|
84
|
+
x1: "0",
|
|
85
|
+
y1: "0",
|
|
86
|
+
x2: "0",
|
|
87
|
+
y2: "1",
|
|
88
|
+
children: [/* @__PURE__ */ jsx("stop", {
|
|
89
|
+
offset: "0%",
|
|
90
|
+
stopColor: sparkColor,
|
|
91
|
+
stopOpacity: .4
|
|
92
|
+
}), /* @__PURE__ */ jsx("stop", {
|
|
93
|
+
offset: "100%",
|
|
94
|
+
stopColor: sparkColor,
|
|
95
|
+
stopOpacity: 0
|
|
104
96
|
})]
|
|
105
|
-
})
|
|
97
|
+
}) }), /* @__PURE__ */ jsx(Area, {
|
|
98
|
+
type: "monotone",
|
|
99
|
+
dataKey: "value",
|
|
100
|
+
stroke: sparkColor,
|
|
101
|
+
strokeWidth: 1.6,
|
|
102
|
+
fill: `url(#${gradientId})`,
|
|
103
|
+
isAnimationActive: false,
|
|
104
|
+
dot: ({ cx, cy, index }) => {
|
|
105
|
+
return index === sparkData.length - 1 && cx != null && cy != null ? /* @__PURE__ */ jsx("circle", {
|
|
106
|
+
cx,
|
|
107
|
+
cy,
|
|
108
|
+
r: 2.5,
|
|
109
|
+
fill: sparkColor
|
|
110
|
+
}, index) : /* @__PURE__ */ jsx("g", {}, index);
|
|
111
|
+
}
|
|
112
|
+
})]
|
|
106
113
|
})
|
|
107
|
-
})
|
|
114
|
+
})
|
|
108
115
|
});
|
|
109
116
|
}
|
|
110
117
|
function DeltaPill({ sentiment, className, ...props }) {
|
|
@@ -114,4 +121,4 @@ function DeltaPill({ sentiment, className, ...props }) {
|
|
|
114
121
|
});
|
|
115
122
|
}
|
|
116
123
|
//#endregion
|
|
117
|
-
export { Stat, statDeltaVariants };
|
|
124
|
+
export { Stat, StatSparkline, statDeltaVariants };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@alpic-ai/ui",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.157.0",
|
|
4
4
|
"description": "Alpic design system — shared UI components",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -23,12 +23,12 @@
|
|
|
23
23
|
"src"
|
|
24
24
|
],
|
|
25
25
|
"peerDependencies": {
|
|
26
|
-
"lucide-react": "^1.
|
|
26
|
+
"lucide-react": "^1.25.0",
|
|
27
27
|
"react": "^19.2.7",
|
|
28
28
|
"react-dom": "^19.2.7",
|
|
29
|
-
"react-hook-form": "^7.
|
|
29
|
+
"react-hook-form": "^7.82.0",
|
|
30
30
|
"sonner": "^2.0.7",
|
|
31
|
-
"tailwindcss": "^4.3.
|
|
31
|
+
"tailwindcss": "^4.3.3",
|
|
32
32
|
"tw-animate-css": "^1.4.0"
|
|
33
33
|
},
|
|
34
34
|
"dependencies": {
|
|
@@ -57,15 +57,15 @@
|
|
|
57
57
|
},
|
|
58
58
|
"devDependencies": {
|
|
59
59
|
"@ladle/react": "^5.1.1",
|
|
60
|
-
"@tailwindcss/postcss": "^4.3.
|
|
60
|
+
"@tailwindcss/postcss": "^4.3.3",
|
|
61
61
|
"@types/react": "19.2.17",
|
|
62
62
|
"@types/react-dom": "19.2.3",
|
|
63
|
-
"lucide-react": "^1.
|
|
64
|
-
"react-hook-form": "^7.
|
|
63
|
+
"lucide-react": "^1.25.0",
|
|
64
|
+
"react-hook-form": "^7.82.0",
|
|
65
65
|
"shx": "^0.4.0",
|
|
66
66
|
"sonner": "^2.0.7",
|
|
67
|
-
"tailwindcss": "^4.3.
|
|
68
|
-
"tsdown": "^0.22.
|
|
67
|
+
"tailwindcss": "^4.3.3",
|
|
68
|
+
"tsdown": "^0.22.9",
|
|
69
69
|
"tw-animate-css": "^1.4.0",
|
|
70
70
|
"typescript": "^6.0.3"
|
|
71
71
|
},
|
|
@@ -42,6 +42,8 @@ export interface BarChartProps {
|
|
|
42
42
|
valueLabels?: boolean;
|
|
43
43
|
height?: number;
|
|
44
44
|
yAxisWidth?: number;
|
|
45
|
+
bare?: boolean;
|
|
46
|
+
barCategoryGap?: number | string;
|
|
45
47
|
palette?: ChartPaletteName;
|
|
46
48
|
referenceLine?: { y: number; label?: string; band?: boolean };
|
|
47
49
|
markers?: ChartMarker[];
|
|
@@ -63,6 +65,8 @@ function BarChart({
|
|
|
63
65
|
valueLabels = false,
|
|
64
66
|
height = 200,
|
|
65
67
|
yAxisWidth = 48,
|
|
68
|
+
bare = false,
|
|
69
|
+
barCategoryGap,
|
|
66
70
|
palette,
|
|
67
71
|
referenceLine,
|
|
68
72
|
markers,
|
|
@@ -99,7 +103,9 @@ function BarChart({
|
|
|
99
103
|
});
|
|
100
104
|
}, [data, rendered, stacked]);
|
|
101
105
|
|
|
102
|
-
const margin =
|
|
106
|
+
const margin = bare
|
|
107
|
+
? { top: 0, right: 0, bottom: 0, left: 0 }
|
|
108
|
+
: { top: markers?.length || valueLabels ? 18 : 8, right: 8, bottom: 2, left: 0 };
|
|
103
109
|
|
|
104
110
|
const axis = makeChartAxis(theme);
|
|
105
111
|
|
|
@@ -174,7 +180,7 @@ function BarChart({
|
|
|
174
180
|
data={data as Record<string, string | number>[]}
|
|
175
181
|
stackOffset={variant === "expand" ? "expand" : "none"}
|
|
176
182
|
margin={margin}
|
|
177
|
-
barCategoryGap={stacked ? "20%" : "16%"}
|
|
183
|
+
barCategoryGap={barCategoryGap ?? (stacked ? "20%" : "16%")}
|
|
178
184
|
>
|
|
179
185
|
<defs>
|
|
180
186
|
{!stacked &&
|
|
@@ -198,17 +204,20 @@ function BarChart({
|
|
|
198
204
|
)}
|
|
199
205
|
</defs>
|
|
200
206
|
|
|
201
|
-
<CartesianGrid vertical={false} stroke={theme.grid} strokeDasharray="2 4" />
|
|
207
|
+
{!bare && <CartesianGrid vertical={false} stroke={theme.grid} strokeDasharray="2 4" />}
|
|
202
208
|
<XAxis
|
|
203
209
|
dataKey={index}
|
|
204
210
|
{...axis}
|
|
211
|
+
hide={bare}
|
|
212
|
+
height={bare ? 0 : undefined}
|
|
205
213
|
tick={makeXAxisTick(theme)}
|
|
206
214
|
interval="preserveStartEnd"
|
|
207
215
|
minTickGap={56}
|
|
208
216
|
/>
|
|
209
217
|
<YAxis
|
|
210
218
|
{...axis}
|
|
211
|
-
|
|
219
|
+
hide={bare}
|
|
220
|
+
width={bare ? 0 : yAxisWidth}
|
|
212
221
|
domain={
|
|
213
222
|
referenceLine && variant !== "expand"
|
|
214
223
|
? [0, Math.ceil(Math.max(numericMax, referenceLine.y) * 1.15)]
|
|
@@ -218,19 +227,21 @@ function BarChart({
|
|
|
218
227
|
variant === "expand" ? `${Math.round(value * 100)}%` : valueFormatter(value)
|
|
219
228
|
}
|
|
220
229
|
/>
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
230
|
+
{!bare && (
|
|
231
|
+
<Tooltip
|
|
232
|
+
offset={12}
|
|
233
|
+
allowEscapeViewBox={{ x: false, y: false }}
|
|
234
|
+
cursor={{ fill: lead?.color ?? theme.mutedForeground, fillOpacity: theme.isDark ? 0.1 : 0.06 }}
|
|
235
|
+
content={
|
|
236
|
+
<ChartTooltipContent
|
|
237
|
+
valueFormatter={valueFormatter}
|
|
238
|
+
labelFormatter={labelFormatter}
|
|
239
|
+
showTotal={withTotal}
|
|
240
|
+
/>
|
|
241
|
+
}
|
|
242
|
+
/>
|
|
243
|
+
)}
|
|
244
|
+
{!bare && renderReferenceLine({ referenceLine, numericMax, theme })}
|
|
234
245
|
|
|
235
246
|
{rendered.map((entry, slot) => (
|
|
236
247
|
<Bar
|
|
@@ -272,13 +283,13 @@ function BarChart({
|
|
|
272
283
|
</Bar>
|
|
273
284
|
))}
|
|
274
285
|
|
|
275
|
-
{renderMarkers(markers, theme)}
|
|
286
|
+
{!bare && renderMarkers(markers, theme)}
|
|
276
287
|
</RechartsBarChart>
|
|
277
288
|
</ResponsiveContainer>
|
|
278
289
|
)}
|
|
279
290
|
</div>
|
|
280
291
|
|
|
281
|
-
{legend && !isEmpty && <ChartLegend items={legendItems} align={legendAlign} insetLeft={yAxisWidth} />}
|
|
292
|
+
{legend && !bare && !isEmpty && <ChartLegend items={legendItems} align={legendAlign} insetLeft={yAxisWidth} />}
|
|
282
293
|
</div>
|
|
283
294
|
);
|
|
284
295
|
}
|
package/src/components/stat.tsx
CHANGED
|
@@ -26,6 +26,7 @@ export interface StatDelta {
|
|
|
26
26
|
direction: "up" | "down";
|
|
27
27
|
invert?: boolean;
|
|
28
28
|
label?: React.ReactNode;
|
|
29
|
+
indicator?: "arrow" | "sign";
|
|
29
30
|
}
|
|
30
31
|
|
|
31
32
|
export interface StatProps extends React.ComponentProps<"div"> {
|
|
@@ -34,6 +35,7 @@ export interface StatProps extends React.ComponentProps<"div"> {
|
|
|
34
35
|
delta?: StatDelta | null;
|
|
35
36
|
sparkline?: number[] | Array<{ value: number }>;
|
|
36
37
|
semantic?: "error" | "warning" | "success";
|
|
38
|
+
compact?: boolean;
|
|
37
39
|
}
|
|
38
40
|
|
|
39
41
|
const toSparkData = (sparkline: StatProps["sparkline"]) =>
|
|
@@ -43,61 +45,87 @@ const toSparkData = (sparkline: StatProps["sparkline"]) =>
|
|
|
43
45
|
|
|
44
46
|
const SEMANTIC_KEY = { error: "destructive", warning: "warning", success: "success" } as const;
|
|
45
47
|
|
|
46
|
-
function Stat({ value, unit, delta, sparkline, semantic, className, ...props }: StatProps) {
|
|
47
|
-
const { palette, theme } = useChartContext();
|
|
48
|
-
const gradientId = React.useId().replace(/:/g, "");
|
|
49
|
-
const sparkData = React.useMemo(() => toSparkData(sparkline), [sparkline]);
|
|
50
|
-
const hasSpark = sparkData.some((point) => point.value > 0);
|
|
51
|
-
// biome-ignore lint/style/noNonNullAssertion: palettes are never empty
|
|
52
|
-
const sparkColor = semantic ? theme[SEMANTIC_KEY[semantic]] : palette[0]!;
|
|
53
|
-
|
|
48
|
+
function Stat({ value, unit, delta, sparkline, semantic, compact = false, className, ...props }: StatProps) {
|
|
54
49
|
const sentiment =
|
|
55
50
|
delta && (delta.invert ? delta.direction === "down" : delta.direction === "up") ? "positive" : "negative";
|
|
56
51
|
|
|
57
52
|
return (
|
|
58
53
|
<div data-slot="stat" className={cn("flex min-w-0 flex-col gap-2.5", className)} {...props}>
|
|
59
|
-
<div className="flex min-w-0 items-baseline gap-x-2.5">
|
|
60
|
-
<span
|
|
54
|
+
<div className={cn("flex min-w-0 items-baseline gap-x-2.5", compact && "gap-x-1.5")}>
|
|
55
|
+
<span
|
|
56
|
+
className={cn(
|
|
57
|
+
"min-w-0 font-bold leading-none tracking-tight tabular-nums text-foreground",
|
|
58
|
+
compact ? "type-display-xs whitespace-nowrap" : "type-display-sm truncate",
|
|
59
|
+
)}
|
|
60
|
+
>
|
|
61
61
|
{value}
|
|
62
62
|
</span>
|
|
63
63
|
{unit && <span className="font-mono text-[11px] leading-none text-quaternary-foreground mb-0.5">{unit}</span>}
|
|
64
64
|
{delta && (
|
|
65
|
-
<DeltaPill sentiment={sentiment} className="mb-0.5">
|
|
66
|
-
{delta.
|
|
67
|
-
|
|
65
|
+
<DeltaPill sentiment={sentiment} className={cn("mb-0.5 shrink-0", compact && "px-1.5")}>
|
|
66
|
+
{delta.indicator === "sign" ? (
|
|
67
|
+
<>
|
|
68
|
+
{delta.direction === "up" ? "+" : "-"}
|
|
69
|
+
{delta.label ?? `${delta.value}%`}
|
|
70
|
+
</>
|
|
71
|
+
) : (
|
|
72
|
+
<>
|
|
73
|
+
{delta.direction === "up" ? <ArrowUp className="size-3.5" /> : <ArrowDown className="size-3.5" />}
|
|
74
|
+
{delta.label ?? `${delta.value}%`}
|
|
75
|
+
</>
|
|
76
|
+
)}
|
|
68
77
|
</DeltaPill>
|
|
69
78
|
)}
|
|
70
79
|
</div>
|
|
71
|
-
{
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
80
|
+
<StatSparkline sparkline={sparkline} semantic={semantic} />
|
|
81
|
+
</div>
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function StatSparkline({
|
|
86
|
+
sparkline,
|
|
87
|
+
semantic,
|
|
88
|
+
className,
|
|
89
|
+
}: Pick<StatProps, "sparkline" | "semantic"> & { className?: string }) {
|
|
90
|
+
const { palette, theme } = useChartContext();
|
|
91
|
+
const gradientId = React.useId().replace(/:/g, "");
|
|
92
|
+
const sparkData = React.useMemo(() => toSparkData(sparkline), [sparkline]);
|
|
93
|
+
const hasSpark = sparkData.some((point) => point.value > 0);
|
|
94
|
+
// biome-ignore lint/style/noNonNullAssertion: palettes are never empty
|
|
95
|
+
const sparkColor = semantic ? theme[SEMANTIC_KEY[semantic]] : palette[0]!;
|
|
96
|
+
|
|
97
|
+
if (!hasSpark) {
|
|
98
|
+
return null;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return (
|
|
102
|
+
<div className={cn("pointer-events-none h-9 w-full", className)}>
|
|
103
|
+
<ResponsiveContainer width="100%" height="100%" initialDimension={{ width: 0, height: 36 }}>
|
|
104
|
+
<AreaChart data={sparkData} margin={{ top: 4, right: 2, bottom: 0, left: 2 }}>
|
|
105
|
+
<defs>
|
|
106
|
+
<linearGradient id={gradientId} x1="0" y1="0" x2="0" y2="1">
|
|
107
|
+
<stop offset="0%" stopColor={sparkColor} stopOpacity={0.4} />
|
|
108
|
+
<stop offset="100%" stopColor={sparkColor} stopOpacity={0} />
|
|
109
|
+
</linearGradient>
|
|
110
|
+
</defs>
|
|
111
|
+
<Area
|
|
112
|
+
type="monotone"
|
|
113
|
+
dataKey="value"
|
|
114
|
+
stroke={sparkColor}
|
|
115
|
+
strokeWidth={1.6}
|
|
116
|
+
fill={`url(#${gradientId})`}
|
|
117
|
+
isAnimationActive={false}
|
|
118
|
+
dot={({ cx, cy, index }) => {
|
|
119
|
+
const isLast = index === sparkData.length - 1;
|
|
120
|
+
return isLast && cx != null && cy != null ? (
|
|
121
|
+
<circle key={index} cx={cx} cy={cy} r={2.5} fill={sparkColor} />
|
|
122
|
+
) : (
|
|
123
|
+
<g key={index} />
|
|
124
|
+
);
|
|
125
|
+
}}
|
|
126
|
+
/>
|
|
127
|
+
</AreaChart>
|
|
128
|
+
</ResponsiveContainer>
|
|
101
129
|
</div>
|
|
102
130
|
);
|
|
103
131
|
}
|
|
@@ -110,4 +138,4 @@ function DeltaPill({
|
|
|
110
138
|
return <span className={cn(statDeltaVariants({ sentiment }), className)} {...props} />;
|
|
111
139
|
}
|
|
112
140
|
|
|
113
|
-
export { Stat, statDeltaVariants };
|
|
141
|
+
export { Stat, StatSparkline, statDeltaVariants };
|