mirror of
https://github.com/ant-design/ant-design.git
synced 2025-01-19 06:43:16 +08:00
docs: move copy button into code previewer (#45099)
This commit is contained in:
parent
e91abf3df3
commit
07eb5ba82d
@ -1,11 +1,5 @@
|
|||||||
import React, { useContext, useEffect, useRef, useState } from 'react';
|
import React, { useContext, useEffect, useRef, useState } from 'react';
|
||||||
import {
|
import { LinkOutlined, ThunderboltOutlined, UpOutlined } from '@ant-design/icons';
|
||||||
CheckOutlined,
|
|
||||||
LinkOutlined,
|
|
||||||
SnippetsOutlined,
|
|
||||||
ThunderboltOutlined,
|
|
||||||
UpOutlined,
|
|
||||||
} from '@ant-design/icons';
|
|
||||||
import type { Project } from '@stackblitz/sdk';
|
import type { Project } from '@stackblitz/sdk';
|
||||||
import stackblitzSdk from '@stackblitz/sdk';
|
import stackblitzSdk from '@stackblitz/sdk';
|
||||||
import { Alert, Badge, Space, Tooltip } from 'antd';
|
import { Alert, Badge, Space, Tooltip } from 'antd';
|
||||||
@ -13,7 +7,6 @@ import { createStyles, css } from 'antd-style';
|
|||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { FormattedMessage, useSiteData } from 'dumi';
|
import { FormattedMessage, useSiteData } from 'dumi';
|
||||||
import LZString from 'lz-string';
|
import LZString from 'lz-string';
|
||||||
import CopyToClipboard from 'react-copy-to-clipboard';
|
|
||||||
|
|
||||||
import type { AntdPreviewerProps } from '.';
|
import type { AntdPreviewerProps } from '.';
|
||||||
import useLocation from '../../../hooks/useLocation';
|
import useLocation from '../../../hooks/useLocation';
|
||||||
@ -125,8 +118,6 @@ const CodePreviewer: React.FC<AntdPreviewerProps> = (props) => {
|
|||||||
const riddleIconRef = useRef<HTMLFormElement>(null);
|
const riddleIconRef = useRef<HTMLFormElement>(null);
|
||||||
const codepenIconRef = useRef<HTMLFormElement>(null);
|
const codepenIconRef = useRef<HTMLFormElement>(null);
|
||||||
const [codeExpand, setCodeExpand] = useState<boolean>(false);
|
const [codeExpand, setCodeExpand] = useState<boolean>(false);
|
||||||
const [copyTooltipOpen, setCopyTooltipOpen] = useState<boolean>(false);
|
|
||||||
const [copied, setCopied] = useState<boolean>(false);
|
|
||||||
const [codeType, setCodeType] = useState<string>('tsx');
|
const [codeType, setCodeType] = useState<string>('tsx');
|
||||||
const { theme } = useContext<SiteContextProps>(SiteContext);
|
const { theme } = useContext<SiteContextProps>(SiteContext);
|
||||||
|
|
||||||
@ -147,18 +138,6 @@ const CodePreviewer: React.FC<AntdPreviewerProps> = (props) => {
|
|||||||
track({ type: 'expand', demo });
|
track({ type: 'expand', demo });
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleCodeCopied = (demo: string) => {
|
|
||||||
setCopied(true);
|
|
||||||
track({ type: 'copy', demo });
|
|
||||||
};
|
|
||||||
|
|
||||||
const onCopyTooltipOpenChange = (open: boolean) => {
|
|
||||||
setCopyTooltipOpen(open);
|
|
||||||
if (open) {
|
|
||||||
setCopied(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (asset.id === hash.slice(1)) {
|
if (asset.id === hash.slice(1)) {
|
||||||
anchorRef.current?.click();
|
anchorRef.current?.click();
|
||||||
@ -483,17 +462,6 @@ createRoot(document.getElementById('container')).render(<Demo />);
|
|||||||
<ThunderboltOutlined className="code-box-stackblitz" />
|
<ThunderboltOutlined className="code-box-stackblitz" />
|
||||||
</span>
|
</span>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
<CopyToClipboard text={parsedSourceCode} onCopy={() => handleCodeCopied(asset.id)}>
|
|
||||||
<Tooltip
|
|
||||||
open={copyTooltipOpen as boolean}
|
|
||||||
onOpenChange={onCopyTooltipOpenChange}
|
|
||||||
title={<FormattedMessage id={`app.demo.${copied ? 'copied' : 'copy'}`} />}
|
|
||||||
>
|
|
||||||
{React.createElement(copied && copyTooltipOpen ? CheckOutlined : SnippetsOutlined, {
|
|
||||||
className: 'code-box-code-copy code-box-code-action',
|
|
||||||
})}
|
|
||||||
</Tooltip>
|
|
||||||
</CopyToClipboard>
|
|
||||||
<Tooltip title={<FormattedMessage id="app.demo.separate" />}>
|
<Tooltip title={<FormattedMessage id="app.demo.separate" />}>
|
||||||
<a
|
<a
|
||||||
className="code-box-code-action"
|
className="code-box-code-action"
|
||||||
@ -564,10 +532,10 @@ createRoot(document.getElementById('container')).render(<Demo />);
|
|||||||
if (!style) {
|
if (!style) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const styleTag = document.createElement('style');
|
const styleTag = document.createElement('style') as HTMLStyleElement;
|
||||||
styleTag.type = 'text/css';
|
styleTag.type = 'text/css';
|
||||||
styleTag.innerHTML = style;
|
styleTag.innerHTML = style;
|
||||||
styleTag['data-demo-url'] = demoUrl;
|
(styleTag as any)['data-demo-url'] = demoUrl;
|
||||||
document.head.appendChild(styleTag);
|
document.head.appendChild(styleTag);
|
||||||
return () => {
|
return () => {
|
||||||
document.head.removeChild(styleTag);
|
document.head.removeChild(styleTag);
|
||||||
@ -576,7 +544,7 @@ createRoot(document.getElementById('container')).render(<Demo />);
|
|||||||
|
|
||||||
if (version) {
|
if (version) {
|
||||||
return (
|
return (
|
||||||
<Badge.Ribbon text={version} color={version.includes('<') ? 'red' : null}>
|
<Badge.Ribbon text={version} color={version.includes('<') ? 'red' : undefined}>
|
||||||
{codeBox}
|
{codeBox}
|
||||||
</Badge.Ribbon>
|
</Badge.Ribbon>
|
||||||
);
|
);
|
||||||
|
@ -5,7 +5,7 @@ import { CheckOutlined, SketchOutlined } from '@ant-design/icons';
|
|||||||
import { nodeToGroup } from 'html2sketch';
|
import { nodeToGroup } from 'html2sketch';
|
||||||
import copy from 'copy-to-clipboard';
|
import copy from 'copy-to-clipboard';
|
||||||
import { App } from 'antd';
|
import { App } from 'antd';
|
||||||
import type { AntdPreviewerProps } from '.';
|
import type { AntdPreviewerProps } from './Previewer';
|
||||||
|
|
||||||
const useStyle = createStyles(({ token, css }) => ({
|
const useStyle = createStyles(({ token, css }) => ({
|
||||||
wrapper: css`
|
wrapper: css`
|
||||||
|
@ -1,8 +1,43 @@
|
|||||||
import React, { useEffect, useMemo } from 'react';
|
import React, { useEffect, useMemo } from 'react';
|
||||||
import { Tabs } from 'antd';
|
import { Tabs, Typography, Button } from 'antd';
|
||||||
import toReactElement from 'jsonml-to-react-element';
|
import toReactElement from 'jsonml-to-react-element';
|
||||||
import JsonML from 'jsonml.js/lib/utils';
|
import JsonML from 'jsonml.js/lib/utils';
|
||||||
import Prism from 'prismjs';
|
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 = {
|
const LANGS = {
|
||||||
tsx: 'TypeScript',
|
tsx: 'TypeScript',
|
||||||
@ -40,7 +75,7 @@ const CodePreview: React.FC<CodePreviewProps> = ({
|
|||||||
onCodeTypeChange,
|
onCodeTypeChange,
|
||||||
}) => {
|
}) => {
|
||||||
// 避免 Tabs 数量不稳定的闪动问题
|
// 避免 Tabs 数量不稳定的闪动问题
|
||||||
const initialCodes = {};
|
const initialCodes = {} as Record<'tsx' | 'jsx' | 'style', string>;
|
||||||
if (sourceCode) {
|
if (sourceCode) {
|
||||||
initialCodes.tsx = '';
|
initialCodes.tsx = '';
|
||||||
}
|
}
|
||||||
@ -51,7 +86,11 @@ const CodePreview: React.FC<CodePreviewProps> = ({
|
|||||||
initialCodes.style = '';
|
initialCodes.style = '';
|
||||||
}
|
}
|
||||||
const [highlightedCodes, setHighlightedCodes] = React.useState(initialCodes);
|
const [highlightedCodes, setHighlightedCodes] = React.useState(initialCodes);
|
||||||
|
const sourceCodes = {
|
||||||
|
tsx: sourceCode,
|
||||||
|
jsx: jsxCode,
|
||||||
|
style: styleCode,
|
||||||
|
} as Record<'tsx' | 'jsx' | 'style', string>;
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const codes = {
|
const codes = {
|
||||||
tsx: Prism.highlight(sourceCode, Prism.languages.javascript, 'jsx'),
|
tsx: Prism.highlight(sourceCode, Prism.languages.javascript, 'jsx'),
|
||||||
@ -59,7 +98,7 @@ const CodePreview: React.FC<CodePreviewProps> = ({
|
|||||||
style: Prism.highlight(styleCode, Prism.languages.css, 'css'),
|
style: Prism.highlight(styleCode, Prism.languages.css, 'css'),
|
||||||
};
|
};
|
||||||
// 去掉空的代码类型
|
// 去掉空的代码类型
|
||||||
Object.keys(codes).forEach((key) => {
|
Object.keys(codes).forEach((key: keyof typeof codes) => {
|
||||||
if (!codes[key]) {
|
if (!codes[key]) {
|
||||||
delete codes[key];
|
delete codes[key];
|
||||||
}
|
}
|
||||||
@ -68,12 +107,22 @@ const CodePreview: React.FC<CodePreviewProps> = ({
|
|||||||
}, [jsxCode, sourceCode, styleCode]);
|
}, [jsxCode, sourceCode, styleCode]);
|
||||||
|
|
||||||
const langList = Object.keys(highlightedCodes);
|
const langList = Object.keys(highlightedCodes);
|
||||||
|
|
||||||
|
const { styles } = useStyle();
|
||||||
|
|
||||||
const items = useMemo(
|
const items = useMemo(
|
||||||
() =>
|
() =>
|
||||||
langList.map((lang) => ({
|
langList.map((lang: keyof typeof LANGS) => ({
|
||||||
label: LANGS[lang],
|
label: LANGS[lang],
|
||||||
key: lang,
|
key: lang,
|
||||||
children: toReactComponent(['pre', { lang, highlighted: highlightedCodes[lang] }]),
|
children: (
|
||||||
|
<div className={styles.code}>
|
||||||
|
{toReactComponent(['pre', { lang, highlighted: highlightedCodes[lang] }])}
|
||||||
|
<Button type="text" className={styles.copyButton}>
|
||||||
|
<Typography.Text className={styles.copyIcon} copyable={{ text: sourceCodes[lang] }} />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
})),
|
})),
|
||||||
[JSON.stringify(highlightedCodes)],
|
[JSON.stringify(highlightedCodes)],
|
||||||
);
|
);
|
||||||
@ -85,7 +134,11 @@ const CodePreview: React.FC<CodePreviewProps> = ({
|
|||||||
if (langList.length === 1) {
|
if (langList.length === 1) {
|
||||||
return toReactComponent([
|
return toReactComponent([
|
||||||
'pre',
|
'pre',
|
||||||
{ lang: langList[0], highlighted: highlightedCodes[langList[0]], className: 'highlight' },
|
{
|
||||||
|
lang: langList[0],
|
||||||
|
highlighted: highlightedCodes[langList[0] as keyof typeof LANGS],
|
||||||
|
className: 'highlight',
|
||||||
|
},
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user