import React, { useEffect, useMemo } from 'react'; import { Tabs, Typography, Button } from 'antd'; import toReactElement from 'jsonml-to-react-element'; import JsonML from 'jsonml.js/lib/utils'; import Prism from 'prismjs'; import { createStyles } from 'antd-style'; const useStyle = createStyles(({ token, css }) => { const { colorIcon, colorBgTextHover, antCls } = token; return { code: css` position: relative; `, copyButton: css` color: ${colorIcon}; position: absolute; top: 0; inset-inline-end: 16px; width: 32px; text-align: center; background: ${colorBgTextHover}; padding: 0; `, copyIcon: css` ${antCls}-typography-copy { margin-inline-start: 0; } ${antCls}-typography-copy:not(${antCls}-typography-copy-success) { color: ${colorIcon}; &:hover { color: ${colorIcon}; } } `, }; }); const LANGS = { tsx: 'TypeScript', jsx: 'JavaScript', style: 'CSS', }; interface CodePreviewProps { sourceCode?: string; jsxCode?: string; styleCode?: string; onCodeTypeChange?: (activeKey: string) => void; } function toReactComponent(jsonML: any[]) { return toReactElement(jsonML, [ [ (node: any) => JsonML.isElement(node) && JsonML.getTagName(node) === 'pre', (node: any, index: number) => { const attr = JsonML.getAttributes(node); return (
            
          
); }, ], ]); } const CodePreview: React.FC = ({ sourceCode = '', jsxCode = '', styleCode = '', onCodeTypeChange, }) => { // 避免 Tabs 数量不稳定的闪动问题 const initialCodes = {} as Record<'tsx' | 'jsx' | 'style', string>; if (sourceCode) { initialCodes.tsx = ''; } if (jsxCode) { initialCodes.jsx = ''; } if (styleCode) { initialCodes.style = ''; } const [highlightedCodes, setHighlightedCodes] = React.useState(initialCodes); const sourceCodes = { tsx: sourceCode, jsx: jsxCode, style: styleCode, } as Record<'tsx' | 'jsx' | 'style', string>; useEffect(() => { const codes = { tsx: Prism.highlight(sourceCode, Prism.languages.javascript, 'jsx'), jsx: Prism.highlight(jsxCode, Prism.languages.javascript, 'jsx'), style: Prism.highlight(styleCode, Prism.languages.css, 'css'), }; // 去掉空的代码类型 Object.keys(codes).forEach((key: keyof typeof codes) => { if (!codes[key]) { delete codes[key]; } }); setHighlightedCodes(codes); }, [jsxCode, sourceCode, styleCode]); const langList = Object.keys(highlightedCodes); const { styles } = useStyle(); const items = useMemo( () => langList.map((lang: keyof typeof LANGS) => ({ label: LANGS[lang], key: lang, children: (
{toReactComponent(['pre', { lang, highlighted: highlightedCodes[lang] }])}
), })), [JSON.stringify(highlightedCodes)], ); if (!langList.length) { return null; } if (langList.length === 1) { return toReactComponent([ 'pre', { lang: langList[0], highlighted: highlightedCodes[langList[0] as keyof typeof LANGS], className: 'highlight', }, ]); } return ; }; export default CodePreview;