{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "code-block",
  "title": "Code Block",
  "description": "A code panel with a filename header, line numbers, line highlights, wrap toggle and copy button, plus a tabbed variant for several files.",
  "dependencies": [
    "@radix-ui/react-tabs",
    "lucide-react"
  ],
  "files": [
    {
      "path": "registry/tealmac/code-block.tsx",
      "content": "// Adapted from Ballmac UI (https://github.com/vamsiy78/ballmac-ui), MIT License, Copyright (c) 2026 Ballmac.\n\"use client\"\n\nimport * as React from \"react\"\nimport * as TabsPrimitive from \"@radix-ui/react-tabs\"\nimport { Check, Copy, FileCode2, WrapText } from \"lucide-react\"\n\nimport { cn } from \"@/lib/utils\"\n\n/* -------------------------------------------------------------------------------------------------\n * Shared pieces\n * -----------------------------------------------------------------------------------------------*/\n\nfunction CopyCodeButton({\n  getText,\n  label,\n}: {\n  getText: () => string\n  label?: string\n}) {\n  label ??= \"Copy code\"\n  const [copied, setCopied] = React.useState(false)\n  const timer = React.useRef<ReturnType<typeof setTimeout>>(undefined)\n  React.useEffect(() => () => clearTimeout(timer.current), [])\n  return (\n    <>\n      <button\n        type=\"button\"\n        data-slot=\"code-block-copy\"\n        aria-label={copied ? \"Copied\" : label}\n        title={label}\n        onClick={async () => {\n          try {\n            await navigator.clipboard.writeText(getText())\n          } catch {\n            return\n          }\n          setCopied(true)\n          clearTimeout(timer.current)\n          timer.current = setTimeout(() => setCopied(false), 1800)\n        }}\n        className=\"text-muted-foreground hover:bg-accent hover:text-foreground focus-visible:ring-ring/50 flex size-7 shrink-0 items-center justify-center rounded-md transition-colors duration-150 outline-none focus-visible:ring-[3px]\"\n      >\n        {copied ? (\n          <Check aria-hidden=\"true\" className=\"size-3.5\" />\n        ) : (\n          <Copy aria-hidden=\"true\" className=\"size-3.5\" />\n        )}\n      </button>\n      <span className=\"sr-only\" aria-live=\"polite\">\n        {copied ? \"Copied to clipboard\" : \"\"}\n      </span>\n    </>\n  )\n}\n\nfunction WrapToggle({\n  wrap,\n  onWrapChange,\n}: {\n  wrap: boolean\n  onWrapChange: (wrap: boolean) => void\n}) {\n  return (\n    <button\n      type=\"button\"\n      data-slot=\"code-block-wrap\"\n      aria-label=\"Wrap lines\"\n      aria-pressed={wrap}\n      title=\"Wrap lines\"\n      onClick={() => onWrapChange(!wrap)}\n      className=\"text-muted-foreground hover:bg-accent hover:text-foreground focus-visible:ring-ring/50 aria-pressed:bg-accent aria-pressed:text-foreground flex size-7 shrink-0 items-center justify-center rounded-md transition-colors duration-150 outline-none focus-visible:ring-[3px]\"\n    >\n      <WrapText aria-hidden=\"true\" className=\"size-3.5\" />\n    </button>\n  )\n}\n\ntype CodeBodyProps = {\n  code?: string\n  children?: React.ReactNode\n  lineNumbers: boolean\n  highlight: number[]\n  wrap: boolean\n  label: string\n  codeRef: React.RefObject<HTMLDivElement | null>\n  className?: string\n}\n\n/**\n * Lines are `<span class=\"line\">`, the same structure Shiki emits, so line numbers (a CSS counter)\n * work for plain `code` and for pre-highlighted children alike.\n */\nfunction CodeBody({\n  code,\n  children,\n  lineNumbers,\n  highlight,\n  wrap,\n  label,\n  codeRef,\n  className,\n}: CodeBodyProps) {\n  const lines = React.useMemo(\n    () => (code ?? \"\").replace(/\\n$/, \"\").split(\"\\n\"),\n    [code]\n  )\n  const highlighted = React.useMemo(() => new Set(highlight), [highlight])\n  return (\n    <div\n      ref={codeRef}\n      data-slot=\"code-block-body\"\n      data-line-numbers={lineNumbers || undefined}\n      data-wrap={wrap || undefined}\n      // eslint-disable-next-line jsx-a11y/no-noninteractive-tabindex -- scrollable region; focusable so keyboard users can scroll it\n      tabIndex={0}\n      role=\"region\"\n      aria-label={label}\n      className={cn(\n        \"focus-visible:ring-ring/50 overflow-x-auto py-3 font-mono text-[13px] leading-6 outline-none focus-visible:ring-[3px] focus-visible:ring-inset\",\n        lineNumbers &&\n          \"[&_.line]:before:text-muted-foreground/60 [counter-reset:line] [&_.line]:before:me-4 [&_.line]:before:inline-block [&_.line]:before:w-[3ch] [&_.line]:before:text-end [&_.line]:before:content-[counter(line)] [&_.line]:before:select-none [&_.line]:before:[counter-increment:line]\",\n        // Pre-highlighted children (e.g. Shiki's <pre>) keep their colors but lose their own box.\n        \"[&_pre]:m-0 [&_pre]:bg-transparent! [&_pre]:p-0 [&_pre]:font-mono\",\n        wrap\n          ? \"[&_pre]:break-words [&_pre]:whitespace-pre-wrap\"\n          : \"[&_pre]:whitespace-pre\",\n        className\n      )}\n    >\n      {code !== undefined ? (\n        <pre dir=\"ltr\" className={cn(\"min-w-full\", wrap ? \"w-full\" : \"w-max\")}>\n          <code className=\"block\">\n            {lines.map((line, i) => (\n              <span\n                key={i}\n                data-highlighted={highlighted.has(i + 1) || undefined}\n                className=\"line data-[highlighted]:bg-accent block px-4 data-[highlighted]:shadow-[inset_2px_0_0_0_var(--ring)]\"\n              >\n                {line || \" \"}\n              </span>\n            ))}\n          </code>\n        </pre>\n      ) : (\n        <div className=\"px-4\">{children}</div>\n      )}\n    </div>\n  )\n}\n\nconst headerClass =\n  \"flex h-10 min-w-0 items-center gap-2 border-b bg-muted/40 pe-1.5 ps-3.5\"\n\n/* -------------------------------------------------------------------------------------------------\n * CodeBlock\n * -----------------------------------------------------------------------------------------------*/\n\ntype CodeBlockProps = Omit<React.ComponentProps<\"div\">, \"children\"> & {\n  /** Source as plain text. Rendered unhighlighted in monospace; required for `highlight`. */\n  code?: string\n  /** Pre-highlighted markup (e.g. Shiki's output as JSX) used instead of `code`. Pass `code` too so copy gets clean text. */\n  children?: React.ReactNode\n  /** Shown in the header, e.g. \"app/page.tsx\". */\n  filename?: string\n  /** Language label shown in the header, e.g. \"tsx\". */\n  language?: string\n  /** Show line numbers. */\n  lineNumbers?: boolean\n  /** 1-based line numbers to highlight (plain `code` only). */\n  highlight?: number[]\n  /** Wrap long lines instead of scrolling horizontally (initial value when the toggle is shown). */\n  wrap?: boolean\n  /** Show a button that toggles line wrapping. */\n  wrapToggle?: boolean\n  /** Show the copy button. */\n  copyable?: boolean\n  /** Classes for the scrolling body, e.g. a max height. */\n  bodyClassName?: string\n}\n\nfunction CodeBlock({\n  code,\n  children,\n  filename,\n  language,\n  lineNumbers = false,\n  highlight = [],\n  wrap: wrapProp = false,\n  wrapToggle = false,\n  copyable = true,\n  bodyClassName,\n  className,\n  ...props\n}: CodeBlockProps) {\n  const [wrap, setWrap] = React.useState(wrapProp)\n  React.useEffect(() => setWrap(wrapProp), [wrapProp])\n  const codeRef = React.useRef<HTMLDivElement>(null)\n  const getText = () => code ?? codeRef.current?.textContent ?? \"\"\n  const hasHeader = Boolean(filename || language)\n\n  return (\n    <div\n      data-slot=\"code-block\"\n      className={cn(\n        \"group/code bg-card text-card-foreground relative w-full min-w-0 overflow-hidden rounded-xl border\",\n        className\n      )}\n      {...props}\n    >\n      {hasHeader ? (\n        <div data-slot=\"code-block-header\" className={headerClass}>\n          {filename ? (\n            <>\n              <FileCode2\n                aria-hidden=\"true\"\n                className=\"text-muted-foreground size-3.5 shrink-0\"\n              />\n              <span className=\"text-foreground truncate font-mono text-xs\">\n                {filename}\n              </span>\n            </>\n          ) : null}\n          {language ? (\n            <span className=\"text-muted-foreground ms-auto shrink-0 font-mono text-[11px] tracking-wide uppercase\">\n              {language}\n            </span>\n          ) : null}\n          <span\n            className={cn(\"flex shrink-0 items-center\", !language && \"ms-auto\")}\n          >\n            {wrapToggle ? (\n              <WrapToggle wrap={wrap} onWrapChange={setWrap} />\n            ) : null}\n            {copyable ? <CopyCodeButton getText={getText} /> : null}\n          </span>\n        </div>\n      ) : wrapToggle || copyable ? (\n        <div className=\"bg-card/90 absolute end-1.5 top-1.5 z-10 flex items-center rounded-md\">\n          {wrapToggle ? (\n            <WrapToggle wrap={wrap} onWrapChange={setWrap} />\n          ) : null}\n          {copyable ? <CopyCodeButton getText={getText} /> : null}\n        </div>\n      ) : null}\n      <CodeBody\n        code={children == null ? (code ?? \"\") : undefined}\n        lineNumbers={lineNumbers}\n        highlight={highlight}\n        wrap={wrap}\n        label={filename ?? (language ? `${language} code` : \"Code\")}\n        codeRef={codeRef}\n        className={cn(\n          !hasHeader && (copyable || wrapToggle) && \"pe-10\",\n          bodyClassName\n        )}\n      >\n        {children}\n      </CodeBody>\n    </div>\n  )\n}\n\n/* -------------------------------------------------------------------------------------------------\n * CodeBlockTabs\n * -----------------------------------------------------------------------------------------------*/\n\ntype CodeBlockFile = {\n  /** Tab label and region name, e.g. \"components/ui/button.tsx\". */\n  filename: string\n  /** Plain source. */\n  code: string\n  /** Language label for this file. */\n  language?: string\n  /** 1-based lines to highlight. */\n  highlight?: number[]\n  /** Pre-highlighted markup for this file (copy still uses `code`). */\n  children?: React.ReactNode\n}\n\ntype CodeBlockTabsProps = Omit<\n  React.ComponentProps<\"div\">,\n  \"children\" | \"defaultValue\" | \"dir\"\n> & {\n  /** One tab per file. */\n  files: CodeBlockFile[]\n  /** Filename of the tab open at first. */\n  defaultValue?: string\n  /** Controlled open tab (a filename). */\n  value?: string\n  /** Called with the filename of the newly selected tab. */\n  onValueChange?: (filename: string) => void\n  /** Show line numbers. */\n  lineNumbers?: boolean\n  /** Wrap long lines instead of scrolling horizontally. */\n  wrap?: boolean\n  /** Show the copy button (copies the open file). */\n  copyable?: boolean\n  /** Classes for each scrolling body. */\n  bodyClassName?: string\n}\n\nfunction CodeBlockTabs({\n  files,\n  defaultValue,\n  value: valueProp,\n  onValueChange,\n  lineNumbers = false,\n  wrap = false,\n  copyable = true,\n  bodyClassName,\n  className,\n  ...props\n}: CodeBlockTabsProps) {\n  const [internal, setInternal] = React.useState(\n    defaultValue ?? files[0]?.filename ?? \"\"\n  )\n  const value = valueProp ?? internal\n  const current = files.find((f) => f.filename === value) ?? files[0]\n  const codeRef = React.useRef<HTMLDivElement>(null)\n\n  return (\n    <TabsPrimitive.Root\n      data-slot=\"code-block-tabs\"\n      value={current?.filename}\n      onValueChange={(next) => {\n        if (valueProp === undefined) setInternal(next)\n        onValueChange?.(next)\n      }}\n      className={cn(\n        \"bg-card text-card-foreground w-full min-w-0 overflow-hidden rounded-xl border\",\n        className\n      )}\n      {...props}\n    >\n      <div className={cn(headerClass, \"ps-1.5\")}>\n        <TabsPrimitive.List\n          aria-label=\"Files\"\n          className=\"flex min-w-0 flex-1 [scrollbar-width:none] items-center gap-0.5 overflow-x-auto\"\n        >\n          {files.map((file) => (\n            <TabsPrimitive.Trigger\n              key={file.filename}\n              value={file.filename}\n              className=\"text-muted-foreground hover:text-foreground focus-visible:ring-ring/50 data-[state=active]:text-foreground data-[state=active]:after:bg-foreground relative h-10 shrink-0 px-2.5 font-mono text-xs transition-colors duration-150 outline-none after:absolute after:inset-x-2.5 after:bottom-0 after:h-px after:bg-transparent focus-visible:ring-[3px] focus-visible:ring-inset\"\n            >\n              {file.filename.split(\"/\").pop()}\n            </TabsPrimitive.Trigger>\n          ))}\n        </TabsPrimitive.List>\n        {current?.language ? (\n          <span className=\"text-muted-foreground shrink-0 font-mono text-[11px] tracking-wide uppercase\">\n            {current.language}\n          </span>\n        ) : null}\n        {copyable ? (\n          <CopyCodeButton\n            getText={() => current?.code ?? codeRef.current?.textContent ?? \"\"}\n          />\n        ) : null}\n      </div>\n      {files.map((file) => (\n        <TabsPrimitive.Content\n          key={file.filename}\n          value={file.filename}\n          tabIndex={-1}\n          className=\"outline-none\"\n        >\n          <CodeBody\n            code={file.children == null ? file.code : undefined}\n            lineNumbers={lineNumbers}\n            highlight={file.highlight ?? []}\n            wrap={wrap}\n            label={file.filename}\n            codeRef={codeRef}\n            className={bodyClassName}\n          >\n            {file.children}\n          </CodeBody>\n        </TabsPrimitive.Content>\n      ))}\n    </TabsPrimitive.Root>\n  )\n}\n\nexport {\n  CodeBlock,\n  CodeBlockTabs,\n  type CodeBlockProps,\n  type CodeBlockTabsProps,\n  type CodeBlockFile,\n}\n",
      "type": "registry:ui"
    }
  ],
  "type": "registry:ui"
}