{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "ai-code-block",
  "title": "AI: Code Block",
  "description": "A Shiki-highlighted code block with light and dark rendering and a copy button.",
  "dependencies": [
    "@untitledui/icons@^0.0.22",
    "shiki@^4.3.0"
  ],
  "registryDependencies": [
    "@nx-ui/utils",
    "@nx-ui/button"
  ],
  "files": [
    {
      "path": "components/ai/code-block.tsx",
      "content": "\"use client\"\n\nimport {\n  type ComponentProps,\n  type HTMLAttributes,\n  createContext,\n  useContext,\n  useEffect,\n  useRef,\n  useState,\n} from \"react\"\nimport { Check, Copy01 } from \"@untitledui/icons\"\nimport { type BundledLanguage, type ShikiTransformer, codeToHtml } from \"shiki\"\n\nimport { Button } from \"@/components/ui/button\"\nimport { cn } from \"@/lib/utils\"\n\ntype CodeBlockProps = HTMLAttributes<HTMLDivElement> & {\n  code: string\n  language: BundledLanguage\n  showLineNumbers?: boolean\n}\n\ntype CodeBlockContextType = {\n  code: string\n}\n\nconst CodeBlockContext = createContext<CodeBlockContextType>({\n  code: \"\",\n})\n\nconst lineNumberTransformer: ShikiTransformer = {\n  name: \"line-numbers\",\n  line(node, line) {\n    node.children.unshift({\n      type: \"element\",\n      tagName: \"span\",\n      properties: {\n        className: [\n          \"inline-block\",\n          \"min-w-10\",\n          \"mr-4\",\n          \"text-right\",\n          \"select-none\",\n          \"text-muted-foreground\",\n        ],\n      },\n      children: [{ type: \"text\", value: String(line) }],\n    })\n  },\n}\n\nexport async function highlightCode(\n  code: string,\n  language: BundledLanguage,\n  showLineNumbers = false\n) {\n  const transformers: ShikiTransformer[] = showLineNumbers\n    ? [lineNumberTransformer]\n    : []\n\n  return await Promise.all([\n    codeToHtml(code, {\n      lang: language,\n      theme: \"one-light\",\n      transformers,\n    }),\n    codeToHtml(code, {\n      lang: language,\n      theme: \"one-dark-pro\",\n      transformers,\n    }),\n  ])\n}\n\nexport const CodeBlock = ({\n  code,\n  language,\n  showLineNumbers = false,\n  className,\n  children,\n  ...props\n}: CodeBlockProps) => {\n  const [html, setHtml] = useState<string>(\"\")\n  const [darkHtml, setDarkHtml] = useState<string>(\"\")\n  const mounted = useRef(false)\n\n  useEffect(() => {\n    highlightCode(code, language, showLineNumbers).then(([light, dark]) => {\n      if (!mounted.current) {\n        setHtml(light)\n        setDarkHtml(dark)\n        mounted.current = true\n      }\n    })\n\n    return () => {\n      mounted.current = false\n    }\n  }, [code, language, showLineNumbers])\n\n  return (\n    <CodeBlockContext.Provider value={{ code }}>\n      <div\n        className={cn(\n          \"group relative w-full overflow-hidden rounded-lg border bg-background text-foreground\",\n          className\n        )}\n        {...props}\n      >\n        <div className=\"relative\">\n          <div\n            className=\"overflow-auto dark:hidden [&>pre]:m-0 [&>pre]:bg-background! [&>pre]:p-4 [&>pre]:text-foreground! [&>pre]:text-sm [&_code]:font-mono [&_code]:text-sm\"\n            dangerouslySetInnerHTML={{ __html: html }}\n          />\n          <div\n            className=\"hidden overflow-auto dark:block [&>pre]:m-0 [&>pre]:bg-background! [&>pre]:p-4 [&>pre]:text-foreground! [&>pre]:text-sm [&_code]:font-mono [&_code]:text-sm\"\n            dangerouslySetInnerHTML={{ __html: darkHtml }}\n          />\n          {children && (\n            <div className=\"absolute top-2 right-2 flex items-center gap-2\">\n              {children}\n            </div>\n          )}\n        </div>\n      </div>\n    </CodeBlockContext.Provider>\n  )\n}\n\nexport type CodeBlockCopyButtonProps = ComponentProps<typeof Button> & {\n  onCopy?: () => void\n  onError?: (error: Error) => void\n  timeout?: number\n}\n\nexport const CodeBlockCopyButton = ({\n  onCopy,\n  onError,\n  timeout = 2000,\n  children,\n  className,\n  ...props\n}: CodeBlockCopyButtonProps) => {\n  const [isCopied, setIsCopied] = useState(false)\n  const { code } = useContext(CodeBlockContext)\n\n  const copyToClipboard = async () => {\n    if (typeof window === \"undefined\" || !navigator?.clipboard?.writeText) {\n      onError?.(new Error(\"Clipboard API not available\"))\n      return\n    }\n\n    try {\n      await navigator.clipboard.writeText(code)\n      setIsCopied(true)\n      onCopy?.()\n      setTimeout(() => setIsCopied(false), timeout)\n    } catch (error) {\n      onError?.(error as Error)\n    }\n  }\n\n  const Icon = isCopied ? Check : Copy01\n\n  return (\n    <Button\n      className={cn(\"shrink-0\", className)}\n      onClick={copyToClipboard}\n      size=\"icon-sm\"\n      variant=\"ghost\"\n      aria-label=\"Copy code\"\n      {...props}\n    >\n      {children ?? <Icon className=\"size-3.5\" />}\n    </Button>\n  )\n}\n",
      "type": "registry:ui",
      "target": "components/ai/code-block.tsx"
    }
  ],
  "type": "registry:ui"
}