{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "recharts-bar-chart",
  "description": "Bar chart component",
  "dependencies": [
    "recharts",
    "motion"
  ],
  "registryDependencies": [
    "@evilcharts/recharts-chart",
    "@evilcharts/recharts-tooltip",
    "@evilcharts/recharts-legend",
    "@evilcharts/recharts-brush",
    "@evilcharts/recharts-background"
  ],
  "files": [
    {
      "path": "src/registry/charts/recharts-bar-chart.tsx",
      "content": "\"use client\";\n\nimport {\n  Bar as RechartsBar,\n  BarChart as RechartsBarChart,\n  CartesianGrid,\n  Rectangle,\n  ReferenceLine,\n  XAxis as RechartsXAxis,\n  YAxis as RechartsYAxis,\n} from \"recharts\";\nimport {\n  Children,\n  createContext,\n  isValidElement,\n  use,\n  useCallback,\n  useId,\n  useMemo,\n  useRef,\n  useState,\n  type ComponentProps,\n  type ReactNode,\n} from \"react\";\nimport {\n  type ChartConfig,\n  ChartContainer,\n  getColorsCount,\n  getLoadingData,\n  LoadingIndicator,\n} from \"@/registry/ui/recharts-chart\";\nimport {\n  ChartTooltip,\n  ChartTooltipContent,\n  type TooltipRoundness,\n  type TooltipVariant,\n} from \"@/registry/ui/recharts-tooltip\";\nimport { ChartLegend, ChartLegendContent, type ChartLegendVariant } from \"@/registry/ui/recharts-legend\";\nimport { Brush, EvilBrush, useEvilBrush, type BrushProps, type EvilBrushRange } from \"@/registry/ui/recharts-brush\";\nimport { ChartBackground, type BackgroundVariant } from \"@/registry/ui/recharts-background\";\nimport { RectRadius } from \"recharts/types/shape/Rectangle\";\nimport { motion, useReducedMotion } from \"motion/react\";\n\n// Constants\nconst DEFAULT_BAR_RADIUS = 2;\nconst LOADING_BAR_DATA_KEY = \"loading\";\nconst LOADING_ANIMATION_DURATION = 2000; // in milliseconds\nconst STACK_ID = \"evil-stacked\";\nconst BAR_GROW_DURATION = 0.5; // per-bar grow-in length, in seconds\nconst BAR_STAGGER = 0.05; // delay between consecutive bars, in seconds\nconst REVEAL_EASE: [number, number, number, number] = [0, 0.7, 0.5, 1]; // grow-in easing\n\ntype BarVariant = \"default\" | \"hatched\" | \"duotone\" | \"duotone-reverse\" | \"gradient\" | \"stripped\";\ntype StackType = \"default\" | \"stacked\" | \"percent\";\ntype BarLayout = \"vertical\" | \"horizontal\";\n\n/**\n * Order in which bars grow into view. Recharts' own bar animation is permanently\n * disabled — every bar instead grows from its baseline (bottom for vertical\n * layout, left for horizontal), and this controls the stagger sequence.\n *\n * NOTE: the grow-in is a per-frame animation, so it is heavier than a static\n * chart. `\"none\"` opts out entirely; it is also what a device with the OS\n * \"reduce motion\" preference falls back to automatically.\n */\ntype BarAnimationType = \"none\" | \"left-to-right\" | \"right-to-left\" | \"center-out\" | \"edges-in\";\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Shared context\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Shared state for every part of the chart. Lifted into <EvilBarChart /> so that\n * <Bar />, <XAxis />, <Legend />, and friends can read it without prop drilling.\n * Sub-components are composed freely — the provider is the single source of truth.\n */\ntype BarChartContextValue = {\n  config: ChartConfig; // colors + labels for every series\n  isStacked: boolean; // whether bars stack on top of each other\n  isHorizontal: boolean; // whether bars are laid out horizontally\n  isLoading: boolean; // whether the chart shows its loading skeleton\n  barRadius: number; // default corner radius each <Bar /> inherits\n  animationType: BarAnimationType; // default grow-in order each <Bar /> inherits\n  introStartedAt: number; // timestamp the chart mounted — anchors the one-shot grow-in\n  dataLength: number; // number of rows currently rendered\n  selectedDataKey: string | null; // currently selected series, or null when none\n  selectDataKey: (dataKey: string | null) => void; // sets the selected series\n  isMouseInChart: boolean; // whether the pointer is currently over the chart\n};\n\nconst BarChartContext = createContext<BarChartContextValue | null>(null);\n\n// Reads the chart context, throwing a helpful error when used outside <EvilBarChart />\nfunction useBarChart() {\n  const context = use(BarChartContext);\n\n  if (!context) {\n    throw new Error(\"Bar chart parts (<Bar />, <XAxis />, …) must be used within <EvilBarChart />\");\n  }\n\n  return context;\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Root container\n// ─────────────────────────────────────────────────────────────────────────────\n\n// Validates that every config key also exists on the data row type\ntype ValidateConfigKeys<TData, TConfig> = {\n  [K in keyof TConfig]: K extends keyof TData ? ChartConfig[string] : never;\n};\n\ntype EvilBarChartBaseProps<\n  TData extends Record<string, unknown>,\n  TConfig extends Record<string, ChartConfig[string]>,\n> = {\n  config: TConfig & ValidateConfigKeys<TData, TConfig>; // series colors + labels\n  data: TData[]; // rows rendered by the chart\n  children: ReactNode; // composed parts — <Bar />, <XAxis />, <Legend />, …\n  className?: string; // extra classes for the chart container\n  chartProps?: ComponentProps<typeof RechartsBarChart>; // escape hatch for the raw Recharts chart\n  stackType?: StackType; // how multiple bars combine\n  layout?: BarLayout; // orientation of the bars\n  barRadius?: number; // default corner radius for every <Bar />\n  animationType?: BarAnimationType; // default grow-in order for every <Bar />\n  barGap?: number; // gap between bars within the same category\n  barCategoryGap?: number; // gap between categories of bars\n  backgroundVariant?: BackgroundVariant; // background pattern drawn behind the chart\n  defaultSelectedDataKey?: string | null; // series selected on first render\n  onSelectionChange?: (selectedDataKey: string | null) => void; // fires when the selected series changes\n  isLoading?: boolean; // shows the animated loading skeleton\n  loadingBars?: number; // number of bars in the loading skeleton\n  xDataKey?: keyof TData & string; // x-axis key — only needed for the <Brush /> footer\n};\n\ntype EvilBarChartProps<\n  TData extends Record<string, unknown>,\n  TConfig extends Record<string, ChartConfig[string]>,\n> = EvilBarChartBaseProps<TData, TConfig>;\n\n/**\n * Root of the composible bar chart. Owns the data, the shared context, the\n * loading skeleton, and the optional zoom brush. Everything visual — axes,\n * grid, tooltip, legend, and the bars themselves — is composed as children,\n * so a consumer renders exactly the parts they need.\n */\nexport function EvilBarChart<\n  TData extends Record<string, unknown>,\n  TConfig extends Record<string, ChartConfig[string]>,\n>({\n  config,\n  data,\n  children,\n  className,\n  chartProps,\n  stackType = \"default\",\n  layout = \"vertical\",\n  barRadius = DEFAULT_BAR_RADIUS,\n  animationType = \"left-to-right\",\n  barGap,\n  barCategoryGap,\n  backgroundVariant,\n  defaultSelectedDataKey = null,\n  onSelectionChange,\n  isLoading = false,\n  loadingBars,\n  xDataKey,\n}: EvilBarChartProps<TData, TConfig>) {\n  const chartId = useId().replace(/:/g, \"\"); // colon-free id keeps CSS/SVG selectors valid\n  // Anchors the grow-in to a fixed moment so it plays exactly once — re-renders\n  // and Recharts' bar remounts read elapsed time from here instead of replaying.\n  // Lazy useState stamps the time once, on the initial render only.\n  const [introStartedAt] = useState(() => Date.now());\n  const [selectedDataKey, setSelectedDataKey] = useState<string | null>(defaultSelectedDataKey);\n  const [isMouseInChart, setIsMouseInChart] = useState(false);\n  const { loadingData, onShimmerExit } = useLoadingData(isLoading, loadingBars);\n  const { visibleData, brushProps } = useEvilBrush({ data });\n\n  // Brush is a <Brush /> child now (not props): pull it out of the children so\n  // it never reaches the Recharts tree, and drive the footer from its props.\n  const brush = useMemo(() => {\n    // Pull the <Brush> element out of the children (config-only, never rendered\n    // into the Recharts tree); toArray also assigns stable keys to the rest.\n    const parts = Children.toArray(children);\n    const brushEl = parts.find((child) => isValidElement(child) && child.type === Brush);\n    const bp = (isValidElement(brushEl) ? brushEl.props : {}) as BrushProps;\n    return {\n      slot: {\n        present: isValidElement(brushEl),\n        height: bp.height,\n        formatLabel: bp.formatLabel,\n        onChange: bp.onChange,\n      },\n      chartChildren: parts.filter((child) => !(isValidElement(child) && child.type === Brush)),\n    };\n  }, [children]);\n  const showBrush = brush.slot.present;\n\n  const isStacked = stackType === \"stacked\" || stackType === \"percent\";\n  const isHorizontal = layout === \"horizontal\";\n  const displayData = showBrush && !isLoading ? visibleData : data;\n\n  // Updates selection state and notifies the parent\n  const selectDataKey = useCallback(\n    (newSelectedDataKey: string | null) => {\n      setSelectedDataKey(newSelectedDataKey);\n      onSelectionChange?.(newSelectedDataKey);\n    },\n    [onSelectionChange],\n  );\n\n  const contextValue = useMemo<BarChartContextValue>(\n    () => ({\n      config,\n      isStacked,\n      isHorizontal,\n      isLoading,\n      barRadius,\n      animationType,\n      introStartedAt,\n      dataLength: displayData.length,\n      selectedDataKey,\n      selectDataKey,\n      isMouseInChart,\n    }),\n    [\n      config,\n      isStacked,\n      isHorizontal,\n      isLoading,\n      barRadius,\n      animationType,\n      introStartedAt,\n      displayData.length,\n      selectedDataKey,\n      selectDataKey,\n      isMouseInChart,\n    ],\n  );\n\n  return (\n    <BarChartContext value={contextValue}>\n      <ChartContainer\n        className={className}\n        config={config}\n        footer={\n          showBrush &&\n          !isLoading && (\n            <EvilBrush\n              data={data}\n              chartConfig={config}\n              xDataKey={xDataKey}\n              variant=\"bar\"\n              barRadius={barRadius}\n              height={brush.slot.height}\n              formatLabel={brush.slot.formatLabel}\n              stacked={isStacked}\n              skipStyle\n              className=\"mt-1\"\n              {...brushProps}\n              onChange={(range) => {\n                brushProps.onChange(range);\n                brush.slot.onChange?.(range);\n              }}\n            />\n          )\n        }\n      >\n        <LoadingIndicator isLoading={isLoading} />\n        <RechartsBarChart\n          id={chartId}\n          accessibilityLayer\n          layout={isHorizontal ? \"vertical\" : \"horizontal\"}\n          data={isLoading ? loadingData : displayData}\n          barGap={barGap}\n          barCategoryGap={barCategoryGap}\n          stackOffset={stackType === \"percent\" ? \"expand\" : undefined}\n          onMouseEnter={() => setIsMouseInChart(true)}\n          onMouseLeave={() => setIsMouseInChart(false)}\n          {...chartProps}\n        >\n          {backgroundVariant && <ChartBackground variant={backgroundVariant} />}\n          <ReferenceLine color=\"white\" />\n          {brush.chartChildren}\n          {isLoading && <LoadingBar chartId={chartId} onShimmerExit={onShimmerExit} />}\n        </RechartsBarChart>\n      </ChartContainer>\n    </BarChartContext>\n  );\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Composible parts\n// ─────────────────────────────────────────────────────────────────────────────\n\ntype BarProps = {\n  dataKey: string; // series key — must exist on the data and config\n  variant?: BarVariant; // fill style for this bar only\n  radius?: number; // corner radius — falls back to the chart default\n  animationType?: BarAnimationType; // grow-in order — falls back to the chart default\n  isClickable?: boolean; // lets this bar be selected by clicking it\n  enableHoverHighlight?: boolean; // dims this bar while another bar is hovered\n  glowing?: boolean; // applies a soft outer glow to this bar\n  bufferBar?: boolean; // renders the last data point as a hatched \"buffer\" bar\n  barProps?: ComponentProps<typeof RechartsBar>; // escape hatch for raw Recharts Bar props\n};\n\n/**\n * A single bar series. Each <Bar /> is fully self-contained: it generates its\n * own gradient/pattern definitions under a unique id, so any number of bars —\n * each with its own variant, radius, glow, and clickability — can live in one\n * chart without style collisions.\n */\nfunction Bar({\n  dataKey,\n  variant = \"default\",\n  radius,\n  animationType,\n  isClickable = false,\n  enableHoverHighlight = false,\n  glowing = false,\n  bufferBar = false,\n  barProps,\n}: BarProps) {\n  const {\n    config,\n    isStacked,\n    isHorizontal,\n    isLoading,\n    barRadius: defaultRadius,\n    animationType: defaultAnimation,\n    introStartedAt,\n    dataLength,\n    selectedDataKey,\n    selectDataKey,\n    isMouseInChart,\n  } = useBarChart();\n  const id = useId().replace(/:/g, \"\"); // unique id scopes this bar's style defs\n  // Devices set to \"reduce motion\" skip the grow-in animation entirely\n  const shouldReduceMotion = useReducedMotion();\n\n  // The root renders the skeleton bar while loading, so real bars step aside\n  if (isLoading) return null;\n\n  const resolvedRadius = radius ?? defaultRadius;\n  const isSelected = selectedDataKey === dataKey;\n\n  // The grow-in is a per-frame animation — heavier than a static chart — so\n  // `\"none\"` and the OS reduce-motion preference both opt out of it.\n  const revealType: BarAnimationType = shouldReduceMotion\n    ? \"none\"\n    : (animationType ?? defaultAnimation);\n\n  const customBarProps = {\n    id,\n    dataKey,\n    variant,\n    barRadius: resolvedRadius,\n    glowing,\n    bufferBar,\n    isClickable,\n    enableHoverHighlight,\n    isMouseInChart,\n    isHorizontal,\n    introStartedAt,\n    selectedDataKey,\n    dataLength,\n    onClick: () => {\n      if (!isClickable) return;\n      // Clicking the selected bar clears the selection, otherwise selects it\n      selectDataKey(isSelected ? null : dataKey);\n    },\n  };\n\n  return (\n    <>\n      <RechartsBar\n        dataKey={dataKey}\n        stackId={isStacked ? STACK_ID : undefined}\n        fill={`url(#${id}-colors-${dataKey})`}\n        radius={resolvedRadius}\n        // Recharts' built-in bar animation is permanently disabled — every bar\n        // instead grows in from its baseline via the staggered motion.dev shape.\n        isAnimationActive={false}\n        style={isClickable || enableHoverHighlight ? { cursor: \"pointer\" } : undefined}\n        shape={(props: unknown) => (\n          <CustomBar {...(props as BarShapeProps)} {...customBarProps} animationType={revealType} />\n        )}\n        activeBar={(props: unknown) => (\n          // The active (hovered) bar must never re-run the grow-in animation\n          <CustomBar {...(props as BarShapeProps)} {...customBarProps} animationType=\"none\" />\n        )}\n        {...barProps}\n      />\n      <defs>\n        <ColorGradient id={id} dataKey={dataKey} config={config} />\n        {variant === \"hatched\" && <HatchedPattern id={id} dataKey={dataKey} />}\n        {variant === \"duotone\" && <DuotonePattern id={id} dataKey={dataKey} config={config} />}\n        {variant === \"duotone-reverse\" && (\n          <DuotoneReversePattern id={id} dataKey={dataKey} config={config} />\n        )}\n        {variant === \"gradient\" && <GradientPattern id={id} dataKey={dataKey} />}\n        {variant === \"stripped\" && <StrippedPattern id={id} dataKey={dataKey} />}\n        {bufferBar && <BufferHatchedPattern id={id} dataKey={dataKey} />}\n        {glowing && <GlowFilter id={id} dataKey={dataKey} />}\n      </defs>\n    </>\n  );\n}\n\ntype XAxisProps = ComponentProps<typeof RechartsXAxis>;\n\n/**\n * The category axis. Ships with the chart's flat default styling and forwards\n * every Recharts XAxis prop, so `dataKey`, `tickFormatter`, etc. are passed\n * straight through. Hidden automatically while the chart is loading. Resolves\n * its axis type from the chart layout — categorical when vertical, numeric\n * when the bars run horizontally.\n */\nfunction XAxis({\n  tickLine = false,\n  axisLine = false,\n  tickMargin = 8,\n  minTickGap = 8,\n  type,\n  ...props\n}: XAxisProps) {\n  const { isLoading, isHorizontal } = useBarChart();\n\n  if (isLoading) return null;\n\n  return (\n    <RechartsXAxis\n      tickLine={tickLine}\n      axisLine={axisLine}\n      tickMargin={tickMargin}\n      minTickGap={minTickGap}\n      type={type ?? (isHorizontal ? \"number\" : \"category\")}\n      {...props}\n    />\n  );\n}\n\ntype YAxisProps = ComponentProps<typeof RechartsYAxis>;\n\n/**\n * The value axis. Forwards every Recharts YAxis prop and resolves its axis type\n * from the chart layout — numeric when vertical, categorical when the bars run\n * horizontally. Hidden automatically while the chart is loading.\n */\nfunction YAxis({\n  tickLine = false,\n  axisLine = false,\n  tickMargin = 8,\n  minTickGap = 8,\n  width = \"auto\",\n  type,\n  ...props\n}: YAxisProps) {\n  const { isLoading, isHorizontal } = useBarChart();\n\n  if (isLoading) return null;\n\n  return (\n    <RechartsYAxis\n      tickLine={tickLine}\n      axisLine={axisLine}\n      tickMargin={tickMargin}\n      minTickGap={minTickGap}\n      width={width}\n      type={type ?? (isHorizontal ? \"category\" : \"number\")}\n      {...props}\n    />\n  );\n}\n\ntype GridProps = ComponentProps<typeof CartesianGrid>;\n\n/**\n * The background grid lines. Defaults to dashed lines aligned to the value\n * axis based on the chart layout, and forwards every Recharts CartesianGrid\n * prop for full control.\n */\nfunction Grid({ strokeDasharray = \"3 3\", vertical, horizontal, ...props }: GridProps) {\n  const { isHorizontal } = useBarChart();\n\n  return (\n    <CartesianGrid\n      strokeDasharray={strokeDasharray}\n      vertical={vertical ?? isHorizontal}\n      horizontal={horizontal ?? !isHorizontal}\n      {...props}\n    />\n  );\n}\n\ntype TooltipProps = {\n  variant?: TooltipVariant; // visual style of the tooltip surface\n  roundness?: TooltipRoundness; // border-radius of the tooltip\n  defaultIndex?: number; // data index shown by default with no hover\n};\n\n/**\n * The hover tooltip. Reads the chart's selection from context so its content\n * dims unselected series. Hidden automatically while the chart is loading.\n */\nfunction Tooltip({ variant, roundness, defaultIndex }: TooltipProps) {\n  const { isLoading, selectedDataKey } = useBarChart();\n\n  if (isLoading) return null;\n\n  return (\n    <ChartTooltip\n      cursor={false}\n      defaultIndex={defaultIndex}\n      content={\n        <ChartTooltipContent selected={selectedDataKey} roundness={roundness} variant={variant} />\n      }\n    />\n  );\n}\n\ntype LegendProps = {\n  variant?: ChartLegendVariant; // visual style of the legend indicators\n  align?: \"left\" | \"center\" | \"right\"; // horizontal placement\n  verticalAlign?: \"top\" | \"middle\" | \"bottom\"; // vertical placement\n  isClickable?: boolean; // lets each entry toggle selection of its series\n};\n\n/**\n * The series legend. When `isClickable` is set, each entry toggles selection of\n * its series, driving the shared selection state read by every <Bar />.\n */\nfunction Legend({\n  variant,\n  align = \"right\",\n  verticalAlign = \"top\",\n  isClickable = false,\n}: LegendProps) {\n  const { selectedDataKey, selectDataKey } = useBarChart();\n\n  return (\n    <ChartLegend\n      verticalAlign={verticalAlign}\n      align={align}\n      content={\n        <ChartLegendContent\n          selected={selectedDataKey}\n          onSelectChange={selectDataKey}\n          isClickable={isClickable}\n          variant={variant}\n        />\n      }\n    />\n  );\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Custom bar shape\n// ─────────────────────────────────────────────────────────────────────────────\n\n// Raw geometry Recharts hands to a custom bar shape\ntype BarShapeProps = {\n  x?: number;\n  y?: number;\n  width?: number;\n  height?: number;\n  fill?: string;\n  fillOpacity?: number;\n  dataKey?: string;\n  index?: number;\n  [key: string]: unknown;\n};\n\n// Per-series config the <Bar /> threads into every CustomBar render\ntype CustomBarProps = {\n  id: string;\n  dataKey: string;\n  variant: BarVariant;\n  barRadius: number;\n  glowing?: boolean;\n  bufferBar?: boolean;\n  isClickable?: boolean;\n  enableHoverHighlight?: boolean;\n  isMouseInChart?: boolean;\n  isHorizontal?: boolean;\n  animationType?: BarAnimationType;\n  introStartedAt?: number;\n  selectedDataKey?: string | null;\n  isActive?: boolean;\n  dataLength?: number;\n  onClick?: () => void;\n} & BarShapeProps;\n\n/**\n * Custom bar shape. Renders the visible bar painted by the owning <Bar />'s\n * variant pattern, with an invisible full-height rectangle behind it to keep\n * the whole column hoverable and clickable.\n */\nconst CustomBar = (props: CustomBarProps) => {\n  const {\n    x = 0,\n    y = 0,\n    width = 0,\n    height = 0,\n    id,\n    dataKey,\n    variant,\n    barRadius,\n    glowing,\n    bufferBar,\n    isClickable,\n    enableHoverHighlight,\n    isMouseInChart,\n    isHorizontal = false,\n    animationType = \"none\",\n    introStartedAt = 0,\n    selectedDataKey,\n    isActive,\n    dataLength = 0,\n    onClick,\n  } = props;\n\n  const index = typeof props.index === \"number\" ? props.index : -1;\n  const isLastBar = bufferBar && dataLength > 0 && index === dataLength - 1;\n  const isStripped = variant === \"stripped\";\n  const grow = getBarGrowAnimation(animationType, index, dataLength, isHorizontal, introStartedAt);\n\n  const fill = isLastBar\n    ? `url(#${id}-buffer-hatched-${dataKey})`\n    : getVariantFill(variant, id, dataKey);\n  const filter = glowing ? `url(#${id}-bar-glow-${dataKey})` : undefined;\n\n  const fillOpacity = getBarOpacity({\n    isClickable,\n    selectedDataKey,\n    dataKey,\n    enableHoverHighlight,\n    isMouseInChart,\n    isActive,\n  });\n  const cursorStyle = isClickable || enableHoverHighlight ? { cursor: \"pointer\" } : undefined;\n\n  // Stripped bars round only their top corners; every other variant rounds all four\n  const radius: RectRadius = isStripped ? [barRadius, barRadius, 0, 0] : barRadius;\n\n  // The visible, painted bar — plus the stripped variant's solid top strip\n  const visibleBar = (\n    <>\n      <Rectangle\n        x={x}\n        y={y}\n        width={width}\n        opacity={fillOpacity}\n        height={Math.max(0, height - 3)}\n        radius={radius}\n        fill={fill}\n        filter={filter}\n        stroke={isLastBar ? `url(#${id}-colors-${dataKey})` : undefined}\n        strokeWidth={isLastBar ? 1 : undefined}\n      />\n      {isStripped && (\n        <Rectangle\n          x={x}\n          y={y - 4}\n          width={width}\n          height={2}\n          radius={1}\n          fill={`url(#${id}-colors-${dataKey})`}\n        />\n      )}\n    </>\n  );\n\n  return (\n    <g style={cursorStyle} onClick={onClick}>\n      {/* Full-height invisible rect keeps the whole column hoverable/clickable */}\n      <Rectangle {...props} fill=\"transparent\" />\n      {/* The painted bar grows in from its baseline; the hit rect above stays put */}\n      {grow ? (\n        <motion.g\n          initial={grow.initial}\n          animate={grow.animate}\n          transition={grow.transition}\n          style={grow.style}\n        >\n          {visibleBar}\n        </motion.g>\n      ) : (\n        visibleBar\n      )}\n    </g>\n  );\n};\n\n/**\n * Builds the motion.dev grow-in animation for a single bar, or returns `null`\n * when the bar should render statically (`\"none\"`, reduced motion, an unknown\n * index, or — crucially — once the bar has already finished growing).\n *\n * Every bar grows from its baseline — `scaleY` from the bottom for vertical\n * layout, `scaleX` from the left for horizontal — and `animationType` decides\n * the stagger order, so the chart fills in one bar at a time.\n *\n * The intro is anchored to `introStartedAt` (stamped once when the chart\n * mounts) rather than to component mount. Recharts remounts every bar whenever\n * the chart re-renders — e.g. on hover-highlight — so a mount-based animation\n * would replay endlessly. Reading elapsed time instead makes it a true\n * one-shot: a bar past its window renders static, and a bar caught mid-grow\n * resumes from the progress it should already be at.\n */\nconst getBarGrowAnimation = (\n  animationType: BarAnimationType,\n  index: number,\n  dataLength: number,\n  isHorizontal: boolean,\n  introStartedAt: number,\n) => {\n  if (animationType === \"none\" || index < 0 || dataLength <= 0) return null;\n\n  const lastIndex = dataLength - 1;\n  const center = lastIndex / 2;\n\n  // How many bars this one waits behind before it starts growing\n  let step: number;\n  switch (animationType) {\n    case \"right-to-left\":\n      step = lastIndex - index;\n      break;\n    case \"center-out\":\n      step = Math.abs(index - center);\n      break;\n    case \"edges-in\":\n      step = center - Math.abs(index - center);\n      break;\n    default: // left-to-right\n      step = index;\n  }\n\n  const startMs = step * BAR_STAGGER * 1000;\n  const durationMs = BAR_GROW_DURATION * 1000;\n  const endMs = startMs + durationMs;\n  const elapsed = Date.now() - introStartedAt;\n\n  // Already finished — render static so re-renders/remounts can't replay it\n  if (elapsed >= endMs) return null;\n\n  // Resume from wherever this bar should already be: 0 before it starts,\n  // partway through if a remount caught it mid-grow.\n  const from = elapsed <= startMs ? 0 : (elapsed - startMs) / durationMs;\n  const transition = {\n    duration: (endMs - Math.max(elapsed, startMs)) / 1000,\n    ease: REVEAL_EASE,\n    delay: Math.max(0, startMs - elapsed) / 1000,\n  };\n\n  // Horizontal bars grow rightward from the left edge, vertical from the bottom\n  return isHorizontal\n    ? { initial: { scaleX: from }, animate: { scaleX: 1 }, transition, style: { originX: 0 } }\n    : { initial: { scaleY: from }, animate: { scaleY: 1 }, transition, style: { originY: 1 } };\n};\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Selection + fill helpers\n// ─────────────────────────────────────────────────────────────────────────────\n\n// Resolves the SVG paint reference for a bar's fill based on its variant\nconst getVariantFill = (variant: BarVariant, id: string, dataKey: string): string => {\n  switch (variant) {\n    case \"hatched\":\n      return `url(#${id}-hatched-${dataKey})`;\n    case \"duotone\":\n      return `url(#${id}-duotone-${dataKey})`;\n    case \"duotone-reverse\":\n      return `url(#${id}-duotone-reverse-${dataKey})`;\n    case \"gradient\":\n      return `url(#${id}-gradient-${dataKey})`;\n    case \"stripped\":\n      return `url(#${id}-stripped-${dataKey})`;\n    default:\n      return `url(#${id}-colors-${dataKey})`;\n  }\n};\n\n// Computes bar opacity from the click selection and hover-highlight state\nconst getBarOpacity = ({\n  isClickable,\n  selectedDataKey,\n  dataKey,\n  enableHoverHighlight,\n  isMouseInChart,\n  isActive,\n}: {\n  isClickable?: boolean;\n  selectedDataKey?: string | null;\n  dataKey: string;\n  enableHoverHighlight?: boolean;\n  isMouseInChart?: boolean;\n  isActive?: boolean;\n}) => {\n  const isSelectedDataKey = selectedDataKey === null || selectedDataKey === dataKey;\n  const clickOpacity = isClickable && selectedDataKey !== null ? (isSelectedDataKey ? 1 : 0.15) : 1;\n\n  // While hovering, the hovered bar keeps its click opacity and the rest dim further\n  if (enableHoverHighlight && isMouseInChart) {\n    return isActive ? clickOpacity : clickOpacity * 0.3;\n  }\n\n  return clickOpacity;\n};\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Style definitions — one set per <Bar />, scoped to its unique id\n// ─────────────────────────────────────────────────────────────────────────────\n\ntype StyleProps = {\n  id: string; // unique id of the owning <Bar />\n  dataKey: string; // series key the colors belong to\n};\n\n/**\n * Vertical top-to-bottom color gradient for a series. Always rendered — every\n * fill variant and the buffer-bar stroke paint from this single gradient.\n */\nconst ColorGradient = ({ id, dataKey, config }: StyleProps & { config: ChartConfig }) => {\n  const colorsCount = getColorsCount(config[dataKey] ?? {});\n\n  return (\n    <linearGradient id={`${id}-colors-${dataKey}`} x1=\"0\" y1=\"0\" x2=\"0\" y2=\"1\">\n      {colorsCount === 1 ? (\n        <>\n          <stop offset=\"0%\" stopColor={`var(--color-${dataKey}-0)`} />\n          <stop offset=\"100%\" stopColor={`var(--color-${dataKey}-0)`} />\n        </>\n      ) : (\n        Array.from({ length: colorsCount }, (_, index) => {\n          const offset = `${(index / (colorsCount - 1)) * 100}%`;\n          return (\n            <stop\n              key={offset}\n              offset={offset}\n              stopColor={`var(--color-${dataKey}-${index}, var(--color-${dataKey}-0))`}\n            />\n          );\n        })\n      )}\n    </linearGradient>\n  );\n};\n\n/** Diagonal hatched-stripe fill, masked from the series color gradient. */\nconst HatchedPattern = ({ id, dataKey }: StyleProps) => {\n  return (\n    <>\n      <pattern\n        id={`${id}-hatched-mask-pattern`}\n        x=\"0\"\n        y=\"0\"\n        width=\"5\"\n        height=\"5\"\n        patternUnits=\"userSpaceOnUse\"\n        patternTransform=\"rotate(-45)\"\n      >\n        <rect width=\"5\" height=\"5\" fill=\"white\" fillOpacity={0.3} />\n        <rect width=\"1.5\" height=\"5\" fill=\"white\" fillOpacity={1} />\n      </pattern>\n      <mask id={`${id}-hatched-mask-${dataKey}`}>\n        <rect width=\"100%\" height=\"100%\" fill={`url(#${id}-hatched-mask-pattern)`} />\n      </mask>\n      <pattern\n        id={`${id}-hatched-${dataKey}`}\n        patternUnits=\"userSpaceOnUse\"\n        width=\"100%\"\n        height=\"100%\"\n      >\n        <rect\n          width=\"100%\"\n          height=\"100%\"\n          fill={`url(#${id}-colors-${dataKey})`}\n          mask={`url(#${id}-hatched-mask-${dataKey})`}\n        />\n      </pattern>\n    </>\n  );\n};\n\n/** Hatched diagonal lines with no background fill, used for the buffer bar. */\nconst BufferHatchedPattern = ({ id, dataKey }: StyleProps) => {\n  return (\n    <>\n      <pattern\n        id={`${id}-buffer-hatched-mask-pattern`}\n        x=\"0\"\n        y=\"0\"\n        width=\"5\"\n        height=\"5\"\n        patternUnits=\"userSpaceOnUse\"\n        patternTransform=\"rotate(-45)\"\n      >\n        <rect width=\"5\" height=\"5\" fill=\"black\" fillOpacity={0} />\n        <rect width=\"1\" height=\"5\" fill=\"white\" fillOpacity={1} />\n      </pattern>\n      <mask id={`${id}-buffer-hatched-mask-${dataKey}`}>\n        <rect width=\"100%\" height=\"100%\" fill={`url(#${id}-buffer-hatched-mask-pattern)`} />\n      </mask>\n      <pattern\n        id={`${id}-buffer-hatched-${dataKey}`}\n        patternUnits=\"userSpaceOnUse\"\n        width=\"100%\"\n        height=\"100%\"\n      >\n        <rect\n          width=\"100%\"\n          height=\"100%\"\n          fill={`url(#${id}-colors-${dataKey})`}\n          mask={`url(#${id}-buffer-hatched-mask-${dataKey})`}\n        />\n      </pattern>\n    </>\n  );\n};\n\n/** Two-tone fill — a half-faded, half-solid split applied per bar bounding box. */\nconst DuotonePattern = ({ id, dataKey, config }: StyleProps & { config: ChartConfig }) => {\n  const colorsCount = getColorsCount(config[dataKey] ?? {});\n\n  return (\n    <>\n      <linearGradient\n        id={`${id}-duotone-mask-gradient-${dataKey}`}\n        gradientUnits=\"objectBoundingBox\"\n        x1=\"0\"\n        y1=\"0\"\n        x2=\"1\"\n        y2=\"0\"\n      >\n        <stop offset=\"50%\" stopColor=\"white\" stopOpacity={0.4} />\n        <stop offset=\"50%\" stopColor=\"white\" stopOpacity={1} />\n      </linearGradient>\n      <linearGradient\n        id={`${id}-duotone-colors-${dataKey}`}\n        gradientUnits=\"objectBoundingBox\"\n        x1=\"0\"\n        y1=\"0\"\n        x2=\"0\"\n        y2=\"1\"\n      >\n        {colorsCount === 1 ? (\n          <>\n            <stop offset=\"0%\" stopColor={`var(--color-${dataKey}-0)`} />\n            <stop offset=\"100%\" stopColor={`var(--color-${dataKey}-0)`} />\n          </>\n        ) : (\n          Array.from({ length: colorsCount }, (_, index) => {\n            const offset = `${(index / (colorsCount - 1)) * 100}%`;\n            return (\n              <stop\n                key={offset}\n                offset={offset}\n                stopColor={`var(--color-${dataKey}-${index}, var(--color-${dataKey}-0))`}\n              />\n            );\n          })\n        )}\n      </linearGradient>\n      <mask id={`${id}-duotone-mask-${dataKey}`} maskContentUnits=\"objectBoundingBox\">\n        <rect\n          x=\"0\"\n          y=\"0\"\n          width=\"1\"\n          height=\"1\"\n          fill={`url(#${id}-duotone-mask-gradient-${dataKey})`}\n        />\n      </mask>\n      <pattern\n        id={`${id}-duotone-${dataKey}`}\n        patternUnits=\"objectBoundingBox\"\n        patternContentUnits=\"objectBoundingBox\"\n        width=\"1\"\n        height=\"1\"\n      >\n        <rect\n          x=\"0\"\n          y=\"0\"\n          width=\"1\"\n          height=\"1\"\n          fill={`url(#${id}-duotone-colors-${dataKey})`}\n          mask={`url(#${id}-duotone-mask-${dataKey})`}\n        />\n      </pattern>\n    </>\n  );\n};\n\n/** Two-tone fill with the solid and faded halves reversed from `duotone`. */\nconst DuotoneReversePattern = ({ id, dataKey, config }: StyleProps & { config: ChartConfig }) => {\n  const colorsCount = getColorsCount(config[dataKey] ?? {});\n\n  return (\n    <>\n      <linearGradient\n        id={`${id}-duotone-reverse-mask-gradient-${dataKey}`}\n        gradientUnits=\"objectBoundingBox\"\n        x1=\"0\"\n        y1=\"0\"\n        x2=\"1\"\n        y2=\"0\"\n      >\n        <stop offset=\"50%\" stopColor=\"white\" stopOpacity={1} />\n        <stop offset=\"50%\" stopColor=\"white\" stopOpacity={0.4} />\n      </linearGradient>\n      <linearGradient\n        id={`${id}-duotone-reverse-colors-${dataKey}`}\n        gradientUnits=\"objectBoundingBox\"\n        x1=\"0\"\n        y1=\"0\"\n        x2=\"0\"\n        y2=\"1\"\n      >\n        {colorsCount === 1 ? (\n          <>\n            <stop offset=\"0%\" stopColor={`var(--color-${dataKey}-0)`} />\n            <stop offset=\"100%\" stopColor={`var(--color-${dataKey}-0)`} />\n          </>\n        ) : (\n          Array.from({ length: colorsCount }, (_, index) => {\n            const offset = `${(index / (colorsCount - 1)) * 100}%`;\n            return (\n              <stop\n                key={offset}\n                offset={offset}\n                stopColor={`var(--color-${dataKey}-${index}, var(--color-${dataKey}-0))`}\n              />\n            );\n          })\n        )}\n      </linearGradient>\n      <mask id={`${id}-duotone-reverse-mask-${dataKey}`} maskContentUnits=\"objectBoundingBox\">\n        <rect\n          x=\"0\"\n          y=\"0\"\n          width=\"1\"\n          height=\"1\"\n          fill={`url(#${id}-duotone-reverse-mask-gradient-${dataKey})`}\n        />\n      </mask>\n      <pattern\n        id={`${id}-duotone-reverse-${dataKey}`}\n        patternUnits=\"objectBoundingBox\"\n        patternContentUnits=\"objectBoundingBox\"\n        width=\"1\"\n        height=\"1\"\n      >\n        <rect\n          x=\"0\"\n          y=\"0\"\n          width=\"1\"\n          height=\"1\"\n          fill={`url(#${id}-duotone-reverse-colors-${dataKey})`}\n          mask={`url(#${id}-duotone-reverse-mask-${dataKey})`}\n        />\n      </pattern>\n    </>\n  );\n};\n\n/** Gradient fill that fades the series color from solid at the top to clear. */\nconst GradientPattern = ({ id, dataKey }: StyleProps) => {\n  return (\n    <>\n      <linearGradient id={`${id}-gradient-mask-gradient`} x1=\"0\" y1=\"0\" x2=\"0\" y2=\"1\">\n        <stop offset=\"20%\" stopColor=\"white\" stopOpacity={1} />\n        <stop offset=\"90%\" stopColor=\"white\" stopOpacity={0} />\n      </linearGradient>\n      <mask id={`${id}-gradient-mask-${dataKey}`}>\n        <rect width=\"100%\" height=\"100%\" fill={`url(#${id}-gradient-mask-gradient)`} />\n      </mask>\n      <pattern\n        id={`${id}-gradient-${dataKey}`}\n        patternUnits=\"userSpaceOnUse\"\n        width=\"100%\"\n        height=\"100%\"\n      >\n        <rect\n          width=\"100%\"\n          height=\"100%\"\n          fill={`url(#${id}-colors-${dataKey})`}\n          mask={`url(#${id}-gradient-mask-${dataKey})`}\n        />\n      </pattern>\n    </>\n  );\n};\n\n/** Low-opacity body fill, paired with a solid top strip drawn by CustomBar. */\nconst StrippedPattern = ({ id, dataKey }: StyleProps) => {\n  return (\n    <>\n      <linearGradient id={`${id}-stripped-mask-gradient`} x1=\"0\" y1=\"0\" x2=\"0\" y2=\"1\">\n        <stop offset=\"0%\" stopColor=\"white\" stopOpacity={0.2} />\n        <stop offset=\"100%\" stopColor=\"white\" stopOpacity={0.2} />\n      </linearGradient>\n      <mask id={`${id}-stripped-mask-${dataKey}`}>\n        <rect width=\"100%\" height=\"100%\" fill={`url(#${id}-stripped-mask-gradient)`} />\n      </mask>\n      <pattern\n        id={`${id}-stripped-${dataKey}`}\n        patternUnits=\"userSpaceOnUse\"\n        width=\"100%\"\n        height=\"100%\"\n      >\n        <rect\n          width=\"100%\"\n          height=\"100%\"\n          fill={`url(#${id}-colors-${dataKey})`}\n          mask={`url(#${id}-stripped-mask-${dataKey})`}\n        />\n      </pattern>\n    </>\n  );\n};\n\n/** Soft outer-glow filter applied to a glowing bar. */\nconst GlowFilter = ({ id, dataKey }: StyleProps) => {\n  return (\n    <filter id={`${id}-bar-glow-${dataKey}`} x=\"-100%\" y=\"-100%\" width=\"300%\" height=\"300%\">\n      <feGaussianBlur in=\"SourceGraphic\" stdDeviation=\"8\" result=\"blur\" />\n      <feColorMatrix\n        in=\"blur\"\n        type=\"matrix\"\n        values=\"1 0 0 0 0\n                0 1 0 0 0\n                0 0 1 0 0\n                0 0 0 0.5 0\"\n        result=\"glow\"\n      />\n      <feMerge>\n        <feMergeNode in=\"glow\" />\n        <feMergeNode in=\"SourceGraphic\" />\n      </feMerge>\n    </filter>\n  );\n};\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Loading skeleton\n// ─────────────────────────────────────────────────────────────────────────────\n\n// Builds bell-curve eased gradient stops for the loading shimmer\nconst generateEasedGradientStops = (\n  steps: number = 17,\n  minOpacity: number = 0.05,\n  maxOpacity: number = 0.9,\n) => {\n  return Array.from({ length: steps }, (_, i) => {\n    const t = i / (steps - 1); // 0 to 1\n    // Sine-based bell curve easing: peaks at center (t=0.5), smooth falloff at edges\n    const eased = Math.sin(t * Math.PI) ** 2;\n    const opacity = minOpacity + eased * (maxOpacity - minOpacity);\n    return { offset: `${(t * 100).toFixed(0)}%`, opacity: Number(opacity.toFixed(3)) };\n  });\n};\n\n/**\n * Hook to manage loading data with pixel-perfect shimmer synchronization.\n *\n * Uses motion.dev's onUpdate callback to ensure chart data is only regenerated\n * when the shimmer has completely exited the visible area. This eliminates\n * timing drift issues from setTimeout/setInterval.\n */\nexport function useLoadingData(isLoading: boolean, loadingBars: number = 12) {\n  const [loadingDataKey, setLoadingDataKey] = useState(false);\n\n  // Callback fired by motion.dev when the shimmer exits the visible area\n  const onShimmerExit = useCallback(() => {\n    if (isLoading) {\n      setLoadingDataKey((prev) => !prev);\n    }\n  }, [isLoading]);\n\n  const loadingData = useMemo(\n    () => getLoadingData(loadingBars, 20, 80),\n    // loadingDataKey toggle triggers re-computation when the shimmer exits\n    // eslint-disable-next-line react-hooks/exhaustive-deps\n    [loadingBars, loadingDataKey],\n  );\n\n  return { loadingData, onShimmerExit };\n}\n\n/**\n * The skeleton bar shown while the chart is loading. Rendered by the root in\n * place of the real bars, paired with its own masked shimmer pattern.\n */\nconst LoadingBar = ({ chartId, onShimmerExit }: { chartId: string; onShimmerExit: () => void }) => {\n  return (\n    <>\n      <RechartsBar\n        dataKey={LOADING_BAR_DATA_KEY}\n        fill=\"currentColor\"\n        fillOpacity={0.15}\n        radius={DEFAULT_BAR_RADIUS}\n        isAnimationActive={false}\n        legendType=\"none\"\n        style={{ mask: `url(#${chartId}-loading-mask)` }}\n      />\n      <defs>\n        <LoadingBarPattern chartId={chartId} onShimmerExit={onShimmerExit} />\n      </defs>\n    </>\n  );\n};\n\n/**\n * Animated shimmer pattern for the loading skeleton.\n *\n * The visible chart area is normalized to 0-1, the shimmer gradient has width 1,\n * and the pattern is 3x wide so the shimmer has buffer on both sides. The motion\n * rect travels x from -1 to 2; onShimmerExit fires as it crosses x=1, letting the\n * data swap happen while the shimmer is off-screen for a seamless loop.\n */\nconst LoadingBarPattern = ({\n  chartId,\n  onShimmerExit,\n}: {\n  chartId: string;\n  onShimmerExit: () => void;\n}) => {\n  const gradientStops = generateEasedGradientStops();\n\n  // 1 (left buffer) + 1 (visible) + 1 (right buffer)\n  const patternWidth = 3;\n  const startX = -1;\n  const endX = 2;\n\n  // Tracks the last x value to detect the exit threshold crossing\n  const lastXRef = useRef(startX);\n\n  return (\n    <>\n      <linearGradient id={`${chartId}-loading-mask-gradient`} x1=\"0\" y1=\"0\" x2=\"1\" y2=\"0\">\n        {gradientStops.map(({ offset, opacity }) => (\n          <stop key={offset} offset={offset} stopColor=\"white\" stopOpacity={opacity} />\n        ))}\n      </linearGradient>\n      <pattern\n        id={`${chartId}-loading-mask-pattern`}\n        patternUnits=\"objectBoundingBox\"\n        patternContentUnits=\"objectBoundingBox\"\n        patternTransform=\"rotate(25)\"\n        width={patternWidth}\n        height=\"1\"\n        x=\"0\"\n        y=\"0\"\n      >\n        <motion.rect\n          y=\"0\"\n          width=\"1\"\n          height=\"1\"\n          fill={`url(#${chartId}-loading-mask-gradient)`}\n          initial={{ x: startX }}\n          animate={{ x: endX }}\n          transition={{\n            duration: LOADING_ANIMATION_DURATION / 1000,\n            ease: \"linear\",\n            repeat: Infinity,\n            repeatType: \"loop\",\n          }}\n          onUpdate={(latest) => {\n            const xValue = typeof latest.x === \"number\" ? latest.x : startX;\n            const lastX = lastXRef.current;\n\n            // Fire once per loop, when the shimmer fully exits the visible area\n            if (xValue >= 1 && lastX < 1) {\n              onShimmerExit();\n            }\n\n            lastXRef.current = xValue;\n          }}\n        />\n      </pattern>\n      <mask id={`${chartId}-loading-mask`} maskUnits=\"userSpaceOnUse\">\n        <rect width=\"100%\" height=\"100%\" fill={`url(#${chartId}-loading-mask-pattern)`} />\n      </mask>\n    </>\n  );\n};\n\n// Compound API: every part hangs off the root as a static member, so a consumer\n// writes <EvilBarChart.Bar/>, <EvilBarChart.Tooltip/>, … from a single import\n// — no colliding named marker exports when several charts share one file.\nEvilBarChart.Bar = Bar;\nEvilBarChart.XAxis = XAxis;\nEvilBarChart.YAxis = YAxis;\nEvilBarChart.Grid = Grid;\nEvilBarChart.Tooltip = Tooltip;\nEvilBarChart.Legend = Legend;\nEvilBarChart.Brush = Brush;\n",
      "type": "registry:component",
      "target": "components/evilcharts/charts/recharts-bar-chart.tsx"
    }
  ],
  "type": "registry:component"
}