{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "core-ring-strip",
  "type": "registry:ui",
  "title": "Ring Strip",
  "description": "The line of thirds laid flat: a key's roots in a row with the step between each pair (on Aura a major third is one pad right, a minor third one pad up) and every chord as a clickable bracket over the consecutive notes it stacks, one lap and a bit so each chord appears whole.",
  "dependencies": [
    "maruaudio@>=0.0.3 <0.1.0"
  ],
  "registryDependencies": [],
  "files": [
    {
      "path": "components/ui/ring-strip.tsx",
      "content": "/**\n * Source: https://maru.audio/ui-kit/ring-strip\n * Copyright © [Maru](https://maru.audio)\n *\n * Maru UI Kit — https://maru.audio/ui-kit\n *\n * Installed from the Maru UI registry. This file is yours now: edit it\n * freely, it will not be overwritten unless you re-run the add command.\n *\n * Keep this item’s companion files and CSS imports together. Dependencies\n * declared by the registry are installed with the source.\n */\n/**\n * Ring Strip — the line of thirds laid flat.\n *\n * The ring's roots in a row, the interval to the next written between each\n * pair (on Aura a major third is one pad right, R, and a minor third one pad\n * up, U), and every chord as a labelled bracket over the `span` consecutive\n * notes it stacks. The chain runs one lap and a bit so each chord appears\n * whole at least once; brackets are staggered over `span` rows.\n *\n * View-only and structural: names, intervals and bracket labels are chosen by\n * the caller. Pure SVG from props; brackets are keyboard-focusable buttons\n * that report the node index.\n */\n\nimport {\n  RING_STRIP,\n  ringStripLayout,\n  ringThirdStepLabel,\n} from \"maruaudio/ui/shared/ring-geometry\"\n\nexport interface RingStripNode {\n  /** The root's name, printed in the note circle. */\n  readonly name: string\n  /** Semitones up to the next node's root (3 or 4 on a line of thirds). */\n  readonly interval: number\n  /** Filled note and heavier bracket: the hinge. */\n  readonly emphasis?: boolean\n  /** Text on the chord's bracket. */\n  readonly label: string\n  /** Accessible name of the bracket; defaults to `label`. */\n  readonly ariaLabel?: string\n}\n\nexport interface RingStripProps {\n  readonly nodes: readonly RingStripNode[]\n  /** Notes per chord: 3 for triads, 4 for sevenths. */\n  readonly span: number\n  readonly active: number | null\n  readonly onPick: (index: number) => void\n  /** Label of the step between two neighbours; Aura's R/U by default. */\n  readonly stepLabel?: (interval: number) => string\n  readonly ground?: string\n  readonly className?: string\n  readonly ariaLabel?: string\n}\n\nexport function RingStrip({\n  nodes,\n  span,\n  active,\n  onPick,\n  stepLabel = ringThirdStepLabel,\n  ground = \"var(--background, #fff)\",\n  className,\n  ariaLabel = \"The line of thirds laid flat with its chords\",\n}: RingStripProps) {\n  const n = nodes.length\n  if (n === 0) return null\n  const { step, x0, bracketTop, bracketRow } = RING_STRIP\n  const { notes, brackets, width, height } = ringStripLayout(n, span)\n  return (\n    <svg viewBox={`0 0 ${width} ${height}`} className={className} role=\"img\" aria-label={ariaLabel} style={{ display: \"block\", width: \"100%\", maxWidth: 760 }}>\n      {Array.from({ length: notes }, (_, i) => {\n        const node = nodes[i % n]!\n        const x = x0 + i * step\n        return (\n          <g key={`n${i}`}>\n            {i + 1 < notes && (\n              <>\n                <line x1={x + 12} y1={30} x2={x + step - 12} y2={30} stroke=\"currentColor\" strokeWidth={1} opacity={0.45} />\n                <text x={x + step / 2} y={16} textAnchor=\"middle\" fontSize={8} fill=\"currentColor\" opacity={0.75}>{stepLabel(node.interval)}</text>\n              </>\n            )}\n            <circle cx={x} cy={30} r={12} fill={node.emphasis ? \"currentColor\" : ground} stroke=\"currentColor\" strokeWidth={node.emphasis ? 2 : 1.2} />\n            <text x={x} y={33.5} textAnchor=\"middle\" fontSize={9} fontWeight={600} fill={node.emphasis ? ground : \"currentColor\"}>{node.name}</text>\n          </g>\n        )\n      })}\n      {Array.from({ length: brackets }, (_, k) => {\n        const index = k % n\n        const node = nodes[index]!\n        const y = bracketTop + (k % span) * bracketRow\n        const xa = x0 + k * step - 15\n        const xb = x0 + (k + span - 1) * step + 15\n        const isActive = active === index\n        const pick = () => onPick(index)\n        return (\n          <g\n            key={`b${k}`}\n            onClick={pick}\n            role=\"button\"\n            tabIndex={0}\n            onKeyDown={(e) => { if (e.key === \"Enter\" || e.key === \" \") { e.preventDefault(); pick() } }}\n            style={{ cursor: \"pointer\" }}\n            aria-label={node.ariaLabel ?? node.label}\n          >\n            <rect x={xa} y={y} width={xb - xa} height={15} rx={5} fill={isActive ? \"currentColor\" : ground} stroke=\"currentColor\" strokeWidth={node.emphasis ? 2 : 1} opacity={isActive ? 1 : 0.8} />\n            <text x={(xa + xb) / 2} y={y + 10.5} textAnchor=\"middle\" fontSize={8.5} fill={isActive ? ground : \"currentColor\"}>{node.label}</text>\n          </g>\n        )\n      })}\n    </svg>\n  )\n}\n",
      "type": "registry:component",
      "target": "@ui/ring-strip.tsx"
    }
  ]
}
