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:
MadCcc 2023-10-23 22:49:49 +08:00 committed by GitHub
parent c41eb668e8
commit 67165a78fc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 193 additions and 115 deletions

View File

@ -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">

View File

@ -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;

View File

@ -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} />;

View 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;

View File

@ -324,7 +324,6 @@ const GlobalDemoStyles: React.FC = () => {
border: none;
box-shadow: unset;
padding: 12px 16px;
margin-top: -16px;
font-size: 13px;
}
}

View 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;

View 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;

View File

@ -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'],

View File

@ -1,3 +1,4 @@
import type React from 'react';
import type { AlertProps } from './Alert';
import InternalAlert from './Alert';
import ErrorBoundary from './ErrorBoundary';

View File

@ -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;
}
```

View File

@ -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;

View File

@ -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>

View File

@ -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>

View File

@ -1,3 +1,7 @@
/**
* live: false
*/
import React from 'react';
import dayjs from 'dayjs';
import 'dayjs/locale/zh-cn';

View File

@ -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';

View File

@ -1,3 +1,7 @@
/**
* live: false
*/
import { EllipsisOutlined } from '@ant-design/icons';
import dayjs from 'dayjs';
import React, { useState } from 'react';

View File

@ -1,3 +1,7 @@
/**
* live: false
*/
import React from 'react';
import {
HomeOutlined,

View File

@ -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",