File size: 2,573 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 5f144e6 6927c07 621b880 6927c07 621b880 5f144e6 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-transparant 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-accent-500 p-[6px] justify-center before:bg-white before:rounded-l-md before:text-gray-500 before:border-r before:border-gray-300 rounded-md transition-theme',
{
'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>
);
},
);
|