import { memo, useEffect, useState } from 'react'; import { bundledLanguages, codeToHtml, isSpecialLang, type BundledLanguage, type SpecialLanguage } from 'shiki'; import { classNames } from '~/utils/classNames'; import { createScopedLogger } from '~/utils/logger'; import styles from './CodeBlock.module.scss'; const logger = createScopedLogger('CodeBlock'); interface CodeBlockProps { className?: string; code: string; language?: BundledLanguage | SpecialLanguage; theme?: 'light-plus' | 'dark-plus'; disableCopy?: boolean; } export const CodeBlock = memo( ({ className, code, language = 'plaintext', theme = 'dark-plus', disableCopy = false }: CodeBlockProps) => { const [html, setHTML] = useState(undefined); const [copied, setCopied] = useState(false); const copyToClipboard = () => { if (copied) { return; } navigator.clipboard.writeText(code); setCopied(true); setTimeout(() => { setCopied(false); }, 2000); }; useEffect(() => { if (language && !isSpecialLang(language) && !(language in bundledLanguages)) { logger.warn(`Unsupported language '${language}'`); } logger.trace(`Language = ${language}`); const processCode = async () => { setHTML(await codeToHtml(code, { lang: language, theme })); }; processCode(); }, [code]); return (
{!disableCopy && ( )}
); }, );