import { cacheLife } from "next/cache"; import { isValidElement } from "react"; import { codeToHtml } from "shiki"; import { CopyButton } from "@/components/copy-button"; import { cn } from "@/lib/utils"; /** * Recursively extracts plain text content from React nodes. * Replacement for the `react-to-text` package. */ const getTextContent = (node: React.ReactNode): string => { if (node == null || typeof node === "boolean") return ""; if (typeof node === "string" || typeof node === "number") return String(node); if (Array.isArray(node)) return node.map(getTextContent).join(""); if (isValidElement<{ children?: React.ReactNode }>(node)) { return getTextContent(node.props.children); } return ""; }; interface CodeBlockProps extends React.ComponentProps<"pre"> { showLineNumbers?: boolean; } const renderCode = async (code: string, lang: string): Promise => { "use cache"; cacheLife("max"); return codeToHtml(code, { lang, themes: { light: "github-light", dark: "github-dark" }, }); }; const CodeBlock = async ({ children, className, showLineNumbers = true, ...props }: CodeBlockProps) => { // Escape hatch for non-code pre blocks if (!isValidElement<{ children?: React.ReactNode; className?: string }>(children)) { return (
        {children}
      
); } const codeString = getTextContent(children.props.children).trim(); const lang = children.props.className?.split("language-")[1] ?? "text"; const html = await renderCode(codeString, lang); return (
); }; export { CodeBlock };