torch-glare 2.1.0 → 2.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/apps/lib/components/Badge.tsx +34 -137
- package/apps/lib/components/BadgeField.tsx +2 -2
- package/apps/lib/components/Charts-dev.tsx +365 -0
- package/apps/lib/components/Command-dev.tsx +151 -0
- package/apps/lib/components/IosDatePicker-dev.tsx +341 -0
- package/dist/bin/index.js +0 -0
- package/docs/components/badge-field.md +21 -21
- package/docs/components/badge.md +156 -483
- package/docs/reference/components.md +8 -7
- package/docs/reference/types.md +34 -26
- package/docs/tutorials/theming-basics.md +30 -27
- package/package.json +6 -6
|
@@ -60,7 +60,7 @@ const solidCompoundVariants = [
|
|
|
60
60
|
];
|
|
61
61
|
|
|
62
62
|
const SUBTLE_TEXT =
|
|
63
|
-
"text-content-presentation-global-subtle [&_i]:text-content-presentation-global-subtle mix-blend-luminosity";
|
|
63
|
+
"text-content-presentation-global-subtle [&>div]:mix-blend-luminosity [&_i]:text-content-presentation-global-subtle [&_i]:mix-blend-luminosity [&>button]:mix-blend-luminosity";
|
|
64
64
|
|
|
65
65
|
const subtleCompoundVariants = [
|
|
66
66
|
{
|
|
@@ -163,8 +163,8 @@ interface BadgeProps
|
|
|
163
163
|
label?: string;
|
|
164
164
|
badgeIcon?: ReactNode;
|
|
165
165
|
showIcon?: boolean;
|
|
166
|
-
|
|
167
|
-
|
|
166
|
+
isClosable?: boolean;
|
|
167
|
+
onClose?: () => void;
|
|
168
168
|
theme?: Themes;
|
|
169
169
|
className?: string;
|
|
170
170
|
}
|
|
@@ -173,8 +173,8 @@ export const Badge: React.FC<BadgeProps> = ({
|
|
|
173
173
|
label,
|
|
174
174
|
badgeIcon,
|
|
175
175
|
showIcon = true,
|
|
176
|
-
|
|
177
|
-
|
|
176
|
+
isClosable,
|
|
177
|
+
onClose,
|
|
178
178
|
theme,
|
|
179
179
|
badgeStyle,
|
|
180
180
|
color,
|
|
@@ -188,7 +188,7 @@ export const Badge: React.FC<BadgeProps> = ({
|
|
|
188
188
|
data-theme={theme}
|
|
189
189
|
className={cn(
|
|
190
190
|
badgeStyles({ badgeStyle, color, size }),
|
|
191
|
-
{ "cursor-default":
|
|
191
|
+
{ "cursor-default": isClosable },
|
|
192
192
|
className,
|
|
193
193
|
)}
|
|
194
194
|
>
|
|
@@ -198,149 +198,46 @@ export const Badge: React.FC<BadgeProps> = ({
|
|
|
198
198
|
</div>
|
|
199
199
|
)}
|
|
200
200
|
|
|
201
|
-
{label &&
|
|
202
|
-
<div className={cn("px-[3px]", { "pt-[1px]": size === "M" })}>
|
|
203
|
-
{label}
|
|
204
|
-
</div>
|
|
205
|
-
)}
|
|
201
|
+
{label && <div className="px-[3px]">{label}</div>}
|
|
206
202
|
|
|
207
|
-
{
|
|
203
|
+
{isClosable && (
|
|
208
204
|
<button
|
|
209
205
|
type="button"
|
|
210
|
-
onClick={
|
|
206
|
+
onClick={onClose}
|
|
211
207
|
onKeyDown={(e) => {
|
|
212
208
|
if (e.key === "Enter" || e.key === " ") {
|
|
213
209
|
e.preventDefault();
|
|
214
|
-
|
|
210
|
+
onClose?.();
|
|
215
211
|
}
|
|
216
212
|
}}
|
|
217
|
-
className=
|
|
213
|
+
className={cn(
|
|
214
|
+
"rounded-[4px]",
|
|
215
|
+
"flex items-center justify-center cursor-pointer",
|
|
216
|
+
"hover:bg-background-presentation-action-secondary",
|
|
217
|
+
"transition-colors duration-150",
|
|
218
|
+
size === "M"
|
|
219
|
+
? "w-[16px] h-[16px]"
|
|
220
|
+
: size === "S"
|
|
221
|
+
? "w-[14px] h-[14px]"
|
|
222
|
+
: "w-[12px] h-[12px]",
|
|
223
|
+
)}
|
|
218
224
|
aria-label="Remove badge"
|
|
219
225
|
>
|
|
220
|
-
<
|
|
226
|
+
<svg
|
|
227
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
228
|
+
width="100%"
|
|
229
|
+
height="100%"
|
|
230
|
+
viewBox="0 0 12 12"
|
|
231
|
+
fill="none"
|
|
232
|
+
aria-hidden="true"
|
|
233
|
+
>
|
|
234
|
+
<path
|
|
235
|
+
d="M6.00002 5.33336L8.33334 3L9 3.66667L6.66668 6.00002L9 8.33334L8.33334 9L6.00002 6.66668L3.66667 9L3 8.33334L5.33336 6.00002L3 3.66667L3.66667 3L6.00002 5.33336Z"
|
|
236
|
+
fill="currentColor"
|
|
237
|
+
/>
|
|
238
|
+
</svg>
|
|
221
239
|
</button>
|
|
222
240
|
)}
|
|
223
241
|
</span>
|
|
224
242
|
);
|
|
225
243
|
};
|
|
226
|
-
|
|
227
|
-
/* ============================================================
|
|
228
|
-
* REFERENCE — original Badge implementation (pre-NEWCOLORS2)
|
|
229
|
-
* Kept as a commented snippet so the old token names and variant
|
|
230
|
-
* surface remain consultable while migrating callers. Do not
|
|
231
|
-
* uncomment — the referenced tokens no longer exist in the
|
|
232
|
-
* regenerated mapping-color-system plugin.
|
|
233
|
-
* ============================================================
|
|
234
|
-
*
|
|
235
|
-
* export const badgeBase = cva(
|
|
236
|
-
* [
|
|
237
|
-
* "px-[6px]",
|
|
238
|
-
* "[&_div]:text-content-presentation-action-light-primary",
|
|
239
|
-
* "[&_i]:!leading-none",
|
|
240
|
-
* "flex",
|
|
241
|
-
* "justify-center",
|
|
242
|
-
* "items-center",
|
|
243
|
-
* "border",
|
|
244
|
-
* "rounded-[6px]",
|
|
245
|
-
* "transition-all",
|
|
246
|
-
* "duration-300",
|
|
247
|
-
* "ease-in-out",
|
|
248
|
-
* "w-fit",
|
|
249
|
-
* "cursor-pointer",
|
|
250
|
-
* "[&_i]:leading-0",
|
|
251
|
-
* ],
|
|
252
|
-
* {
|
|
253
|
-
* variants: {
|
|
254
|
-
* size: {
|
|
255
|
-
* XS: "h-[18px] [&_i]:text-[12px] [&_div]:typography-body-small-medium",
|
|
256
|
-
* S: "h-[22px] [&_i]:text-[12px] [&_div]:typography-body-small-medium",
|
|
257
|
-
* M: "h-[26px] [&_i]:text-[16px] [&_div]:typography-body-medium-medium",
|
|
258
|
-
* },
|
|
259
|
-
* variant: {
|
|
260
|
-
* highlight: ["h-[20px] [&_i]:text-[12px] [&_div]:typography-body-small-medium",
|
|
261
|
-
* "bg-background-presentation-badge-gray border-transparent px-[3px]"
|
|
262
|
-
* ],
|
|
263
|
-
* green: "border-border-presentation-badge-green bg-background-presentation-badge-green [&_i]:text-content-presentation-badge-green",
|
|
264
|
-
* greenLight: "border-border-presentation-badge-green-light bg-background-presentation-badge-green-light [&_i]:text-content-presentation-badge-green-light",
|
|
265
|
-
* cocktailGreen: "border-border-presentation-badge-cocktail-green bg-background-presentation-badge-cocktail-green [&_i]:text-content-presentation-badge-cocktail-green",
|
|
266
|
-
* yellow: "border-border-presentation-badge-yellow bg-background-presentation-badge-yellow [&_i]:text-content-presentation-badge-yellow",
|
|
267
|
-
* redOrange: "border-border-presentation-badge-red-orange bg-background-presentation-badge-red-orange [&_i]:text-content-presentation-badge-red-orange",
|
|
268
|
-
* redLight: "border-border-presentation-badge-red bg-background-presentation-badge-red [&_i]:text-content-presentation-badge-red",
|
|
269
|
-
* rose: "border-border-presentation-badge-rose bg-background-presentation-badge-rose [&_i]:text-content-presentation-badge-rose",
|
|
270
|
-
* purple: "border-border-presentation-badge-purple bg-background-presentation-badge-purple [&_i]:text-content-presentation-badge-purple",
|
|
271
|
-
* bluePurple: "border-border-presentation-badge-blue-purple bg-background-presentation-badge-blue-purple [&_i]:text-content-presentation-badge-blue-purple",
|
|
272
|
-
* blue: "border-border-presentation-badge-blue bg-background-presentation-badge-blue [&_i]:text-content-presentation-badge-blue",
|
|
273
|
-
* navy: "border-border-presentation-badge-navy bg-background-presentation-badge-navy [&_i]:text-content-presentation-badge-navy",
|
|
274
|
-
* gray: "border-border-presentation-badge-gray bg-background-presentation-badge-gray [&_i]:text-content-presentation-badge-gray",
|
|
275
|
-
* },
|
|
276
|
-
* },
|
|
277
|
-
* defaultVariants: {
|
|
278
|
-
* size: "S",
|
|
279
|
-
* variant: "green",
|
|
280
|
-
* },
|
|
281
|
-
* }
|
|
282
|
-
* );
|
|
283
|
-
*
|
|
284
|
-
* interface BadgeProps extends HTMLAttributes<HTMLButtonElement>,
|
|
285
|
-
* VariantProps<typeof badgeBase> {
|
|
286
|
-
* label?: string;
|
|
287
|
-
* onUnselect?: () => void;
|
|
288
|
-
* isSelected?: boolean;
|
|
289
|
-
* badgeIcon?: ReactNode;
|
|
290
|
-
* className?: string;
|
|
291
|
-
* theme?: Themes
|
|
292
|
-
* }
|
|
293
|
-
*
|
|
294
|
-
* export const Badge: React.FC<BadgeProps> = ({
|
|
295
|
-
* label,
|
|
296
|
-
* onUnselect,
|
|
297
|
-
* isSelected,
|
|
298
|
-
* badgeIcon,
|
|
299
|
-
* theme,
|
|
300
|
-
* size = "S",
|
|
301
|
-
* variant = "green",
|
|
302
|
-
* className,
|
|
303
|
-
* ...props
|
|
304
|
-
* }) => {
|
|
305
|
-
* return (
|
|
306
|
-
* <span
|
|
307
|
-
* {...props}
|
|
308
|
-
* data-theme={theme}
|
|
309
|
-
* className={cn(
|
|
310
|
-
* badgeBase({ size, variant }),
|
|
311
|
-
* {
|
|
312
|
-
* "cursor-default": isSelected,
|
|
313
|
-
* },
|
|
314
|
-
* className
|
|
315
|
-
* )}
|
|
316
|
-
* >
|
|
317
|
-
* <div className={"flex justify-center items-center"}>
|
|
318
|
-
* {!badgeIcon ? (
|
|
319
|
-
* <i className={cn("ri-circle-fill !text-[8px]", { "hidden": variant === "highlight" })}></i>
|
|
320
|
-
* ) : (
|
|
321
|
-
* badgeIcon
|
|
322
|
-
* )}
|
|
323
|
-
* </div>
|
|
324
|
-
*
|
|
325
|
-
* <div className="px-[3px] whitespace-nowrap">{label}</div>
|
|
326
|
-
* {isSelected && (
|
|
327
|
-
* <button
|
|
328
|
-
* onClick={onUnselect}
|
|
329
|
-
* className="rounded-[2px] flex justify-center items-center cursor-pointer"
|
|
330
|
-
* tabIndex={0}
|
|
331
|
-
* role="button"
|
|
332
|
-
* aria-label="Remove badge"
|
|
333
|
-
* onKeyDown={(e) => {
|
|
334
|
-
* if (e.key === 'Enter' || e.key === ' ') {
|
|
335
|
-
* e.preventDefault();
|
|
336
|
-
* onUnselect?.();
|
|
337
|
-
* }
|
|
338
|
-
* }}
|
|
339
|
-
* >
|
|
340
|
-
* <i className="ri-close-line !text-content-presentation-action-light-primary"></i>
|
|
341
|
-
* </button>
|
|
342
|
-
* )}
|
|
343
|
-
* </span>
|
|
344
|
-
* );
|
|
345
|
-
* };
|
|
346
|
-
*/
|
|
@@ -126,8 +126,8 @@ export const BadgeField = forwardRef<HTMLInputElement, Props>(
|
|
|
126
126
|
size={size}
|
|
127
127
|
color={tag.variant as any}
|
|
128
128
|
label={tag.name}
|
|
129
|
-
|
|
130
|
-
|
|
129
|
+
isClosable={true}
|
|
130
|
+
onClose={() => handleUnselectTag(tag.id)}
|
|
131
131
|
className={
|
|
132
132
|
focusedTagIndex === index ? "ring-2 ring-blue-500" : ""
|
|
133
133
|
}
|
|
@@ -0,0 +1,365 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import * as React from "react"
|
|
4
|
+
import * as RechartsPrimitive from "recharts"
|
|
5
|
+
|
|
6
|
+
import { cn } from "../utils/cn"
|
|
7
|
+
|
|
8
|
+
// Format: { THEME_NAME: CSS_SELECTOR }
|
|
9
|
+
const THEMES = { light: "", dark: ".dark" } as const
|
|
10
|
+
|
|
11
|
+
export type ChartConfig = {
|
|
12
|
+
[k in string]: {
|
|
13
|
+
label?: React.ReactNode
|
|
14
|
+
icon?: React.ComponentType
|
|
15
|
+
} & (
|
|
16
|
+
| { color?: string; theme?: never }
|
|
17
|
+
| { color?: never; theme: Record<keyof typeof THEMES, string> }
|
|
18
|
+
)
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
type ChartContextProps = {
|
|
22
|
+
config: ChartConfig
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const ChartContext = React.createContext<ChartContextProps | null>(null)
|
|
26
|
+
|
|
27
|
+
function useChart() {
|
|
28
|
+
const context = React.useContext(ChartContext)
|
|
29
|
+
|
|
30
|
+
if (!context) {
|
|
31
|
+
throw new Error("useChart must be used within a <ChartContainer />")
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return context
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const ChartContainer = React.forwardRef<
|
|
38
|
+
HTMLDivElement,
|
|
39
|
+
React.ComponentProps<"div"> & {
|
|
40
|
+
config: ChartConfig
|
|
41
|
+
children: React.ComponentProps<
|
|
42
|
+
typeof RechartsPrimitive.ResponsiveContainer
|
|
43
|
+
>["children"]
|
|
44
|
+
}
|
|
45
|
+
>(({ id, className, children, config, ...props }, ref) => {
|
|
46
|
+
const uniqueId = React.useId()
|
|
47
|
+
const chartId = `chart-${id || uniqueId.replace(/:/g, "")}`
|
|
48
|
+
|
|
49
|
+
return (
|
|
50
|
+
<ChartContext.Provider value={{ config }}>
|
|
51
|
+
<div
|
|
52
|
+
data-chart={chartId}
|
|
53
|
+
ref={ref}
|
|
54
|
+
className={cn(
|
|
55
|
+
"flex aspect-video justify-center text-xs [&_.recharts-cartesian-axis-tick_text]:fill-muted-foreground [&_.recharts-cartesian-grid_line[stroke='#ccc']]:stroke-border/50 [&_.recharts-curve.recharts-tooltip-cursor]:stroke-border [&_.recharts-dot[stroke='#fff']]:stroke-transparent [&_.recharts-layer]:outline-none [&_.recharts-polar-grid_[stroke='#ccc']]:stroke-border [&_.recharts-radial-bar-background-sector]:fill-muted [&_.recharts-rectangle.recharts-tooltip-cursor]:fill-muted [&_.recharts-reference-line_[stroke='#ccc']]:stroke-border [&_.recharts-sector[stroke='#fff']]:stroke-transparent [&_.recharts-sector]:outline-none [&_.recharts-surface]:outline-none",
|
|
56
|
+
className
|
|
57
|
+
)}
|
|
58
|
+
{...props}
|
|
59
|
+
>
|
|
60
|
+
<ChartStyle id={chartId} config={config} />
|
|
61
|
+
<RechartsPrimitive.ResponsiveContainer>
|
|
62
|
+
{children}
|
|
63
|
+
</RechartsPrimitive.ResponsiveContainer>
|
|
64
|
+
</div>
|
|
65
|
+
</ChartContext.Provider>
|
|
66
|
+
)
|
|
67
|
+
})
|
|
68
|
+
ChartContainer.displayName = "Chart"
|
|
69
|
+
|
|
70
|
+
const ChartStyle = ({ id, config }: { id: string; config: ChartConfig }) => {
|
|
71
|
+
const colorConfig = Object.entries(config).filter(
|
|
72
|
+
([, config]) => config.theme || config.color
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
if (!colorConfig.length) {
|
|
76
|
+
return null
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return (
|
|
80
|
+
<style
|
|
81
|
+
dangerouslySetInnerHTML={{
|
|
82
|
+
__html: Object.entries(THEMES)
|
|
83
|
+
.map(
|
|
84
|
+
([theme, prefix]) => `
|
|
85
|
+
${prefix} [data-chart=${id}] {
|
|
86
|
+
${colorConfig
|
|
87
|
+
.map(([key, itemConfig]) => {
|
|
88
|
+
const color =
|
|
89
|
+
itemConfig.theme?.[theme as keyof typeof itemConfig.theme] ||
|
|
90
|
+
itemConfig.color
|
|
91
|
+
return color ? ` --color-${key}: ${color};` : null
|
|
92
|
+
})
|
|
93
|
+
.join("\n")}
|
|
94
|
+
}
|
|
95
|
+
`
|
|
96
|
+
)
|
|
97
|
+
.join("\n"),
|
|
98
|
+
}}
|
|
99
|
+
/>
|
|
100
|
+
)
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const ChartTooltip = RechartsPrimitive.Tooltip
|
|
104
|
+
|
|
105
|
+
const ChartTooltipContent = React.forwardRef<
|
|
106
|
+
HTMLDivElement,
|
|
107
|
+
React.ComponentProps<typeof RechartsPrimitive.Tooltip> &
|
|
108
|
+
React.ComponentProps<"div"> & {
|
|
109
|
+
hideLabel?: boolean
|
|
110
|
+
hideIndicator?: boolean
|
|
111
|
+
indicator?: "line" | "dot" | "dashed"
|
|
112
|
+
nameKey?: string
|
|
113
|
+
labelKey?: string
|
|
114
|
+
}
|
|
115
|
+
>(
|
|
116
|
+
(
|
|
117
|
+
{
|
|
118
|
+
active,
|
|
119
|
+
payload,
|
|
120
|
+
className,
|
|
121
|
+
indicator = "dot",
|
|
122
|
+
hideLabel = false,
|
|
123
|
+
hideIndicator = false,
|
|
124
|
+
label,
|
|
125
|
+
labelFormatter,
|
|
126
|
+
labelClassName,
|
|
127
|
+
formatter,
|
|
128
|
+
color,
|
|
129
|
+
nameKey,
|
|
130
|
+
labelKey,
|
|
131
|
+
},
|
|
132
|
+
ref
|
|
133
|
+
) => {
|
|
134
|
+
const { config } = useChart()
|
|
135
|
+
|
|
136
|
+
const tooltipLabel = React.useMemo(() => {
|
|
137
|
+
if (hideLabel || !payload?.length) {
|
|
138
|
+
return null
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const [item] = payload
|
|
142
|
+
const key = `${labelKey || item?.dataKey || item?.name || "value"}`
|
|
143
|
+
const itemConfig = getPayloadConfigFromPayload(config, item, key)
|
|
144
|
+
const value =
|
|
145
|
+
!labelKey && typeof label === "string"
|
|
146
|
+
? config[label as keyof typeof config]?.label || label
|
|
147
|
+
: itemConfig?.label
|
|
148
|
+
|
|
149
|
+
if (labelFormatter) {
|
|
150
|
+
return (
|
|
151
|
+
<div className={cn("font-medium", labelClassName)}>
|
|
152
|
+
{labelFormatter(value, payload)}
|
|
153
|
+
</div>
|
|
154
|
+
)
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
if (!value) {
|
|
158
|
+
return null
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
return <div className={cn("font-medium", labelClassName)}>{value}</div>
|
|
162
|
+
}, [
|
|
163
|
+
label,
|
|
164
|
+
labelFormatter,
|
|
165
|
+
payload,
|
|
166
|
+
hideLabel,
|
|
167
|
+
labelClassName,
|
|
168
|
+
config,
|
|
169
|
+
labelKey,
|
|
170
|
+
])
|
|
171
|
+
|
|
172
|
+
if (!active || !payload?.length) {
|
|
173
|
+
return null
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
const nestLabel = payload.length === 1 && indicator !== "dot"
|
|
177
|
+
|
|
178
|
+
return (
|
|
179
|
+
<div
|
|
180
|
+
ref={ref}
|
|
181
|
+
className={cn(
|
|
182
|
+
"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",
|
|
183
|
+
className
|
|
184
|
+
)}
|
|
185
|
+
>
|
|
186
|
+
{!nestLabel ? tooltipLabel : null}
|
|
187
|
+
<div className="grid gap-1.5">
|
|
188
|
+
{payload.map((item: any, index: number) => {
|
|
189
|
+
const key = `${nameKey || item.name || item.dataKey || "value"}`
|
|
190
|
+
const itemConfig = getPayloadConfigFromPayload(config, item, key)
|
|
191
|
+
const indicatorColor = color || item.payload.fill || item.color
|
|
192
|
+
|
|
193
|
+
return (
|
|
194
|
+
<div
|
|
195
|
+
key={item.dataKey}
|
|
196
|
+
className={cn(
|
|
197
|
+
"flex w-full flex-wrap items-stretch gap-2 [&>svg]:h-2.5 [&>svg]:w-2.5 [&>svg]:text-muted-foreground",
|
|
198
|
+
indicator === "dot" && "items-center"
|
|
199
|
+
)}
|
|
200
|
+
>
|
|
201
|
+
{formatter && item?.value !== undefined && item.name ? (
|
|
202
|
+
formatter(item.value, item.name, item, index, item.payload)
|
|
203
|
+
) : (
|
|
204
|
+
<>
|
|
205
|
+
{itemConfig?.icon ? (
|
|
206
|
+
<itemConfig.icon />
|
|
207
|
+
) : (
|
|
208
|
+
!hideIndicator && (
|
|
209
|
+
<div
|
|
210
|
+
className={cn(
|
|
211
|
+
"shrink-0 rounded-[2px] border-[--color-border] bg-[--color-bg]",
|
|
212
|
+
{
|
|
213
|
+
"h-2.5 w-2.5": indicator === "dot",
|
|
214
|
+
"w-1": indicator === "line",
|
|
215
|
+
"w-0 border-[1.5px] border-dashed bg-transparent":
|
|
216
|
+
indicator === "dashed",
|
|
217
|
+
"my-0.5": nestLabel && indicator === "dashed",
|
|
218
|
+
}
|
|
219
|
+
)}
|
|
220
|
+
style={
|
|
221
|
+
{
|
|
222
|
+
"--color-bg": indicatorColor,
|
|
223
|
+
"--color-border": indicatorColor,
|
|
224
|
+
} as React.CSSProperties
|
|
225
|
+
}
|
|
226
|
+
/>
|
|
227
|
+
)
|
|
228
|
+
)}
|
|
229
|
+
<div
|
|
230
|
+
className={cn(
|
|
231
|
+
"flex flex-1 justify-between leading-none",
|
|
232
|
+
nestLabel ? "items-end" : "items-center"
|
|
233
|
+
)}
|
|
234
|
+
>
|
|
235
|
+
<div className="grid gap-1.5">
|
|
236
|
+
{nestLabel ? tooltipLabel : null}
|
|
237
|
+
<span className="text-muted-foreground">
|
|
238
|
+
{itemConfig?.label || item.name}
|
|
239
|
+
</span>
|
|
240
|
+
</div>
|
|
241
|
+
{item.value && (
|
|
242
|
+
<span className="font-mono font-medium tabular-nums text-foreground">
|
|
243
|
+
{item.value.toLocaleString()}
|
|
244
|
+
</span>
|
|
245
|
+
)}
|
|
246
|
+
</div>
|
|
247
|
+
</>
|
|
248
|
+
)}
|
|
249
|
+
</div>
|
|
250
|
+
)
|
|
251
|
+
})}
|
|
252
|
+
</div>
|
|
253
|
+
</div>
|
|
254
|
+
)
|
|
255
|
+
}
|
|
256
|
+
)
|
|
257
|
+
ChartTooltipContent.displayName = "ChartTooltip"
|
|
258
|
+
|
|
259
|
+
const ChartLegend = RechartsPrimitive.Legend
|
|
260
|
+
|
|
261
|
+
const ChartLegendContent = React.forwardRef<
|
|
262
|
+
HTMLDivElement,
|
|
263
|
+
React.ComponentProps<"div"> &
|
|
264
|
+
Pick<RechartsPrimitive.LegendProps, "payload" | "verticalAlign"> & {
|
|
265
|
+
hideIcon?: boolean
|
|
266
|
+
nameKey?: string
|
|
267
|
+
}
|
|
268
|
+
>(
|
|
269
|
+
(
|
|
270
|
+
{ className, hideIcon = false, payload, verticalAlign = "bottom", nameKey },
|
|
271
|
+
ref
|
|
272
|
+
) => {
|
|
273
|
+
const { config } = useChart()
|
|
274
|
+
|
|
275
|
+
if (!payload?.length) {
|
|
276
|
+
return null
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
return (
|
|
280
|
+
<div
|
|
281
|
+
ref={ref}
|
|
282
|
+
className={cn(
|
|
283
|
+
"flex items-center justify-center gap-4",
|
|
284
|
+
verticalAlign === "top" ? "pb-3" : "pt-3",
|
|
285
|
+
className
|
|
286
|
+
)}
|
|
287
|
+
>
|
|
288
|
+
{payload.map((item) => {
|
|
289
|
+
const key = `${nameKey || item.dataKey || "value"}`
|
|
290
|
+
const itemConfig = getPayloadConfigFromPayload(config, item, key)
|
|
291
|
+
|
|
292
|
+
return (
|
|
293
|
+
<div
|
|
294
|
+
key={item.value}
|
|
295
|
+
className={cn(
|
|
296
|
+
"flex items-center gap-1.5 [&>svg]:h-3 [&>svg]:w-3 [&>svg]:text-muted-foreground"
|
|
297
|
+
)}
|
|
298
|
+
>
|
|
299
|
+
{itemConfig?.icon && !hideIcon ? (
|
|
300
|
+
<itemConfig.icon />
|
|
301
|
+
) : (
|
|
302
|
+
<div
|
|
303
|
+
className="h-2 w-2 shrink-0 rounded-[2px]"
|
|
304
|
+
style={{
|
|
305
|
+
backgroundColor: item.color,
|
|
306
|
+
}}
|
|
307
|
+
/>
|
|
308
|
+
)}
|
|
309
|
+
{itemConfig?.label}
|
|
310
|
+
</div>
|
|
311
|
+
)
|
|
312
|
+
})}
|
|
313
|
+
</div>
|
|
314
|
+
)
|
|
315
|
+
}
|
|
316
|
+
)
|
|
317
|
+
ChartLegendContent.displayName = "ChartLegend"
|
|
318
|
+
|
|
319
|
+
// Helper to extract item config from a payload.
|
|
320
|
+
function getPayloadConfigFromPayload(
|
|
321
|
+
config: ChartConfig,
|
|
322
|
+
payload: unknown,
|
|
323
|
+
key: string
|
|
324
|
+
) {
|
|
325
|
+
if (typeof payload !== "object" || payload === null) {
|
|
326
|
+
return undefined
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
const payloadPayload =
|
|
330
|
+
"payload" in payload &&
|
|
331
|
+
typeof payload.payload === "object" &&
|
|
332
|
+
payload.payload !== null
|
|
333
|
+
? payload.payload
|
|
334
|
+
: undefined
|
|
335
|
+
|
|
336
|
+
let configLabelKey: string = key
|
|
337
|
+
|
|
338
|
+
if (
|
|
339
|
+
key in payload &&
|
|
340
|
+
typeof payload[key as keyof typeof payload] === "string"
|
|
341
|
+
) {
|
|
342
|
+
configLabelKey = payload[key as keyof typeof payload] as string
|
|
343
|
+
} else if (
|
|
344
|
+
payloadPayload &&
|
|
345
|
+
key in payloadPayload &&
|
|
346
|
+
typeof payloadPayload[key as keyof typeof payloadPayload] === "string"
|
|
347
|
+
) {
|
|
348
|
+
configLabelKey = payloadPayload[
|
|
349
|
+
key as keyof typeof payloadPayload
|
|
350
|
+
] as string
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
return configLabelKey in config
|
|
354
|
+
? config[configLabelKey]
|
|
355
|
+
: config[key as keyof typeof config]
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
export {
|
|
359
|
+
ChartContainer,
|
|
360
|
+
ChartTooltip,
|
|
361
|
+
ChartTooltipContent,
|
|
362
|
+
ChartLegend,
|
|
363
|
+
ChartLegendContent,
|
|
364
|
+
ChartStyle,
|
|
365
|
+
}
|