(props: {
code: string;
lang?: string;
title?: string;
maxHeight?: string;
className?: string;
theme?: ShikiThemeProp;
/** Fires after a successful copy. Receives nothing — the code may be sensitive. */
onCopy?: () => void;
})
| 83 | // --------------------------------------------------------------------------- |
| 84 | |
| 85 | export function CodeBlock(props: { |
| 86 | code: string; |
| 87 | lang?: string; |
| 88 | title?: string; |
| 89 | maxHeight?: string; |
| 90 | className?: string; |
| 91 | theme?: ShikiThemeProp; |
| 92 | /** Fires after a successful copy. Receives nothing — the code may be sensitive. */ |
| 93 | onCopy?: () => void; |
| 94 | }) { |
| 95 | const { code, lang: langHint, title, className, theme, onCopy } = props; |
| 96 | const [expanded, setExpanded] = useState(false); |
| 97 | const [copied, setCopied] = useState(false); |
| 98 | |
| 99 | const language = useMemo(() => detectLanguage(code, langHint), [code, langHint]); |
| 100 | const highlighted = useHighlighted(code, language, theme); |
| 101 | |
| 102 | const lines = code.split("\n"); |
| 103 | const isLong = lines.length > 24; |
| 104 | const maxH = !expanded && isLong ? (props.maxHeight ?? "24rem") : undefined; |
| 105 | |
| 106 | const handleCopy = useCallback(() => { |
| 107 | void copyToClipboard(code).then((ok) => { |
| 108 | if (!ok) { |
| 109 | toast.error("Failed to copy to clipboard"); |
| 110 | return; |
| 111 | } |
| 112 | setCopied(true); |
| 113 | onCopy?.(); |
| 114 | setTimeout(() => setCopied(false), 1500); |
| 115 | }); |
| 116 | }, [code, onCopy]); |
| 117 | |
| 118 | return ( |
| 119 | <div className={cn("rounded-lg border border-border bg-card/60 overflow-hidden", className)}> |
| 120 | {title && ( |
| 121 | <div className="flex items-center justify-between border-b border-border px-3 py-2"> |
| 122 | <span className="text-sm font-medium uppercase tracking-wider text-muted-foreground"> |
| 123 | {title} |
| 124 | </span> |
| 125 | <Button |
| 126 | variant="ghost" |
| 127 | size="icon-xs" |
| 128 | onClick={handleCopy} |
| 129 | className="text-muted-foreground hover:text-muted-foreground" |
| 130 | title="Copy" |
| 131 | > |
| 132 | {copied ? <CheckIcon /> : <CopyIcon />} |
| 133 | </Button> |
| 134 | </div> |
| 135 | )} |
| 136 | <div className="group relative"> |
| 137 | {!title && ( |
| 138 | <Button |
| 139 | variant="ghost" |
| 140 | size="icon-xs" |
| 141 | onClick={handleCopy} |
| 142 | className="absolute right-2 top-2 z-10 rounded-md border border-border bg-card/90 p-1.5 text-muted-foreground opacity-0 backdrop-blur-sm hover:text-foreground group-hover:opacity-100 transition-opacity" |
nothing calls this directly
no test coverage detected