"use client"; import { type ComponentPropsWithoutRef, type ReactNode, type ReactElement, isValidElement, } from "react"; import { CopyButton } from "./copy-button"; type Props = ComponentPropsWithoutRef<"pre"> & { title?: string; "data-language"?: string; }; const TERMINAL_LANGS = new Set(["bash", "sh", "shell", "zsh"]); const WIZARD_GLYPHS = /^\s*[◆◇◯◐○●]/; function extractText(node: ReactNode): string { if (typeof node === "string") return node; if (typeof node === "number") return String(node); if (Array.isArray(node)) return node.map(extractText).join(""); if (isValidElement(node)) { const props = (node as ReactElement<{ children?: ReactNode }>).props; return extractText(props.children); } return ""; } function detectLanguage(props: Props, children: ReactNode): string | null { const dataLang = props["data-language"]; if (typeof dataLang === "string" && dataLang) return dataLang; const className = typeof props.className === "string" ? props.className : ""; const m = className.match(/language-([\w-]+)/); if (m) return m[1]; if (isValidElement(children)) { const childProps = (children as ReactElement<{ className?: string }>).props; const childClass = typeof childProps.className === "string" ? childProps.className : ""; const cm = childClass.match(/language-([\w-]+)/); if (cm) return cm[1]; } return null; } export function CodeBlock(props: Props) { const { children, title, className: _ignored, ...rest } = props; const language = detectLanguage(props, children); const codeText = extractText(children); const isTerminal = language !== null && TERMINAL_LANGS.has(language); const isOutput = !isTerminal && WIZARD_GLYPHS.test(codeText); const hasTitle = typeof title === "string" && title.length > 0; // Mode A — Terminal (commands the user types) if (isTerminal) { return (
{children}
{children}
{children}
{children}