import { forwardRef, useState, useId, useEffect, useRef, useCallback, useMemo, } from 'react' import styled from 'styled-components' import { Check, Copy, FileCode } from 'lucide-react' import Block from '../Primitives/Block/Block' import Button from '../Button/Button' import { ButtonSize, ButtonSubType, ButtonType } from '../Button/types' import { useResponsiveTokens } from '../../hooks/useResponsiveTokens' import type { CodeBlockTokenType } from './codeBlock.token' import { CodeBlockVariant, DiffLineType, type CodeBlockProps } from './types' import CodeBlockDiffView from './CodeBlockDiffView/CodeBlockDiffView' import { PreElement, CodeLineWrapper, LineNumberGutter, TokenizedCodeLine, type DiffRowRenderContext, } from './CodeBlockLineParts' import { tokenizeLine, getDiffLineBackground, getTokenColor, getDiffGutterStyle, shouldShowLineNumbers as shouldShowLineNumbersUtil, processLines, formatCode, buildDiffViewSegments, } from './utils' const CopyOverlay = styled.div` position: absolute; top: 12px; right: 12px; z-index: 10; opacity: 0; pointer-events: none; transition: opacity 0.15s ease; .code-body:hover & { opacity: 1; pointer-events: auto; } ` const CodeBlock = forwardRef( ( { code, variant = CodeBlockVariant.DEFAULT, showLineNumbers, showHeader = true, header = 'Header', headerLeftSlot, headerRightSlot, diffLines, showCopyButton = true, autoFormat = false, language, isDiffUnchangedCollapsed = true, diffContextLines = 3, diffExpandChunk = 20, maxHeight, }, ref ) => { const tokens = useResponsiveTokens('CODE_BLOCK') const [isCopied, setIsCopied] = useState(false) const codeBlockId = useId().replace(/:/g, '-') const codeContentId = `${codeBlockId}-code` const headerId = `${codeBlockId}-header` const timeoutRef = useRef | null>(null) // Format code if autoFormat is enabled const formattedCode = autoFormat ? formatCode(code, language) : code // Determine if line numbers should be shown based on variant or explicit prop const shouldShowLineNumbers = shouldShowLineNumbersUtil( showLineNumbers, variant ) // Use diffLines if variant is diff, otherwise use formatted code const isDiffMode = variant === CodeBlockVariant.DIFF && Boolean(diffLines) const lines = processLines(isDiffMode, diffLines, formattedCode) const diffViewSegments = useMemo(() => { if (!isDiffMode || !diffLines?.length) { return [] } return buildDiffViewSegments( diffLines, diffContextLines, isDiffUnchangedCollapsed ) }, [isDiffMode, diffLines, diffContextLines, isDiffUnchangedCollapsed]) const copyToClipboard = useCallback(() => { navigator.clipboard.writeText(formattedCode) setIsCopied(true) if (timeoutRef.current) { clearTimeout(timeoutRef.current) } timeoutRef.current = setTimeout(() => { setIsCopied(false) timeoutRef.current = null }, 2000) }, [formattedCode]) useEffect(() => { return () => { if (timeoutRef.current) { clearTimeout(timeoutRef.current) timeoutRef.current = null } } }, []) // Screen reader announcement for copy status useEffect(() => { if (isCopied) { // Announce copy success to screen readers const announcement = document.createElement('div') announcement.setAttribute('role', 'status') announcement.setAttribute('aria-live', 'polite') announcement.setAttribute('aria-atomic', 'true') announcement.style.position = 'absolute' announcement.style.left = '-10000px' announcement.style.width = '1px' announcement.style.height = '1px' announcement.style.overflow = 'hidden' announcement.textContent = `Code from ${header || 'code block'} copied to clipboard` document.body.appendChild(announcement) // Reset after 2 seconds const timer = setTimeout(() => { setIsCopied(false) if (document.body.contains(announcement)) { document.body.removeChild(announcement) } }, 2000) return () => { clearTimeout(timer) if (document.body.contains(announcement)) { document.body.removeChild(announcement) } } } }, [isCopied, header]) // Generate accessible label for code block const codeBlockLabel = header ? `Code block: ${header}` : 'Code block' const codeBlockDescription = language ? `${codeBlockLabel}, ${language} language` : codeBlockLabel // Use utility functions const tokenizeLineLocal = (line: string) => tokenizeLine(line, language) const getTokenColorLocal = (type: string) => getTokenColor(type, tokens.body.syntax) const getDiffGutterStyleLocal = (lineType?: DiffLineType) => getDiffGutterStyle(lineType, isDiffMode, tokens.body.gutter) const getDiffLineBackgroundLocal = (lineType?: DiffLineType) => getDiffLineBackground( lineType, isDiffMode, tokens.body.highlightedLine ) const diffRowCtx = useMemo( () => ({ shouldShowLineNumbers, tokens, tokenizeLine: (line: string) => tokenizeLine(line, language), getTokenColor: (type: string) => getTokenColor(type, tokens.body.syntax), getDiffGutterStyle: (lineType?: DiffLineType) => getDiffGutterStyle( lineType, isDiffMode, tokens.body.gutter ), getDiffLineBackground: (lineType?: DiffLineType) => getDiffLineBackground( lineType, isDiffMode, tokens.body.highlightedLine ), }), [shouldShowLineNumbers, tokens, language, isDiffMode] ) return ( {/* Header */} {showHeader && ( {headerLeftSlot || ( {showCopyButton && !isDiffMode && (