Preview
Switch between light and dark to inspect the embedded Storybook preview.
Installation
pnpm dlx shadcn@latest add https://ui.vllnt.ai/r/code-block.jsonStorybook
Explore all variants, controls, and accessibility checks in the interactive Storybook playground.
View in StorybookCode
"use client";
import {
type ComponentType,
type ReactNode,
useEffect,
useRef,
useState,
} from "react";
import { Check, Copy } from "lucide-react";
import { useTheme } from "next-themes";
import type { SyntaxHighlighterProps } from "react-syntax-highlighter";
import { cn } from "../../lib/utils";
import { Button } from "../button/button";
type PrismStyle = SyntaxHighlighterProps["style"];
type LoadedHighlighter = {
oneDark: PrismStyle;
oneLight: PrismStyle;
SyntaxHighlighter: ComponentType<SyntaxHighlighterProps>;
};
type CodeBlockProps = {
children: ReactNode;
className?: string;
language?: string;
showLanguage?: boolean;
};
function extractTextFromChildren(children: ReactNode): string {
if (typeof children === "string") {
return children;
}
if (typeof children === "number") {
return String(children);
}
if (Array.isArray(children)) {
return children.map(extractTextFromChildren).join("");
}
if (
children &&
typeof children === "object" &&
"props" in children &&
children.props &&
typeof children.props === "object" &&
"children" in children.props
) {
return extractTextFromChildren(children.props.children as ReactNode);
}
return String(children ?? "");
}
function findScrollableParent(
element: HTMLElement | null,
): HTMLElement | undefined {
if (!element) return undefined;
if (element.scrollHeight > element.clientHeight) return element;
return findScrollableParent(element.parentElement);
}
export function CodeBlock({
children,
className,
language = "typescript",
showLanguage = false,
}: CodeBlockProps) {
const [copied, setCopied] = useState(false);
// react-syntax-highlighter (~10MB) is dynamic-imported on mount so the
// @vllnt/ui barrel's static graph never reaches it — barrel consumers that
// never render a CodeBlock ship zero bytes of it. Null until the chunk loads.
const [highlighter, setHighlighter] = useState<LoadedHighlighter | null>(
null,
);
const { systemTheme, theme } = useTheme();
const resolvedTheme = theme === "system" ? systemTheme : theme;
const isDark = resolvedTheme !== "light";
const code = extractTextFromChildren(children);
const scrollRef = useRef<HTMLDivElement>(null);
useEffect(() => {
let active = true;
void Promise.all([
import("react-syntax-highlighter"),
import("react-syntax-highlighter/dist/esm/styles/prism"),
]).then(([module_, styles]) => {
if (!active) return;
setHighlighter({
oneDark: styles.oneDark,
oneLight: styles.oneLight,
SyntaxHighlighter: module_.Prism,
});
});
return () => {
active = false;
};
}, []);
useEffect(() => {
const element = scrollRef.current;
if (!element) return;
const onWheel = (event: WheelEvent) => {
if (Math.abs(event.deltaY) <= Math.abs(event.deltaX)) return;
const scrollable = findScrollableParent(element);
if (scrollable) {
scrollable.scrollTop += event.deltaY;
event.preventDefault();
}
};
element.addEventListener("wheel", onWheel, { passive: false });
return () => {
element.removeEventListener("wheel", onWheel);
};
}, []);
const handleCopy = async () => {
await navigator.clipboard.writeText(code);
setCopied(true);
setTimeout(() => {
setCopied(false);
}, 2000);
};
const SyntaxHighlighter = highlighter?.SyntaxHighlighter;
const codeStyle = isDark ? highlighter?.oneDark : highlighter?.oneLight;
return (
<div
className={cn(
"relative w-full overflow-hidden rounded-md border bg-background",
className,
)}
>
<div
className="relative overflow-x-auto overflow-y-hidden touch-pan-y"
ref={scrollRef}
>
{SyntaxHighlighter ? (
<SyntaxHighlighter
codeTagProps={{
className: "font-mono text-sm",
style: {
background: "transparent",
display: "block",
},
}}
customStyle={{
background: "oklch(var(--background))",
fontSize: "0.875rem",
margin: 0,
minWidth: "fit-content",
overflowY: "hidden",
padding: "1rem",
}}
language={language}
style={codeStyle}
>
{code}
</SyntaxHighlighter>
) : (
<pre className="m-0 min-w-fit overflow-y-hidden p-4 font-mono text-sm">
<code className="block bg-transparent">{code}</code>
</pre>
)}
<div className="absolute right-2 top-2 flex items-center gap-2">
{showLanguage ? (
<span className="text-xs font-mono text-muted-foreground uppercase tracking-wider">
{language}
</span>
) : null}
<Button
aria-label={copied ? "Copied" : "Copy code"}
className="size-8"
onClick={handleCopy}
size="icon"
variant="ghost"
>
{copied ? (
<Check className="size-3" />
) : (
<Copy className="size-3" />
)}
</Button>
</div>
</div>
</div>
);
}