From 3a3dfeeebab95d85d0a6b045e9a3b58d9999ec63 Mon Sep 17 00:00:00 2001 From: Luca Martial Date: Mon, 11 May 2026 00:35:14 -0700 Subject: [PATCH] feat(docs): add CodeBlock with three context-aware modes - Mode A (Terminal): bash/sh languages or wizard-glyph content - Mode B (VS Code tab): blocks with a title attribute - Mode C (Minimal): everything else Component renders only structural markup; CSS for each mode is added in the following commits. Co-Authored-By: Claude Sonnet 4.6 --- docs/components/code-block.tsx | 110 +++++++++++++++++++++++++++++++++ 1 file changed, 110 insertions(+) create mode 100644 docs/components/code-block.tsx diff --git a/docs/components/code-block.tsx b/docs/components/code-block.tsx new file mode 100644 index 00000000..37c7a44e --- /dev/null +++ b/docs/components/code-block.tsx @@ -0,0 +1,110 @@ +"use client"; + +import { + type ReactNode, + type ReactElement, + isValidElement, +} from "react"; +import { CopyButton } from "./copy-button"; + +type Props = { + children?: ReactNode; + className?: string; + title?: string; + // rehype-pretty-code adds data attributes such as data-language; capture them via index signature + [key: string]: unknown; +}; + +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)) || + WIZARD_GLYPHS.test(codeText); + const hasTitle = typeof title === "string" && title.length > 0; + + // Mode A — Terminal + if (isTerminal) { + return ( +
+
+ + + + + {hasTitle ? title : "zsh"} + + +
+
+          {children}
+        
+
+ ); + } + + // Mode B — VS Code tab (filename present) + if (hasTitle) { + return ( +
+
+ + {title} + {language && {language}} + +
+
+          {children}
+        
+
+ ); + } + + // Mode C — Minimal default + return ( +
+ {language && {language}} + +
+        {children}
+      
+
+ ); +}