@hilum/ui 3.2.3 → 3.2.5
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/index.d.ts +348 -32
- package/dist/index.js +2511 -676
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/components/alert-dialog.tsx +19 -6
- package/src/components/callout.tsx +99 -0
- package/src/components/card.tsx +13 -0
- package/src/components/chart.tsx +398 -29
- package/src/components/color-picker.tsx +39 -45
- package/src/components/combobox.tsx +83 -64
- package/src/components/command.tsx +35 -0
- package/src/components/context-menu.tsx +49 -27
- package/src/components/data-transfer-controls.tsx +156 -0
- package/src/components/dialog.tsx +19 -6
- package/src/components/dropdown-menu.tsx +50 -28
- package/src/components/file-dropzone.tsx +171 -0
- package/src/components/help-tooltip.tsx +90 -0
- package/src/components/media-asset-card.tsx +118 -0
- package/src/components/menubar.tsx +52 -30
- package/src/components/popover.tsx +31 -17
- package/src/components/rich-text-editor.tsx +448 -0
- package/src/components/searchable-table.tsx +412 -0
- package/src/components/select.tsx +44 -28
- package/src/components/sidebar.tsx +105 -14
- package/src/components/stat-card.tsx +122 -8
- package/src/components/status-badge.tsx +144 -0
- package/src/components/titled-card.tsx +102 -0
- package/src/components/url-redirect-prompt.tsx +85 -0
- package/src/index.ts +10 -0
- package/src/lib/mobile-popper-sheet.ts +78 -0
package/package.json
CHANGED
|
@@ -3,6 +3,11 @@
|
|
|
3
3
|
import * as React from "react";
|
|
4
4
|
import { AlertDialog } from "radix-ui";
|
|
5
5
|
import { cn } from "../lib/utils";
|
|
6
|
+
import {
|
|
7
|
+
desktopDialogContentClassName,
|
|
8
|
+
dialogSheetMotionClassName,
|
|
9
|
+
mobileDialogSheetContentClassName,
|
|
10
|
+
} from "../lib/mobile-popper-sheet";
|
|
6
11
|
|
|
7
12
|
const AlertDialogRoot = AlertDialog.Root;
|
|
8
13
|
const AlertDialogTrigger = AlertDialog.Trigger;
|
|
@@ -34,11 +39,10 @@ const AlertDialogContent = React.forwardRef<
|
|
|
34
39
|
<AlertDialog.Content
|
|
35
40
|
ref={ref}
|
|
36
41
|
className={cn(
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
"
|
|
40
|
-
|
|
41
|
-
"data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95",
|
|
42
|
+
mobileDialogSheetContentClassName,
|
|
43
|
+
desktopDialogContentClassName,
|
|
44
|
+
"sm:max-w-md",
|
|
45
|
+
dialogSheetMotionClassName,
|
|
42
46
|
className,
|
|
43
47
|
)}
|
|
44
48
|
{...props}
|
|
@@ -55,7 +59,16 @@ function AlertDialogHeader({ className, ...props }: React.HTMLAttributes<HTMLDiv
|
|
|
55
59
|
AlertDialogHeader.displayName = "AlertDialogHeader";
|
|
56
60
|
|
|
57
61
|
function AlertDialogFooter({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) {
|
|
58
|
-
return
|
|
62
|
+
return (
|
|
63
|
+
<div
|
|
64
|
+
className={cn(
|
|
65
|
+
"mt-6 flex flex-col-reverse gap-2 sm:flex-row sm:items-center sm:justify-end",
|
|
66
|
+
"[&>*]:w-full sm:[&>*]:w-auto",
|
|
67
|
+
className,
|
|
68
|
+
)}
|
|
69
|
+
{...props}
|
|
70
|
+
/>
|
|
71
|
+
);
|
|
59
72
|
}
|
|
60
73
|
AlertDialogFooter.displayName = "AlertDialogFooter";
|
|
61
74
|
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { cva, type VariantProps } from "class-variance-authority";
|
|
3
|
+
import { cn } from "../lib/utils";
|
|
4
|
+
|
|
5
|
+
const calloutVariants = cva(
|
|
6
|
+
"flex w-full min-w-0 gap-3 rounded-xl border px-4 py-3.5 shadow-natural",
|
|
7
|
+
{
|
|
8
|
+
variants: {
|
|
9
|
+
tone: {
|
|
10
|
+
default: "border-border bg-card text-foreground",
|
|
11
|
+
info: "border-brand-secondary/50 bg-brand-secondary/15 text-foreground",
|
|
12
|
+
success: "border-success/25 bg-success/10 text-foreground",
|
|
13
|
+
warning: "border-warning/35 bg-warning/15 text-foreground",
|
|
14
|
+
destructive: "border-destructive/25 bg-destructive/10 text-foreground",
|
|
15
|
+
},
|
|
16
|
+
compact: {
|
|
17
|
+
true: "px-3 py-2.5",
|
|
18
|
+
false: "",
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
defaultVariants: {
|
|
22
|
+
tone: "default",
|
|
23
|
+
compact: false,
|
|
24
|
+
},
|
|
25
|
+
},
|
|
26
|
+
);
|
|
27
|
+
|
|
28
|
+
const calloutIconVariants = cva(
|
|
29
|
+
"mt-0.5 flex size-9 shrink-0 items-center justify-center rounded-lg [&_svg]:size-4",
|
|
30
|
+
{
|
|
31
|
+
variants: {
|
|
32
|
+
tone: {
|
|
33
|
+
default: "bg-muted text-muted-foreground",
|
|
34
|
+
info: "bg-brand-secondary/35 text-muted-foreground",
|
|
35
|
+
success: "bg-success/15 text-success",
|
|
36
|
+
warning: "bg-warning/20 text-warning",
|
|
37
|
+
destructive: "bg-destructive/15 text-destructive",
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
defaultVariants: {
|
|
41
|
+
tone: "default",
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
interface CalloutProps
|
|
47
|
+
extends Omit<React.ComponentProps<"div">, "title">,
|
|
48
|
+
VariantProps<typeof calloutVariants> {
|
|
49
|
+
title?: React.ReactNode;
|
|
50
|
+
description?: React.ReactNode;
|
|
51
|
+
icon?: React.ReactNode;
|
|
52
|
+
actions?: React.ReactNode;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function Callout({
|
|
56
|
+
title,
|
|
57
|
+
description,
|
|
58
|
+
icon,
|
|
59
|
+
actions,
|
|
60
|
+
tone = "default",
|
|
61
|
+
compact = false,
|
|
62
|
+
className,
|
|
63
|
+
children,
|
|
64
|
+
role,
|
|
65
|
+
...props
|
|
66
|
+
}: CalloutProps) {
|
|
67
|
+
const resolvedRole = role ?? (tone === "destructive" ? "alert" : "status");
|
|
68
|
+
|
|
69
|
+
return (
|
|
70
|
+
<div
|
|
71
|
+
role={resolvedRole}
|
|
72
|
+
className={cn(calloutVariants({ tone, compact }), className)}
|
|
73
|
+
{...props}
|
|
74
|
+
>
|
|
75
|
+
{icon && <div className={cn(calloutIconVariants({ tone }))}>{icon}</div>}
|
|
76
|
+
<div className="flex min-w-0 flex-1 flex-col gap-3 sm:flex-row sm:items-start sm:justify-between">
|
|
77
|
+
<div className="min-w-0">
|
|
78
|
+
{title && <p className="body font-semibold text-balance">{title}</p>}
|
|
79
|
+
{description && (
|
|
80
|
+
<p className={cn("body text-pretty text-muted-foreground", title && "mt-1")}>
|
|
81
|
+
{description}
|
|
82
|
+
</p>
|
|
83
|
+
)}
|
|
84
|
+
{children && <div className={cn(title || description ? "mt-2" : "")}>{children}</div>}
|
|
85
|
+
</div>
|
|
86
|
+
{actions && (
|
|
87
|
+
<div className="flex shrink-0 items-center gap-2 max-sm:w-full max-sm:flex-col max-sm:items-stretch">
|
|
88
|
+
{actions}
|
|
89
|
+
</div>
|
|
90
|
+
)}
|
|
91
|
+
</div>
|
|
92
|
+
</div>
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
Callout.displayName = "Callout";
|
|
97
|
+
|
|
98
|
+
export { Callout };
|
|
99
|
+
export type { CalloutProps };
|
package/src/components/card.tsx
CHANGED
|
@@ -92,6 +92,18 @@ const CardFooter = React.forwardRef<HTMLDivElement, React.ComponentProps<"div">>
|
|
|
92
92
|
);
|
|
93
93
|
CardFooter.displayName = "CardFooter";
|
|
94
94
|
|
|
95
|
+
const CardAction = React.forwardRef<HTMLDivElement, React.ComponentProps<"div">>(
|
|
96
|
+
({ className, ...props }, ref) => (
|
|
97
|
+
<div
|
|
98
|
+
ref={ref}
|
|
99
|
+
data-slot="card-action"
|
|
100
|
+
className={cn("col-start-2 row-span-2 row-start-1 self-start justify-self-end", className)}
|
|
101
|
+
{...props}
|
|
102
|
+
/>
|
|
103
|
+
),
|
|
104
|
+
);
|
|
105
|
+
CardAction.displayName = "CardAction";
|
|
106
|
+
|
|
95
107
|
/* ------------------------------------------------------------------ */
|
|
96
108
|
/* CardMedia — full-bleed image/gradient header */
|
|
97
109
|
/* ------------------------------------------------------------------ */
|
|
@@ -115,6 +127,7 @@ export {
|
|
|
115
127
|
CardDescription,
|
|
116
128
|
CardContent,
|
|
117
129
|
CardFooter,
|
|
130
|
+
CardAction,
|
|
118
131
|
CardMedia,
|
|
119
132
|
cardVariants,
|
|
120
133
|
};
|
package/src/components/chart.tsx
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
3
|
import * as React from "react";
|
|
4
|
-
import
|
|
4
|
+
import * as RechartsPrimitive from "recharts";
|
|
5
5
|
import { cn } from "../lib/utils";
|
|
6
6
|
import { tokens } from "../tokens/tokens";
|
|
7
7
|
|
|
@@ -14,49 +14,418 @@ export const CHART_COLORS = {
|
|
|
14
14
|
dark: tokens.semantic.light.foreground,
|
|
15
15
|
};
|
|
16
16
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
17
|
+
const THEMES = { light: "", dark: ".dark" } as const;
|
|
18
|
+
|
|
19
|
+
export type ChartConfig = {
|
|
20
|
+
[k in string]: {
|
|
21
|
+
label?: React.ReactNode;
|
|
22
|
+
icon?: React.ComponentType;
|
|
23
|
+
} & (
|
|
24
|
+
| { color?: string; theme?: never }
|
|
25
|
+
| { color?: never; theme: Record<keyof typeof THEMES, string> }
|
|
26
|
+
);
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
type ChartContextProps = {
|
|
30
|
+
config: ChartConfig;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
const ChartContext = React.createContext<ChartContextProps | null>(null);
|
|
34
|
+
|
|
35
|
+
function useChart() {
|
|
36
|
+
const context = React.useContext(ChartContext);
|
|
37
|
+
|
|
38
|
+
if (!context) {
|
|
39
|
+
throw new Error("useChart must be used within a <ChartContainer />");
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return context;
|
|
21
43
|
}
|
|
22
44
|
|
|
23
|
-
|
|
45
|
+
type ChartContainerProps = React.ComponentProps<"div"> & {
|
|
46
|
+
config?: ChartConfig;
|
|
47
|
+
height?: number;
|
|
48
|
+
children: React.ComponentProps<typeof RechartsPrimitive.ResponsiveContainer>["children"];
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
function ChartContainer({
|
|
52
|
+
id,
|
|
53
|
+
className,
|
|
54
|
+
children,
|
|
55
|
+
config = {},
|
|
56
|
+
height,
|
|
57
|
+
style,
|
|
58
|
+
...props
|
|
59
|
+
}: ChartContainerProps) {
|
|
60
|
+
const uniqueId = React.useId();
|
|
61
|
+
const chartId = `chart-${id || uniqueId.replace(/:/g, "")}`;
|
|
62
|
+
const resolvedStyle =
|
|
63
|
+
height === undefined ? style : ({ ...style, height } as React.CSSProperties);
|
|
64
|
+
|
|
24
65
|
return (
|
|
25
|
-
<
|
|
26
|
-
<
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
66
|
+
<ChartContext.Provider value={{ config }}>
|
|
67
|
+
<div
|
|
68
|
+
data-slot="chart"
|
|
69
|
+
data-chart={chartId}
|
|
70
|
+
className={cn(
|
|
71
|
+
"flex aspect-video justify-center text-xs",
|
|
72
|
+
"[&_.recharts-cartesian-axis-tick_text]:fill-muted-foreground",
|
|
73
|
+
"[&_.recharts-cartesian-grid_line[stroke='#ccc']]:stroke-border/50",
|
|
74
|
+
"[&_.recharts-curve.recharts-tooltip-cursor]:stroke-border",
|
|
75
|
+
"[&_.recharts-dot[stroke='#fff']]:stroke-transparent",
|
|
76
|
+
"[&_.recharts-layer]:outline-hidden",
|
|
77
|
+
"[&_.recharts-polar-grid_[stroke='#ccc']]:stroke-border",
|
|
78
|
+
"[&_.recharts-radial-bar-background-sector]:fill-muted",
|
|
79
|
+
"[&_.recharts-rectangle.recharts-tooltip-cursor]:fill-muted",
|
|
80
|
+
"[&_.recharts-reference-line_[stroke='#ccc']]:stroke-border",
|
|
81
|
+
"[&_.recharts-sector]:outline-hidden",
|
|
82
|
+
"[&_.recharts-sector[stroke='#fff']]:stroke-transparent",
|
|
83
|
+
"[&_.recharts-surface]:outline-hidden",
|
|
84
|
+
className,
|
|
85
|
+
)}
|
|
86
|
+
style={resolvedStyle}
|
|
87
|
+
{...props}
|
|
88
|
+
>
|
|
89
|
+
<ChartStyle id={chartId} config={config} />
|
|
90
|
+
<RechartsPrimitive.ResponsiveContainer>
|
|
91
|
+
{children}
|
|
92
|
+
</RechartsPrimitive.ResponsiveContainer>
|
|
93
|
+
</div>
|
|
94
|
+
</ChartContext.Provider>
|
|
30
95
|
);
|
|
31
96
|
}
|
|
32
97
|
ChartContainer.displayName = "ChartContainer";
|
|
33
98
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
99
|
+
const ChartStyle = ({ id, config }: { id: string; config: ChartConfig }) => {
|
|
100
|
+
const colorConfig = Object.entries(config).filter(
|
|
101
|
+
([, itemConfig]) => itemConfig.theme || itemConfig.color,
|
|
102
|
+
);
|
|
103
|
+
|
|
104
|
+
if (!colorConfig.length) {
|
|
105
|
+
return null;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
return (
|
|
109
|
+
<style
|
|
110
|
+
dangerouslySetInnerHTML={{
|
|
111
|
+
__html: Object.entries(THEMES)
|
|
112
|
+
.map(
|
|
113
|
+
([theme, prefix]) => `
|
|
114
|
+
${prefix} [data-chart=${id}] {
|
|
115
|
+
${colorConfig
|
|
116
|
+
.map(([key, itemConfig]) => {
|
|
117
|
+
const color =
|
|
118
|
+
itemConfig.theme?.[theme as keyof typeof itemConfig.theme] ||
|
|
119
|
+
itemConfig.color;
|
|
120
|
+
return color ? ` --color-${key}: ${color};` : null;
|
|
121
|
+
})
|
|
122
|
+
.join("\n")}
|
|
37
123
|
}
|
|
124
|
+
`,
|
|
125
|
+
)
|
|
126
|
+
.join("\n"),
|
|
127
|
+
}}
|
|
128
|
+
/>
|
|
129
|
+
);
|
|
130
|
+
};
|
|
38
131
|
|
|
39
|
-
|
|
40
|
-
|
|
132
|
+
type ChartTooltipContentProps = Omit<
|
|
133
|
+
React.HTMLAttributes<HTMLDivElement>,
|
|
134
|
+
"children" | "content"
|
|
135
|
+
> & {
|
|
136
|
+
active?: boolean;
|
|
137
|
+
payload?: Array<Record<string, any>>;
|
|
138
|
+
indicator?: "line" | "dot" | "dashed";
|
|
139
|
+
hideLabel?: boolean;
|
|
140
|
+
hideIndicator?: boolean;
|
|
141
|
+
label?: React.ReactNode;
|
|
142
|
+
labelFormatter?: (
|
|
143
|
+
value: React.ReactNode,
|
|
144
|
+
payload: Array<Record<string, any>>,
|
|
145
|
+
) => React.ReactNode;
|
|
146
|
+
labelClassName?: string;
|
|
147
|
+
formatter?: (
|
|
148
|
+
value: unknown,
|
|
149
|
+
name: unknown,
|
|
150
|
+
item: Record<string, any>,
|
|
151
|
+
index: number,
|
|
152
|
+
payload: unknown,
|
|
153
|
+
) => React.ReactNode;
|
|
154
|
+
color?: string;
|
|
155
|
+
nameKey?: string;
|
|
156
|
+
labelKey?: string;
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
type ChartTooltipProps =
|
|
160
|
+
| React.ComponentProps<typeof RechartsPrimitive.Tooltip>
|
|
161
|
+
| ChartTooltipContentProps;
|
|
162
|
+
|
|
163
|
+
function ChartTooltip(props: ChartTooltipProps) {
|
|
164
|
+
const hasDirectTooltipPayload = "active" in props || "payload" in props;
|
|
165
|
+
if (hasDirectTooltipPayload) {
|
|
166
|
+
return <ChartTooltipContent {...(props as ChartTooltipContentProps)} />;
|
|
167
|
+
}
|
|
41
168
|
return (
|
|
42
|
-
<
|
|
43
|
-
{
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
169
|
+
<RechartsPrimitive.Tooltip
|
|
170
|
+
{...(props as React.ComponentProps<typeof RechartsPrimitive.Tooltip>)}
|
|
171
|
+
/>
|
|
172
|
+
);
|
|
173
|
+
}
|
|
174
|
+
ChartTooltip.displayName = "ChartTooltip";
|
|
175
|
+
|
|
176
|
+
function ChartTooltipContent({
|
|
177
|
+
active,
|
|
178
|
+
payload,
|
|
179
|
+
className,
|
|
180
|
+
indicator = "dot",
|
|
181
|
+
hideLabel = false,
|
|
182
|
+
hideIndicator = false,
|
|
183
|
+
label,
|
|
184
|
+
labelFormatter,
|
|
185
|
+
labelClassName,
|
|
186
|
+
formatter,
|
|
187
|
+
color,
|
|
188
|
+
nameKey,
|
|
189
|
+
labelKey,
|
|
190
|
+
}: ChartTooltipContentProps) {
|
|
191
|
+
const chartContext = React.useContext(ChartContext);
|
|
192
|
+
const config = chartContext?.config ?? {};
|
|
193
|
+
|
|
194
|
+
const tooltipLabel = React.useMemo(() => {
|
|
195
|
+
if (hideLabel || !payload?.length) {
|
|
196
|
+
return null;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
const [item] = payload;
|
|
200
|
+
const key = `${labelKey || item?.dataKey || item?.name || "value"}`;
|
|
201
|
+
const itemConfig = getPayloadConfigFromPayload(config, item, key);
|
|
202
|
+
const value =
|
|
203
|
+
!labelKey && typeof label === "string"
|
|
204
|
+
? config[label as keyof typeof config]?.label || label
|
|
205
|
+
: itemConfig?.label;
|
|
206
|
+
|
|
207
|
+
if (labelFormatter) {
|
|
208
|
+
return (
|
|
209
|
+
<div className={cn("font-medium", labelClassName)}>
|
|
210
|
+
{labelFormatter(value, payload)}
|
|
52
211
|
</div>
|
|
53
|
-
)
|
|
212
|
+
);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
if (!value) {
|
|
216
|
+
return null;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
return <div className={cn("font-medium", labelClassName)}>{value}</div>;
|
|
220
|
+
}, [
|
|
221
|
+
label,
|
|
222
|
+
labelFormatter,
|
|
223
|
+
payload,
|
|
224
|
+
hideLabel,
|
|
225
|
+
labelClassName,
|
|
226
|
+
config,
|
|
227
|
+
labelKey,
|
|
228
|
+
]);
|
|
229
|
+
|
|
230
|
+
if (!active || !payload?.length) {
|
|
231
|
+
return null;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
const nestLabel = payload.length === 1 && indicator !== "dot";
|
|
235
|
+
|
|
236
|
+
return (
|
|
237
|
+
<div
|
|
238
|
+
className={cn(
|
|
239
|
+
"grid min-w-[8rem] items-start gap-1.5 rounded-lg border border-border/50 bg-background px-2.5 py-1.5 text-xs shadow-xl",
|
|
240
|
+
className,
|
|
241
|
+
)}
|
|
242
|
+
>
|
|
243
|
+
{!nestLabel ? tooltipLabel : null}
|
|
244
|
+
<div className="grid gap-1.5">
|
|
245
|
+
{payload.map((item: Record<string, any>, index: number) => {
|
|
246
|
+
const key = `${nameKey || item.name || item.dataKey || "value"}`;
|
|
247
|
+
const itemConfig = getPayloadConfigFromPayload(config, item, key);
|
|
248
|
+
const itemPayload = item.payload as Record<string, unknown> | undefined;
|
|
249
|
+
const indicatorColor =
|
|
250
|
+
color ||
|
|
251
|
+
(typeof itemPayload?.fill === "string" ? itemPayload.fill : undefined) ||
|
|
252
|
+
item.color;
|
|
253
|
+
|
|
254
|
+
return (
|
|
255
|
+
<div
|
|
256
|
+
key={`${item.dataKey || item.name || index}`}
|
|
257
|
+
className={cn(
|
|
258
|
+
"flex w-full flex-wrap items-stretch gap-2 [&>svg]:h-2.5 [&>svg]:w-2.5 [&>svg]:text-muted-foreground",
|
|
259
|
+
indicator === "dot" && "items-center",
|
|
260
|
+
)}
|
|
261
|
+
>
|
|
262
|
+
{formatter && item?.value !== undefined && item.name ? (
|
|
263
|
+
formatter(item.value, item.name, item, index, item.payload)
|
|
264
|
+
) : (
|
|
265
|
+
<>
|
|
266
|
+
{itemConfig?.icon ? (
|
|
267
|
+
<itemConfig.icon />
|
|
268
|
+
) : (
|
|
269
|
+
!hideIndicator && (
|
|
270
|
+
<div
|
|
271
|
+
className={cn(
|
|
272
|
+
"shrink-0 rounded-[2px] border-(--color-border) bg-(--color-bg)",
|
|
273
|
+
{
|
|
274
|
+
"h-2.5 w-2.5": indicator === "dot",
|
|
275
|
+
"w-1": indicator === "line",
|
|
276
|
+
"w-0 border-[1.5px] border-dashed bg-transparent":
|
|
277
|
+
indicator === "dashed",
|
|
278
|
+
"my-0.5": nestLabel && indicator === "dashed",
|
|
279
|
+
},
|
|
280
|
+
)}
|
|
281
|
+
style={
|
|
282
|
+
{
|
|
283
|
+
"--color-bg": indicatorColor,
|
|
284
|
+
"--color-border": indicatorColor,
|
|
285
|
+
} as React.CSSProperties
|
|
286
|
+
}
|
|
287
|
+
/>
|
|
288
|
+
)
|
|
289
|
+
)}
|
|
290
|
+
<div
|
|
291
|
+
className={cn(
|
|
292
|
+
"flex flex-1 justify-between leading-none",
|
|
293
|
+
nestLabel ? "items-end" : "items-center",
|
|
294
|
+
)}
|
|
295
|
+
>
|
|
296
|
+
<div className="grid gap-1.5">
|
|
297
|
+
{nestLabel ? tooltipLabel : null}
|
|
298
|
+
<span className="text-muted-foreground">
|
|
299
|
+
{itemConfig?.label || item.name}
|
|
300
|
+
</span>
|
|
301
|
+
</div>
|
|
302
|
+
{item.value !== undefined && item.value !== null && (
|
|
303
|
+
<span className="font-mono font-medium tabular-nums text-foreground">
|
|
304
|
+
{typeof item.value === "number"
|
|
305
|
+
? item.value.toLocaleString()
|
|
306
|
+
: item.value}
|
|
307
|
+
</span>
|
|
308
|
+
)}
|
|
309
|
+
</div>
|
|
310
|
+
</>
|
|
311
|
+
)}
|
|
312
|
+
</div>
|
|
313
|
+
);
|
|
314
|
+
})}
|
|
315
|
+
</div>
|
|
54
316
|
</div>
|
|
55
317
|
);
|
|
56
318
|
}
|
|
57
|
-
|
|
319
|
+
ChartTooltipContent.displayName = "ChartTooltipContent";
|
|
320
|
+
|
|
321
|
+
const ChartLegend = RechartsPrimitive.Legend;
|
|
322
|
+
|
|
323
|
+
type ChartLegendPayloadItem = {
|
|
324
|
+
color?: string;
|
|
325
|
+
dataKey?: string | number;
|
|
326
|
+
value?: string | number;
|
|
327
|
+
};
|
|
328
|
+
|
|
329
|
+
function ChartLegendContent({
|
|
330
|
+
className,
|
|
331
|
+
hideIcon = false,
|
|
332
|
+
payload,
|
|
333
|
+
verticalAlign = "bottom",
|
|
334
|
+
nameKey,
|
|
335
|
+
}: Omit<React.HTMLAttributes<HTMLDivElement>, "children"> & {
|
|
336
|
+
hideIcon?: boolean;
|
|
337
|
+
nameKey?: string;
|
|
338
|
+
payload?: ChartLegendPayloadItem[];
|
|
339
|
+
verticalAlign?: "top" | "middle" | "bottom";
|
|
340
|
+
}) {
|
|
341
|
+
const { config } = useChart();
|
|
58
342
|
|
|
59
|
-
|
|
343
|
+
if (!payload?.length) {
|
|
344
|
+
return null;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
return (
|
|
348
|
+
<div
|
|
349
|
+
className={cn(
|
|
350
|
+
"flex items-center justify-center gap-4",
|
|
351
|
+
verticalAlign === "top" ? "pb-3" : "pt-3",
|
|
352
|
+
className,
|
|
353
|
+
)}
|
|
354
|
+
>
|
|
355
|
+
{payload.map((item: ChartLegendPayloadItem) => {
|
|
356
|
+
const key = `${nameKey || item.dataKey || "value"}`;
|
|
357
|
+
const itemConfig = getPayloadConfigFromPayload(config, item, key);
|
|
358
|
+
|
|
359
|
+
return (
|
|
360
|
+
<div
|
|
361
|
+
key={`${item.value}`}
|
|
362
|
+
className="flex items-center gap-1.5 [&>svg]:h-3 [&>svg]:w-3 [&>svg]:text-muted-foreground"
|
|
363
|
+
>
|
|
364
|
+
{itemConfig?.icon && !hideIcon ? (
|
|
365
|
+
<itemConfig.icon />
|
|
366
|
+
) : (
|
|
367
|
+
<div
|
|
368
|
+
className="h-2 w-2 shrink-0 rounded-[2px]"
|
|
369
|
+
style={{
|
|
370
|
+
backgroundColor: item.color,
|
|
371
|
+
}}
|
|
372
|
+
/>
|
|
373
|
+
)}
|
|
374
|
+
{itemConfig?.label}
|
|
375
|
+
</div>
|
|
376
|
+
);
|
|
377
|
+
})}
|
|
378
|
+
</div>
|
|
379
|
+
);
|
|
380
|
+
}
|
|
381
|
+
ChartLegendContent.displayName = "ChartLegendContent";
|
|
382
|
+
|
|
383
|
+
function getPayloadConfigFromPayload(
|
|
384
|
+
config: ChartConfig,
|
|
385
|
+
payload: unknown,
|
|
386
|
+
key: string,
|
|
387
|
+
) {
|
|
388
|
+
if (typeof payload !== "object" || payload === null) {
|
|
389
|
+
return undefined;
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
const payloadPayload =
|
|
393
|
+
"payload" in payload &&
|
|
394
|
+
typeof payload.payload === "object" &&
|
|
395
|
+
payload.payload !== null
|
|
396
|
+
? payload.payload
|
|
397
|
+
: undefined;
|
|
398
|
+
|
|
399
|
+
let configLabelKey: string = key;
|
|
400
|
+
|
|
401
|
+
if (
|
|
402
|
+
key in payload &&
|
|
403
|
+
typeof payload[key as keyof typeof payload] === "string"
|
|
404
|
+
) {
|
|
405
|
+
configLabelKey = payload[key as keyof typeof payload] as string;
|
|
406
|
+
} else if (
|
|
407
|
+
payloadPayload &&
|
|
408
|
+
key in payloadPayload &&
|
|
409
|
+
typeof payloadPayload[key as keyof typeof payloadPayload] === "string"
|
|
410
|
+
) {
|
|
411
|
+
configLabelKey = payloadPayload[
|
|
412
|
+
key as keyof typeof payloadPayload
|
|
413
|
+
] as string;
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
return configLabelKey in config
|
|
417
|
+
? config[configLabelKey]
|
|
418
|
+
: config[key as keyof typeof config];
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
export {
|
|
422
|
+
ChartContainer,
|
|
423
|
+
ChartTooltip,
|
|
424
|
+
ChartTooltipContent,
|
|
425
|
+
ChartLegend,
|
|
426
|
+
ChartLegendContent,
|
|
427
|
+
ChartStyle,
|
|
428
|
+
};
|
|
60
429
|
|
|
61
430
|
export {
|
|
62
431
|
BarChart,
|