feat: 🆕 new Space component (#22363)

* feat: new component: Space

* add config-provider space doc

* fix lint fail

* revert test -u

* improve demo

* compatible invalidElement

* use inline-flex and wrap

* review change

* add space version

* improve classname

* review change

* review change
This commit is contained in:
骗你是小猫咪 2020-03-22 11:38:02 +08:00 committed by GitHub
parent 1e95c6cef0
commit b8109bd20e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 986 additions and 0 deletions

View File

@ -50,6 +50,7 @@ Array [
"Select",
"Skeleton",
"Slider",
"Space",
"Spin",
"Steps",
"Switch",

View File

@ -1,6 +1,7 @@
import * as React from 'react';
import defaultRenderEmpty, { RenderEmptyHandler } from './renderEmpty';
import { Locale } from '../locale-provider';
import { SizeType } from './SizeContext';
export interface CSPConfig {
nonce?: string;
@ -18,6 +19,9 @@ export interface ConfigConsumerProps {
ghost: boolean;
};
direction?: 'ltr' | 'rtl';
space?: {
size?: SizeType | number;
};
}
export const ConfigContext = React.createContext<ConfigConsumerProps>({

View File

@ -47,6 +47,7 @@ Some components use dynamic style to support wave effect. You can config `csp` p
| prefixCls | set prefix class. `Note:` This will discard default styles from `antd`. | string | ant | |
| pageHeader | Unify the ghost of PageHeader, ref [PageHeader](/components/page-header) | { ghost:boolean } | 'true' | |
| direction | set direction of layout. See [demo](#components-config-provider-demo-direction) | `ltr` \| `rtl` | `ltr` | |
| space | set Space `size`, ref [Space](/components/space) | { size: `small` \| `middle` \| `large` \| `number` } | - | 4.1.0 |
## FAQ

View File

@ -39,6 +39,9 @@ export interface ConfigProviderProps {
};
componentSize?: SizeType;
direction?: 'ltr' | 'rtl';
space?: {
size?: SizeType | number;
};
}
class ConfigProvider extends React.Component<ConfigProviderProps> {
@ -62,6 +65,7 @@ class ConfigProvider extends React.Component<ConfigProviderProps> {
pageHeader,
componentSize,
direction,
space,
} = this.props;
const config: ConfigConsumerProps = {
@ -71,6 +75,7 @@ class ConfigProvider extends React.Component<ConfigProviderProps> {
autoInsertSpaceInButton,
locale: locale || legacyLocale,
direction,
space,
};
if (getPopupContainer) {

View File

@ -48,6 +48,7 @@ return (
| prefixCls | 设置统一样式前缀。`注意:这将不会应用由 antd 提供的默认样式` | string | ant | |
| pageHeader | 统一设置 PageHeader 的 ghost参考 [PageHeader](/components/page-header) | { ghost: boolean } | 'true' | |
| direction | 设置文本展示方向。 [示例](#components-config-provider-demo-direction) | `ltr` \| `rtl` | `ltr` | |
| space | 设置 Space 的 `size`,参考 [Space](/components/space) | { size: `small` \| `middle` \| `large` \| `number` } | - | 4.1.0 |
## FAQ

View File

@ -112,6 +112,8 @@ export { default as Skeleton } from './skeleton';
export { default as Slider } from './slider';
export { default as Space } from './space';
export { default as Spin } from './spin';
export { default as Steps } from './steps';

View File

@ -0,0 +1,454 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`renders ./components/space/demo/base.md correctly 1`] = `
<div
class="ant-space ant-space-horizontal"
>
<div
class="ant-space-item"
style="margin-right:8px"
>
Space
</div>
<div
class="ant-space-item"
style="margin-right:8px"
>
<button
class="ant-btn ant-btn-primary"
type="button"
>
<span>
Button
</span>
</button>
</div>
<div
class="ant-space-item"
style="margin-right:8px"
>
<span
class=""
>
<div
class="ant-upload ant-upload-select ant-upload-select-text"
>
<span
class="ant-upload"
role="button"
tabindex="0"
>
<input
accept=""
style="display:none"
type="file"
/>
<button
class="ant-btn"
type="button"
>
<span
aria-label="upload"
class="anticon anticon-upload"
role="img"
>
<svg
aria-hidden="true"
class=""
data-icon="upload"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M400 317.7h73.9V656c0 4.4 3.6 8 8 8h60c4.4 0 8-3.6 8-8V317.7H624c6.7 0 10.4-7.7 6.3-12.9L518.3 163a8 8 0 00-12.6 0l-112 141.7c-4.1 5.3-.4 13 6.3 13zM878 626h-60c-4.4 0-8 3.6-8 8v154H214V634c0-4.4-3.6-8-8-8h-60c-4.4 0-8 3.6-8 8v198c0 17.7 14.3 32 32 32h684c17.7 0 32-14.3 32-32V634c0-4.4-3.6-8-8-8z"
/>
</svg>
</span>
<span>
Click to Upload
</span>
</button>
</span>
</div>
<div
class="ant-upload-list ant-upload-list-text"
/>
</span>
</div>
<div
class="ant-space-item"
>
<button
class="ant-btn"
type="button"
>
<span>
Confirm
</span>
</button>
</div>
</div>
`;
exports[`renders ./components/space/demo/customize.md correctly 1`] = `
Array [
<div
class="ant-slider"
>
<div
class="ant-slider-rail"
/>
<div
class="ant-slider-track"
style="left:0%;right:auto;width:8%"
/>
<div
class="ant-slider-step"
/>
<div
aria-disabled="false"
aria-valuemax="100"
aria-valuemin="0"
aria-valuenow="8"
class="ant-slider-handle"
role="slider"
style="left:8%;right:auto;transform:translateX(-50%)"
tabindex="0"
/>
<div
class="ant-slider-mark"
/>
</div>,
<br />,
<br />,
<div
class="ant-space ant-space-horizontal"
>
<div
class="ant-space-item"
style="margin-right:8px"
>
<button
class="ant-btn ant-btn-primary"
type="button"
>
<span>
Primary
</span>
</button>
</div>
<div
class="ant-space-item"
style="margin-right:8px"
>
<button
class="ant-btn"
type="button"
>
<span>
Default
</span>
</button>
</div>
<div
class="ant-space-item"
style="margin-right:8px"
>
<button
class="ant-btn ant-btn-dashed"
type="button"
>
<span>
Dashed
</span>
</button>
</div>
<div
class="ant-space-item"
>
<button
class="ant-btn ant-btn-link"
type="button"
>
<span>
Link
</span>
</button>
</div>
</div>,
]
`;
exports[`renders ./components/space/demo/debug.md correctly 1`] = `
<div
class="ant-space ant-space-horizontal"
>
<div
class="ant-space-item"
style="margin-right:8px"
>
Button
</div>
<div
class="ant-space-item"
style="margin-right:8px"
>
<button
class="ant-btn"
type="button"
>
<span>
Button
</span>
</button>
</div>
<div
class="ant-space-item"
style="margin-right:8px"
>
Button
</div>
<div
class="ant-space-item"
style="margin-right:8px"
>
<button
class="ant-btn"
type="button"
>
<span>
Delete
</span>
</button>
</div>
<div
class="ant-space-item"
style="margin-right:8px"
>
<span
style="display:inline-block;cursor:not-allowed"
>
<button
class="ant-btn"
disabled=""
style="pointer-events:none"
type="button"
>
<span>
Delete
</span>
</button>
</span>
</div>
<div
class="ant-space-item"
style="margin-right:8px"
>
1
</div>
<div
class="ant-space-item"
>
Button
</div>
</div>
`;
exports[`renders ./components/space/demo/size.md correctly 1`] = `
Array [
<div
class="ant-radio-group ant-radio-group-outline"
>
<label
class="ant-radio-wrapper ant-radio-wrapper-checked"
>
<span
class="ant-radio ant-radio-checked"
>
<input
checked=""
class="ant-radio-input"
type="radio"
value="small"
/>
<span
class="ant-radio-inner"
/>
</span>
<span>
Small
</span>
</label>
<label
class="ant-radio-wrapper"
>
<span
class="ant-radio"
>
<input
class="ant-radio-input"
type="radio"
value="middle"
/>
<span
class="ant-radio-inner"
/>
</span>
<span>
Middle
</span>
</label>
<label
class="ant-radio-wrapper"
>
<span
class="ant-radio"
>
<input
class="ant-radio-input"
type="radio"
value="large"
/>
<span
class="ant-radio-inner"
/>
</span>
<span>
Large
</span>
</label>
</div>,
<br />,
<br />,
<div
class="ant-space ant-space-horizontal"
>
<div
class="ant-space-item"
style="margin-right:8px"
>
<button
class="ant-btn ant-btn-primary"
type="button"
>
<span>
Primary
</span>
</button>
</div>
<div
class="ant-space-item"
style="margin-right:8px"
>
<button
class="ant-btn"
type="button"
>
<span>
Default
</span>
</button>
</div>
<div
class="ant-space-item"
style="margin-right:8px"
>
<button
class="ant-btn ant-btn-dashed"
type="button"
>
<span>
Dashed
</span>
</button>
</div>
<div
class="ant-space-item"
>
<button
class="ant-btn ant-btn-link"
type="button"
>
<span>
Link
</span>
</button>
</div>
</div>,
]
`;
exports[`renders ./components/space/demo/vertical.md correctly 1`] = `
<div
class="ant-space ant-space-vertical"
>
<div
class="ant-space-item"
style="margin-bottom:8px"
>
<div
class="ant-card ant-card-bordered"
style="width:300px"
>
<div
class="ant-card-head"
>
<div
class="ant-card-head-wrapper"
>
<div
class="ant-card-head-title"
>
Card
</div>
</div>
</div>
<div
class="ant-card-body"
>
<p>
Card content
</p>
<p>
Card content
</p>
</div>
</div>
</div>
<div
class="ant-space-item"
>
<div
class="ant-card ant-card-bordered"
style="width:300px"
>
<div
class="ant-card-head"
>
<div
class="ant-card-head-wrapper"
>
<div
class="ant-card-head-title"
>
Card
</div>
</div>
</div>
<div
class="ant-card-body"
>
<p>
Card content
</p>
<p>
Card content
</p>
</div>
</div>
</div>
</div>
`;

View File

@ -0,0 +1,89 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Space should render correct with children 1`] = `
<div
class="ant-space ant-space-horizontal"
>
<div
class="ant-space-item"
style="margin-right:8px"
>
text1
</div>
<div
class="ant-space-item"
style="margin-right:8px"
>
<span>
text1
</span>
</div>
<div
class="ant-space-item"
>
text3
</div>
</div>
`;
exports[`Space should render width ConfigProvider 1`] = `
Array [
<div
class="ant-space ant-space-horizontal"
>
<div
class="ant-space-item"
style="margin-right:24px"
>
<span>
1
</span>
</div>
<div
class="ant-space-item"
>
<span>
2
</span>
</div>
</div>,
<div
class="ant-space ant-space-horizontal"
>
<div
class="ant-space-item"
style="margin-right:16px"
>
<span>
1
</span>
</div>
<div
class="ant-space-item"
>
<span>
2
</span>
</div>
</div>,
<div
class="ant-space ant-space-horizontal"
>
<div
class="ant-space-item"
style="margin-right:24px"
>
<span>
1
</span>
</div>
<div
class="ant-space-item"
>
<span>
2
</span>
</div>
</div>,
]
`;

View File

@ -0,0 +1,3 @@
import demoTest from '../../../tests/shared/demoTest';
demoTest('space');

View File

@ -0,0 +1,102 @@
import React from 'react';
import { render, mount } from 'enzyme';
import Space from '..';
import ConfigProvider from '../../config-provider';
import mountTest from '../../../tests/shared/mountTest';
describe('Space', () => {
mountTest(Space);
it('should render width empty children', () => {
const wrapper = mount(<Space />);
expect(wrapper.instance()).toBe(null);
});
it('should render width ConfigProvider', () => {
const wrapper = mount(
<ConfigProvider space={{ size: 'large' }}>
<Space>
<span>1</span>
<span>2</span>
</Space>
<Space size="middle">
<span>1</span>
<span>2</span>
</Space>
<Space size="large">
<span>1</span>
<span>2</span>
</Space>
</ConfigProvider>,
);
expect(render(wrapper)).toMatchSnapshot();
});
it('should render width customize size', () => {
const wrapper = mount(
<Space size={10}>
<span>1</span>
<span>2</span>
</Space>,
);
expect(
wrapper
.find('.ant-space-item')
.at(0)
.prop('style').marginRight,
).toBe(10);
expect(
wrapper
.find('.ant-space-item')
.at(1)
.prop('style').marginRight,
).toBeUndefined();
});
it('should render vertical space width customize size', () => {
const wrapper = mount(
<Space size={10} direction="vertical">
<span>1</span>
<span>2</span>
</Space>,
);
expect(
wrapper
.find('.ant-space-item')
.at(0)
.prop('style').marginBottom,
).toBe(10);
expect(
wrapper
.find('.ant-space-item')
.at(1)
.prop('style').marginBottom,
).toBeUndefined();
});
it('should render correct with children', () => {
const wrapper = mount(
<Space>
text1<span>text1</span>
<>text3</>
</Space>,
);
expect(render(wrapper)).toMatchSnapshot();
});
it('should render with invalidElement', () => {
const wrapper = mount(
<Space>
text1<span>text1</span>
text1
</Space>,
);
expect(wrapper.find('.ant-space-item').length).toBe(3);
});
});

View File

@ -0,0 +1,38 @@
---
order: 0
title:
zh-CN: 基本用法
en-US: Basic Usage
---
## zh-CN
相邻组件水平间距。
## en-US
Crowded components horizontal spacing.
```jsx
import { Button, Space, Upload, Popconfirm } from 'antd';
import { UploadOutlined } from '@ant-design/icons';
function SpaceDemo() {
return (
<Space>
Space
<Button type="primary">Button</Button>
<Upload>
<Button>
<UploadOutlined /> Click to Upload
</Button>
</Upload>
<Popconfirm title="Are you sure delete this task?" okText="Yes" cancelText="No">
<Button>Confirm</Button>
</Popconfirm>
</Space>
);
}
ReactDOM.render(<SpaceDemo />, mountNode);
```

View File

@ -0,0 +1,39 @@
---
order: 3
title:
zh-CN: 自定义尺寸
en-US: Customize Size
---
## zh-CN
自定义间距大小。
## en-US
Custom spacing size.
```jsx
import React, { useState } from 'react';
import { Space, Slider, Button } from 'antd';
function SpaceCutomizeSize() {
const [size, setSize] = useState(8);
return (
<>
<Slider value={size} onChange={value => setSize(value)} />
<br />
<br />
<Space size={size}>
<Button type="primary">Primary</Button>
<Button>Default</Button>
<Button type="dashed">Dashed</Button>
<Button type="link">Link</Button>
</Space>
</>
);
}
ReactDOM.render(<SpaceCutomizeSize />, mountNode);
```

View File

@ -0,0 +1,38 @@
---
order: 99
title:
zh-CN: 多样的 Child
en-US: Diverse Child
debug: true
---
## zh-CN
Debug usage
## en-US
Debug usage
```jsx
import { Space, Button, Popconfirm } from 'antd';
ReactDOM.render(
<Space>
Button
<Button>Button</Button>
Button
<Popconfirm title="Are you sure delete this task?" okText="Yes" cancelText="No">
<Button>Delete</Button>
</Popconfirm>
<Popconfirm title="Are you sure delete this task?" okText="Yes" cancelText="No">
<Button disabled>Delete</Button>
</Popconfirm>
{null}
{false}
{1}
Button
</Space>,
mountNode,
);
```

View File

@ -0,0 +1,47 @@
---
order: 2
title:
zh-CN: 间距大小
en-US: Space Size
---
## zh-CN
间距预设大、中、小三种大小。
通过设置 `size``large` `middle` 分别把间距设为大、中间距。若不设置 `size`,则间距为小。
## en-US
`large`, `middle` and `small` preset sizes.
Set the size to `large` and `middle` by setting size to large and middle respectively. If `size` is not set, the spacing is `small`.
```jsx
import React, { useState } from 'react';
import { Space, Radio, Button } from 'antd';
function SpaceSize() {
const [size, setSize] = useState('small');
return (
<>
<Radio.Group value={size} onChange={e => setSize(e.target.value)}>
<Radio value="small">Small</Radio>
<Radio value="middle">Middle</Radio>
<Radio value="large">Large</Radio>
</Radio.Group>
<br />
<br />
<Space size={size}>
<Button type="primary">Primary</Button>
<Button>Default</Button>
<Button type="dashed">Dashed</Button>
<Button type="link">Link</Button>
</Space>
</>
);
}
ReactDOM.render(<SpaceSize />, mountNode);
```

View File

@ -0,0 +1,39 @@
---
order: 1
title:
zh-CN: 垂直间距
en-US: Vertical Space
---
## zh-CN
相邻组件垂直间距。
可以设置 `width: 100%` 独占一行。
## en-US
Crowded components vertical spacing.
Can set `width: 100%` fill a row.
```jsx
import { Space, Card } from 'antd';
function SpaceVertical() {
return (
<Space direction="vertical">
<Card title="Card" style={{ width: 300 }}>
<p>Card content</p>
<p>Card content</p>
</Card>
<Card title="Card" style={{ width: 300 }}>
<p>Card content</p>
<p>Card content</p>
</Card>
</Space>
);
}
ReactDOM.render(<SpaceVertical />, mountNode);
```

View File

@ -0,0 +1,19 @@
---
category: Components
type: Layout
title: Space
cols: 1
---
Set components spacing.
## When To Use
Avoid components clinging together and set a unified space.
## API
| Property | Description | Type | Default | Version |
| --- | --- | --- | --- | --- |
| size | space size | `small` \| `middle` \| `large` \| `number` | `small` | 4.1.0 |
| direction | space direction | `vertical` \| `horizontal` | `horizontal` | 4.1.0 |

View File

@ -0,0 +1,67 @@
import * as React from 'react';
import classnames from 'classnames';
import toArray from 'rc-util/lib/Children/toArray';
import { ConfigConsumerProps, ConfigContext } from '../config-provider';
import { SizeType } from '../config-provider/SizeContext';
export interface SpaceProps {
prefixCls?: string;
className?: string;
style?: React.CSSProperties;
size?: SizeType | number;
direction?: 'horizontal' | 'vertical';
}
const spaceSize = {
small: 8,
middle: 16,
large: 24,
};
const Space: React.FC<SpaceProps> = props => {
const { getPrefixCls, space }: ConfigConsumerProps = React.useContext(ConfigContext);
const {
size = space?.size || 'small',
className,
children,
direction = 'horizontal',
prefixCls: customizePrefixCls,
...otherProps
} = props;
const items = toArray(children);
const len = items.length;
if (len === 0) {
return null;
}
const prefixCls = getPrefixCls('space', customizePrefixCls);
const cn = classnames(prefixCls, `${prefixCls}-${direction}`, className);
const itemClassName = `${prefixCls}-item`;
return (
<div className={cn} {...otherProps}>
{items.map((child, i) => (
<div
className={itemClassName}
key={`${itemClassName}-i`}
style={
i === len - 1
? {}
: {
[direction === 'vertical' ? 'marginBottom' : 'marginRight']:
typeof size === 'string' ? spaceSize[size] : size,
}
}
>
{child}
</div>
))}
</div>
);
};
export default Space;

View File

@ -0,0 +1,20 @@
---
category: Components
type: 布局
subtitle: 间距
title: Space
cols: 1
---
设置组件之间的间距。
## 何时使用
避免组件紧贴在一起,拉开统一的空间。
## API
| 参数 | 说明 | 类型 | 默认值 | 版本 |
| --------- | -------- | ------------------------------------------ | ------------ | ----- |
| size | 间距大小 | `small` \| `middle` \| `large` \| `number` | `small` | 4.1.0 |
| direction | 间距方向 | `vertical` \| `horizontal` | `horizontal` | 4.1.0 |

View File

@ -0,0 +1,14 @@
@import '../../style/themes/index';
@import '../../style/mixins/index';
@space-prefix-cls: ~'@{ant-prefix}-space';
.@{space-prefix-cls} {
display: inline-flex;
&-horizontal {
align-items: center;
}
&-vertical {
flex-direction: column;
}
}

View File

@ -0,0 +1,2 @@
import '../../style/index.less';
import './index.less';

View File

@ -50,6 +50,7 @@ Array [
"Select",
"Skeleton",
"Slider",
"Space",
"Spin",
"Steps",
"Switch",