{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "code-block",
  "type": "registry:ui",
  "title": "Code Block",
  "description": "Minimal syntax-highlighted code block with line numbers, copy action, and accent color customization.",
  "dependencies": [
    "clsx",
    "tailwind-merge",
    "@radix-ui/react-icons"
  ],
  "registryDependencies": [
    "utils"
  ],
  "files": [
    {
      "path": "src/registry/ui/code-block.tsx",
      "content": "\"use client\";\n\nimport React, { useState, useMemo } from \"react\";\nimport { CheckIcon, CopyIcon } from \"@radix-ui/react-icons\";\nimport { cn } from \"@/lib/utils\";\n\nexport interface CodeBlockProps {\n  code?: string;\n  language?: string;\n  color?: string;\n  showLineNumbers?: boolean;\n  className?: string;\n  filename?: string;\n}\n\nconst DEFAULT_CODE = `import { useSpring, animated } from \"motion/react\";\n\ntype OrbitProps = {\n  radius?: number;\n  speed?: number;\n};\n\nexport function Orbit({ radius = 120, speed = 1 }: OrbitProps) {\n  const angle = useSpring(0, { stiffness: 80, damping: 20 });\n\n  const x = Math.cos(angle.get()) * radius;\n  const y = Math.sin(angle.get()) * radius;\n\n  return (\n    <animated.div\n      style={{ x, y }}\n      className=\"size-4 rounded-full bg-current\"\n    />\n  );\n}`;\n\nconst KEYWORDS = new Set([\n  \"import\",\n  \"from\",\n  \"export\",\n  \"default\",\n  \"const\",\n  \"let\",\n  \"var\",\n  \"function\",\n  \"return\",\n  \"interface\",\n  \"type\",\n  \"extends\",\n  \"as\",\n  \"typeof\",\n  \"keyof\",\n  \"new\",\n  \"true\",\n  \"false\",\n  \"null\",\n  \"undefined\",\n  \"if\",\n  \"else\",\n  \"switch\",\n  \"case\",\n]);\n\nexport const CodeBlock = ({\n  code = DEFAULT_CODE,\n  color = \"#4ade80\",\n  showLineNumbers = true,\n  className,\n  filename,\n}: CodeBlockProps) => {\n  const [copied, setCopied] = useState(false);\n\n  const handleCopy = async () => {\n    try {\n      await navigator.clipboard.writeText(code);\n      setCopied(true);\n      setTimeout(() => setCopied(false), 2000);\n    } catch {\n      setCopied(false);\n    }\n  };\n\n  const lines = useMemo(() => {\n    return code.split(\"\\n\").map((line, lineIdx) => {\n      const regex =\n        /(\".*?\"|'.*?'|`.*?`|<\\/?[A-Za-z0-9_$.]+|\\/?>|[A-Za-z_$][A-Za-z0-9_$]*|0[xX][\\da-fA-F]+|\\d*\\.?\\d+|[{}()[\\];:,.=><&|!+*/?-]|\\s+|.)/gu;\n      const tokens: React.ReactNode[] = [];\n      let match;\n      const isImportLine = line.trimStart().startsWith(\"import \");\n\n      while ((match = regex.exec(line)) !== null) {\n        const token = match[0];\n        const key = `${lineIdx}-${match.index}`;\n\n        if (\n          token.startsWith('\"') ||\n          token.startsWith(\"'\") ||\n          token.startsWith(\"`\")\n        ) {\n          if (isImportLine) {\n            tokens.push(\n              <span key={key} className=\"text-[#9333ea] dark:text-[#c084fc]\">\n                {token}\n              </span>,\n            );\n          } else {\n            tokens.push(\n              <span key={key} style={{ color }}>\n                {token}\n              </span>,\n            );\n          }\n        } else if (token.startsWith(\"<\") || token === \"/>\" || token === \">\") {\n          tokens.push(\n            <span key={key} style={{ color }}>\n              {token}\n            </span>,\n          );\n        } else if (token === \"style\" || token === \"className\") {\n          tokens.push(\n            <span key={key} className=\"italic text-zinc-500 dark:text-zinc-400\">\n              {token}\n            </span>,\n          );\n        } else if (KEYWORDS.has(token)) {\n          tokens.push(\n            <span key={key} style={{ color }}>\n              {token}\n            </span>,\n          );\n        } else if (/^(?:0[xX][\\da-fA-F]+|\\d*\\.?\\d+)$/.test(token)) {\n          tokens.push(\n            <span key={key} style={{ color }}>\n              {token}\n            </span>,\n          );\n        } else if (/^[{}()[\\];:,.=><&|!+*/?-]+$/.test(token)) {\n          tokens.push(\n            <span key={key} className=\"text-zinc-500 dark:text-zinc-400\">\n              {token}\n            </span>,\n          );\n        } else if (/^\\s+$/.test(token)) {\n          tokens.push(<span key={key}>{token}</span>);\n        } else {\n          tokens.push(\n            <span key={key} className=\"text-zinc-900 dark:text-zinc-100\">\n              {token}\n            </span>,\n          );\n        }\n      }\n\n      return {\n        text: line,\n        tokens,\n      };\n    });\n  }, [code, color]);\n\n  return (\n    <div className={cn(\"relative w-full max-w-2xl select-text\", className)}>\n      {filename && (\n        <div className=\"mb-2.5 text-xs font-sans font-medium text-muted-foreground\">\n          {filename}\n        </div>\n      )}\n      <button\n        type=\"button\"\n        onClick={handleCopy}\n        className=\"absolute top-0 right-0 z-10 flex h-8 w-8 items-center justify-center rounded-full border border-border dark:border-white/10 bg-muted/40 dark:bg-white/4 text-muted-foreground dark:text-zinc-400 backdrop-blur-sm transition-colors hover:border-foreground/20 dark:hover:border-white/20 hover:bg-muted dark:hover:bg-white/8 hover:text-foreground dark:hover:text-white cursor-pointer\"\n        title={copied ? \"Copied\" : \"Copy code\"}\n        aria-label=\"Copy code\"\n      >\n        {copied ? (\n          <CheckIcon className=\"h-4 w-4 text-emerald-500 dark:text-emerald-400\" />\n        ) : (\n          <CopyIcon className=\"h-4 w-4\" />\n        )}\n      </button>\n      <span className=\"sr-only\" aria-live=\"polite\">\n        {copied ? \"Copied\" : \"\"}\n      </span>\n\n      <div className=\"mr-10 overflow-x-auto\">\n        <div className=\"font-mono text-[13px] leading-6 sm:text-sm sm:leading-6\">\n          {lines.map((line, idx) => (\n            <div key={idx} className=\"flex\">\n              {showLineNumbers && (\n                <span\n                  className=\"box-content shrink-0 select-none whitespace-nowrap text-right pr-6 font-mono text-zinc-400 dark:text-zinc-600\"\n                  style={{ width: `${String(lines.length).length}ch` }}\n                >\n                  {idx + 1}\n                </span>\n              )}\n              <div className=\"flex-1 whitespace-pre font-mono\">\n                {line.tokens.length > 0 ? line.tokens : \"\\u00A0\"}\n              </div>\n            </div>\n          ))}\n        </div>\n      </div>\n    </div>\n  );\n};\n",
      "type": "registry:ui",
      "target": "@ui/code-block.tsx"
    }
  ]
}
