mirror of
https://github.com/ant-design/ant-design.git
synced 2024-11-27 12:39:49 +08:00
docs: live demo (#45383)
* docs: live demo * chore: bump dumi * chore: fix lint * chore: fix lint * chore: update demo * chore: bump dumi * docs: enhance live demo * docs: update text
This commit is contained in:
parent
c41eb668e8
commit
67165a78fc
@ -5,7 +5,7 @@ import stackblitzSdk from '@stackblitz/sdk';
|
||||
import { Alert, Badge, Space, Tooltip } from 'antd';
|
||||
import { createStyles, css } from 'antd-style';
|
||||
import classNames from 'classnames';
|
||||
import { FormattedMessage, useSiteData } from 'dumi';
|
||||
import { FormattedMessage, useSiteData, LiveContext } from 'dumi';
|
||||
import LZString from 'lz-string';
|
||||
|
||||
import type { AntdPreviewerProps } from './Previewer';
|
||||
@ -21,6 +21,7 @@ import RiddleIcon from '../../common/RiddleIcon';
|
||||
import type { SiteContextProps } from '../../slots/SiteContext';
|
||||
import SiteContext from '../../slots/SiteContext';
|
||||
import { ping } from '../../utils';
|
||||
import LiveDemo from 'dumi/theme-default/slots/LiveDemo';
|
||||
|
||||
const { ErrorBoundary } = Alert;
|
||||
|
||||
@ -107,6 +108,8 @@ const CodePreviewer: React.FC<AntdPreviewerProps> = (props) => {
|
||||
const { pkg } = useSiteData();
|
||||
const location = useLocation();
|
||||
|
||||
const { enabled: liveEnabled } = useContext(LiveContext);
|
||||
|
||||
const { styles } = useStyle();
|
||||
|
||||
const entryCode = asset.dependencies['index.tsx'].value;
|
||||
@ -363,9 +366,13 @@ createRoot(document.getElementById('container')).render(<Demo />);
|
||||
const codeBox: React.ReactNode = (
|
||||
<section className={codeBoxClass} id={asset.id}>
|
||||
<section className="code-box-demo" style={codeBoxDemoStyle}>
|
||||
<ErrorBoundary>
|
||||
<React.StrictMode>{liveDemo.current}</React.StrictMode>
|
||||
</ErrorBoundary>
|
||||
{!liveEnabled ? (
|
||||
<ErrorBoundary>
|
||||
<React.StrictMode>{liveDemo.current}</React.StrictMode>
|
||||
</ErrorBoundary>
|
||||
) : (
|
||||
<LiveDemo />
|
||||
)}
|
||||
</section>
|
||||
<section className="code-box-meta markdown">
|
||||
<div className="code-box-title">
|
||||
|
@ -1,6 +1,6 @@
|
||||
import React from 'react';
|
||||
import type { IPreviewerProps } from 'dumi';
|
||||
import { useTabMeta } from 'dumi';
|
||||
import { LiveProvider, useTabMeta } from 'dumi';
|
||||
|
||||
import CodePreviewer from './CodePreviewer';
|
||||
import DesignPreviewer from './DesignPreviewer';
|
||||
@ -16,7 +16,23 @@ const Previewer: React.FC<AntdPreviewerProps> = (props) => {
|
||||
return <DesignPreviewer {...props} />;
|
||||
}
|
||||
|
||||
return <CodePreviewer {...props} />;
|
||||
const codePreviewer = <CodePreviewer {...props} />;
|
||||
|
||||
if (props.live === false || props.iframe) {
|
||||
return codePreviewer;
|
||||
}
|
||||
|
||||
return (
|
||||
<LiveProvider
|
||||
initialCode={
|
||||
Object.entries(props.asset.dependencies).filter(([, { type }]) => type === 'FILE')[0][1]
|
||||
.value
|
||||
}
|
||||
demoId={props.asset.id}
|
||||
>
|
||||
{codePreviewer}
|
||||
</LiveProvider>
|
||||
);
|
||||
};
|
||||
|
||||
export default Previewer;
|
||||
|
@ -1,9 +1,11 @@
|
||||
import React, { useEffect, useMemo } from 'react';
|
||||
import { Tabs, Typography, Button } from 'antd';
|
||||
import { Button, Tabs, Typography } from 'antd';
|
||||
import { createStyles } from 'antd-style';
|
||||
import { LiveContext } from 'dumi';
|
||||
import toReactElement from 'jsonml-to-react-element';
|
||||
import JsonML from 'jsonml.js/lib/utils';
|
||||
import Prism from 'prismjs';
|
||||
import { createStyles } from 'antd-style';
|
||||
import React, { useContext, useEffect, useMemo } from 'react';
|
||||
import LiveCode from './LiveCode';
|
||||
|
||||
const useStyle = createStyles(({ token, css }) => {
|
||||
const { colorIcon, colorBgTextHover, antCls } = token;
|
||||
@ -11,12 +13,13 @@ const useStyle = createStyles(({ token, css }) => {
|
||||
return {
|
||||
code: css`
|
||||
position: relative;
|
||||
margin-top: -16px;
|
||||
`,
|
||||
|
||||
copyButton: css`
|
||||
color: ${colorIcon};
|
||||
position: absolute;
|
||||
top: 0;
|
||||
top: 16px;
|
||||
inset-inline-end: 16px;
|
||||
width: 32px;
|
||||
text-align: center;
|
||||
@ -110,6 +113,8 @@ const CodePreview: React.FC<CodePreviewProps> = ({
|
||||
|
||||
const { styles } = useStyle();
|
||||
|
||||
const { enabled: liveEnabled } = useContext(LiveContext);
|
||||
|
||||
const items = useMemo(
|
||||
() =>
|
||||
langList.map((lang: keyof typeof LANGS) => ({
|
||||
@ -117,7 +122,11 @@ const CodePreview: React.FC<CodePreviewProps> = ({
|
||||
key: lang,
|
||||
children: (
|
||||
<div className={styles.code}>
|
||||
{toReactComponent(['pre', { lang, highlighted: highlightedCodes[lang] }])}
|
||||
{lang === 'tsx' && liveEnabled ? (
|
||||
<LiveCode />
|
||||
) : (
|
||||
toReactComponent(['pre', { lang, highlighted: highlightedCodes[lang] }])
|
||||
)}
|
||||
<Button type="text" className={styles.copyButton}>
|
||||
<Typography.Text className={styles.copyIcon} copyable={{ text: sourceCodes[lang] }} />
|
||||
</Button>
|
||||
@ -132,14 +141,18 @@ const CodePreview: React.FC<CodePreviewProps> = ({
|
||||
}
|
||||
|
||||
if (langList.length === 1) {
|
||||
return toReactComponent([
|
||||
'pre',
|
||||
{
|
||||
lang: langList[0],
|
||||
highlighted: highlightedCodes[langList[0] as keyof typeof LANGS],
|
||||
className: 'highlight',
|
||||
},
|
||||
]);
|
||||
return liveEnabled ? (
|
||||
<LiveCode />
|
||||
) : (
|
||||
toReactComponent([
|
||||
'pre',
|
||||
{
|
||||
lang: langList[0],
|
||||
highlighted: highlightedCodes[langList[0] as keyof typeof LANGS],
|
||||
className: 'highlight',
|
||||
},
|
||||
])
|
||||
);
|
||||
}
|
||||
|
||||
return <Tabs centered className="highlight" onChange={onCodeTypeChange} items={items} />;
|
||||
|
85
.dumi/theme/common/LiveCode.tsx
Normal file
85
.dumi/theme/common/LiveCode.tsx
Normal file
@ -0,0 +1,85 @@
|
||||
import type { FC } from 'react';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { createStyles } from 'antd-style';
|
||||
import LiveEditor from '../slots/LiveEditor';
|
||||
import LiveError from '../slots/LiveError';
|
||||
import { EditFilled } from '@ant-design/icons';
|
||||
import { Tooltip } from 'antd';
|
||||
import useLocale from '../../hooks/useLocale';
|
||||
|
||||
const useStyle = createStyles(({ token, css }) => {
|
||||
const { colorPrimaryBorder, colorIcon, colorPrimary } = token;
|
||||
|
||||
return {
|
||||
editor: css`
|
||||
.npm__react-simple-code-editor__textarea {
|
||||
outline: none;
|
||||
|
||||
&:hover {
|
||||
border: 1px solid ${colorPrimaryBorder} !important;
|
||||
}
|
||||
|
||||
&:focus {
|
||||
border: 1px solid ${colorPrimary} !important;
|
||||
}
|
||||
}
|
||||
`,
|
||||
|
||||
editableIcon: css`
|
||||
position: absolute;
|
||||
height: 32px;
|
||||
width: 32px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
top: 16px;
|
||||
inset-inline-end: 56px;
|
||||
color: ${colorIcon};
|
||||
`,
|
||||
};
|
||||
});
|
||||
|
||||
const locales = {
|
||||
cn: {
|
||||
demoEditable: '编辑 Demo 可实时预览',
|
||||
},
|
||||
en: {
|
||||
demoEditable: 'Edit demo with real-time preview',
|
||||
},
|
||||
};
|
||||
|
||||
const HIDE_LIVE_DEMO_TIP = 'hide-live-demo-tip';
|
||||
|
||||
const LiveCode: FC = () => {
|
||||
const [open, setOpen] = useState(false);
|
||||
const { styles } = useStyle();
|
||||
const [locale] = useLocale(locales);
|
||||
|
||||
useEffect(() => {
|
||||
const shouldOpen = !localStorage.getItem(HIDE_LIVE_DEMO_TIP);
|
||||
if (shouldOpen) {
|
||||
setOpen(true);
|
||||
}
|
||||
}, []);
|
||||
|
||||
const handleOpenChange = (newOpen: boolean) => {
|
||||
setOpen(newOpen);
|
||||
if (!newOpen) {
|
||||
localStorage.setItem(HIDE_LIVE_DEMO_TIP, 'true');
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className={styles.editor}>
|
||||
<LiveEditor />
|
||||
<LiveError />
|
||||
</div>
|
||||
<Tooltip title={locale.demoEditable} open={open} onOpenChange={handleOpenChange}>
|
||||
<EditFilled className={styles.editableIcon} />
|
||||
</Tooltip>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default LiveCode;
|
@ -324,7 +324,6 @@ const GlobalDemoStyles: React.FC = () => {
|
||||
border: none;
|
||||
box-shadow: unset;
|
||||
padding: 12px 16px;
|
||||
margin-top: -16px;
|
||||
font-size: 13px;
|
||||
}
|
||||
}
|
||||
|
21
.dumi/theme/slots/LiveEditor/index.tsx
Normal file
21
.dumi/theme/slots/LiveEditor/index.tsx
Normal file
@ -0,0 +1,21 @@
|
||||
import type { FC } from 'react';
|
||||
import React from 'react';
|
||||
import DumiLiveEditor from 'dumi/theme-default/slots/LiveEditor';
|
||||
|
||||
const LiveEditor: FC = () => (
|
||||
<DumiLiveEditor
|
||||
style={{
|
||||
fontSize: 13,
|
||||
lineHeight: 2,
|
||||
fontFamily: `'Lucida Console', Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace`,
|
||||
}}
|
||||
padding={{
|
||||
top: 12,
|
||||
right: 16,
|
||||
bottom: 12,
|
||||
left: 16,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
||||
export default LiveEditor;
|
15
.dumi/theme/slots/LiveError/index.tsx
Normal file
15
.dumi/theme/slots/LiveError/index.tsx
Normal file
@ -0,0 +1,15 @@
|
||||
import type { FC } from 'react';
|
||||
import React, { useContext } from 'react';
|
||||
import { LiveContext } from 'dumi';
|
||||
import { Alert, theme } from 'antd';
|
||||
|
||||
const LiveError: FC = () => {
|
||||
const { error } = useContext(LiveContext);
|
||||
const { token } = theme.useToken();
|
||||
|
||||
return error ? (
|
||||
<Alert banner type="error" message={error} style={{ color: token.colorError }} />
|
||||
) : null;
|
||||
};
|
||||
|
||||
export default LiveError;
|
@ -14,6 +14,7 @@ export default defineConfig({
|
||||
ssr: process.env.NODE_ENV === 'production' ? {} : false,
|
||||
hash: true,
|
||||
mfsu: false,
|
||||
live: true,
|
||||
crossorigin: {},
|
||||
outputPath: '_site',
|
||||
favicons: ['https://gw.alipayobjects.com/zos/rmsportal/rlpTLlbMzTNYuZGGCVYM.png'],
|
||||
|
@ -1,3 +1,4 @@
|
||||
import type React from 'react';
|
||||
import type { AlertProps } from './Alert';
|
||||
import InternalAlert from './Alert';
|
||||
import ErrorBoundary from './ErrorBoundary';
|
||||
|
@ -1,26 +0,0 @@
|
||||
## zh-CN
|
||||
|
||||
与 `react-router@6+` 结合使用,生成和路由绑定的面包屑。
|
||||
|
||||
## en-US
|
||||
|
||||
Used together with `react-router@6+`.
|
||||
|
||||
```css
|
||||
.demo {
|
||||
margin: 16px;
|
||||
}
|
||||
.demo-nav {
|
||||
height: 30px;
|
||||
margin-bottom: 16px;
|
||||
line-height: 30px;
|
||||
background: #f8f8f8;
|
||||
}
|
||||
.demo-nav a {
|
||||
padding: 0 8px;
|
||||
line-height: 30px;
|
||||
}
|
||||
.app-list {
|
||||
margin-top: 16px;
|
||||
}
|
||||
```
|
@ -1,65 +0,0 @@
|
||||
import React from 'react';
|
||||
import { HashRouter, Link, Route, Routes, useLocation } from 'react-router-dom';
|
||||
import { Alert, Breadcrumb } from 'antd';
|
||||
|
||||
const Apps = () => (
|
||||
<ul className="app-list">
|
||||
<li>
|
||||
<Link to="/apps/1">Application1</Link>:<Link to="/apps/1/detail">Detail</Link>
|
||||
</li>
|
||||
<li>
|
||||
<Link to="/apps/2">Application2</Link>:<Link to="/apps/2/detail">Detail</Link>
|
||||
</li>
|
||||
</ul>
|
||||
);
|
||||
|
||||
const breadcrumbNameMap: Record<string, string> = {
|
||||
'/apps': 'Application List',
|
||||
'/apps/1': 'Application1',
|
||||
'/apps/2': 'Application2',
|
||||
'/apps/1/detail': 'Detail',
|
||||
'/apps/2/detail': 'Detail',
|
||||
};
|
||||
|
||||
const Home = () => {
|
||||
const location = useLocation();
|
||||
const pathSnippets = location.pathname.split('/').filter((i) => i);
|
||||
|
||||
const extraBreadcrumbItems = pathSnippets.map((_, index) => {
|
||||
const url = `/${pathSnippets.slice(0, index + 1).join('/')}`;
|
||||
return {
|
||||
key: url,
|
||||
title: <Link to={url}>{breadcrumbNameMap[url]}</Link>,
|
||||
};
|
||||
});
|
||||
|
||||
const breadcrumbItems = [
|
||||
{
|
||||
title: <Link to="/">Home</Link>,
|
||||
key: 'home',
|
||||
},
|
||||
].concat(extraBreadcrumbItems);
|
||||
|
||||
return (
|
||||
<div className="demo">
|
||||
<div className="demo-nav">
|
||||
<Link to="/">Home</Link>
|
||||
<Link to="/apps">Application List</Link>
|
||||
</div>
|
||||
<Routes>
|
||||
<Route path="/apps" element={<Apps />} />
|
||||
<Route path="*" element={<span>Home Page</span>} />
|
||||
</Routes>
|
||||
<Alert style={{ margin: '16px 0' }} message="Click the navigation above to switch:" />
|
||||
<Breadcrumb items={breadcrumbItems} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const App: React.FC = () => (
|
||||
<HashRouter>
|
||||
<Home />
|
||||
</HashRouter>
|
||||
);
|
||||
|
||||
export default App;
|
@ -37,7 +37,6 @@ return <Breadcrumb routes={[{ breadcrumbName: 'sample' }]} />;
|
||||
<!-- prettier-ignore -->
|
||||
<code src="./demo/basic.tsx">Basic Usage</code>
|
||||
<code src="./demo/withIcon.tsx">With an Icon</code>
|
||||
<code src="./demo/react-router.tsx" iframe="200">react-router V6</code>
|
||||
<code src="./demo/separator.tsx">Configuring the Separator</code>
|
||||
<code src="./demo/overlay.tsx">Bread crumbs with drop down menu</code>
|
||||
<code src="./demo/separator-component.tsx">Configuring the Separator Independently</code>
|
||||
|
@ -38,7 +38,6 @@ return <Breadcrumb routes={[{ breadcrumbName: 'sample' }]} />;
|
||||
<!-- prettier-ignore -->
|
||||
<code src="./demo/basic.tsx">基本</code>
|
||||
<code src="./demo/withIcon.tsx">带有图标的</code>
|
||||
<code src="./demo/react-router.tsx" iframe="200">react-router V6</code>
|
||||
<code src="./demo/separator.tsx">分隔符</code>
|
||||
<code src="./demo/overlay.tsx">带下拉菜单的面包屑</code>
|
||||
<code src="./demo/separator-component.tsx">独立的分隔符</code>
|
||||
|
@ -1,3 +1,7 @@
|
||||
/**
|
||||
* live: false
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import dayjs from 'dayjs';
|
||||
import 'dayjs/locale/zh-cn';
|
||||
|
@ -1,4 +1,5 @@
|
||||
import dayjs, { type Dayjs } from 'dayjs';
|
||||
import dayjs from 'dayjs';
|
||||
import type { Dayjs } from 'dayjs';
|
||||
import React from 'react';
|
||||
import { Lunar, HolidayUtil } from 'lunar-typescript';
|
||||
import { createStyles } from 'antd-style';
|
||||
|
@ -1,3 +1,7 @@
|
||||
/**
|
||||
* live: false
|
||||
*/
|
||||
|
||||
import { EllipsisOutlined } from '@ant-design/icons';
|
||||
import dayjs from 'dayjs';
|
||||
import React, { useState } from 'react';
|
||||
|
@ -1,3 +1,7 @@
|
||||
/**
|
||||
* live: false
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import {
|
||||
HomeOutlined,
|
||||
|
@ -218,7 +218,7 @@
|
||||
"cross-fetch": "^4.0.0",
|
||||
"crypto": "^1.0.1",
|
||||
"dekko": "^0.2.1",
|
||||
"dumi": "^2.3.0-alpha.7",
|
||||
"dumi": "^2.3.0-alpha.9",
|
||||
"dumi-plugin-color-chunk": "^1.0.2",
|
||||
"duplicate-package-checker-webpack-plugin": "^3.0.0",
|
||||
"esbuild-loader": "^4.0.0",
|
||||
|
Loading…
Reference in New Issue
Block a user