{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "extends": "none",
  "name": "chart",
  "title": "chart",
  "description": "chart",
  "dependencies": [
    "react-aria-components",
    "recharts",
    "tailwind-merge"
  ],
  "registryDependencies": [
    "https://intentui.com/r/use-mobile",
    "https://intentui.com/r/primitive"
  ],
  "files": [
    {
      "path": "src/components/ui/chart.tsx",
      "content": "'use client'\n\nimport { createContext, type ReactElement, use, useCallback, useId, useMemo, useState } from 'react'\nimport { ToggleButton } from 'react-aria-components/ToggleButton'\nimport {\n  ToggleButtonGroup,\n  type ToggleButtonGroupProps,\n} from 'react-aria-components/ToggleButtonGroup'\nimport type {\n  CartesianGridProps as CartesianGridPrimitiveProps,\n  CartesianGridProps,\n  LegendPayload,\n  LegendProps,\n  TooltipProps,\n  XAxisProps as XAxisPropsPrimitive,\n  YAxisProps as YAxisPrimitiveProps,\n} from 'recharts'\nimport {\n  CartesianGrid as CartesianGridPrimitive,\n  Legend as LegendPrimitive,\n  ResponsiveContainer,\n  Tooltip as TooltipPrimitive,\n  XAxis as XAxisPrimitive,\n  YAxis as YAxisPrimitive,\n} from 'recharts'\nimport type { ContentType as LegendContentType } from 'recharts/types/component/DefaultLegendContent'\nimport type {\n  NameType,\n  Props as TooltipContentProps,\n  ValueType,\n} from 'recharts/types/component/DefaultTooltipContent'\nimport type { ContentType as TooltipContentType } from 'recharts/types/component/Tooltip'\nimport { twJoin, twMerge } from 'tailwind-merge'\nimport { useIsMobile } from '@/hooks/use-mobile'\nimport { cx } from '@/lib/primitive'\n\n// #region Chart Types\ntype ChartType = 'default' | 'stacked' | 'percent'\ntype ChartLayout = 'horizontal' | 'vertical' | 'radial'\ntype IntervalType = 'preserveStartEnd' | 'equidistantPreserveStart'\n\ntype ChartConfig = {\n  [k in string]: {\n    label?: React.ReactNode\n    icon?: React.ComponentType\n  } & (\n    | { color?: ChartColorKeys | (string & {}); theme?: never }\n    | { color?: never; theme: Record<keyof typeof THEMES, string> }\n  )\n}\n\nconst CHART_COLORS = {\n  'chart-1': 'var(--chart-1)',\n  'chart-2': 'var(--chart-2)',\n  'chart-3': 'var(--chart-3)',\n  'chart-4': 'var(--chart-4)',\n  'chart-5': 'var(--chart-5)',\n} as const\n\ntype ChartColorKeys = keyof typeof CHART_COLORS | (string & {})\n\nconst DEFAULT_COLORS = ['chart-1', 'chart-2', 'chart-3', 'chart-4', 'chart-5'] as const\n\ntype ChartContextProps = {\n  config: ChartConfig\n  data?: Record<string, any>[]\n  layout: ChartLayout\n  dataKey?: string\n  selectedLegend: string | null\n  onLegendSelect: (legendItem: string | null) => void\n}\n\nconst ChartContext = createContext<ChartContextProps | null>(null)\n\nexport function useChart() {\n  const context = use(ChartContext)\n\n  if (!context) {\n    throw new Error('useChart must be used within a <Chart />')\n  }\n\n  return context\n}\n\nexport function valueToPercent(value: number) {\n  return `${(value * 100).toFixed(0)}%`\n}\n\nconst constructCategoryColors = (\n  categories: string[],\n  colors: readonly ChartColorKeys[]\n): Map<string, ChartColorKeys> => {\n  const categoryColors = new Map<string, ChartColorKeys>()\n\n  categories.forEach((category, index) => {\n    const color = colors[index % colors.length]\n    if (color !== undefined) {\n      categoryColors.set(category, color)\n    }\n  })\n\n  return categoryColors\n}\n\nconst getColorValue = (color?: string): string => {\n  if (!color) {\n    return 'var(--chart-1)'\n  }\n\n  return CHART_COLORS[color as 'chart-1'] ?? color\n}\n\nfunction getPayloadConfigFromPayload(config: ChartConfig, payload: unknown, key: string) {\n  if (typeof payload !== 'object' || payload === null) {\n    return undefined\n  }\n\n  const payloadPayload =\n    'payload' in payload && typeof payload.payload === 'object' && payload.payload !== null\n      ? payload.payload\n      : undefined\n\n  let configLabelKey: string = key\n\n  if (key in payload && typeof payload[key as keyof typeof payload] === 'string') {\n    configLabelKey = payload[key as keyof typeof payload] as string\n  } else if (\n    payloadPayload &&\n    key in payloadPayload &&\n    typeof payloadPayload[key as keyof typeof payloadPayload] === 'string'\n  ) {\n    configLabelKey = payloadPayload[key as keyof typeof payloadPayload] as string\n  }\n\n  return configLabelKey in config ? config[configLabelKey] : config[key as keyof typeof config]\n}\n\ninterface BaseChartProps extends React.HTMLAttributes<HTMLDivElement> {\n  containerHeight?: number\n  config: ChartConfig\n  data: Record<string, any>[]\n  dataKey: string\n  colors?: readonly (ChartColorKeys | (string & {}))[]\n  type?: ChartType\n  intervalType?: IntervalType\n  layout?: ChartLayout\n  valueFormatter?: (value: number) => string\n\n  tooltip?: TooltipContentType<ValueType, NameType> | boolean\n  tooltipProps?: Omit<TooltipProps, 'content'> & {\n    hideLabel?: boolean\n    labelSeparator?: boolean\n    hideIndicator?: boolean\n    indicator?: 'line' | 'dot' | 'dashed'\n    nameKey?: string\n    labelKey?: string\n  }\n\n  cartesianGridProps?: CartesianGridProps\n\n  legend?: LegendContentType | boolean\n  legendProps?: Omit<React.ComponentProps<typeof LegendPrimitive>, 'content' | 'ref'>\n\n  xAxisProps?: XAxisPropsPrimitive\n  yAxisProps?: YAxisPrimitiveProps\n\n  // XAxis\n  displayEdgeLabelsOnly?: boolean\n\n  hideGridLines?: boolean\n  hideXAxis?: boolean\n  hideYAxis?: boolean\n}\n\nconst Chart = ({\n  id,\n  className,\n  children,\n  config,\n  data,\n  dataKey,\n  ref,\n  layout = 'horizontal',\n  containerHeight,\n  ...props\n}: Omit<React.ComponentProps<'div'>, 'children'> &\n  Pick<ChartContextProps, 'data' | 'dataKey'> & {\n    config: ChartConfig\n    containerHeight?: number\n    layout?: ChartLayout\n    children: ReactElement | ((props: ChartContextProps) => ReactElement)\n  }) => {\n  const isMobile = useIsMobile()\n  const uniqueId = useId()\n  const chartId = useMemo(() => `chart-${id || uniqueId.replace(/:/g, '')}`, [id, uniqueId])\n\n  const [selectedLegend, setSelectedLegend] = useState<string | null>(null)\n\n  const onLegendSelect = useCallback((legendItem: string | null) => {\n    setSelectedLegend(legendItem)\n  }, [])\n\n  const _data = data ?? []\n  const _dataKey = dataKey ?? 'value'\n\n  const value = useMemo(\n    () => ({\n      config,\n      selectedLegend,\n      onLegendSelect,\n      data: _data,\n      dataKey: _dataKey,\n      layout,\n    }),\n    [config, selectedLegend, onLegendSelect, _data, _dataKey, layout]\n  )\n\n  return (\n    <ChartContext value={value}>\n      <div\n        data-chart={chartId}\n        ref={ref}\n        className={twMerge(\n          'z-20 flex w-full justify-center text-xs',\n          \"[&_.recharts-cartesian-axis-tick_text]:fill-muted-fg [&_.recharts-cartesian-grid_line[stroke='#ccc']]:stroke-border/80 [&_.recharts-curve.recharts-tooltip-cursor]:stroke-border [&_.recharts-layer]:outline-hidden [&_.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-hidden [&_.recharts-surface]:outline-hidden [&_.recharts-surface_.recharts-text.recharts-cartesian-axis-tick-value]:*:fill-muted-fg\",\n          \"[&_.recharts-dot[fill='#fff']]:fill-(--line-color)\",\n          '[&_.recharts-active-dot>.recharts-dot]:stroke-fg/10',\n\n          '[&_.recharts-surface_g]:focus:outline-hidden',\n\n          className\n        )}\n        {...props}\n      >\n        <ChartStyle id={chartId} config={config} />\n        <ResponsiveContainer height={containerHeight ?? (isMobile ? 200 : 370)}>\n          {typeof children === 'function' ? children(value) : children}\n        </ResponsiveContainer>\n      </div>\n    </ChartContext>\n  )\n}\n\nconst THEMES = { light: '', dark: '.dark' } as const\nconst ChartStyle = ({ id, config }: { id: string; config: ChartConfig }) => {\n  const colorConfig = Object.entries(config).filter(([_, config]) => config.theme || config.color)\n\n  if (!colorConfig.length) {\n    return null\n  }\n\n  return (\n    <style\n      dangerouslySetInnerHTML={{\n        __html: Object.entries(THEMES)\n          .map(\n            ([theme, prefix]) => `\n${prefix} [data-chart=${id}] {\n${colorConfig\n  .map(([key, itemConfig]) => {\n    const color = itemConfig.theme?.[theme as keyof typeof itemConfig.theme] || itemConfig.color\n    return color ? `  --color-${key}: ${color};` : null\n  })\n  .join('\\n')}\n}\n`\n          )\n          .join('\\n'),\n      }}\n    />\n  )\n}\n\ntype ChartTooltipProps<TValue extends ValueType, TName extends NameType> = TooltipProps<\n  TValue,\n  TName\n>\n\nconst tooltipWrapperStyle = { outline: 'none' } as const\n\nconst cursorStyleRadial = {\n  stroke: 'var(--muted)',\n  strokeWidth: 0.1,\n  fill: 'var(--muted)',\n  fillOpacity: 0.5,\n} as const\n\nconst cursorStyleDefault = {\n  stroke: 'var(--muted)',\n  strokeWidth: 1,\n  fill: 'var(--muted)',\n  fillOpacity: 0.5,\n} as const\n\nconst ChartTooltip = (props: TooltipProps) => {\n  const { layout } = useChart()\n  const cursorStyle = layout === 'radial' ? cursorStyleRadial : cursorStyleDefault\n\n  return <TooltipPrimitive wrapperStyle={tooltipWrapperStyle} cursor={cursorStyle} {...props} />\n}\n\ntype ChartLegendProps = Omit<React.ComponentProps<typeof LegendPrimitive>, 'ref'>\n\nconst ChartLegend = (props: ChartLegendProps) => {\n  return <LegendPrimitive align=\"center\" verticalAlign=\"bottom\" {...props} />\n}\n\ninterface XAxisProps extends Omit<XAxisPropsPrimitive, 'ref'> {\n  displayEdgeLabelsOnly?: boolean\n  intervalType?: IntervalType\n}\n\nconst tickHorizontal = {\n  transform: 'translate(32, 6)',\n} as const\n\nconst XAxis = ({\n  displayEdgeLabelsOnly,\n  className,\n  intervalType = 'preserveStartEnd',\n  minTickGap = 5,\n  ...props\n}: XAxisProps) => {\n  const { dataKey, data, layout } = useChart()\n\n  const ticks =\n    displayEdgeLabelsOnly && data?.length && dataKey\n      ? [data[0]?.[dataKey], data[data.length - 1]?.[dataKey]]\n      : undefined\n\n  const tick = layout === 'horizontal' ? tickHorizontal : undefined\n  return (\n    <XAxisPrimitive\n      className={twMerge('text-muted-fg text-xs **:[text]:fill-muted-fg', className)}\n      interval={displayEdgeLabelsOnly ? 'preserveStartEnd' : intervalType}\n      tick={tick}\n      ticks={ticks}\n      tickLine={false}\n      axisLine={false}\n      minTickGap={minTickGap}\n      dataKey={layout === 'horizontal' ? dataKey : undefined}\n      {...props}\n    />\n  )\n}\n\nconst yAxisTickHorizontal = {\n  transform: 'translate(-3, 0)',\n} as const\n\nconst yAxisTickVertical = {\n  transform: 'translate(0, 0)',\n} as const\n\nconst YAxis = ({\n  className,\n  width,\n  domain = ['auto', 'auto'],\n  type,\n  ...props\n}: Omit<YAxisPrimitiveProps, 'ref'>) => {\n  const { layout, dataKey } = useChart()\n\n  const tick = layout === 'horizontal' ? yAxisTickHorizontal : yAxisTickVertical\n\n  return (\n    <YAxisPrimitive\n      className={twMerge('text-muted-fg text-xs **:[text]:fill-muted-fg', className)}\n      width={(width ?? layout === 'horizontal') ? 40 : 80}\n      domain={domain}\n      tick={tick}\n      dataKey={layout === 'horizontal' ? undefined : dataKey}\n      type={type || layout === 'horizontal' ? 'number' : 'category'}\n      interval={layout === 'horizontal' ? undefined : 'equidistantPreserveStart'}\n      axisLine={false}\n      tickLine={false}\n      {...props}\n    />\n  )\n}\n\nconst CartesianGrid = ({ className, ...props }: CartesianGridPrimitiveProps) => {\n  const { layout } = useChart()\n  return (\n    <CartesianGridPrimitive\n      className={twMerge('stroke-1 stroke-muted', className)}\n      horizontal={layout !== 'vertical'}\n      vertical={layout === 'vertical'}\n      {...props}\n    />\n  )\n}\n\nconst ChartTooltipContent = <TValue extends ValueType, TName extends NameType>({\n  payload,\n  className,\n  indicator = 'dot',\n  hideLabel = false,\n  hideIndicator = false,\n  label,\n  labelSeparator = true,\n  labelFormatter,\n  labelClassName,\n  formatter,\n  color,\n  nameKey,\n  labelKey,\n  ref,\n}: TooltipContentProps<TValue, TName> &\n  React.ComponentProps<'div'> & {\n    hideLabel?: boolean\n    labelSeparator?: boolean\n    hideIndicator?: boolean\n    indicator?: 'line' | 'dot' | 'dashed'\n    nameKey?: string\n    labelKey?: string\n  }) => {\n  const { config } = useChart()\n\n  const tooltipLabel = useMemo(() => {\n    if (hideLabel || !payload?.length) {\n      return null\n    }\n\n    const [item] = payload\n\n    if (!item) {\n      return null\n    }\n\n    const key = `${labelKey || item.dataKey || item.name || 'value'}`\n    const itemConfig = getPayloadConfigFromPayload(config, item, key)\n    const value =\n      !labelKey && typeof label === 'string'\n        ? config[label as keyof typeof config]?.label || label\n        : itemConfig?.label\n\n    if (labelFormatter) {\n      return <div className={labelClassName}>{labelFormatter(value, payload)}</div>\n    }\n\n    if (!value) {\n      return null\n    }\n\n    return <div className={labelClassName}>{value}</div>\n  }, [label, labelFormatter, payload, hideLabel, labelClassName, config, labelKey])\n\n  if (!payload?.length) {\n    return null\n  }\n\n  const nestLabel = payload.length === 1 && indicator !== 'dot'\n\n  return (\n    <div\n      ref={ref}\n      className={twMerge(\n        'grid min-w-48 items-start rounded-lg bg-overlay/70 p-3 py-2 text-overlay-fg text-xs ring ring-current/10 backdrop-blur-lg',\n        className\n      )}\n    >\n      {!hideLabel && (\n        <>\n          {!nestLabel ? <span className=\"font-medium\">{tooltipLabel}</span> : null}\n          {labelSeparator && <span aria-hidden className=\"mt-2 mb-3 block h-px w-full bg-bg/10\" />}\n        </>\n      )}\n      <div className=\"grid gap-3\">\n        {payload.map((item, index) => {\n          const key = `${nameKey || item.name || item.dataKey || 'value'}`\n          const itemConfig = getPayloadConfigFromPayload(config, item, key)\n          const indicatorColor = color || item.payload.fill || item.color\n\n          return (\n            <div\n              key={key}\n              className={twMerge(\n                'flex w-full flex-wrap items-stretch gap-2 *:[svg]:text-muted-fg',\n                indicator === 'dot' && 'items-center *:[svg]:size-2.5',\n                indicator === 'line' && '*:[svg]:h-full *:[svg]:w-2.5'\n              )}\n            >\n              {formatter && item?.value !== undefined && item.name ? (\n                formatter(item.value, item.name, item, index, item.payload)\n              ) : (\n                <>\n                  {itemConfig?.icon ? (\n                    <itemConfig.icon />\n                  ) : (\n                    !hideIndicator && (\n                      <div\n                        className={twMerge(\n                          'shrink-0 rounded-full border-(--color-border) bg-(--color-bg)',\n                          indicator === 'dot' && 'size-2.5',\n                          indicator === 'line' && 'w-1',\n                          indicator === 'dashed' &&\n                            'w-0 border-[1.5px] border-dashed bg-transparent',\n                          nestLabel && indicator === 'dashed' && 'my-0.5'\n                        )}\n                        style={\n                          {\n                            '--color-bg': indicatorColor,\n                            '--color-border': indicatorColor,\n                          } as React.CSSProperties\n                        }\n                      />\n                    )\n                  )}\n                  <div\n                    className={twMerge(\n                      'flex flex-1 justify-between leading-none',\n                      nestLabel ? 'items-end' : 'items-center'\n                    )}\n                  >\n                    <div className=\"grid gap-1.5\">\n                      {nestLabel ? tooltipLabel : null}\n                      <span className=\"text-muted-fg\">{itemConfig?.label || item.name}</span>\n                    </div>\n\n                    {item.value && (\n                      <span className=\"font-medium font-mono text-fg tabular-nums\">\n                        {item.value.toString()}\n                      </span>\n                    )}\n                  </div>\n                </>\n              )}\n            </div>\n          )\n        })}\n      </div>\n    </div>\n  )\n}\n\ntype ChartLegendContentProps = ToggleButtonGroupProps &\n  Pick<LegendProps, 'align' | 'verticalAlign'> & {\n    payload?: ReadonlyArray<LegendPayload>\n    hideIcon?: boolean\n    nameKey?: string\n    ref?: React.Ref<any>\n  }\n\nconst ChartLegendContent = ({\n  className,\n  hideIcon = false,\n  payload,\n  align = 'right',\n  verticalAlign = 'bottom',\n  nameKey,\n  ref,\n}: ChartLegendContentProps) => {\n  const { config, selectedLegend, onLegendSelect } = useChart()\n\n  if (!payload?.length) {\n    return null\n  }\n\n  return (\n    <ToggleButtonGroup\n      ref={ref}\n      className={cx(\n        twJoin(\n          'flex flex-wrap items-center gap-x-1',\n          verticalAlign === 'top' ? 'pb-3' : 'pt-3',\n          align === 'right' ? 'justify-end' : align === 'left' ? 'justify-start' : 'justify-center'\n        ),\n        className\n      )}\n      selectedKeys={selectedLegend ? [selectedLegend] : undefined}\n      onSelectionChange={(v) => {\n        const key = [...v][0]?.toString() ?? null\n        onLegendSelect(key)\n      }}\n      selectionMode=\"single\"\n    >\n      {payload.map((item: LegendPayload) => {\n        const key = `${nameKey || item.dataKey || 'value'}`\n        const itemConfig = getPayloadConfigFromPayload(config, item, key)\n\n        return (\n          <ToggleButton\n            key={key}\n            id={key}\n            className={twMerge(\n              'flex items-center gap-2 rounded-sm px-2 py-1 text-muted-fg *:[svg]:-mx-0.5 *:[svg]:size-2.5 *:[svg]:shrink-0 *:[svg]:text-muted-fg',\n              'selected:bg-secondary/70 selected:text-secondary-fg',\n              'hover:bg-secondary/70 hover:text-secondary-fg'\n            )}\n            aria-label=\"Legend Item\"\n          >\n            {itemConfig?.icon && !hideIcon ? (\n              <itemConfig.icon />\n            ) : (\n              <div\n                className=\"size-2.5 rounded-full\"\n                style={{\n                  backgroundColor: item.color,\n                }}\n              />\n            )}\n            {itemConfig?.label}\n          </ToggleButton>\n        )\n      })}\n    </ToggleButtonGroup>\n  )\n}\n\nexport type {\n  BaseChartProps,\n  ChartColorKeys,\n  ChartConfig,\n  ChartLayout,\n  ChartLegendContentProps,\n  ChartLegendProps,\n  ChartTooltipProps,\n  ChartType,\n  IntervalType,\n  XAxisProps,\n}\nexport {\n  CartesianGrid,\n  CHART_COLORS,\n  Chart,\n  ChartLegend,\n  ChartLegendContent,\n  ChartTooltip,\n  ChartTooltipContent,\n  constructCategoryColors,\n  DEFAULT_COLORS,\n  getColorValue,\n  XAxis,\n  YAxis,\n}\n",
      "type": "registry:ui"
    }
  ],
  "type": "registry:ui"
}