gitscope
https://dev.kronoz.win/r/marzneshin.git
dashboard/src/common/components/ui/chart.tsx · nightly
Source
1 e6e6467 Dawsh 2y ago import * as React from "react"
2 e6e6467 Dawsh 2y ago import * as RechartsPrimitive from "recharts"
3 e6e6467 Dawsh 2y ago
4 d636f3f Dawsh 2y ago import { cn } from "@marzneshin/common/utils"
5 e6e6467 Dawsh 2y ago
6 e6e6467 Dawsh 2y ago // Format: { THEME_NAME: CSS_SELECTOR }
7 e6e6467 Dawsh 2y ago const THEMES = { light: "", dark: ".dark" } as const
8 e6e6467 Dawsh 2y ago
9 e6e6467 Dawsh 2y ago export type ChartConfig = {
10 e6e6467 Dawsh 2y ago [k in string]: {
11 e6e6467 Dawsh 2y ago label?: React.ReactNode
12 e6e6467 Dawsh 2y ago icon?: React.ComponentType
13 e6e6467 Dawsh 2y ago } & (
14 e6e6467 Dawsh 2y ago | { color?: string; theme?: never }
15 e6e6467 Dawsh 2y ago | { color?: never; theme: Record<keyof typeof THEMES, string> }
16 e6e6467 Dawsh 2y ago )
17 e6e6467 Dawsh 2y ago }
18 e6e6467 Dawsh 2y ago
19 e6e6467 Dawsh 2y ago type ChartContextProps = {
20 e6e6467 Dawsh 2y ago config: ChartConfig
21 e6e6467 Dawsh 2y ago }
22 e6e6467 Dawsh 2y ago
23 e6e6467 Dawsh 2y ago const ChartContext = React.createContext<ChartContextProps | null>(null)
24 e6e6467 Dawsh 2y ago
25 e6e6467 Dawsh 2y ago function useChart() {
26 e6e6467 Dawsh 2y ago const context = React.useContext(ChartContext)
27 e6e6467 Dawsh 2y ago
28 e6e6467 Dawsh 2y ago if (!context) {
29 e6e6467 Dawsh 2y ago throw new Error("useChart must be used within a <ChartContainer />")
30 e6e6467 Dawsh 2y ago }
31 e6e6467 Dawsh 2y ago
32 e6e6467 Dawsh 2y ago return context
33 e6e6467 Dawsh 2y ago }
34 e6e6467 Dawsh 2y ago
35 e6e6467 Dawsh 2y ago const ChartContainer = React.forwardRef<
36 e6e6467 Dawsh 2y ago HTMLDivElement,
37 e6e6467 Dawsh 2y ago React.ComponentProps<"div"> & {
38 e6e6467 Dawsh 2y ago config: ChartConfig
39 e6e6467 Dawsh 2y ago children: React.ComponentProps<
40 e6e6467 Dawsh 2y ago typeof RechartsPrimitive.ResponsiveContainer
41 e6e6467 Dawsh 2y ago >["children"]
42 e6e6467 Dawsh 2y ago }
43 e6e6467 Dawsh 2y ago >(({ id, className, children, config, ...props }, ref) => {
44 e6e6467 Dawsh 2y ago const uniqueId = React.useId()
45 e6e6467 Dawsh 2y ago const chartId = `chart-${id || uniqueId.replace(/:/g, "")}`
46 e6e6467 Dawsh 2y ago
47 e6e6467 Dawsh 2y ago return (
48 e6e6467 Dawsh 2y ago <ChartContext.Provider value={{ config }}>
49 e6e6467 Dawsh 2y ago <div
50 e6e6467 Dawsh 2y ago data-chart={chartId}
51 e6e6467 Dawsh 2y ago ref={ref}
52 e6e6467 Dawsh 2y ago className={cn(
53 e6e6467 Dawsh 2y ago "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",
54 e6e6467 Dawsh 2y ago className
55 e6e6467 Dawsh 2y ago )}
56 e6e6467 Dawsh 2y ago {...props}
57 e6e6467 Dawsh 2y ago >
58 e6e6467 Dawsh 2y ago <ChartStyle id={chartId} config={config} />
59 e6e6467 Dawsh 2y ago <RechartsPrimitive.ResponsiveContainer>
60 e6e6467 Dawsh 2y ago {children}
61 e6e6467 Dawsh 2y ago </RechartsPrimitive.ResponsiveContainer>
62 e6e6467 Dawsh 2y ago </div>
63 e6e6467 Dawsh 2y ago </ChartContext.Provider>
64 e6e6467 Dawsh 2y ago )
65 e6e6467 Dawsh 2y ago })
66 e6e6467 Dawsh 2y ago ChartContainer.displayName = "Chart"
67 e6e6467 Dawsh 2y ago
68 e6e6467 Dawsh 2y ago const ChartStyle = ({ id, config }: { id: string; config: ChartConfig }) => {
69 e6e6467 Dawsh 2y ago const colorConfig = Object.entries(config).filter(
70 e6e6467 Dawsh 2y ago ([, config]) => config.theme || config.color
71 e6e6467 Dawsh 2y ago )
72 e6e6467 Dawsh 2y ago
73 e6e6467 Dawsh 2y ago if (!colorConfig.length) {
74 e6e6467 Dawsh 2y ago return null
75 e6e6467 Dawsh 2y ago }
76 e6e6467 Dawsh 2y ago
77 e6e6467 Dawsh 2y ago return (
78 e6e6467 Dawsh 2y ago <style
79 e6e6467 Dawsh 2y ago dangerouslySetInnerHTML={{
80 e6e6467 Dawsh 2y ago __html: Object.entries(THEMES)
81 e6e6467 Dawsh 2y ago .map(
82 e6e6467 Dawsh 2y ago ([theme, prefix]) => `
83 e6e6467 Dawsh 2y ago ${prefix} [data-chart=${id}] {
84 e6e6467 Dawsh 2y ago ${colorConfig
85 e6e6467 Dawsh 2y ago .map(([key, itemConfig]) => {
86 e6e6467 Dawsh 2y ago const color =
87 e6e6467 Dawsh 2y ago itemConfig.theme?.[theme as keyof typeof itemConfig.theme] ||
88 e6e6467 Dawsh 2y ago itemConfig.color
89 e6e6467 Dawsh 2y ago return color ? ` --color-${key}: ${color};` : null
90 e6e6467 Dawsh 2y ago })
91 e6e6467 Dawsh 2y ago .join("\n")}
92 e6e6467 Dawsh 2y ago }
93 e6e6467 Dawsh 2y ago `
94 e6e6467 Dawsh 2y ago )
95 e6e6467 Dawsh 2y ago .join("\n"),
96 e6e6467 Dawsh 2y ago }}
97 e6e6467 Dawsh 2y ago />
98 e6e6467 Dawsh 2y ago )
99 e6e6467 Dawsh 2y ago }
100 e6e6467 Dawsh 2y ago
101 e6e6467 Dawsh 2y ago const ChartTooltip = RechartsPrimitive.Tooltip
102 e6e6467 Dawsh 2y ago
103 e6e6467 Dawsh 2y ago const ChartTooltipContent = React.forwardRef<
104 e6e6467 Dawsh 2y ago HTMLDivElement,
105 e6e6467 Dawsh 2y ago React.ComponentProps<typeof RechartsPrimitive.Tooltip> &
106 e6e6467 Dawsh 2y ago React.ComponentProps<"div"> & {
107 e6e6467 Dawsh 2y ago hideLabel?: boolean
108 e6e6467 Dawsh 2y ago hideIndicator?: boolean
109 e6e6467 Dawsh 2y ago indicator?: "line" | "dot" | "dashed"
110 e6e6467 Dawsh 2y ago nameKey?: string
111 e6e6467 Dawsh 2y ago labelKey?: string
112 e6e6467 Dawsh 2y ago valueFormatter?: (value: any) => string
113 e6e6467 Dawsh 2y ago }
114 e6e6467 Dawsh 2y ago >(
115 e6e6467 Dawsh 2y ago (
116 e6e6467 Dawsh 2y ago {
117 e6e6467 Dawsh 2y ago active,
118 e6e6467 Dawsh 2y ago payload,
119 e6e6467 Dawsh 2y ago className,
120 e6e6467 Dawsh 2y ago indicator = "dot",
121 e6e6467 Dawsh 2y ago hideLabel = false,
122 e6e6467 Dawsh 2y ago hideIndicator = false,
123 e6e6467 Dawsh 2y ago label,
124 e6e6467 Dawsh 2y ago labelFormatter,
125 e6e6467 Dawsh 2y ago valueFormatter,
126 e6e6467 Dawsh 2y ago labelClassName,
127 e6e6467 Dawsh 2y ago formatter,
128 e6e6467 Dawsh 2y ago color,
129 e6e6467 Dawsh 2y ago nameKey,
130 e6e6467 Dawsh 2y ago labelKey,
131 e6e6467 Dawsh 2y ago },
132 e6e6467 Dawsh 2y ago ref
133 e6e6467 Dawsh 2y ago ) => {
134 e6e6467 Dawsh 2y ago const { config } = useChart()
135 e6e6467 Dawsh 2y ago
136 e6e6467 Dawsh 2y ago const tooltipLabel = React.useMemo(() => {
137 e6e6467 Dawsh 2y ago if (hideLabel || !payload?.length) {
138 e6e6467 Dawsh 2y ago return null
139 e6e6467 Dawsh 2y ago }
140 e6e6467 Dawsh 2y ago
141 e6e6467 Dawsh 2y ago const [item] = payload
142 e6e6467 Dawsh 2y ago const key = `${labelKey || item.dataKey || item.name || "value"}`
143 e6e6467 Dawsh 2y ago const itemConfig = getPayloadConfigFromPayload(config, item, key)
144 e6e6467 Dawsh 2y ago const value =
145 e6e6467 Dawsh 2y ago !labelKey && typeof label === "string"
146 e6e6467 Dawsh 2y ago ? config[label as keyof typeof config]?.label || label
147 e6e6467 Dawsh 2y ago : itemConfig?.label
148 e6e6467 Dawsh 2y ago
149 e6e6467 Dawsh 2y ago if (labelFormatter) {
150 e6e6467 Dawsh 2y ago return (
151 e6e6467 Dawsh 2y ago <div className={cn("font-medium", labelClassName)}>
152 e6e6467 Dawsh 2y ago {labelFormatter(value, payload)}
153 e6e6467 Dawsh 2y ago </div>
154 e6e6467 Dawsh 2y ago )
155 e6e6467 Dawsh 2y ago }
156 e6e6467 Dawsh 2y ago
157 e6e6467 Dawsh 2y ago if (!value) {
158 e6e6467 Dawsh 2y ago return null
159 e6e6467 Dawsh 2y ago }
160 e6e6467 Dawsh 2y ago
161 e6e6467 Dawsh 2y ago return <div className={cn("font-medium", labelClassName)}>{value}</div>
162 e6e6467 Dawsh 2y ago }, [
163 e6e6467 Dawsh 2y ago label,
164 e6e6467 Dawsh 2y ago labelFormatter,
165 e6e6467 Dawsh 2y ago payload,
166 e6e6467 Dawsh 2y ago hideLabel,
167 e6e6467 Dawsh 2y ago labelClassName,
168 e6e6467 Dawsh 2y ago config,
169 e6e6467 Dawsh 2y ago labelKey,
170 e6e6467 Dawsh 2y ago ])
171 e6e6467 Dawsh 2y ago
172 e6e6467 Dawsh 2y ago if (!active || !payload?.length) {
173 e6e6467 Dawsh 2y ago return null
174 e6e6467 Dawsh 2y ago }
175 e6e6467 Dawsh 2y ago
176 e6e6467 Dawsh 2y ago const nestLabel = payload.length === 1 && indicator !== "dot"
177 e6e6467 Dawsh 2y ago
178 e6e6467 Dawsh 2y ago return (
179 e6e6467 Dawsh 2y ago <div
180 e6e6467 Dawsh 2y ago ref={ref}
181 e6e6467 Dawsh 2y ago className={cn(
182 e6e6467 Dawsh 2y ago "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 e6e6467 Dawsh 2y ago className
184 e6e6467 Dawsh 2y ago )}
185 e6e6467 Dawsh 2y ago >
186 e6e6467 Dawsh 2y ago {!nestLabel ? tooltipLabel : null}
187 e6e6467 Dawsh 2y ago <div className="grid gap-1.5">
188 e6e6467 Dawsh 2y ago {payload.map((item, index) => {
189 e6e6467 Dawsh 2y ago const key = `${nameKey || item.name || item.dataKey || "value"}`
190 e6e6467 Dawsh 2y ago const itemConfig = getPayloadConfigFromPayload(config, item, key)
191 e6e6467 Dawsh 2y ago const indicatorColor = color || item.payload.fill || item.color
192 e6e6467 Dawsh 2y ago
193 e6e6467 Dawsh 2y ago return (
194 e6e6467 Dawsh 2y ago <div
195 e6e6467 Dawsh 2y ago key={item.dataKey}
196 e6e6467 Dawsh 2y ago className={cn(
197 e6e6467 Dawsh 2y ago "flex w-full flex-wrap items-stretch gap-2 [&>svg]:h-2.5 [&>svg]:w-2.5 [&>svg]:text-muted-foreground",
198 e6e6467 Dawsh 2y ago indicator === "dot" && "items-center"
199 e6e6467 Dawsh 2y ago )}
200 e6e6467 Dawsh 2y ago >
201 e6e6467 Dawsh 2y ago {formatter && item?.value !== undefined && item.name ? (
202 e6e6467 Dawsh 2y ago formatter(item.value, item.name, item, index, item.payload)
203 e6e6467 Dawsh 2y ago ) : (
204 e6e6467 Dawsh 2y ago <>
205 e6e6467 Dawsh 2y ago {itemConfig?.icon ? (
206 e6e6467 Dawsh 2y ago <itemConfig.icon />
207 e6e6467 Dawsh 2y ago ) : (
208 e6e6467 Dawsh 2y ago !hideIndicator && (
209 e6e6467 Dawsh 2y ago <div
210 e6e6467 Dawsh 2y ago className={cn(
211 e6e6467 Dawsh 2y ago "shrink-0 rounded-[2px] border-[--color-border] bg-[--color-bg]",
212 e6e6467 Dawsh 2y ago {
213 e6e6467 Dawsh 2y ago "h-2.5 w-2.5": indicator === "dot",
214 e6e6467 Dawsh 2y ago "w-1": indicator === "line",
215 e6e6467 Dawsh 2y ago "w-0 border-[1.5px] border-dashed bg-transparent":
216 e6e6467 Dawsh 2y ago indicator === "dashed",
217 e6e6467 Dawsh 2y ago "my-0.5": nestLabel && indicator === "dashed",
218 e6e6467 Dawsh 2y ago }
219 e6e6467 Dawsh 2y ago )}
220 e6e6467 Dawsh 2y ago style={
221 e6e6467 Dawsh 2y ago {
222 e6e6467 Dawsh 2y ago "--color-bg": indicatorColor,
223 e6e6467 Dawsh 2y ago "--color-border": indicatorColor,
224 e6e6467 Dawsh 2y ago } as React.CSSProperties
225 e6e6467 Dawsh 2y ago }
226 e6e6467 Dawsh 2y ago />
227 e6e6467 Dawsh 2y ago )
228 e6e6467 Dawsh 2y ago )}
229 e6e6467 Dawsh 2y ago <div
230 e6e6467 Dawsh 2y ago className={cn(
231 e6e6467 Dawsh 2y ago "flex flex-1 justify-between leading-none",
232 e6e6467 Dawsh 2y ago nestLabel ? "items-end" : "items-center"
233 e6e6467 Dawsh 2y ago )}
234 e6e6467 Dawsh 2y ago >
235 e6e6467 Dawsh 2y ago <div className="grid gap-1.5">
236 e6e6467 Dawsh 2y ago {nestLabel ? tooltipLabel : null}
237 e6e6467 Dawsh 2y ago <span className="text-muted-foreground">
238 e6e6467 Dawsh 2y ago {itemConfig?.label || item.name}
239 e6e6467 Dawsh 2y ago </span>
240 e6e6467 Dawsh 2y ago </div>
241 e6e6467 Dawsh 2y ago {item.value && (
242 e6e6467 Dawsh 2y ago <span className="font-mono font-medium tabular-nums text-foreground">
243 e6e6467 Dawsh 2y ago {valueFormatter ? valueFormatter(item.value) : item.value.toLocaleString()}
244 e6e6467 Dawsh 2y ago </span>
245 e6e6467 Dawsh 2y ago )}
246 e6e6467 Dawsh 2y ago </div>
247 e6e6467 Dawsh 2y ago </>
248 e6e6467 Dawsh 2y ago )}
249 e6e6467 Dawsh 2y ago </div>
250 e6e6467 Dawsh 2y ago )
251 e6e6467 Dawsh 2y ago })}
252 e6e6467 Dawsh 2y ago </div>
253 e6e6467 Dawsh 2y ago </div>
254 e6e6467 Dawsh 2y ago )
255 e6e6467 Dawsh 2y ago }
256 e6e6467 Dawsh 2y ago )
257 e6e6467 Dawsh 2y ago ChartTooltipContent.displayName = "ChartTooltip"
258 e6e6467 Dawsh 2y ago
259 e6e6467 Dawsh 2y ago const ChartLegend = RechartsPrimitive.Legend
260 e6e6467 Dawsh 2y ago
261 e6e6467 Dawsh 2y ago const ChartLegendContent = React.forwardRef<
262 e6e6467 Dawsh 2y ago HTMLDivElement,
263 e6e6467 Dawsh 2y ago React.ComponentProps<"div"> &
264 e6e6467 Dawsh 2y ago Pick<RechartsPrimitive.LegendProps, "payload" | "verticalAlign"> & {
265 e6e6467 Dawsh 2y ago hideIcon?: boolean
266 e6e6467 Dawsh 2y ago nameKey?: string
267 e6e6467 Dawsh 2y ago }
268 e6e6467 Dawsh 2y ago >(
269 e6e6467 Dawsh 2y ago (
270 e6e6467 Dawsh 2y ago { className, hideIcon = false, payload, verticalAlign = "bottom", nameKey },
271 e6e6467 Dawsh 2y ago ref
272 e6e6467 Dawsh 2y ago ) => {
273 e6e6467 Dawsh 2y ago const { config } = useChart()
274 e6e6467 Dawsh 2y ago
275 e6e6467 Dawsh 2y ago if (!payload?.length) {
276 e6e6467 Dawsh 2y ago return null
277 e6e6467 Dawsh 2y ago }
278 e6e6467 Dawsh 2y ago
279 e6e6467 Dawsh 2y ago return (
280 e6e6467 Dawsh 2y ago <div
281 e6e6467 Dawsh 2y ago ref={ref}
282 e6e6467 Dawsh 2y ago className={cn(
283 e6e6467 Dawsh 2y ago "flex items-center justify-center gap-4",
284 e6e6467 Dawsh 2y ago verticalAlign === "top" ? "pb-3" : "pt-3",
285 e6e6467 Dawsh 2y ago className
286 e6e6467 Dawsh 2y ago )}
287 e6e6467 Dawsh 2y ago >
288 e6e6467 Dawsh 2y ago {payload.map((item) => {
289 e6e6467 Dawsh 2y ago const key = `${nameKey || item.dataKey || "value"}`
290 e6e6467 Dawsh 2y ago const itemConfig = getPayloadConfigFromPayload(config, item, key)
291 e6e6467 Dawsh 2y ago
292 e6e6467 Dawsh 2y ago return (
293 e6e6467 Dawsh 2y ago <div
294 e6e6467 Dawsh 2y ago key={item.value}
295 e6e6467 Dawsh 2y ago className={cn(
296 e6e6467 Dawsh 2y ago "flex items-center gap-1.5 [&>svg]:h-3 [&>svg]:w-3 [&>svg]:text-muted-foreground"
297 e6e6467 Dawsh 2y ago )}
298 e6e6467 Dawsh 2y ago >
299 e6e6467 Dawsh 2y ago {itemConfig?.icon && !hideIcon ? (
300 e6e6467 Dawsh 2y ago <itemConfig.icon />
301 e6e6467 Dawsh 2y ago ) : (
302 e6e6467 Dawsh 2y ago <div
303 e6e6467 Dawsh 2y ago className="h-2 w-2 shrink-0 rounded-[2px]"
304 e6e6467 Dawsh 2y ago style={{
305 e6e6467 Dawsh 2y ago backgroundColor: item.color,
306 e6e6467 Dawsh 2y ago }}
307 e6e6467 Dawsh 2y ago />
308 e6e6467 Dawsh 2y ago )}
309 e6e6467 Dawsh 2y ago {itemConfig?.label}
310 e6e6467 Dawsh 2y ago </div>
311 e6e6467 Dawsh 2y ago )
312 e6e6467 Dawsh 2y ago })}
313 e6e6467 Dawsh 2y ago </div>
314 e6e6467 Dawsh 2y ago )
315 e6e6467 Dawsh 2y ago }
316 e6e6467 Dawsh 2y ago )
317 e6e6467 Dawsh 2y ago ChartLegendContent.displayName = "ChartLegend"
318 e6e6467 Dawsh 2y ago
319 e6e6467 Dawsh 2y ago // Helper to extract item config from a payload.
320 e6e6467 Dawsh 2y ago function getPayloadConfigFromPayload(
321 e6e6467 Dawsh 2y ago config: ChartConfig,
322 e6e6467 Dawsh 2y ago payload: unknown,
323 e6e6467 Dawsh 2y ago key: string
324 e6e6467 Dawsh 2y ago ) {
325 e6e6467 Dawsh 2y ago if (typeof payload !== "object" || payload === null) {
326 e6e6467 Dawsh 2y ago return undefined
327 e6e6467 Dawsh 2y ago }
328 e6e6467 Dawsh 2y ago
329 e6e6467 Dawsh 2y ago const payloadPayload =
330 e6e6467 Dawsh 2y ago "payload" in payload &&
331 e6e6467 Dawsh 2y ago typeof payload.payload === "object" &&
332 e6e6467 Dawsh 2y ago payload.payload !== null
333 e6e6467 Dawsh 2y ago ? payload.payload
334 e6e6467 Dawsh 2y ago : undefined
335 e6e6467 Dawsh 2y ago
336 e6e6467 Dawsh 2y ago let configLabelKey: string = key
337 e6e6467 Dawsh 2y ago
338 e6e6467 Dawsh 2y ago if (
339 e6e6467 Dawsh 2y ago key in payload &&
340 e6e6467 Dawsh 2y ago typeof payload[key as keyof typeof payload] === "string"
341 e6e6467 Dawsh 2y ago ) {
342 e6e6467 Dawsh 2y ago configLabelKey = payload[key as keyof typeof payload] as string
343 e6e6467 Dawsh 2y ago } else if (
344 e6e6467 Dawsh 2y ago payloadPayload &&
345 e6e6467 Dawsh 2y ago key in payloadPayload &&
346 e6e6467 Dawsh 2y ago typeof payloadPayload[key as keyof typeof payloadPayload] === "string"
347 e6e6467 Dawsh 2y ago ) {
348 e6e6467 Dawsh 2y ago configLabelKey = payloadPayload[
349 e6e6467 Dawsh 2y ago key as keyof typeof payloadPayload
350 e6e6467 Dawsh 2y ago ] as string
351 e6e6467 Dawsh 2y ago }
352 e6e6467 Dawsh 2y ago
353 e6e6467 Dawsh 2y ago return configLabelKey in config
354 e6e6467 Dawsh 2y ago ? config[configLabelKey]
355 e6e6467 Dawsh 2y ago : config[key as keyof typeof config]
356 e6e6467 Dawsh 2y ago }
357 e6e6467 Dawsh 2y ago
358 e6e6467 Dawsh 2y ago export {
359 e6e6467 Dawsh 2y ago ChartContainer,
360 e6e6467 Dawsh 2y ago ChartTooltip,
361 e6e6467 Dawsh 2y ago ChartTooltipContent,
362 e6e6467 Dawsh 2y ago ChartLegend,
363 e6e6467 Dawsh 2y ago ChartLegendContent,
364 e6e6467 Dawsh 2y ago ChartStyle,
365 e6e6467 Dawsh 2y ago }