'use client'; import * as React from 'react'; import * as RechartsPrimitive from 'recharts'; import type { Payload as TooltipPayloadItem, ValueType as TooltipValueType, NameType as TooltipNameType, } from 'recharts/types/component/DefaultTooltipContent'; import type { Payload as LegendPayloadItem } from 'recharts/types/component/DefaultLegendContent'; import { cn } from '../../shared/utils'; import { Alert, AlertDescription, AlertTitle } from '../alert'; import { Button } from '../button'; import { Card, CardAction, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, } from '../card'; import { Empty, EmptyAction, EmptyDescription, EmptyIcon, EmptyTitle } from '../empty'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '../select'; import { Skeleton } from '../skeleton'; import { BarChart3, RefreshCw, WifiOff } from 'lucide-react'; // 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; }; export type DashboardChartDatum = Record; export type DashboardChartSeries = { key: string; label?: React.ReactNode; type?: 'bar' | 'line' | 'area'; stackId?: string; yAxisId?: string; }; export type DashboardChartColors = string[] | Record; export type ChartPeriod = { value: string; label: string; }; export type ChartBarSize = 'sm' | 'md' | 'lg' | 'xl' | number; /** Curve interpolation type for line and area charts */ export type ChartCurveType = | 'monotone' | 'linear' | 'step' | 'stepBefore' | 'stepAfter' | 'natural' | 'basis'; type ChartValueFormatter = (value: number | string) => string; export type ChartErrorState = boolean | string | Error | React.ReactNode; export type ChartStateProps = { isLoading?: boolean; error?: ChartErrorState; onRetry?: () => void; retryLabel?: React.ReactNode; emptyTitle?: React.ReactNode; emptyDescription?: React.ReactNode; errorTitle?: React.ReactNode; errorDescription?: React.ReactNode; loadingLabel?: React.ReactNode; stateClassName?: string; }; const defaultPeriods: ChartPeriod[] = [ { value: '7d', label: '7 days' }, { value: '30d', label: '30 days' }, { value: '90d', label: '90 days' }, ]; const defaultChartColors = [ 'var(--chart-1)', 'var(--chart-2)', 'var(--chart-3)', 'var(--chart-4)', 'var(--chart-5)', 'var(--chart-6)', 'var(--chart-7)', 'var(--chart-8)', ]; const chartBarSizes: Record, number> = { sm: 8, md: 14, lg: 22, xl: 32, }; const ChartContext = React.createContext(null); export function useChart() { const context = React.useContext(ChartContext); if (!context) { throw new Error('useChart must be used within a '); } return context; } /** * Root container for Recharts-based charts with theme-aware color injection. * * @description * Wraps Recharts' `ResponsiveContainer` and injects CSS custom properties * (`--color-*`) from a `ChartConfig` object, enabling full dark-mode * support without hard-coded hex values in chart elements. * * @ai-rules * 1. NEVER pass hex colors directly to Recharts elements (e.g., `fill="#4F46E5"`). * Always use `fill="var(--color-keyName)"` referencing the injected CSS variables. * 2. This wrapper is REQUIRED to use `ChartTooltipContent` and `ChartLegendContent`. * 3. Set height via `className="h-[300px]"` on `ChartContainer`, not on Recharts components. * 4. Do NOT add another `` — it is already included inside. */ function ChartContainer({ id, className, children, config, ...props }: React.ComponentProps<'div'> & { config: ChartConfig; children: React.ComponentProps['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; } return (