import * as React from "react"; import { Slot } from "@radix-ui/react-slot"; import { CopyIcon, CheckIcon } from "@phosphor-icons/react"; import { cn } from "../../utils.js"; import { copyToClipboard } from "./clipboard-text.js"; import "./code.css"; export interface CodeProps extends React.HTMLAttributes { code?: string; language?: string; filename?: string; showLineNumbers?: boolean; copyable?: boolean; copyTimeout?: number; asChild?: boolean; } interface CodeContextValue { code: string; language?: string; filename?: string; copied: boolean; copy: () => Promise; showLineNumbers: boolean; } const CodeContext = React.createContext(null); export function useCodeContext() { const context = React.useContext(CodeContext); if (!context) { throw new Error("Code compound subcomponents must be used within Code"); } return context; } export const Code = React.forwardRef( ( { className, code = "", language, filename, showLineNumbers = false, copyable = true, copyTimeout = 2000, asChild = false, children, ...props }, ref ) => { const [copied, setCopied] = React.useState(false); const timeoutRef = React.useRef | null>(null); const resolvedCode = code || (typeof children === "string" ? children : ""); const copy = React.useCallback(async () => { if (!resolvedCode) return; const ok = await copyToClipboard(resolvedCode); if (ok) { setCopied(true); if (timeoutRef.current) clearTimeout(timeoutRef.current); timeoutRef.current = setTimeout(() => { setCopied(false); }, copyTimeout); } }, [resolvedCode, copyTimeout]); React.useEffect(() => { return () => { if (timeoutRef.current) clearTimeout(timeoutRef.current); }; }, []); const Comp = asChild ? Slot : "div"; return ( {children ? ( children ) : ( <> {(filename || language || copyable) && (
{filename && {filename}} {language && {language}}
{copyable && }
)} {resolvedCode} )}
); } ); Code.displayName = "Code"; export interface CodeHeaderProps extends React.HTMLAttributes { asChild?: boolean; } export const CodeHeader = React.forwardRef( ({ className, asChild = false, ...props }, ref) => { const Comp = asChild ? Slot : "div"; return ( ); } ); CodeHeader.displayName = "CodeHeader"; export interface CodeFilenameProps extends React.HTMLAttributes { asChild?: boolean; } export const CodeFilename = React.forwardRef< HTMLSpanElement, CodeFilenameProps >(({ className, asChild = false, children, ...props }, ref) => { const { filename } = useCodeContext(); const Comp = asChild ? Slot : "span"; return ( {children ?? filename} ); }); CodeFilename.displayName = "CodeFilename"; export const CodeTitle = CodeFilename; export interface CodeLanguageProps extends React.HTMLAttributes { asChild?: boolean; } export const CodeLanguage = React.forwardRef< HTMLSpanElement, CodeLanguageProps >(({ className, asChild = false, children, ...props }, ref) => { const { language } = useCodeContext(); const Comp = asChild ? Slot : "span"; return ( {children ?? language} ); }); CodeLanguage.displayName = "CodeLanguage"; export interface CodeCopyButtonProps extends React.ButtonHTMLAttributes { asChild?: boolean; } export const CodeCopyButton = React.forwardRef< HTMLButtonElement, CodeCopyButtonProps >(({ className, asChild = false, type = "button", onClick, children, ...props }, ref) => { const { copy, copied } = useCodeContext(); const handleClick = (e: React.MouseEvent) => { onClick?.(e); if (!e.defaultPrevented) { copy(); } }; const Comp = asChild ? Slot : "button"; return ( {children ?? (copied ? ( ) : ( ))} ); }); CodeCopyButton.displayName = "CodeCopyButton"; export const CodeCopy = CodeCopyButton; export interface CodeContentProps extends React.HTMLAttributes { asChild?: boolean; } export const CodeContent = React.forwardRef( ({ className, asChild = false, children, ...props }, ref) => { const { code, showLineNumbers } = useCodeContext(); const Comp = asChild ? Slot : "pre"; const contentText = typeof children === "string" ? children : code; const lines = contentText ? contentText.split("\n") : []; return ( {showLineNumbers && lines.length > 0 ? (
{lines.map((_, i) => (
{i + 1}
))}
{lines.map((line, i) => (
{line || " "}
))}
) : ( children ?? code )}
); } ); CodeContent.displayName = "CodeContent"; export const CodePre = CodeContent; /* InlineCode component */ export interface InlineCodeProps extends React.HTMLAttributes { asChild?: boolean; } export const InlineCode = React.forwardRef( ({ className, asChild = false, ...props }, ref) => { const Comp = asChild ? Slot : "code"; return ( ); } ); InlineCode.displayName = "InlineCode";