{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "bar-chart",
  "title": "Bar Chart",
  "description": "Vertical or horizontal bar chart, grouped or stacked, with hover trace and in-progress bars.",
  "dependencies": [
    "motion",
    "recharts"
  ],
  "registryDependencies": [
    "@ikas/animated-number",
    "@ikas/chart",
    "@ikas/chart-kit",
    "@ikas/motion",
    "@ikas/theme",
    "@ikas/utils"
  ],
  "files": [
    {
      "path": "registry/ikas/bar-chart.tsx",
      "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { motion, useReducedMotion } from \"motion/react\"\nimport { Bar, BarChart as RechartsBarChart, CartesianGrid, ReferenceLine, Tooltip, XAxis, YAxis, type BarShapeProps } from \"recharts\"\n\nimport { SPRING } from \"@/lib/motion\"\nimport { cn } from \"@/lib/utils\"\nimport { ChartContainer, type ChartConfig } from \"@/components/ui/chart\"\nimport { AnimatedNumber } from \"@/components/ikas/animated-number\"\nimport {\n  ChartBackground,\n  ChartBackgroundDefs,\n  ChartLegend,\n  GlowFilter,\n  type ChartBackgroundVariant,\n  ChartLoading,\n  ChartTooltipCard,\n  compactNumberFormat,\n  defaultNumberFormat,\n  resolveSeries,\n  type ChartSeries,\n} from \"@/components/ikas/chart-kit\"\n\n/** Bars never fill their slot: 24px max leaves air between categories. */\nconst MAX_BAR = 24\n/** Rounded data-end; the baseline stays square. */\nconst RADIUS = 4\n/** Surface gap between stacked segments and grouped bars. */\nconst GAP = 2\nconst Y_AXIS_WIDTH = 52\n\ntype BarChartVariant = \"solid\" | \"hatched\" | \"duotone\" | \"gradient\"\n\ntype BarChartProps<T extends Record<string, unknown>> = {\n  data: T[]\n  /** Category key, e.g. \"month\". */\n  index: keyof T & string\n  /** One entry per bar series. Colors follow the categorical order. */\n  series: ChartSeries[]\n  /** Fill style. Solid for most charts; the others add texture without adding color. */\n  variant?: BarChartVariant\n  /** vertical = columns, horizontal = bars growing to the right (long category names). */\n  layout?: \"vertical\" | \"horizontal\"\n  /** Stack series into one bar per category. */\n  stacked?: boolean\n  /** hover-trace dims the other bars and draws a line at the hovered value. Default for a single series. */\n  highlight?: \"hover-trace\" | \"none\"\n  /** Shows a placeholder instead of the data. It pulses or shimmers like Skeleton (see SkeletonProvider). */\n  loading?: boolean\n  /** Formats values in the tooltip. Defaults to tr-TR numbers. */\n  valueFormat?: (value: number) => string\n  /** Axis tick format. Defaults to compact (12,4 B). */\n  axisFormat?: (value: number) => string\n  /** Plot height in px. */\n  height?: number\n  className?: string\n  /** Accessible summary of what the chart shows. */\n  \"aria-label\"?: string\n  /** Expressive, opt-in: the hovered bar glows in its own color. */\n  glow?: boolean\n  /** The last category is still in progress: hatched fill and \"Devam ediyor\" in the tooltip. */\n  incompleteLast?: boolean\n  /** Expressive, opt-in: subtle texture behind the plot. */\n  background?: ChartBackgroundVariant\n}\n\n/** Path with a rounded data-end and a square baseline, the dataviz bar spec. */\nfunction barPath(x: number, y: number, w: number, h: number, horizontal: boolean, rounded: boolean) {\n  if (w <= 0 || h <= 0) return \"\"\n  const r = rounded ? Math.min(RADIUS, horizontal ? h / 2 : w / 2, horizontal ? w : h) : 0\n  if (horizontal)\n    return `M${x},${y} L${x + w - r},${y} Q${x + w},${y} ${x + w},${y + r} L${x + w},${y + h - r} Q${x + w},${y + h} ${x + w - r},${y + h} L${x},${y + h} Z`\n  return `M${x},${y + h} L${x},${y + r} Q${x},${y} ${x + r},${y} L${x + w - r},${y} Q${x + w},${y} ${x + w},${y + r} L${x + w},${y + h} Z`\n}\n\nfunction FillDefs({ id, color, variant, horizontal }: { id: string; color: string; variant: BarChartVariant; horizontal: boolean }) {\n  if (variant === \"gradient\")\n    return (\n      <linearGradient id={id} x1=\"0\" y1=\"0\" x2={horizontal ? \"1\" : \"0\"} y2={horizontal ? \"0\" : \"1\"}>\n        <stop offset=\"0%\" stopColor={color} stopOpacity={horizontal ? 0.35 : 1} />\n        <stop offset=\"100%\" stopColor={color} stopOpacity={horizontal ? 1 : 0.35} />\n      </linearGradient>\n    )\n  if (variant === \"duotone\")\n    return (\n      <linearGradient id={id} x1=\"0\" y1=\"0\" x2={horizontal ? \"0\" : \"1\"} y2={horizontal ? \"1\" : \"0\"}>\n        <stop offset=\"50%\" stopColor={color} stopOpacity={1} />\n        <stop offset=\"50%\" stopColor={color} stopOpacity={0.72} />\n      </linearGradient>\n    )\n  if (variant === \"hatched\")\n    return (\n      <pattern id={id} width=\"5\" height=\"5\" patternUnits=\"userSpaceOnUse\" patternTransform=\"rotate(45)\">\n        <rect width=\"5\" height=\"5\" fill={color} fillOpacity={0.16} />\n        <line x1=\"0\" y1=\"0\" x2=\"0\" y2=\"5\" stroke={color} strokeOpacity={0.7} strokeWidth=\"1.5\" />\n      </pattern>\n    )\n  return null\n}\n\n/**\n * recharts remounts every rectangle whenever the tooltip state changes, so a\n * plain mount animation would regrow all bars on each hover. The chart records\n * when its intro has played; remounted bars after that start in place.\n */\ntype BarHoverState = {\n  dimmed: (index: number, seriesKey: string) => boolean\n  glowing: (index: number, seriesKey: string) => boolean\n  introPlayed: React.RefObject<boolean>\n}\n\nconst BarHoverContext = React.createContext<BarHoverState>({ dimmed: () => false, glowing: () => false, introPlayed: { current: false } })\n\ntype ShapeExtra = {\n  /** Fill for this bar when it is the in-progress last category. */\n  incompleteFill?: string\n  lastIndex: number\n  glowFilter?: string\n  seriesKey: string\n  seriesKeys: string[]\n  fill: string\n  horizontal: boolean\n  stacked: boolean\n  animate: boolean\n}\n\nfunction BarShape(props: BarShapeProps & ShapeExtra) {\n  const { x = 0, y = 0, width = 0, height = 0, index, payload, background, seriesKey, seriesKeys, fill, horizontal, stacked, animate, incompleteFill, lastIndex, glowFilter } = props\n  const { dimmed, glowing, introPlayed } = React.useContext(BarHoverContext)\n  const grow = animate && !introPlayed.current\n  const row = payload as Record<string, unknown>\n  // In a stack only the outermost non-empty segment gets the rounded end.\n  const outermost = !stacked || [...seriesKeys].reverse().find((k) => Number(row?.[k] ?? 0) > 0) === seriesKey\n\n  const bx = Number(x)\n  let by = Number(y)\n  let bw = Number(width)\n  let bh = Number(height)\n  if (stacked && !outermost) {\n    if (horizontal) bw -= GAP\n    else {\n      by += GAP\n      bh -= GAP\n    }\n  }\n\n  const baseline = horizontal ? Number(background?.x ?? bx) : Number(background?.y ?? 0) + Number(background?.height ?? 0)\n\n  return (\n    <motion.path\n      d={barPath(bx, by, bw, bh, horizontal, outermost)}\n      fill={incompleteFill && index === lastIndex ? incompleteFill : fill}\n      filter={glowFilter && glowing(index, seriesKey) ? glowFilter : undefined}\n      initial={grow ? (horizontal ? { scaleX: 0 } : { scaleY: 0 }) : false}\n      animate={{ scaleX: 1, scaleY: 1, opacity: dimmed(index, seriesKey) ? 0.3 : 1 }}\n      transition={{\n        scaleX: { ...SPRING, delay: index * 0.035 },\n        scaleY: { ...SPRING, delay: index * 0.035 },\n        opacity: { duration: 0.15 },\n      }}\n      style={{\n        transformBox: \"view-box\",\n        originX: horizontal ? `${baseline}px` : `${bx + bw / 2}px`,\n        originY: horizontal ? `${by + bh / 2}px` : `${baseline}px`,\n      }}\n    />\n  )\n}\n\n/** The line and value pill that follow the hovered bar. */\nfunction TraceLine({ x1 = 0, y1 = 0, x2 = 0, y2 = 0, value, horizontal, format }: { x1?: number; y1?: number; x2?: number; y2?: number; value: number; horizontal: boolean; format: (v: number) => string }) {\n  const reduce = useReducedMotion()\n  const transition = reduce ? { duration: 0 } : SPRING\n\n  if (horizontal)\n    return (\n      <motion.g initial={false} animate={{ x: x1 }} transition={transition}>\n        <line x1={0} x2={0} y1={y1} y2={y2} stroke=\"var(--foreground)\" strokeOpacity={0.35} strokeWidth={1} />\n        <foreignObject x={-40} y={y2 + 4} width={80} height={24} overflow=\"visible\">\n          <div className=\"flex justify-center\">\n            <AnimatedNumber value={value} format={format} className=\"rounded-md bg-foreground px-1.5 py-0.5 text-[11px] font-medium text-background tabular-nums shadow-overlay\" />\n          </div>\n        </foreignObject>\n      </motion.g>\n    )\n\n  return (\n    <motion.g initial={false} animate={{ y: y1 }} transition={transition}>\n      <line x1={x1} x2={x2} y1={0} y2={0} stroke=\"var(--foreground)\" strokeOpacity={0.35} strokeWidth={1} />\n      <foreignObject x={x1 - Y_AXIS_WIDTH} y={-11} width={Y_AXIS_WIDTH - 4} height={22} overflow=\"visible\">\n        <div className=\"flex h-full items-center justify-end\">\n          <AnimatedNumber value={value} format={format} className=\"rounded-md bg-foreground px-1.5 py-0.5 text-[11px] font-medium text-background tabular-nums shadow-overlay\" />\n        </div>\n      </foreignObject>\n    </motion.g>\n  )\n}\n\n/** Placeholder columns while data loads; ChartLoading pulses or shimmers them. */\nfunction BarChartSkeleton({ height, horizontal }: { height: number; horizontal: boolean }) {\n  const lengths = [42, 68, 55, 80, 62, 90, 48, 74, 58, 84, 66, 52]\n  return (\n    <ChartLoading>\n      <div\n        className={cn(\"flex gap-3 border-b border-border px-2\", horizontal ? \"flex-col justify-around border-b-0 border-l py-2\" : \"items-end justify-around\")}\n        style={{ height }}\n      >\n        {(horizontal ? lengths.slice(0, 6) : lengths).map((l, i) => (\n          <span\n            key={i}\n            className={cn(\"rounded-t-[4px] bg-skeleton\", horizontal && \"rounded-t-none rounded-r-[4px]\")}\n            style={horizontal ? { width: `${l}%`, height: 16 } : { height: `${l}%`, width: 20 }}\n          />\n        ))}\n      </div>\n    </ChartLoading>\n  )\n}\n\n/**\n * Column and bar chart. Bars grow from the baseline on mount with a short\n * stagger; hovering traces the value with a line and an animated pill.\n */\nfunction BarChart<T extends Record<string, unknown>>({\n  data,\n  index,\n  series: seriesProp,\n  variant = \"solid\",\n  layout = \"vertical\",\n  stacked = false,\n  highlight,\n  loading = false,\n  valueFormat = defaultNumberFormat,\n  axisFormat = compactNumberFormat,\n  height = 280,\n  className,\n  \"aria-label\": ariaLabel,\n  glow = false,\n  incompleteLast = false,\n  background = \"none\",\n}: BarChartProps<T>) {\n  const id = React.useId().replace(/:/g, \"\")\n  const reduce = useReducedMotion()\n  const series = resolveSeries(seriesProp)\n  const horizontal = layout === \"horizontal\"\n  const trace = (highlight ?? (series.length === 1 ? \"hover-trace\" : \"none\")) === \"hover-trace\"\n  const [activeIndex, setActiveIndex] = React.useState<number | null>(null)\n  const [activeSeries, setActiveSeries] = React.useState<string | null>(null)\n  const seriesKeys = series.map((s) => s.key)\n\n  const config = Object.fromEntries(series.map((s) => [s.key, { label: s.label, color: s.color }])) satisfies ChartConfig\n\n  const lastIndex = data.length - 1\n  const introPlayed = React.useRef(false)\n  React.useEffect(() => {\n    // Longest stagger plus the spring settling; after this bars never regrow.\n    const done = window.setTimeout(() => (introPlayed.current = true), data.length * 35 + 700)\n    return () => window.clearTimeout(done)\n  }, [data.length])\n\n  if (loading)\n    return (\n      <div className={cn(\"flex flex-col gap-3\", className)}>\n        <BarChartSkeleton height={height} horizontal={horizontal} />\n      </div>\n    )\n\n  const tracedValue =\n    trace && activeIndex != null && data[activeIndex]\n      ? stacked\n        ? seriesKeys.reduce((sum, k) => sum + Number(data[activeIndex][k] ?? 0), 0)\n        : Number(data[activeIndex][series[0].key] ?? 0)\n      : null\n\n  const glowing = (i: number, key: string) => activeIndex === i && (activeSeries == null || activeSeries === key)\n\n  const dimmed = (i: number, key: string) => (activeSeries != null && activeSeries !== key) || (trace && activeIndex != null && activeIndex !== i)\n\n  const categoryAxis = {\n    dataKey: index as string,\n    type: \"category\" as const,\n    tickLine: false,\n    axisLine: false,\n    tickMargin: 8,\n    interval: \"preserveStartEnd\" as const,\n  }\n  const valueAxis = {\n    type: \"number\" as const,\n    tickLine: false,\n    axisLine: false,\n    tickMargin: 4,\n    tickFormatter: (v: number) => axisFormat(v),\n  }\n\n  return (\n    <div className={cn(\"flex flex-col gap-3\", className)}>\n      {series.length > 1 && <ChartLegend series={series} active={activeSeries} onActiveChange={setActiveSeries} />}\n      <BarHoverContext.Provider value={{ dimmed, glowing, introPlayed }}>\n      <ChartContainer config={config} className=\"aspect-auto w-full\" style={{ height }} aria-label={ariaLabel} role=\"img\">\n        <RechartsBarChart\n          accessibilityLayer\n          data={data as Record<string, unknown>[]}\n          layout={horizontal ? \"vertical\" : \"horizontal\"}\n          margin={{ top: 8, right: 8, bottom: 0, left: 0 }}\n          barCategoryGap={horizontal ? \"28%\" : \"22%\"}\n          barGap={GAP}\n          onMouseMove={(state) => {\n            const i = state?.activeTooltipIndex\n            setActiveIndex(i == null ? null : Number(i))\n          }}\n          onMouseLeave={() => setActiveIndex(null)}\n        >\n          <defs>\n            {series.map((s) => (\n              <FillDefs key={s.key} id={`${id}-${s.key}`} color={s.color} variant={variant} horizontal={horizontal} />\n            ))}\n            {incompleteLast &&\n              series.map((s) => <FillDefs key={`${s.key}-incomplete`} id={`${id}-${s.key}-incomplete`} color={s.color} variant=\"hatched\" horizontal={horizontal} />)}\n            {glow && <GlowFilter id={`${id}-glow`} blur={5} strength={0.45} />}\n            {background !== \"none\" && <ChartBackgroundDefs id={`${id}-bg`} variant={background} />}\n          </defs>\n          {background !== \"none\" && <ChartBackground id={`${id}-bg`} variant={background} />}\n          <CartesianGrid vertical={horizontal} horizontal={!horizontal} stroke=\"var(--border)\" strokeDasharray=\"\" />\n          {horizontal ? (\n            <>\n              <XAxis {...valueAxis} />\n              <YAxis {...categoryAxis} width={104} interval={0} />\n            </>\n          ) : (\n            <>\n              <XAxis {...categoryAxis} />\n              <YAxis {...valueAxis} width={Y_AXIS_WIDTH} />\n            </>\n          )}\n          <Tooltip\n            cursor={trace ? false : { fill: \"var(--muted)\", radius: 4 }}\n            isAnimationActive={false}\n            content={({ active, payload, label }) =>\n              active && payload?.length ? (\n                <ChartTooltipCard\n                  title={String(label)}\n                  note={incompleteLast && payload[0]?.payload === data[lastIndex] ? \"Devam ediyor\" : undefined}\n                  format={valueFormat}\n                  rows={series\n                    .filter((s) => activeSeries == null || s.key === activeSeries)\n                    .map((s) => ({ key: s.key, label: s.label, color: s.color, value: payload.find((p) => p.dataKey === s.key)?.value as number | undefined }))}\n                />\n              ) : null\n            }\n          />\n          {series.map((s) => (\n            <Bar\n              key={s.key}\n              dataKey={s.key}\n              stackId={stacked ? \"stack\" : undefined}\n              maxBarSize={MAX_BAR}\n              isAnimationActive={false}\n              shape={(props: BarShapeProps) => (\n                <BarShape\n                  {...props}\n                  seriesKey={s.key}\n                  seriesKeys={seriesKeys}\n                  fill={variant === \"solid\" ? s.color : `url(#${id}-${s.key})`}\n                  horizontal={horizontal}\n                  stacked={stacked}\n                  animate={!reduce}\n                  incompleteFill={incompleteLast ? `url(#${id}-${s.key}-incomplete)` : undefined}\n                  lastIndex={lastIndex}\n                  glowFilter={glow ? `url(#${id}-glow)` : undefined}\n                />\n              )}\n            />\n          ))}\n          {tracedValue != null && (\n            <ReferenceLine\n              {...(horizontal ? { x: tracedValue } : { y: tracedValue })}\n              ifOverflow=\"extendDomain\"\n              shape={(p: { x1?: number; y1?: number; x2?: number; y2?: number }) => (\n                <TraceLine {...p} value={tracedValue} horizontal={horizontal} format={axisFormat} />\n              )}\n            />\n          )}\n        </RechartsBarChart>\n      </ChartContainer>\n      </BarHoverContext.Provider>\n    </div>\n  )\n}\n\nexport { BarChart, type BarChartProps, type BarChartVariant }\n",
      "type": "registry:component",
      "target": "@components/ikas/bar-chart.tsx"
    }
  ],
  "docs": "https://builders.ikas.com/tr/docs/app-development/ui-kit/components/bar-chart",
  "categories": [
    "charts"
  ],
  "type": "registry:component"
}