import { useMemo, useState } from 'react' import hljs from 'highlight.js' import { CopyButton } from '../shared/CopyButton' type Props = { code: string language?: string maxLines?: number showLineNumbers?: boolean } export function CodeViewer({ code, language, maxLines = 20, showLineNumbers = true }: Props) { const [expanded, setExpanded] = useState(false) const lines = code.split('\n') const isTruncated = !expanded && lines.length > maxLines const visibleCode = isTruncated ? lines.slice(0, maxLines).join('\n') : code const highlightedLines = useMemo(() => { return visibleCode.split('\n').map((line) => { try { if (language && hljs.getLanguage(language)) { return hljs.highlight(line || ' ', { language, ignoreIllegals: true }).value } return hljs.highlightAuto(line || ' ').value } catch { return escapeHtml(line) } }) }, [language, visibleCode]) const startLine = 1 const languageLabel = language || 'code' const visibleLines = visibleCode.split('\n') const lineCountLabel = `${lines.length} ${lines.length === 1 ? 'line' : 'lines'}` const showExpandToggle = lines.length > maxLines return (
{languageLabel} {lineCountLabel}
{visibleLines.map((line, index) => (
{showLineNumbers ? ( {startLine + index} ) : ( )}
))}
{showExpandToggle && ( )}
) } function escapeHtml(str: string): string { return str .replace(/&/g, '&') .replace(//g, '>') .replace(/"/g, '"') }