"use client"; import * as React from "react"; import * as RechartsPrimitive from "recharts"; import { cn } from "@/lib/utils"; // Format: { THEME_NAME: CSS_SELECTOR } const THEMES = { light: "", dark: ".dark" } as const; export type ChartConfig = { [k in string]: { label?: React.ReactNode; icon?: React.ComponentType; } & ( | { color?: string; theme?: never } | { color?: never; theme: Record } ); }; type ChartContextProps = { config: ChartConfig; }; const ChartContext = React.createContext(null); function useChart() { const context = React.useContext(ChartContext); if (!context) { throw new Error("useChart must be used within a "); } return context; } function ChartContainer({ id, className, children, config, ...props }: React.ComponentProps<"div"> & { config: ChartConfig; children: React.ComponentProps< typeof RechartsPrimitive.ResponsiveContainer >["children"]; }) { const uniqueId = React.useId(); const chartId = `chart-${id || uniqueId.replace(/:/g, "")}`; return (
{children}
); } const ChartStyle = ({ id, config }: { id: string; config: ChartConfig }) => { const colorConfig = Object.entries(config).filter( ([, config]) => config.theme || config.color, ); if (!colorConfig.length) { return null; } const css = Object.entries(THEMES) .map( ([theme, prefix]) => ` ${prefix} [data-chart=${id}] { ${colorConfig .map(([key, itemConfig]) => { const color = itemConfig.theme?.[theme as keyof typeof itemConfig.theme] || itemConfig.color; return color ? ` --color-${key}: ${color};` : null; }) .join("\n")} } `, ) .join("\n"); return ; }; const ChartTooltip = RechartsPrimitive.Tooltip; function ChartTooltipContent({ active, payload, className, indicator = "dot", hideLabel = false, hideIndicator = false, label, labelFormatter, labelClassName, formatter, color, nameKey, labelKey, }: RechartsPrimitive.TooltipContentProps & React.ComponentProps<"div"> & { hideLabel?: boolean; hideIndicator?: boolean; indicator?: "line" | "dot" | "dashed"; nameKey?: string; labelKey?: string; }) { const { config } = useChart(); const tooltipLabel = React.useMemo(() => { if (hideLabel || !payload?.length) { return null; } const [item] = payload; const key = `${labelKey || item?.dataKey || item?.name || "value"}`; const itemConfig = getPayloadConfigFromPayload(config, item, key); const value = !labelKey && typeof label === "string" ? config[label as keyof typeof config]?.label || label : itemConfig?.label; if (labelFormatter) { return (
{labelFormatter(value, payload)}
); } if (!value) { return null; } return
{value}
; }, [ label, labelFormatter, payload, hideLabel, labelClassName, config, labelKey, ]); if (!active || !payload?.length) { return null; } const nestLabel = payload.length === 1 && indicator !== "dot"; return (
{!nestLabel ? tooltipLabel : null}
{payload.map((item, index) => { const key = `${nameKey || item.name || item.dataKey || "value"}`; const itemConfig = getPayloadConfigFromPayload(config, item, key); const indicatorColor = color || item.payload.fill || item.color; return (
svg]:text-muted-foreground flex w-full flex-wrap items-stretch gap-2 [&>svg]:h-2.5 [&>svg]:w-2.5", indicator === "dot" && "items-center", )} > {formatter && item?.value !== undefined && item.name ? ( formatter(item.value, item.name, item, index, item.payload) ) : ( <> {itemConfig?.icon ? ( ) : ( !hideIndicator && (
) )}
{nestLabel ? tooltipLabel : null} {itemConfig?.label || item.name}
{item.value && ( {item.value.toLocaleString()} )}
)}
); })}
); } const ChartLegend = RechartsPrimitive.Legend; function ChartLegendContent({ className, hideIcon = false, payload, verticalAlign = "bottom", nameKey, }: React.ComponentProps<"div"> & Pick< RechartsPrimitive.DefaultLegendContentProps, "payload" | "verticalAlign" > & { hideIcon?: boolean; nameKey?: string; }) { const { config } = useChart(); if (!payload?.length) { return null; } return (
{payload.map((item) => { const key = `${nameKey || item.dataKey || "value"}`; const itemConfig = getPayloadConfigFromPayload(config, item, key); return (
svg]:text-muted-foreground flex items-center gap-1.5 [&>svg]:h-3 [&>svg]:w-3" } > {itemConfig?.icon && !hideIcon ? ( ) : (
)} {itemConfig?.label}
); })}
); } // Helper to extract item config from a payload. function getPayloadConfigFromPayload( config: ChartConfig, payload: unknown, key: string, ) { if (typeof payload !== "object" || payload === null) { return undefined; } const payloadPayload = "payload" in payload && typeof payload.payload === "object" && payload.payload !== null ? payload.payload : undefined; let configLabelKey: string = key; if ( key in payload && typeof payload[key as keyof typeof payload] === "string" ) { configLabelKey = payload[key as keyof typeof payload] as string; } else if ( payloadPayload && key in payloadPayload && typeof payloadPayload[key as keyof typeof payloadPayload] === "string" ) { configLabelKey = payloadPayload[ key as keyof typeof payloadPayload ] as string; } return configLabelKey in config ? config[configLabelKey] : config[key as keyof typeof config]; } export { ChartContainer, ChartLegend, ChartLegendContent, ChartStyle, ChartTooltip, ChartTooltipContent, };