{"$schema":"https://ui.shadcn.com/schema/registry-item.json","name":"code-block","type":"registry:component","title":"Code Block","description":"AI-powered code block component.","files":[{"path":"registry/default/ai-elements/code-block.tsx","type":"registry:component","content":"\"use client\";\n\nimport { Button } from \"@/components/ui/button\";\nimport { cn } from \"@/lib/utils\";\nimport { CheckIcon, CopyIcon } from \"lucide-react\";\nimport {\n  type ComponentProps,\n  createContext,\n  type HTMLAttributes,\n  useContext,\n  useEffect,\n  useRef,\n  useState,\n} from \"react\";\nimport { type BundledLanguage, codeToHtml, type ShikiTransformer } from \"shiki\";\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-md 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            // biome-ignore lint/security/noDangerouslySetInnerHtml: \"this is needed.\"\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            // biome-ignore lint/security/noDangerouslySetInnerHtml: \"this is needed.\"\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 ? CheckIcon : CopyIcon;\n\n  return (\n    <Button\n      className={cn(\"shrink-0\", className)}\n      onClick={copyToClipboard}\n      size=\"icon\"\n      variant=\"ghost\"\n      {...props}\n    >\n      {children ?? <Icon size={14} />}\n    </Button>\n  );\n};\n","target":"components/ai-elements/code-block.tsx"}],"dependencies":["lucide-react","shiki"],"devDependencies":[],"registryDependencies":["button"]}