File size: 2,540 Bytes
6927c07 621b880 2a3d5f5 fcfef74 6927c07 621b880 6927c07 621b880 6927c07 621b880 6927c07 621b880 6927c07 621b880 6927c07 621b880 6927c07 621b880 6927c07 621b880 6927c07 621b880 6927c07 621b880 6927c07 621b880 6927c07 621b880 6927c07 621b880 6927c07 621b880 6927c07 621b880 6927c07 621b880 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 |
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<string | undefined>(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 (
<div className={classNames('relative group text-left', className)}>
<div
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',
{
'rounded-l-0 opacity-100': copied,
},
)}
>
{!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>
</div>
);
},
);
|