{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "code-tabs",
  "title": "Code Tabs",
  "description": "Several files, or one command per package manager, in one code block: the underline glides between tabs, the code cross-fades 6px in the direction of travel while the height springs, and with a groupId every block on the page switches together and remembers the choice.",
  "dependencies": [
    "motion",
    "@base-ui/react",
    "shiki"
  ],
  "registryDependencies": [
    "https://eased.dev/r/eased-tokens.json",
    "https://eased.dev/r/eased-code-core.json",
    "https://eased.dev/r/tabs.json"
  ],
  "files": [
    {
      "path": "src/registry/eased/code-tabs/code-tabs.tsx",
      "content": "\"use client\";\n\nimport { useState } from \"react\";\nimport { Tab, Tabs, TabsList, TabsPanel, TabsPanels } from \"../tabs/tabs\";\nimport { useChoice } from \"../_primitives/code-core/choice\";\nimport { CopyButton } from \"../_primitives/code-core/copy\";\nimport { CODE_FRAME, LangPill } from \"../_primitives/code-core/code-chrome\";\nimport { CodeCollapse, CodeLines, useCodeLines } from \"../_primitives/code-core/code-lines\";\nimport { langFromFilename, resolveLang, type CodeLang, type CodeLine } from \"../_primitives/code-core/highlighter\";\nimport type { LineDecorations } from \"../_primitives/code-core/lines\";\n\nexport type CodeTabItem = LineDecorations & {\n  /** The tab's label: a file name (\"button.tsx\") or a choice (\"pnpm\"). */\n  label: string;\n  /** Identifies the tab, and is what a `groupId` syncs. Default: `label`. */\n  value?: string;\n  code: string;\n  /** Default: from the label's extension (`button.tsx` → tsx). */\n  lang?: CodeLang | (string & {});\n  /** Pre-highlighted lines from `highlight()`. */\n  tokens?: CodeLine[];\n};\n\nexport type CodeTabsProps = {\n  items: CodeTabItem[];\n  /**\n   * Sync the selection with every Code Tabs (and `useChoice`) sharing this id, across the\n   * page, other browser tabs and visits: pick npm once and every install block follows.\n   */\n  groupId?: string;\n  /** Controlled selection (an item's `value`). */\n  value?: string;\n  defaultValue?: string;\n  onValueChange?: (value: string) => void;\n  /** Accessible name for the tab list. Default \"Files\". */\n  label?: string;\n  /** Show the language pill. Default: only when the items' languages differ. */\n  showLang?: boolean;\n  lineNumbers?: boolean;\n  /** As Code Block: collapse panels past this many lines. Default 20; 0 never. */\n  collapseAfter?: number;\n  /** Show the copy button, which copies the active tab. Default true. */\n  copy?: boolean;\n  onCopy?: (code: string, value: string) => void;\n  wrap?: boolean;\n  className?: string;\n};\n\n// The frame clips, so panels draw Eased's focus ring inside their edge instead of around it.\nconst INSET_FOCUS =\n  \"rounded-control-xs focus-visible:inset-ring-2 focus-visible:inset-ring-focus focus-visible:[--tw-ring-offset-shadow:0_0_#0000]! focus-visible:[--tw-ring-shadow:0_0_#0000]!\";\n\nconst valueOf = (item: CodeTabItem) => item.value ?? item.label;\nconst langOf = (item: CodeTabItem) => item.lang ?? langFromFilename(item.label) ?? undefined;\n\n/**\n * Code Tabs: several files, or one command for each package manager, in one block. The\n * underline glides between tabs on the snappy spring, the code cross-fades 6px in the\n * direction of travel while the block's height springs, and the language pill swaps in\n * place. With `groupId`, every block on the page switches together and the choice persists.\n * Base UI Tabs: arrow keys, Home/End, roving focus.\n */\nexport function CodeTabs({\n  items,\n  groupId,\n  value: controlled,\n  defaultValue,\n  onValueChange,\n  label = \"Files\",\n  showLang,\n  lineNumbers = false,\n  collapseAfter = 20,\n  copy = true,\n  onCopy,\n  wrap,\n  className = \"\",\n}: CodeTabsProps) {\n  const values = items.map(valueOf);\n  const first = defaultValue ?? values[0];\n  const [synced, setSynced] = useChoice(groupId, first, values);\n  const [local, setLocal] = useState(first);\n  const current = controlled ?? (groupId ? synced : local);\n  const active = items.find((i) => valueOf(i) === current) ?? items[0];\n  const langs = new Set(items.map((i) => resolveLang(langOf(i)) ?? langOf(i)));\n  const pill = showLang ?? langs.size > 1;\n\n  const select = (next: string) => {\n    if (groupId) setSynced(next);\n    else setLocal(next);\n    onValueChange?.(next);\n  };\n\n  if (!active) return null;\n\n  return (\n    <Tabs value={valueOf(active)} onValueChange={(v) => select(String(v))} className={`${CODE_FRAME} ${className}`}>\n      {/* 4px above 36px tabs: room for the focus ring inside the clipped frame, and 41px in all, like Code Block's title bar. */}\n      <div className=\"flex min-w-0 items-end gap-2 border-b border-line-subtle bg-surface pt-1 pr-2 pl-4\">\n        <TabsList aria-label={label} className=\"min-w-0 flex-1 after:hidden\">\n          {items.map((item) => (\n            <Tab key={valueOf(item)} value={valueOf(item)} className=\"h-control-md!\">\n              <span className=\"font-mono text-ui-sm\">{item.label}</span>\n            </Tab>\n          ))}\n        </TabsList>\n        <div className=\"flex h-control-md shrink-0 items-center gap-2\">\n          {pill && <LangPill lang={langOf(active)} />}\n          {copy && (\n            <CopyButton\n              text={active.code}\n              label={`Copy ${active.label}`}\n              onCopy={(code) => onCopy?.(code, valueOf(active))}\n            />\n          )}\n        </div>\n      </div>\n      <TabsPanels>\n        {items.map((item) => (\n          <TabsPanel\n            key={valueOf(item)}\n            value={valueOf(item)}\n            // The panel sits flush in a clipped frame: an inset ring instead of the outer glow.\n            className={INSET_FOCUS}\n          >\n            <CodePanel item={item} lineNumbers={lineNumbers} collapseAfter={collapseAfter} wrap={wrap} />\n          </TabsPanel>\n        ))}\n      </TabsPanels>\n    </Tabs>\n  );\n}\n\nfunction CodePanel({ item, lineNumbers, collapseAfter, wrap }: { item: CodeTabItem; lineNumbers: boolean; collapseAfter: number; wrap?: boolean }) {\n  const { lines } = useCodeLines(item.code, langOf(item), item.tokens);\n  return (\n    <CodeCollapse lines={lines.length} collapseAfter={collapseAfter}>\n      <CodeLines\n        lines={lines}\n        lineNumbers={lineNumbers}\n        wrap={wrap}\n        label={item.label}\n        highlight={item.highlight}\n        focus={item.focus}\n        added={item.added}\n        removed={item.removed}\n      />\n    </CodeCollapse>\n  );\n}\n\nexport type PackageManager = \"pnpm\" | \"npm\" | \"yarn\" | \"bun\";\nexport const PACKAGE_MANAGERS: readonly PackageManager[] = [\"pnpm\", \"npm\", \"yarn\", \"bun\"];\n\n/**\n * One command in each package manager's dialect.\n *   { add: \"motion\" }                 pnpm add motion · npm install motion · yarn add motion · bun add motion\n *   { add: \"-D tsx\" }                 dev dependencies: pass the flag through\n *   { dlx: \"shadcn@latest add …\" }    pnpm dlx · npx · yarn dlx · bunx --bun\n *   { run: \"dev\" }                    pnpm dev · npm run dev · yarn dev · bun run dev\n */\nexport function packageCommand(pm: PackageManager, cmd: { add?: string; dlx?: string; run?: string }): string {\n  if (cmd.add !== undefined) return `${pm} ${pm === \"npm\" ? \"install\" : \"add\"} ${cmd.add}`;\n  if (cmd.dlx !== undefined) return `${{ pnpm: \"pnpm dlx\", npm: \"npx\", yarn: \"yarn dlx\", bun: \"bunx --bun\" }[pm]} ${cmd.dlx}`;\n  if (cmd.run !== undefined) return `${pm === \"npm\" || pm === \"bun\" ? `${pm} run` : pm} ${cmd.run}`;\n  return pm;\n}\n\n/** Code Tabs items for one command across pnpm, npm, yarn and bun. Pair with `groupId=\"package-manager\"`. */\nexport function packageManagerItems(cmd: { add?: string; dlx?: string; run?: string }): CodeTabItem[] {\n  return PACKAGE_MANAGERS.map((pm) => ({ label: pm, value: pm, lang: \"bash\", code: packageCommand(pm, cmd) }));\n}\n",
      "type": "registry:ui",
      "target": "@components/eased/code-tabs/code-tabs.tsx"
    }
  ],
  "docs": "Import CodeTabs, packageManagerItems and packageCommand from components/eased/code-tabs/code-tabs. Use groupId=\"package-manager\" on install blocks so pnpm/npm/yarn/bun stays in sync across the page.",
  "type": "registry:ui"
}