feat(Space): add new component space (#10857)

* feat(Space): add new component space

* feat(Space): improve functions,documents and add test
This commit is contained in:
luopei 2022-07-31 18:20:16 +08:00 committed by GitHub
parent c88b034aac
commit c3a8275ebf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 686 additions and 0 deletions

View File

@ -0,0 +1,132 @@
# Space
### Intro
Set the spacing between elements.
### Install
Register component globally via `app.use`, refer to [Component Registration](#/en-US/advanced-usage#zu-jian-zhu-ce) for more registration ways.
```js
import { createApp } from 'vue';
import { Space } from 'vant';
const app = createApp();
app.use(Space);
```
## Usage
### Basic Usage
```html
<van-space>
<van-button type="primary">按钮</van-button>
<van-button type="primary">按钮</van-button>
<van-button type="primary">按钮</van-button>
<van-button type="primary">按钮</van-button>
<van-button type="primary">按钮</van-button>
</van-space>
```
### Vertical Arrangement
```html
<van-space direction="vertical" fill>
<van-button type="primary" block>按钮</van-button>
<van-button type="primary" block>按钮</van-button>
<van-button type="primary" block>按钮</van-button>
</van-space>
```
### Size
```html
<van-radio-group v-model="size" direction="horizontal">
<van-radio name="small">small</van-radio>
<van-radio name="">默认</van-radio>
<van-radio name="large">large</van-radio>
</van-radio-group>
<van-space>
<van-button type="primary">按钮</van-button>
<van-button type="primary">按钮</van-button>
<van-button type="primary">按钮</van-button>
</van-space>
```
```js
import { SpaceSize } from '../Space';
const size = ref < SpaceSize > '';
```
### Alignment
```html
<van-radio-group v-model="align" direction="horizontal">
<van-radio name="start">start</van-radio>
<van-radio name="center">center</van-radio>
<van-radio name="end">end</van-radio>
<van-radio name="baseline">baseline</van-radio>
</van-radio-group>
<br />
<van-space :align="align" style="padding: 10px;background: #f3f2f5;">
<div>Space</div>
<van-button type="primary">按钮</van-button>
<div style="padding: 20px;border: 1px solid #eee">
<div>标题</div>
<div>内容</div>
</div>
</van-space>
```
```js
import { SpaceAlign } from '../Space';
const align = ref < SpaceAlign > 'center';
```
### Wrap
```html
<van-space wrap>
<van-button type="primary" block>按钮</van-button>
<van-button type="primary" block>按钮</van-button>
<van-button type="primary" block>按钮</van-button>
<van-button type="primary" block>按钮</van-button>
<van-button type="primary" block>按钮</van-button>
<van-button type="primary" block>按钮</van-button>
<van-button type="primary" block>按钮</van-button>
<van-button type="primary" block>按钮</van-button>
<van-button type="primary" block>按钮</van-button>
<van-button type="primary" block>按钮</van-button>
<van-button type="primary" block>按钮</van-button>
<van-button type="primary" block>按钮</van-button>
<van-button type="primary" block>按钮</van-button>
</van-space>
```
## API
### Props
| Attribute | Description | Type | Default |
| --- | --- | --- | --- |
| direction | Spacing direction | _vertical \| horizontal_ | `horizontal` |
| align | Spacing alignment | _start \| end \| center \| baseline_ | - |
| size | Spacing size, For example, 20px 2em, the default unit is px, supports array form, and sets horizontal and vertical spacing | _number \| string \| number[] \| string[]_ | `8px` |
| wrap | Whether to wrap lines automatically is only applicable to horizontal arrangement | boolean | `false` |
| fill | Whether to fill the whole line | boolean | `false` |
### Slots
| Name | Description |
| ------- | ------------ |
| default | Default slot |
### Types
The component exports the following type definitions:
```ts
import type { SpaceProps, SpaceSize, SpaceAlign } from 'vant';
```

View File

@ -0,0 +1,143 @@
# Space 间距
### 介绍
设置元素之间的间距。
### 引入
通过以下方式来全局注册组件,更多注册方式请参考[组件注册](#/zh-CN/advanced-usage#zu-jian-zhu-ce)。
```js
import { createApp } from 'vue';
import { Space } from 'vant';
const app = createApp();
app.use(Space);
```
## 代码演示
### 基础用法
间距组件的基本用法。
```html
<van-space>
<van-button type="primary">按钮</van-button>
<van-button type="primary">按钮</van-button>
<van-button type="primary">按钮</van-button>
<van-button type="primary">按钮</van-button>
<van-button type="primary">按钮</van-button>
</van-space>
```
### 垂直排列
可以设置垂直方向排列的间距。
```html
<van-space direction="vertical" fill>
<van-button type="primary" block>按钮</van-button>
<van-button type="primary" block>按钮</van-button>
<van-button type="primary" block>按钮</van-button>
</van-space>
```
### 尺寸
通过调整 `size` 的值来控制间距的大小。
通过 `size` 控制组件大小, small, large, 分别对应 `8px`, `16px`的间距. 默认的间距大小为 `12px`
```html
<van-radio-group v-model="size" direction="horizontal">
<van-radio name="small">small</van-radio>
<van-radio name="">默认</van-radio>
<van-radio name="large">large</van-radio>
</van-radio-group>
<van-space>
<van-button type="primary">按钮</van-button>
<van-button type="primary">按钮</van-button>
<van-button type="primary">按钮</van-button>
</van-space>
```
```js
import { SpaceSize } from '../Space';
const size = ref < SpaceSize > '';
```
### 对齐方式
通过调整 `align` 的值来设置对齐方式, 分别为 `start`, `center` ,`end` ,`baseline在水平模式下默认为` center。
```html
<van-radio-group v-model="align" direction="horizontal">
<van-radio name="start">start</van-radio>
<van-radio name="center">center</van-radio>
<van-radio name="end">end</van-radio>
<van-radio name="baseline">baseline</van-radio>
</van-radio-group>
<br />
<van-space :align="align" style="padding: 10px;background: #f3f2f5;">
<div>Space</div>
<van-button type="primary">按钮</van-button>
<div style="padding: 20px;border: 1px solid #eee">
<div>标题</div>
<div>内容</div>
</div>
</van-space>
```
```js
import { SpaceAlign } from '../Space';
const align = ref < SpaceAlign > 'center';
```
### 自动换行
在水平模式下, 通过控制`wrap`来控制是否自动换行。
```html
<van-space wrap>
<van-button type="primary" block>按钮</van-button>
<van-button type="primary" block>按钮</van-button>
<van-button type="primary" block>按钮</van-button>
<van-button type="primary" block>按钮</van-button>
<van-button type="primary" block>按钮</van-button>
<van-button type="primary" block>按钮</van-button>
<van-button type="primary" block>按钮</van-button>
<van-button type="primary" block>按钮</van-button>
<van-button type="primary" block>按钮</van-button>
<van-button type="primary" block>按钮</van-button>
<van-button type="primary" block>按钮</van-button>
<van-button type="primary" block>按钮</van-button>
<van-button type="primary" block>按钮</van-button>
</van-space>
```
## API
### Props
| 参数 | 说明 | 类型 | 默认值 |
| --- | --- | --- | --- |
| direction | 间距方向 | _vertical \| horizontal_ | `horizontal` |
| align | 对齐方式 | _start \| end \| center \| baseline_ | - |
| size | 间距大小,如 20px 2em默认单位为 px支持数组形式设置横向和纵向间距 | _number \| string \| number[] \| string[]_ | `8px` |
| wrap | 是否自动换行,仅适用于水平方向排列 | boolean | `false` |
| fill | 是否充满整行 | boolean | `false` |
### Slots
| 名称 | 说明 |
| ------- | ------------ |
| default | 间距组件内容 |
### 类型定义
组件导出以下类型定义:
```js
import type { SpaceProps, SpaceSize, SpaceAlign } from 'vant';
```

View File

@ -0,0 +1,121 @@
import {
computed,
CSSProperties,
defineComponent,
ExtractPropTypes,
Fragment,
PropType,
type VNode,
} from 'vue';
import { createNamespace } from '../utils';
const [name, bem] = createNamespace('space');
export type SpaceSize = number | string;
export type SpaceAlign = 'start' | 'end' | 'center' | 'baseline';
const spaceProps = {
align: String as PropType<SpaceAlign>,
direction: {
type: String as PropType<'vertical' | 'horizontal'>,
default: 'horizontal',
},
size: {
type: [Number, String, Array] as PropType<
number | string | [SpaceSize, SpaceSize]
>,
default: 8,
},
wrap: Boolean,
fill: Boolean,
};
export type SpaceProps = ExtractPropTypes<typeof spaceProps>;
function filterEmpty(children: VNode[] = []) {
const nodes: VNode[] = [];
children.forEach((child) => {
if (Array.isArray(child)) {
nodes.push(...child);
} else if (child.type === Fragment) {
nodes.push(...filterEmpty(child.children as VNode[]));
} else {
nodes.push(child);
}
});
return nodes.filter(
(c) =>
!(
c &&
((typeof Comment !== 'undefined' && c.type === Comment) ||
(c.type === Fragment && c.children?.length === 0) ||
(c.type === Text && (c.children as string).trim() === ''))
)
);
}
export default defineComponent({
name,
props: spaceProps,
setup(props, { slots }) {
const mergedAlign = computed(
() => props.align ?? (props.direction === 'horizontal' ? 'center' : '')
);
const getMargin = (size: SpaceSize) => {
if (typeof size === 'number') {
return size + 'px';
}
return size;
};
const getMarginStyle = (isLast: boolean): CSSProperties => {
const style: CSSProperties = {};
const marginRight = `${getMargin(
Array.isArray(props.size) ? props.size[0] : props.size
)}`;
const marginBottom = `${getMargin(
Array.isArray(props.size) ? props.size[1] : props.size
)}`;
if (isLast) {
return props.wrap ? { marginBottom } : {};
}
if (props.direction === 'horizontal') {
style.marginRight = marginRight;
}
if (props.direction === 'vertical' || props.wrap) {
style.marginBottom = marginBottom;
}
return style;
};
return () => {
const children = filterEmpty(slots.default?.());
return (
<div
class={[
bem({
[props.direction]: props.direction,
[`align-${mergedAlign.value}`]: mergedAlign.value,
wrap: props.wrap,
fill: props.fill,
}),
]}
>
{children.map((c, i) => (
<div
key={`item-${i}`}
class={`${name}-item`}
style={getMarginStyle(i === children.length - 1)}
>
{c}
</div>
))}
</div>
);
};
},
});

View File

@ -0,0 +1,99 @@
<script setup>
import VanSpace from '..';
import VanButton from '../../button';
import VanRadio from '../../radio';
import VanRadioGroup from '../../radio-group';
import { ref } from 'vue';
import { useTranslate } from '../../../docs/site';
const t = useTranslate({
'zh-CN': {
basic: '基础用法',
vertical: '垂直排列',
size: '尺寸',
align: '对齐方式',
wrap: '换行',
},
'en-US': {
basic: 'basic',
vertical: 'vertical',
size: 'size',
align: 'align',
wrap: 'wrap',
},
});
const size = ref('8px');
const align = ref('center');
</script>
<template>
<demo-block :title="t('basic')">
<van-space>
<van-button type="primary">按钮</van-button>
<van-button type="primary">按钮</van-button>
<van-button type="primary">按钮</van-button>
<van-button type="primary">按钮</van-button>
<van-button type="primary">按钮</van-button>
</van-space>
</demo-block>
<demo-block :title="t('vertical')">
<van-space direction="vertical" fill>
<van-button type="primary" block>按钮</van-button>
<van-button type="primary" block>按钮</van-button>
<van-button type="primary" block>按钮</van-button>
</van-space>
</demo-block>
<demo-block :title="t('size')">
<van-radio-group v-model="size" direction="horizontal">
<van-radio name="8px">default</van-radio>
<van-radio name="20px">20px</van-radio>
<van-radio name="2rem">2rem</van-radio>
<van-radio name="5vw">5vw</van-radio>
</van-radio-group>
<br />
<van-space :size="size">
<van-button type="primary">按钮</van-button>
<van-button type="primary">按钮</van-button>
<van-button type="primary">按钮</van-button>
</van-space>
</demo-block>
<demo-block :title="t('align')">
<van-radio-group v-model="align" direction="horizontal">
<van-radio name="start">start</van-radio>
<van-radio name="center">center</van-radio>
<van-radio name="end">end</van-radio>
<van-radio name="baseline">baseline</van-radio>
</van-radio-group>
<br />
<van-space :align="align" style="padding: 10px; background: #f3f2f5">
<div>Space</div>
<van-button type="primary">按钮</van-button>
<div style="padding: 20px; border: 1px solid #eee">
<div>标题</div>
<div>内容</div>
</div>
</van-space>
</demo-block>
<demo-block :title="t('wrap')">
<van-space wrap>
<van-button type="primary" block>按钮</van-button>
<van-button type="primary" block>按钮</van-button>
<van-button type="primary" block>按钮</van-button>
<van-button type="primary" block>按钮</van-button>
<van-button type="primary" block>按钮</van-button>
<van-button type="primary" block>按钮</van-button>
<van-button type="primary" block>按钮</van-button>
<van-button type="primary" block>按钮</van-button>
<van-button type="primary" block>按钮</van-button>
<van-button type="primary" block>按钮</van-button>
<van-button type="primary" block>按钮</van-button>
<van-button type="primary" block>按钮</van-button>
<van-button type="primary" block>按钮</van-button>
</van-space>
</demo-block>
</template>

View File

@ -0,0 +1,38 @@
.van-space {
display: inline-flex;
&--horizontal {
.van-space-item {
display: flex;
align-items: center;
}
}
&--vertical {
flex-direction: column;
}
&--align-baseline {
align-items: baseline;
}
&--align-start {
align-items: flex-start;
}
&--align-end {
align-items: flex-end;
}
&--align-center {
align-items: center;
}
&--wrap {
flex-wrap: wrap;
}
&--fill {
display: flex;
}
}

View File

@ -0,0 +1,12 @@
import { withInstall } from '../utils';
import _Space from './Space';
export const Space = withInstall(_Space);
export default Space;
export type { SpaceProps, SpaceSize, SpaceAlign } from './Space';
declare module 'vue' {
export interface GlobalComponents {
VanSpace: typeof Space;
}
}

View File

@ -0,0 +1,133 @@
import { mount } from '../../../test';
import { Space } from '..';
import { Button } from '../../button';
test('should render space', async () => {
const wrapper = mount({
render() {
return (
<Space>
<Button></Button>
<Button></Button>
<Button></Button>
</Space>
);
},
});
const items = wrapper.findAll('.van-space-item');
expect(items[0].style.marginRight).toBe('8px');
expect(items[1].style.marginRight).toBe('8px');
expect(items[2].style.marginRight).toBe('');
});
test('should render vertical', async () => {
const wrapper = mount({
render() {
return (
<Space direction="vertical" fill>
<Button type="primary" block>
</Button>
<Button type="primary" block>
</Button>
<Button type="primary" block>
</Button>
</Space>
);
},
});
const space = wrapper.find('.van-space');
const items = wrapper.findAll('.van-space-item');
expect(space.classes()).toContain('van-space--vertical');
expect(items[0].style.marginBottom).toBe('8px');
expect(items[1].style.marginBottom).toBe('8px');
expect(items[2].style.marginBottom).toBe('');
});
test('should render size 20px', async () => {
const wrapper = mount({
render() {
return (
<Space size="20px">
<Button type="primary" block>
</Button>
<Button type="primary" block>
</Button>
<Button type="primary" block>
</Button>
</Space>
);
},
});
const items = wrapper.findAll('.van-space-item');
expect(items[0].style.marginRight).toBe('20px');
expect(items[1].style.marginRight).toBe('20px');
expect(items[2].style.marginRight).toBe('');
});
test('should render align start', async () => {
const wrapper = mount({
render() {
return (
<Space align="start">
<Button type="primary" block>
</Button>
<Button type="primary" block>
</Button>
<Button type="primary" block>
</Button>
</Space>
);
},
});
const space = wrapper.find('.van-space');
expect(space.classes()).toContain('van-space--align-start');
});
test('should render wrap', async () => {
const wrapper = mount({
render() {
return (
<Space wrap>
<Button type="primary" block>
</Button>
<Button type="primary" block>
</Button>
<Button type="primary" block>
</Button>
<Button type="primary" block>
</Button>
<Button type="primary" block>
</Button>
<Button type="primary" block>
</Button>
<Button type="primary" block>
</Button>
<Button type="primary" block>
</Button>
<Button type="primary" block>
</Button>
</Space>
);
},
});
const space = wrapper.find('.van-space');
expect(space.classes()).toContain('van-space--wrap');
});

View File

@ -120,6 +120,10 @@ export default {
path: 'popup',
title: 'Popup 弹出层',
},
{
path: 'space',
title: 'Space 间距',
},
{
path: 'style',
title: 'Style 内置样式',
@ -528,6 +532,10 @@ export default {
path: 'popup',
title: 'Popup',
},
{
path: 'space',
title: 'Space',
},
{
path: 'style',
title: 'Built-in style',