{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "split-bar",
  "title": "Split Bar",
  "author": "Ballmac <https://ui.ballmac.com>",
  "description": "One bar cut into shares with a legend that says the same in numbers. Pointing at a legend row lights its slice and dims the rest, and choosing it keeps it lit.",
  "registryDependencies": [
    "utils",
    "https://ui.ballmac.com/r/i18n.json"
  ],
  "files": [
    {
      "path": "registry/ballmac/components/split-bar.tsx",
      "content": "// Ballmac UI: Split Bar. https://ui.ballmac.com/components/split-bar\n\"use client\"\n\nimport * as React from \"react\"\n\nimport { cn } from \"@/lib/utils\"\nimport { useMessages } from \"@/lib/ballmac/i18n\"\n\ntype SplitSegment = {\n  id: string\n  label: string\n  value: number\n  /** A note beside the value, for example \"$1,200\". */\n  hint?: string\n}\n\ntype SplitBarProps = Omit<React.ComponentProps<\"div\">, \"title\"> & {\n  segments: SplitSegment[]\n  title?: string\n  /** Formats each value. */\n  format?: (value: number) => string\n  /** Shows a total above the bar. */\n  total?: string\n}\n\nconst SHADES = [\"bg-foreground\", \"bg-chart-1\", \"bg-chart-2\", \"bg-chart-3\", \"bg-chart-4\", \"bg-chart-5\"]\nconst nf = new Intl.NumberFormat(\"en-US\")\n\n/**\n * One bar cut into shares, with a legend that says the same thing in numbers. Pointing at a legend row or focusing it lights up its slice and dims the rest, and choosing it keeps it lit;\n * the legend is the accessible version and carries the values and the percentages.\n */\nfunction SplitBar({ segments, title, format = (v) => nf.format(v), total, className, ...props }: SplitBarProps) {\n  const msg = useMessages()\n  const id = React.useId()\n  const [hover, setHover] = React.useState<string | null>(null)\n  const [pinned, setPinned] = React.useState<string | null>(null)\n  const active = hover ?? pinned\n  const sum = segments.reduce((s, x) => s + x.value, 0) || 1\n\n  return (\n    <div data-slot=\"split-bar\" className={cn(\"grid min-w-0 gap-3\", className)} {...props}>\n      {(title || total) && (\n        <div className=\"flex items-baseline justify-between gap-3\">\n          {title && <h3 id={`${id}-t`} className=\"text-sm font-medium\">{title}</h3>}\n          {total && <p className=\"text-sm font-semibold tabular-nums\">{total}</p>}\n        </div>\n      )}\n      <div aria-hidden=\"true\" className=\"flex h-3 gap-0.5 overflow-hidden rounded-full\">\n        {segments.map((s, i) => (\n          <span key={s.id} className={cn(\"min-w-1 transition-opacity motion-reduce:transition-none\", SHADES[i % SHADES.length], active && active !== s.id && \"opacity-25\")} style={{ flexGrow: s.value, flexBasis: 0 }} />\n        ))}\n      </div>\n      <ul aria-labelledby={title ? `${id}-t` : undefined} className=\"grid grid-cols-[minmax(0,1fr)] gap-0.5 sm:grid-cols-2 sm:gap-x-6\">\n        {segments.map((s, i) => (\n          <li key={s.id} className=\"min-w-0\">\n            <button\n              type=\"button\"\n              aria-pressed={pinned === s.id}\n              onClick={() => setPinned(pinned === s.id ? null : s.id)}\n              onMouseEnter={() => setHover(s.id)}\n              onMouseLeave={() => setHover(null)}\n              onFocus={() => setHover(s.id)}\n              onBlur={() => setHover(null)}\n              className=\"flex min-h-9 w-full items-center gap-2.5 rounded-md px-2 text-start text-sm outline-none hover:bg-accent focus-visible:ring-[3px] focus-visible:ring-ring/50\"\n            >\n              <span aria-hidden=\"true\" className={cn(\"size-2.5 shrink-0 rounded-[3px]\", SHADES[i % SHADES.length])} />\n              <span className=\"min-w-0 flex-1 truncate\">{s.label}</span>\n              <span className=\"shrink-0 tabular-nums text-muted-foreground\">\n                {s.hint && <span className=\"me-2 text-xs\">{s.hint}</span>}\n                <span className=\"font-medium text-foreground\">{format(s.value)}</span>\n                <span className=\"ms-1.5 text-xs\">{msg(\"split-bar.share\", \"{percent}%\", { percent: Math.round((s.value / sum) * 100) })}</span>\n              </span>\n            </button>\n          </li>\n        ))}\n      </ul>\n    </div>\n  )\n}\n\nexport { SplitBar, type SplitBarProps, type SplitSegment }\n",
      "type": "registry:ui",
      "target": "@components/ballmac/split-bar.tsx"
    }
  ],
  "meta": {
    "schema": 1,
    "tier": "free",
    "tags": [
      "chart",
      "share",
      "stacked",
      "composition",
      "budget",
      "breakdown",
      "legend"
    ],
    "version": "1.0.0",
    "updated": "2026-10-25",
    "tests": true,
    "ai": {
      "summary": "segments is [{ id, label, value, hint? }]; title, total (text above the bar), format(value). The legend lists every segment with its value and percent; hover or focus lights a slice, and a click keeps it lit (aria-pressed).",
      "whenToUse": [
        "Breaking a total into a handful of parts",
        "Spend, storage, traffic or time splits"
      ],
      "whenNotToUse": [
        "Many parts or nested parts; use treemap",
        "Ranking; use bar-list"
      ],
      "composesWith": [
        "bar-list",
        "treemap",
        "stat-card",
        "waterfall-chart"
      ],
      "a11y": [
        {
          "keys": "Legend",
          "action": "The accessible version: buttons with label, value and percent; pressed state"
        },
        {
          "keys": "Bar",
          "action": "Decoration only"
        },
        {
          "keys": "Color",
          "action": "Never the only way to tell parts apart"
        }
      ],
      "customization": [
        "segments and title",
        "total and format"
      ]
    },
    "statesNone": "A presentational chart: the segments are passed in, so it has nothing of its own to load.",
    "examples": [
      "split-bar-demo",
      "split-bar-two"
    ],
    "url": "https://ui.ballmac.com/components/split-bar"
  },
  "categories": [
    "data-display"
  ],
  "type": "registry:ui"
}