bolt.new/packages/bolt/app/components/chat/CodeBlock.tsx

82 lines
2.5 KiB
TypeScript
Raw Normal View History

2024-07-10 16:44:39 +00:00
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';
2024-07-10 16:44:39 +00:00
import styles from './CodeBlock.module.scss';
const logger = createScopedLogger('CodeBlock');
interface CodeBlockProps {
className?: string;
2024-07-10 16:44:39 +00:00
code: string;
language?: BundledLanguage | SpecialLanguage;
theme?: 'light-plus' | 'dark-plus';
disableCopy?: boolean;
2024-07-10 16:44:39 +00:00
}
export const CodeBlock = memo(
({ className, code, language = 'plaintext', theme = 'dark-plus', disableCopy = false }: CodeBlockProps) => {
const [html, setHTML] = useState<string | undefined>(undefined);
const [copied, setCopied] = useState(false);
2024-07-10 16:44:39 +00:00
const copyToClipboard = () => {
if (copied) {
return;
}
2024-07-10 16:44:39 +00:00
navigator.clipboard.writeText(code);
2024-07-10 16:44:39 +00:00
setCopied(true);
2024-07-10 16:44:39 +00:00
setTimeout(() => {
setCopied(false);
}, 2000);
};
2024-07-10 16:44:39 +00:00
useEffect(() => {
if (language && !isSpecialLang(language) && !(language in bundledLanguages)) {
logger.warn(`Unsupported language '${language}'`);
}
2024-07-10 16:44:39 +00:00
logger.trace(`Language = ${language}`);
2024-07-10 16:44:39 +00:00
const processCode = async () => {
setHTML(await codeToHtml(code, { lang: language, theme }));
};
2024-07-10 16:44:39 +00:00
processCode();
}, [code]);
2024-07-10 16:44:39 +00:00
return (
<div className={classNames('relative group text-left', className)}>
<div
2024-07-10 16:44:39 +00:00
className={classNames(
styles.CopyButtonContainer,
'bg-white absolute top-[10px] right-[10px] rounded-md z-10 text-lg flex items-center justify-center opacity-0 group-hover:opacity-100',
2024-07-10 16:44:39 +00:00
{
'rounded-l-0 opacity-100': copied,
2024-07-10 16:44:39 +00:00
},
)}
>
{!disableCopy && (
<button
className={classNames(
'flex items-center bg-transparent p-[6px] justify-center before:bg-white before:rounded-l-md before:text-gray-500 before:border-r before:border-gray-300',
{
'before:opacity-0': !copied,
'before:opacity-100': copied,
},
)}
title="Copy Code"
onClick={() => copyToClipboard()}
>
<div className="i-ph:clipboard-text-duotone"></div>
</button>
)}
</div>
<div dangerouslySetInnerHTML={{ __html: html ?? '' }}></div>
2024-07-10 16:44:39 +00:00
</div>
);
},
);