docs: Support semantic preview (#47332)

* docs: init

* docs: semantic block

* docs: part of demo

* docs: modal

* docs: slider

* docs: space

* docs: all sementic

* docs: clean up

* chore: fix lint

* chore: clean up

* chore: update script

* test: fix test case

* test: update testcase
This commit is contained in:
二货爱吃白萝卜 2024-02-05 11:45:42 +08:00 committed by GitHub
parent e197751d01
commit 7104513a84
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
33 changed files with 849 additions and 310 deletions

View File

@ -0,0 +1,171 @@
import * as React from 'react';
import { Col, ConfigProvider, Flex, Row, Tag, theme, Typography } from 'antd';
export interface SemanticPreviewProps {
semantics: { name: string; desc: string; version?: string }[];
children: React.ReactElement;
height?: number;
}
const SemanticPreview = (props: SemanticPreviewProps) => {
const { semantics = [], children, height } = props;
const { token } = theme.useToken();
// ======================= Semantic =======================
const getMarkClassName = React.useCallback(
(semanticKey: string) => `semantic-mark-${semanticKey}`,
[],
);
const semanticClassNames = React.useMemo(() => {
const classNames: Record<string, string> = {};
semantics.forEach((semantic) => {
classNames[semantic.name] = getMarkClassName(semantic.name);
});
return classNames;
}, [semantics]);
const cloneNode = React.cloneElement(children, {
classNames: semanticClassNames,
});
// ======================== Hover =========================
const MARK_BORDER_SIZE = 2;
const containerRef = React.useRef<HTMLDivElement>(null);
const [positionMotion, setPositionMotion] = React.useState(false);
const [hoverSemantic, setHoverSemantic] = React.useState<string | null>(null);
const [markPos, setMarkPos] = React.useState<
[left: number, top: number, width: number, height: number]
>([0, 0, 0, 0]);
React.useEffect(() => {
if (hoverSemantic) {
const targetClassName = getMarkClassName(hoverSemantic);
const targetElement = containerRef.current?.querySelector(`.${targetClassName}`);
const containerRect = containerRef.current?.getBoundingClientRect();
const targetRect = targetElement?.getBoundingClientRect();
setMarkPos([
(targetRect?.left || 0) - (containerRect?.left || 0),
(targetRect?.top || 0) - (containerRect?.top || 0),
targetRect?.width || 0,
targetRect?.height || 0,
]);
setTimeout(() => {
setPositionMotion(true);
}, 10);
} else {
const timeout = setTimeout(() => {
setPositionMotion(false);
}, 500);
return () => {
clearTimeout(timeout);
};
}
}, [hoverSemantic]);
// ======================== Render ========================
return (
<div style={{ position: 'relative' }} ref={containerRef}>
<Row style={{ minHeight: height }}>
<Col
span={16}
style={{
borderRight: `1px solid ${token.colorBorderSecondary}`,
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
padding: token.paddingMD,
overflow: 'hidden',
}}
>
<ConfigProvider
theme={{
token: {
motion: false,
},
}}
>
{cloneNode}
</ConfigProvider>
</Col>
<Col span={8}>
<ul
style={{
rowGap: token.paddingXS,
listStyle: 'none',
margin: 0,
padding: 0,
flexDirection: 'column',
overflow: 'hidden',
}}
>
{semantics.map((semantic, index) => (
<li
key={semantic.name}
style={{
paddingBlock: token.paddingXS,
paddingInline: token.paddingSM,
cursor: 'pointer',
borderTop: index === 0 ? 'none' : `1px solid ${token.colorBorderSecondary}`,
background:
hoverSemantic === semantic.name ? token.controlItemBgHover : 'transparent',
transition: `background ${token.motionDurationFast} ease`,
}}
onMouseEnter={() => {
setHoverSemantic(semantic.name);
}}
onMouseLeave={() => {
setHoverSemantic(null);
}}
>
<Flex vertical gap={token.paddingXS}>
<Flex gap={token.paddingXS} align="center">
<Typography.Title level={5} style={{ margin: 0 }}>
{semantic.name}
</Typography.Title>
{semantic.version && <Tag color="blue">{semantic.version}</Tag>}
</Flex>
<Typography.Paragraph style={{ margin: 0, fontSize: token.fontSizeSM }}>
{semantic.desc}
</Typography.Paragraph>{' '}
</Flex>
</li>
))}
</ul>
</Col>
</Row>
<div
style={{
position: 'absolute',
border: `${MARK_BORDER_SIZE}px solid ${token.colorWarning}`,
boxSizing: 'border-box',
zIndex: 999999,
left: markPos[0] - MARK_BORDER_SIZE,
top: markPos[1] - MARK_BORDER_SIZE,
width: markPos[2] + MARK_BORDER_SIZE * 2,
height: markPos[3] + MARK_BORDER_SIZE * 2,
boxShadow: '0 0 0 1px #FFF',
pointerEvents: 'none',
transition: [
`opacity ${token.motionDurationSlow} ease`,
positionMotion ? `all ${token.motionDurationSlow} ease` : null,
]
.filter(Boolean)
.join(','),
opacity: hoverSemantic ? 1 : 0,
}}
/>
</div>
);
};
export default SemanticPreview;

View File

@ -5,8 +5,9 @@ import stackblitzSdk from '@stackblitz/sdk';
import { Alert, Badge, Flex, Tooltip } from 'antd';
import { createStyles, css } from 'antd-style';
import classNames from 'classnames';
import { FormattedMessage, useSiteData, useLiveDemo } from 'dumi';
import { FormattedMessage, useLiveDemo, useSiteData } from 'dumi';
import LZString from 'lz-string';
import useLocation from '../../../hooks/useLocation';
import BrowserFrame from '../../common/BrowserFrame';
import ClientOnly from '../../common/ClientOnly';
@ -99,6 +100,7 @@ const CodePreviewer: React.FC<AntdPreviewerProps> = (props) => {
background,
filename,
version,
simplify,
clientOnly,
pkgDependencyList,
} = props;
@ -177,6 +179,7 @@ const CodePreviewer: React.FC<AntdPreviewerProps> = (props) => {
const codeBoxClass = classNames('code-box', {
expand: codeExpand,
'code-box-debug': originDebug,
'code-box-simplify': simplify,
});
const localizedTitle = title;
@ -378,6 +381,7 @@ createRoot(document.getElementById('container')).render(<Demo />);
</ErrorBoundary>
)}
</section>
{!simplify && (
<section className="code-box-meta markdown">
<div className="code-box-title">
<Tooltip title={originDebug ? <FormattedMessage id="app.demo.debug" /> : ''}>
@ -385,11 +389,18 @@ createRoot(document.getElementById('container')).render(<Demo />);
{localizedTitle}
</a>
</Tooltip>
<EditButton title={<FormattedMessage id="app.content.edit-demo" />} filename={filename} />
<EditButton
title={<FormattedMessage id="app.content.edit-demo" />}
filename={filename}
/>
</div>
{description && (
<div className="code-box-description" dangerouslySetInnerHTML={{ __html: description }} />
<div
className="code-box-description"
dangerouslySetInnerHTML={{ __html: description }}
/>
)}
<Flex wrap="wrap" gap="middle" className="code-box-actions">
{showOnlineUrl && (
<Tooltip title={<FormattedMessage id="app.demo.online" />}>
@ -511,6 +522,7 @@ createRoot(document.getElementById('container')).render(<Demo />);
</Tooltip>
</Flex>
</section>
)}
{codeExpand && (
<section className={highlightClass} key="code">
<CodePreview

View File

@ -29,6 +29,16 @@ const GlobalDemoStyles: React.FC = () => {
border-radius: ${token.borderRadiusLG}px;
transition: all 0.2s;
&.code-box-simplify {
border-radius: 0;
margin-bottom: 0;
.code-box-demo {
padding: 0;
border-bottom: 0;
}
}
.code-box-title {
&,
a {

View File

@ -1,6 +1,7 @@
import { globSync } from 'glob';
import * as React from 'react';
import { globSync } from 'glob';
import { renderToString } from 'react-dom/server';
import type { Options } from '../../tests/shared/demoTest';
(global as any).testConfig = {};
@ -28,7 +29,9 @@ describe('node', () => {
// Test for ssr
describe(componentName, () => {
const demoList = globSync(`./components/${componentName}/demo/*.tsx`);
const demoList = globSync(`./components/${componentName}/demo/*.tsx`).filter(
(file) => !file.includes('_semantic'),
);
// Use mock to get config
require(`../../${componentTestFile}`); // eslint-disable-line global-require, import/no-dynamic-require

View File

@ -0,0 +1,43 @@
import React from 'react';
import { Avatar, Badge } from 'antd';
import SemanticPreview from '../../../.dumi/components/SemanticPreview';
import useLocale from '../../../.dumi/hooks/useLocale';
const locales = {
cn: {
root: '根节点',
indicator: '指示器节点',
},
en: {
root: 'Root element',
indicator: 'Indicator element',
},
};
const App: React.FC = () => {
const [locale] = useLocale(locales);
return (
<SemanticPreview
semantics={[
{
name: 'root',
desc: locale.root,
version: '5.7.0',
},
{
name: 'indicator',
desc: locale.indicator,
version: '5.7.0',
},
]}
>
<Badge count={5}>
<Avatar shape="square" size="large" />
</Badge>
</SemanticPreview>
);
};
export default App;

View File

@ -44,14 +44,14 @@ Common props ref[Common props](/docs/react/common-props)
| --- | --- | --- | --- | --- |
| color | Customize Badge dot color | string | - | |
| count | Number to show in badge | ReactNode | - | |
| classNames | Semantic DOM class | Record<SemanticDOM, string> | - | 5.7.0 |
| classNames | Semantic DOM class | [Record<SemanticDOM, string>](#semantic-dom) | - | 5.7.0 |
| dot | Whether to display a red dot instead of `count` | boolean | false | |
| offset | Set offset of the badge dot | \[number, number] | - | |
| overflowCount | Max count to show | number | 99 | |
| showZero | Whether to show badge when `count` is zero | boolean | false | |
| size | If `count` is set, `size` sets the size of badge | `default` \| `small` | - | - |
| status | Set Badge as a status dot | `success` \| `processing` \| `default` \| `error` \| `warning` | - | |
| styles | Semantic DOM style | Record<SemanticDOM, CSSProperties> | - | 5.7.0 |
| styles | Semantic DOM style | [Record<SemanticDOM, CSSProperties>](#semantic-dom) | - | 5.7.0 |
| text | If `status` is set, `text` sets the display text of the status `dot` | ReactNode | - | |
| title | Text to show when hovering over the badge | string | - | |
@ -63,12 +63,9 @@ Common props ref[Common props](/docs/react/common-props)
| placement | The placement of the Ribbon, `start` and `end` follow text direction (RTL or LTR) | `start` \| `end` | `end` | |
| text | Content inside the Ribbon | ReactNode | - | |
### `styles` and `classNames` attribute
## Semantic DOM
| Property | Description | Version |
| --------- | ------------------- | ------- |
| root | set `root` element | 5.7.0 |
| indicator | set `badge` element | 5.7.0 |
<code src="./demo/_semantic.tsx" simplify="true"></code>
## Design Token

View File

@ -45,14 +45,14 @@ group: 数据展示
| --- | --- | --- | --- | --- |
| color | 自定义小圆点的颜色 | string | - | |
| count | 展示的数字,大于 overflowCount 时显示为 `${overflowCount}+`,为 0 时隐藏 | ReactNode | - | |
| classNames | 语义化结构 class | Record<SemanticDOM, string> | - | 5.7.0 |
| classNames | 语义化结构 class | [Record<SemanticDOM, string>](#semantic-dom) | - | 5.7.0 |
| dot | 不展示数字,只有一个小红点 | boolean | false | |
| offset | 设置状态点的位置偏移 | \[number, number] | - | |
| overflowCount | 展示封顶的数字值 | number | 99 | |
| showZero | 当数值为 0 时,是否展示 Badge | boolean | false | |
| size | 在设置了 `count` 的前提下有效,设置小圆点的大小 | `default` \| `small` | - | - |
| status | 设置 Badge 为状态点 | `success` \| `processing` \| `default` \| `error` \| `warning` | - | |
| styles | 语义化结构 style | Record<SemanticDOM, CSSProperties> | - | 5.7.0 |
| styles | 语义化结构 style | [Record<SemanticDOM, CSSProperties>](#semantic-dom) | - | 5.7.0 |
| text | 在设置了 `status` 的前提下有效,设置状态点的文本 | ReactNode | - | |
| title | 设置鼠标放在状态点上时显示的文字 | string | - | |
@ -64,12 +64,9 @@ group: 数据展示
| placement | 缎带的位置,`start` 和 `end` 随文字方向RTL 或 LTR变动 | `start` \| `end` | `end` | |
| text | 缎带中填入的内容 | ReactNode | - | |
### `styles``classNames` 属性
## Semantic DOM
| 名称 | 说明 | 版本 |
| --------- | ------------ | ----- |
| root | 设置根元素 | 5.7.0 |
| indicator | 设置徽标元素 | 5.7.0 |
<code src="./demo/_semantic.tsx" simplify="true"></code>
## 主题变量Design Token

View File

@ -0,0 +1,37 @@
import React from 'react';
import { AntDesignOutlined } from '@ant-design/icons';
import { Button } from 'antd';
import SemanticPreview from '../../../.dumi/components/SemanticPreview';
import useLocale from '../../../.dumi/hooks/useLocale';
const locales = {
cn: {
icon: '图标元素',
},
en: {
icon: 'Icon element',
},
};
const App: React.FC = () => {
const [locale] = useLocale(locales);
return (
<SemanticPreview
semantics={[
{
name: 'icon',
desc: locale.icon,
version: '5.5.0',
},
]}
>
<Button type="primary" icon={<AntDesignOutlined />}>
Ant Design
</Button>
</SemanticPreview>
);
};
export default App;

View File

@ -59,7 +59,7 @@ Different button styles can be generated by setting Button properties. The recom
| Property | Description | Type | Default | Version |
| --- | --- | --- | --- | --- |
| block | Option to fit button width to its parent width | boolean | false | |
| classNames | Semantic DOM class | Record<SemanticDOM, string> | - | 5.4.0 |
| classNames | Semantic DOM class | [Record<SemanticDOM, string>](#semantic-dom) | - | 5.4.0 |
| danger | Set the danger status of button | boolean | false | |
| disabled | Disabled state of button | boolean | false | |
| ghost | Make background transparent and invert text and border colors | boolean | false | |
@ -69,18 +69,16 @@ Different button styles can be generated by setting Button properties. The recom
| loading | Set the loading status of button | boolean \| { delay: number } | false | |
| shape | Can be set button shape | `default` \| `circle` \| `round` | `default` | |
| size | Set the size of button | `large` \| `middle` \| `small` | `middle` | |
| styles | Semantic DOM style | Record<SemanticDOM, CSSProperties> | - | 5.4.0 |
| styles | Semantic DOM style | [Record<SemanticDOM, CSSProperties>](#semantic-dom) | - | 5.4.0 |
| target | Same as target attribute of a, works when href is specified | string | - | |
| type | Set button type | `primary` \| `dashed` \| `link` \| `text` \| `default` | `default` | |
| onClick | Set the handler to handle `click` event | (event: MouseEvent) => void | - | |
It accepts all props which native buttons support.
### `styles` and `classNames` attribute
## Semantic DOM
| Property | Description | Version |
| -------- | ----------------- | ------- |
| icon | set `icon`element | 5.5.0 |
<code src="./demo/_semantic.tsx" simplify="true"></code>
## Design Token

View File

@ -64,7 +64,7 @@ group:
| 属性 | 说明 | 类型 | 默认值 | 版本 |
| --- | --- | --- | --- | --- |
| block | 将按钮宽度调整为其父宽度的选项 | boolean | false | |
| classNames | 语义化结构 class | Record<SemanticDOM, string> | - | 5.4.0 |
| classNames | 语义化结构 class | [Record<SemanticDOM, string>](#semantic-dom) | - | 5.4.0 |
| danger | 设置危险按钮 | boolean | false | |
| disabled | 设置按钮失效状态 | boolean | false | |
| ghost | 幽灵属性,使按钮背景透明 | boolean | false | |
@ -74,18 +74,16 @@ group:
| loading | 设置按钮载入状态 | boolean \| { delay: number } | false | |
| shape | 设置按钮形状 | `default` \| `circle` \| `round` | `default` | |
| size | 设置按钮大小 | `large` \| `middle` \| `small` | `middle` | |
| styles | 语义化结构 style | Record<SemanticDOM, CSSProperties> | - | 5.4.0 |
| styles | 语义化结构 style | [Record<SemanticDOM, CSSProperties>](#semantic-dom) | - | 5.4.0 |
| target | 相当于 a 链接的 target 属性href 存在时生效 | string | - | |
| type | 设置按钮类型 | `primary` \| `dashed` \| `link` \| `text` \| `default` | `default` | |
| onClick | 点击按钮时的回调 | (event: MouseEvent) => void | - | |
支持原生 button 的其他所有属性。
### `styles``classNames` 属性
## Semantic DOM
| 名称 | 说明 | 版本 |
| ---- | ------------ | ----- |
| icon | 设置图标元素 | 5.5.0 |
<code src="./demo/_semantic.tsx" simplify="true"></code>
## 主题变量Design Token

View File

@ -0,0 +1,72 @@
import React from 'react';
import { Drawer, Typography } from 'antd';
import SemanticPreview from '../../../.dumi/components/SemanticPreview';
import useLocale from '../../../.dumi/hooks/useLocale';
const locales = {
cn: {
mask: '遮罩层元素',
content: 'Drawer 容器元素',
header: '头部元素',
body: '内容元素',
footer: '底部元素',
},
en: {
mask: 'Mask element',
content: 'Drawer container element',
header: 'Header element',
body: 'Body element',
footer: 'Footer element',
},
};
const App: React.FC = () => {
const [locale] = useLocale(locales);
return (
<SemanticPreview
semantics={[
{
name: 'mask',
desc: locale.mask,
version: '5.13.0',
},
{
name: 'content',
desc: locale.content,
version: '5.13.0',
},
{
name: 'header',
desc: locale.header,
version: '5.13.0',
},
{
name: 'body',
desc: locale.body,
version: '5.13.0',
},
{
name: 'footer',
desc: locale.footer,
version: '5.13.0',
},
]}
height={300}
>
<Drawer
title="Title"
placement="right"
footer={<Typography.Link>Footer</Typography.Link>}
closable={false}
open
getContainer={false}
>
<p>Some contents...</p>
</Drawer>
</SemanticPreview>
);
};
export default App;

View File

@ -51,8 +51,7 @@ v5 use `rootClassName` & `rootStyle` to config wrapper style instead of `classNa
| autoFocus | Whether Drawer should get focused after open | boolean | true | 4.17.0 |
| afterOpenChange | Callback after the animation ends when switching drawers | function(open) | - | |
| className | Config Drawer Panel className. Use `rootClassName` if want to config top dom style | string | - | |
| classNames | Config Drawer build-in module's className | `header?: string; body?: string; footer?: string; mask?: string; content?: string; wrapper?: string;` | - | |
| styles | Config Drawer build-in module's style | `header?: CSSProperties; body?: CSSProperties; footer?: CSSProperties; mask?: CSSProperties; content?: CSSProperties; wrapper?: CSSProperties;` | - | 5.10.0 |
| classNames | Semantic structure className | [Record<SemanticDOM, string>](#semantic-dom) | - | 5.10.0 |
| closeIcon | Custom close icon. 5.7.0: close button will be hidden when setting to `null` or `false` | ReactNode | &lt;CloseOutlined /> | |
| destroyOnClose | Whether to unmount child components on closing drawer or not | boolean | false | |
| extra | Extra actions area at corner | ReactNode | - | 4.17.0 |
@ -68,6 +67,7 @@ v5 use `rootClassName` & `rootStyle` to config wrapper style instead of `classNa
| push | Nested drawers push behavior | boolean \| { distance: string \| number } | { distance: 180 } | 4.5.0+ |
| rootStyle | Style of wrapper element which **contains mask** compare to `style` | CSSProperties | - | |
| style | Style of Drawer panel. Use `bodyStyle` if want to config body only | CSSProperties | - | |
| styles | Semantic structure style | [Record<SemanticDOM, CSSProperties>](#semantic-dom) | - | 5.10.0 |
| size | preset size of drawer, default `378px` and large `736px` | 'default' \| 'large' | 'default' | 4.17.0 |
| title | The title for Drawer | ReactNode | - | |
| open | Whether the Drawer dialog is visible or not | boolean | false | |
@ -75,6 +75,10 @@ v5 use `rootClassName` & `rootStyle` to config wrapper style instead of `classNa
| zIndex | The `z-index` of the Drawer | number | 1000 | |
| onClose | Specify a callback that will be called when a user clicks mask, close button or Cancel button | function(e) | - | |
## Semantic DOM
<code src="./demo/_semantic.tsx" simplify="true"></code>
## Design Token
<ComponentTokenTable component="Drawer"></ComponentTokenTable>

View File

@ -157,6 +157,9 @@ const Drawer: React.FC<DrawerProps> & {
const [zIndex, contextZIndex] = useZIndex('Drawer', rest.zIndex);
// =========================== Render ===========================
const { classNames: propClassNames = {}, styles: propStyles = {} } = rest;
const { classNames: contextClassNames = {}, styles: contextStyles = {} } = drawer || {};
return wrapCSSVar(
<NoCompactStyle>
<NoFormStyle status override>
@ -168,24 +171,24 @@ const Drawer: React.FC<DrawerProps> & {
motion={panelMotion}
{...rest}
classNames={{
mask: classNames(rest.classNames?.mask, drawer?.classNames?.mask),
content: classNames(rest.classNames?.content, drawer?.classNames?.content),
mask: classNames(propClassNames.mask, contextClassNames.mask),
content: classNames(propClassNames.content, contextClassNames.content),
}}
styles={{
mask: {
...rest.styles?.mask,
...propStyles.mask,
...maskStyle,
...drawer?.styles?.mask,
...contextStyles.mask,
},
content: {
...rest.styles?.content,
...propStyles.content,
...drawerStyle,
...drawer?.styles?.content,
...contextStyles.content,
},
wrapper: {
...rest.styles?.wrapper,
...propStyles.wrapper,
...contentWrapperStyle,
...drawer?.styles?.wrapper,
...contextStyles.wrapper,
},
}}
open={open ?? visible}

View File

@ -50,8 +50,7 @@ v5 使用 `rootClassName` 与 `rootStyle` 来配置最外层元素样式。原 v
| autoFocus | 抽屉展开后是否将焦点切换至其 Dom 节点 | boolean | true | 4.17.0 |
| afterOpenChange | 切换抽屉时动画结束后的回调 | function(open) | - | |
| className | Drawer 容器外层 className 设置,如果需要设置最外层,请使用 rootClassName | string | - | |
| classNames | 配置抽屉内置模块的 className | `header?: string; body?: string; footer?: string; mask?: string; content?: string; wrapper?: string;` | - | |
| styles | 配置抽屉内置模块的 style | `header?: CSSProperties; body?: CSSProperties; footer?: CSSProperties; mask?: CSSProperties; content?: CSSProperties; wrapper?: CSSProperties;` | - | 5.10.0 |
| classNames | 语义化结构 className | [Record<SemanticDOM, string>](#semantic-dom) | - | 5.10.0 |
| closeIcon | 自定义关闭图标。5.7.0:设置为 `null``false` 时隐藏关闭按钮 | ReactNode | &lt;CloseOutlined /> | |
| destroyOnClose | 关闭时销毁 Drawer 里的子元素 | boolean | false | |
| extra | 抽屉右上角的操作区域 | ReactNode | - | 4.17.0 |
@ -67,12 +66,17 @@ v5 使用 `rootClassName` 与 `rootStyle` 来配置最外层元素样式。原 v
| rootStyle | 可用于设置 Drawer 最外层容器的样式,和 `style` 的区别是作用节点包括 `mask` | CSSProperties | - | |
| size | 预设抽屉宽度或高度default `378px` 和 large `736px` | 'default' \| 'large' | 'default' | 4.17.0 |
| style | 设计 Drawer 容器样式,如果你只需要设置内容部分请使用 `bodyStyle` | CSSProperties | - | |
| styles | 语义化结构 style | [Record<SemanticDOM, CSSProperties>](#semantic-dom) | - | 5.10.0 |
| title | 标题 | ReactNode | - | |
| open | Drawer 是否可见 | boolean | - |
| width | 宽度 | string \| number | 378 | |
| zIndex | 设置 Drawer 的 `z-index` | number | 1000 | |
| onClose | 点击遮罩层或左上角叉或取消按钮的回调 | function(e) | - | |
## Semantic DOM
<code src="./demo/_semantic.tsx" simplify="true"></code>
## 主题变量Design Token
<ComponentTokenTable component="Drawer"></ComponentTokenTable>

View File

@ -0,0 +1,61 @@
import React from 'react';
import { EditOutlined, UserOutlined } from '@ant-design/icons';
import { Input } from 'antd';
import SemanticPreview from '../../../.dumi/components/SemanticPreview';
import useLocale from '../../../.dumi/hooks/useLocale';
const locales = {
cn: {
input: '输入框元素',
prefix: '前缀的包裹元素',
suffix: '后缀的包裹元素',
count: '文字计数元素',
},
en: {
input: 'input element',
prefix: 'prefix element',
suffix: 'suffix element',
count: 'count element',
},
};
const App: React.FC = () => {
const [locale] = useLocale(locales);
return (
<SemanticPreview
semantics={[
{
name: 'input',
desc: locale.input,
version: '5.4.0',
},
{
name: 'prefix',
desc: locale.prefix,
version: '5.4.0',
},
{
name: 'suffix',
desc: locale.suffix,
version: '5.4.0',
},
{
name: 'count',
desc: locale.count,
version: '5.4.0',
},
]}
>
<Input
prefix={<UserOutlined />}
suffix={<EditOutlined />}
showCount
defaultValue="Hello, Ant Design"
/>
</SemanticPreview>
);
};
export default App;

View File

@ -0,0 +1,48 @@
import React from 'react';
import { Input } from 'antd';
import SemanticPreview from '../../../.dumi/components/SemanticPreview';
import useLocale from '../../../.dumi/hooks/useLocale';
const locales = {
cn: {
textarea: '输入框元素',
count: '文字计数元素',
},
en: {
textarea: 'textarea element',
count: 'count element',
},
};
const App: React.FC = () => {
const [locale] = useLocale(locales);
return (
<SemanticPreview
semantics={[
{
name: 'textarea',
desc: locale.textarea,
version: '5.4.0',
},
{
name: 'count',
desc: locale.count,
version: '5.4.0',
},
]}
>
<Input.TextArea
defaultValue="Hello, Ant Design"
rows={3}
count={{
max: 100,
show: true,
}}
/>
</SemanticPreview>
);
};
export default App;

View File

@ -138,21 +138,11 @@ Supports all props of `Input`.
#### Input
<!-- prettier-ignore -->
| Property | Description | Version |
| --- | --- | --- |
| input | `input` element | 5.4.0 |
| prefix | Wrapper of prefix | 5.4.0 |
| suffix | Wrapper of suffix | 5.4.0 |
| count | Text count element | 5.4.0 |
<code src="./demo/_semantic_input.tsx" simplify="true"></code>
#### Input.TextArea
<!-- prettier-ignore -->
| Property | Description | Version |
| --- | --- | --- |
| textarea | `textarea` element | 5.4.0 |
| count | Text count element | 5.4.0 |
<code src="./demo/_semantic_textarea.tsx" simplify="true"></code>
## Design Token

View File

@ -139,19 +139,11 @@ interface CountConfig {
#### Input
| 名称 | 说明 | 版本 |
| ------ | ------------------ | ----- |
| input | `input` 元素 | 5.4.0 |
| prefix | 所有前缀的包裹元素 | 5.4.0 |
| suffix | 所有后缀的包裹元素 | 5.4.0 |
| count | 文字计数元素 | 5.4.0 |
<code src="./demo/_semantic_input.tsx" simplify="true"></code>
#### Input.TextArea
| 名称 | 说明 | 版本 |
| -------- | --------------- | ----- |
| textarea | `textarea` 元素 | 5.4.0 |
| count | 文字计数元素 | 5.4.0 |
<code src="./demo/_semantic_textarea.tsx" simplify="true"></code>
## 主题变量Design Token

View File

@ -0,0 +1,90 @@
import React from 'react';
import { Modal, type ModalProps } from 'antd';
import SemanticPreview from '../../../.dumi/components/SemanticPreview';
import useLocale from '../../../.dumi/hooks/useLocale';
const locales = {
cn: {
mask: '遮罩层元素',
wrapper: '包裹层元素,一般用于动画容器',
content: 'Drawer 容器元素',
header: '头部元素',
body: '内容元素',
footer: '底部元素',
},
en: {
mask: 'Mask element',
wrapper: 'Wrapper element. Used for motion container',
content: 'Drawer container element',
header: 'Header element',
body: 'Body element',
footer: 'Footer element',
},
};
const BlockModal = (props: ModalProps) => {
const divRef = React.useRef<HTMLDivElement>(null);
return (
<div ref={divRef} style={{ position: 'absolute', inset: 0 }}>
<Modal
getContainer={() => divRef.current!}
{...props}
styles={
{
mask: {
position: 'absolute',
},
wrapper: {
position: 'absolute',
},
} as any
}
style={{
top: '50%',
transform: 'translateY(-50%)',
marginBottom: 0,
paddingBottom: 0,
}}
/>
</div>
);
};
const App: React.FC = () => {
const [locale] = useLocale(locales);
return (
<SemanticPreview
semantics={[
{
name: 'mask',
desc: locale.mask,
version: '5.13.0',
},
{
name: 'header',
desc: locale.header,
version: '5.13.0',
},
{
name: 'body',
desc: locale.body,
version: '5.13.0',
},
{
name: 'footer',
desc: locale.footer,
version: '5.13.0',
},
]}
>
<BlockModal title="Title" closable={false} open getContainer={false} width={400}>
<p>Some contents...</p>
</BlockModal>
</SemanticPreview>
);
};
export default App;

View File

@ -190,6 +190,10 @@ const confirmed = await modal.confirm({ ... });
| originNode | default node | React.ReactNode | - |
| extra | extended options | { OkBtn: FC; CancelBtn: FC } | - |
### `styles` and `classNames` attribute
<code src="./demo/_semantic.tsx" simplify="true"></code>
## Design Token
<ComponentTokenTable component="Modal"></ComponentTokenTable>

View File

@ -191,6 +191,10 @@ const confirmed = await modal.confirm({ ... });
| originNode | 默认节点 | React.ReactNode | - |
| extra | 扩展选项 | { OkBtn: FC; CancelBtn: FC } | - |
### `styles` and `classNames` 属性
<code src="./demo/_semantic.tsx" simplify="true"></code>
## 主题变量Design Token
<ComponentTokenTable component="Modal"></ComponentTokenTable>

View File

@ -0,0 +1,55 @@
import React from 'react';
import { Slider } from 'antd';
import SemanticPreview from '../../../.dumi/components/SemanticPreview';
import useLocale from '../../../.dumi/hooks/useLocale';
const locales = {
cn: {
track: '范围选择下,点和点之间单个选取条',
tracks: '范围选择下,整个范围选取条',
rail: '背景条元素',
handle: '抓取点元素',
},
en: {
track: 'The selection bar between points and points under the range selection',
tracks: 'The entire range selection bar under the range selection',
rail: 'Background rail element',
handle: 'Grab handle element',
},
};
const App: React.FC = () => {
const [locale] = useLocale(locales);
return (
<SemanticPreview
semantics={[
{
name: 'track',
desc: locale.track,
version: '5.10.0',
},
{
name: 'tracks',
desc: locale.tracks,
version: '5.10.0',
},
{
name: 'rail',
desc: locale.rail,
version: '5.10.0',
},
{
name: 'handle',
desc: locale.handle,
version: '5.10.0',
},
]}
>
<Slider range defaultValue={[20, 30, 50]} style={{ width: '100%' }} />
</SemanticPreview>
);
};
export default App;

View File

@ -38,6 +38,7 @@ Common props ref[Common props](/docs/react/common-props)
| --- | --- | --- | --- | --- |
| autoAdjustOverflow | Whether to automatically adjust the popup position | boolean | true | 5.8.0 |
| autoFocus | Whether get focus when component mounted | boolean | false | |
| classNames | Semantic structure className | [Record<SemanticDOM, string>](#semantic-dom) | - | 5.10.0 |
| defaultValue | The default value of slider. When `range` is false, use number, otherwise, use \[number, number] | number \| \[number, number] | 0 \| \[0, 0] | |
| disabled | If true, the slider will not be intractable | boolean | false | |
| keyboard | Support using keyboard to move handlers | boolean | true | 5.2.0+ |
@ -49,21 +50,13 @@ Common props ref[Common props](/docs/react/common-props)
| range | Dual thumb mode | boolean | false | |
| reverse | Reverse the component | boolean | false | |
| step | The granularity the slider can step through values. Must greater than 0, and be divided by (max - min) . When `marks` no null, `step` can be null | number \| null | 1 | |
| styles | Semantic structure style | [Record<SemanticDOM, React.CSSProperties>](#semantic-dom) | - | 5.10.0 |
| tooltip | The tooltip relate props | [tooltip](#tooltip) | - | 4.23.0 |
| value | The value of slider. When `range` is false, use number, otherwise, use \[number, number] | number \| \[number, number] | - | |
| vertical | If true, the slider will be vertical | boolean | false | |
| onChangeComplete | Fire when `mouseup` or `keyup` is fired | (value) => void | - | |
| onChange | Callback function that is fired when the user changes the slider's value | (value) => void | - | |
### `styles``classNames` 属性
| Property | Description | Version |
| -------- | ------------------------------------------- | ------- |
| track | The track between handle to handle in range | 5.10.0 |
| tracks | Who track in range | 5.10.0 |
| rail | Background rail | 5.10.0 |
| handle | The handle pointer | 5.10.0 |
### range
| Property | Description | Type | Default | Version |
@ -86,6 +79,10 @@ Common props ref[Common props](/docs/react/common-props)
| blur() | Remove focus | |
| focus() | Get focus | |
## Semantic DOM
<code src="./demo/_semantic.tsx" simplify="true"></code>
## Design Token
<ComponentTokenTable component="Slider"></ComponentTokenTable>

View File

@ -38,7 +38,7 @@ demo:
| 参数 | 说明 | 类型 | 默认值 | 版本 |
| --- | --- | --- | --- | --- |
| autoFocus | 自动获取焦点 | boolean | false | |
| classNames | 语义化结构 className | Record<SemanticDOM, string> | - | 5.10.0 |
| classNames | 语义化结构 className | [Record<SemanticDOM, string>](#semantic-dom) | - | 5.10.0 |
| defaultValue | 设置初始取值。当 `range` 为 false 时,使用 number否则用 \[number, number] | number \| \[number, number] | 0 \| \[0, 0] | |
| disabled | 值为 true 时,滑块为禁用状态 | boolean | false | |
| keyboard | 支持使用键盘操作 handler | boolean | true | 5.2.0+ |
@ -50,22 +50,13 @@ demo:
| range | 双滑块模式 | boolean \| [range](#range) | false | |
| reverse | 反向坐标轴 | boolean | false | |
| step | 步长,取值必须大于 0并且可被 (max - min) 整除。当 `marks` 不为空对象时,可以设置 `step` 为 null此时 Slider 的可选值仅有 marks 标出来的部分 | number \| null | 1 | |
| styles | 语义化结构 className | Record<SemanticDOM, React.CSSProperties> | - | 5.10.0 |
| styles | 语义化结构 styles | [Record<SemanticDOM, React.CSSProperties>](#semantic-dom) | - | 5.10.0 |
| tooltip | 设置 Tooltip 相关属性 | [tooltip](#tooltip) | - | 4.23.0 |
| value | 设置当前取值。当 `range` 为 false 时,使用 number否则用 \[number, number] | number \| \[number, number] | - | |
| vertical | 值为 true 时Slider 为垂直方向 | boolean | false | |
| onChangeComplete | 与 `mouseup``keyup` 触发时机一致,把当前值作为参数传入 | (value) => void | - | |
| onChange | 当 Slider 的值发生改变时,会触发 onChange 事件,并把改变后的值作为参数传入 | (value) => void | - | |
### `styles``classNames` 属性
| 名称 | 说明 | 版本 |
| ------ | -------------------------------- | ------ |
| track | 范围选择下,点和点之间单个选取条 | 5.10.0 |
| tracks | 范围选择下,整个范围选取条 | 5.10.0 |
| rail | 背景条 | 5.10.0 |
| handle | 抓取点 | 5.10.0 |
### range
| 参数 | 说明 | 类型 | 默认值 | 版本 |
@ -89,6 +80,10 @@ demo:
| blur() | 移除焦点 | |
| focus() | 获取焦点 | |
## Semantic DOM
<code src="./demo/_semantic.tsx" simplify="true"></code>
## 主题变量Design Token
<ComponentTokenTable component="Slider"></ComponentTokenTable>

View File

@ -0,0 +1,38 @@
import React from 'react';
import { Button, Space } from 'antd';
import SemanticPreview from '../../../.dumi/components/SemanticPreview';
import useLocale from '../../../.dumi/hooks/useLocale';
const locales = {
cn: {
item: '包裹的子组件',
},
en: {
item: 'Wrapped item element',
},
};
const App: React.FC = () => {
const [locale] = useLocale(locales);
return (
<SemanticPreview
semantics={[
{
name: 'item',
desc: locale.item,
version: '5.6.0',
},
]}
>
<Space>
<Button type="primary">Primary</Button>
<Button>Default</Button>
<Button type="dashed">Dashed</Button>
</Space>
</SemanticPreview>
);
};
export default App;

View File

@ -42,9 +42,11 @@ Common props ref[Common props](/docs/react/common-props)
| Property | Description | Type | Default | Version |
| --- | --- | --- | --- | --- |
| align | Align items | `start` \| `end` \|`center` \|`baseline` | - | 4.2.0 |
| classNames | Semantic className | [Record<SemanticDOM, string>](#semantic-dom) | - | |
| direction | The space direction | `vertical` \| `horizontal` | `horizontal` | 4.1.0 |
| size | The space size | [Size](#size) \| [Size\[\]](#size) | `small` | 4.1.0 \| Array: 4.9.0 |
| split | Set split | ReactNode | - | 4.7.0 |
| styles | Semantic style | [Record<SemanticDOM, CSSProperties>](#semantic-dom) | - | |
| wrap | Auto wrap line, when `horizontal` effective | boolean | false | 4.9.0 |
### Size
@ -71,12 +73,9 @@ Use Space.Compact when child form components are compactly connected and the bor
| direction | Set direction of layout | `vertical` \| `horizontal` | `horizontal` | 4.24.0 |
| size | Set child component size | `large` \| `middle` \| `small` | `middle` | 4.24.0 |
### `styles` and `classNames` attribute
## Semantic DOM
<!-- prettier-ignore -->
| Property | Description | Version |
| -------- | ------------------------- | ------- |
| item | set `Space` child element | 5.6.0 |
<code src="./demo/_semantic.tsx" simplify="true"></code>
## Design Token

View File

@ -48,9 +48,11 @@ coverDark: https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*37T2R6O9oi0AAA
| 参数 | 说明 | 类型 | 默认值 | 版本 |
| --- | --- | --- | --- | --- |
| align | 对齐方式 | `start` \| `end` \|`center` \|`baseline` | - | 4.2.0 |
| classNames | 语义化 className | [Record<SemanticDOM, string>](#semantic-dom) | - | |
| direction | 间距方向 | `vertical` \| `horizontal` | `horizontal` | 4.1.0 |
| size | 间距大小 | [Size](#size) \| [Size\[\]](#size) | `small` | 4.1.0 \| Array: 4.9.0 |
| split | 设置拆分 | ReactNode | - | 4.7.0 |
| styles | 语义化 style | [Record<SemanticDOM, CSSProperties>](#semantic-dom) | - | |
| wrap | 是否自动换行,仅在 `horizontal` 时有效 | boolean | false | 4.9.0 |
### Size
@ -79,12 +81,9 @@ coverDark: https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*37T2R6O9oi0AAA
| direction | 指定排列方向 | `vertical` \| `horizontal` | `horizontal` | 4.24.0 |
| size | 子组件大小 | `large` \| `middle` \| `small` | `middle` | 4.24.0 |
### `styles``classNames` 属性
## Semantic DOM
<!-- prettier-ignore -->
| 名称 | 说明 | 版本 |
| ---- | --------------------- | ----- |
| item | 设置 `Space` 包裹的子组件 | 5.6.0 |
<code src="./demo/_semantic.tsx" simplify="true"></code>
## 主题变量Design Token

View File

@ -50,7 +50,6 @@
"build": "npm run compile && NODE_OPTIONS='--max-old-space-size=4096' npm run dist",
"changelog": "git fetch origin && tsx scripts/print-changelog.ts",
"check-commit": "tsx scripts/check-commit.ts",
"check-ts-demo": "tsx scripts/check-ts-demo.ts",
"clean": "antd-tools run clean && rm -rf es lib coverage dist report.html",
"clean-lockfiles": "rm -rf package-lock.json yarn.lock",
"collect-token-statistic": "tsx scripts/collect-token-statistic.ts",

View File

@ -24,17 +24,17 @@ exports[`site test Component components/avatar en Page 1`] = `2`;
exports[`site test Component components/avatar zh Page 1`] = `2`;
exports[`site test Component components/badge en Page 1`] = `3`;
exports[`site test Component components/badge en Page 1`] = `2`;
exports[`site test Component components/badge zh Page 1`] = `3`;
exports[`site test Component components/badge zh Page 1`] = `2`;
exports[`site test Component components/breadcrumb en Page 1`] = `3`;
exports[`site test Component components/breadcrumb zh Page 1`] = `3`;
exports[`site test Component components/button en Page 1`] = `2`;
exports[`site test Component components/button en Page 1`] = `1`;
exports[`site test Component components/button zh Page 1`] = `2`;
exports[`site test Component components/button zh Page 1`] = `1`;
exports[`site test Component components/calendar en Page 1`] = `1`;
@ -116,9 +116,9 @@ exports[`site test Component components/image en Page 1`] = `4`;
exports[`site test Component components/image zh Page 1`] = `4`;
exports[`site test Component components/input en Page 1`] = `8`;
exports[`site test Component components/input en Page 1`] = `6`;
exports[`site test Component components/input zh Page 1`] = `8`;
exports[`site test Component components/input zh Page 1`] = `6`;
exports[`site test Component components/input-number en Page 1`] = `2`;
@ -196,13 +196,13 @@ exports[`site test Component components/skeleton en Page 1`] = `6`;
exports[`site test Component components/skeleton zh Page 1`] = `6`;
exports[`site test Component components/slider en Page 1`] = `5`;
exports[`site test Component components/slider en Page 1`] = `4`;
exports[`site test Component components/slider zh Page 1`] = `5`;
exports[`site test Component components/slider zh Page 1`] = `4`;
exports[`site test Component components/space en Page 1`] = `3`;
exports[`site test Component components/space en Page 1`] = `2`;
exports[`site test Component components/space zh Page 1`] = `3`;
exports[`site test Component components/space zh Page 1`] = `2`;
exports[`site test Component components/spin en Page 1`] = `1`;

View File

@ -1,86 +0,0 @@
/* eslint-disable no-await-in-loop, no-console */
import { spawn } from 'child_process';
import path from 'path';
import chalk from 'chalk';
import fs from 'fs-extra';
import { globSync } from 'glob';
(async () => {
console.time('Execution...');
const demoFiles = globSync(path.join(process.cwd(), 'components/**/demo/*.md'));
const tmpFolder = path.resolve('components', '~tmp');
await fs.remove(tmpFolder);
await fs.ensureDir(tmpFolder);
function getTypescriptDemo(content: string, demoPath: string) {
const lines = content.split(/[\n\r]/);
const tsxStartLine = lines.findIndex((line) =>
line.replace(/\s/g, '').toLowerCase().includes('```tsx'),
);
if (tsxStartLine < 0) {
return null;
}
const tsxEndLine = lines.findIndex(
(line, index) => index > tsxStartLine && line.trim() === '```',
);
let script = lines.slice(tsxStartLine + 1, tsxEndLine).join('\n');
// insert React
if (!script.includes('import React') && !script.includes('import * as React')) {
script = `import React from 'react';\n${script}`;
}
// Replace mountNode
script = script.replace('mountNode', `document.getElementById('#root')`);
// Replace antd
script = script.replace(`from 'antd'`, `from '..'`);
// Add path
script = `/* eslint-disabled */\n// ${demoPath}\n${script}`;
return script;
}
for (let i = 0; i < demoFiles.length; i += 1) {
const demoPath = demoFiles[i];
const content = await fs.readFile(demoPath, 'utf8');
const script = getTypescriptDemo(content, demoPath);
const dirs = path.dirname(demoPath).split(path.sep);
// Parse TSX
if (script) {
const tmpFile = path.join(
tmpFolder,
`${dirs[dirs.length - 2]}-${path.basename(demoPath).replace(/\..*/, '')}.tsx`,
);
await fs.writeFile(tmpFile, script, 'utf8');
}
}
const child = spawn('npm', ['run', 'tsc']);
child.stdout.pipe(process.stdout);
child.stderr.pipe(process.stderr);
child.on('exit', async (code: number) => {
console.timeEnd('Execution...');
if (code) {
console.log(chalk.red('💥 OPS! Seems some tsx demo not pass tsc...'));
} else {
await fs.remove(tmpFolder);
console.log(chalk.green('🤪 All tsx demo passed. Congratulations!'));
}
process.exit(code);
});
})();

View File

@ -2,6 +2,7 @@
import path from 'path';
import * as React from 'react';
import { createCache, StyleProvider } from '@ant-design/cssinjs';
import { ConfigProvider } from 'antd';
import { globSync } from 'glob';
import kebabCase from 'lodash/kebabCase';
import { renderToString } from 'react-dom/server';
@ -11,7 +12,6 @@ import { render } from '../utils';
import { TriggerMockContext } from './demoTestContext';
import { excludeWarning, isSafeWarning } from './excludeWarning';
import rootPropsTest from './rootPropsTest';
import { ConfigProvider } from 'antd';
export { rootPropsTest };
@ -28,7 +28,9 @@ export type Options = {
};
function baseText(doInject: boolean, component: string, options: Options = {}) {
const files = globSync(`./components/${component}/demo/*.tsx`);
const files = globSync(`./components/${component}/demo/*.tsx`).filter(
(file) => !file.includes('_semantic'),
);
files.forEach((file) => {
// to compatible windows path
file = file.split(path.sep).join('/');

View File

@ -251,7 +251,9 @@ type Options = {
// eslint-disable-next-line jest/no-export
export function imageDemoTest(component: string, options: Options = {}) {
let describeMethod = options.skip === true ? describe.skip : describe;
const files = globSync(`./components/${component}/demo/*.tsx`);
const files = globSync(`./components/${component}/demo/*.tsx`).filter(
(file) => !file.includes('_semantic'),
);
files.forEach((file) => {
if (Array.isArray(options.skip) && options.skip.some((c) => file.endsWith(c))) {

View File

@ -8,5 +8,6 @@
"antd/locale/*": ["locale/*"]
}
},
"include": ["components/*/demo/*.tsx"]
"include": ["components/*/demo/*.tsx"],
"exclude": ["components/*/demo/_semantic*"]
}