Compare commits

...

2 Commits

Author SHA1 Message Date
neverland
9e8a0864c3
chore(Space): improve demo and document (#10870) 2022-07-31 20:09:13 +08:00
luopei
c3a8275ebf
feat(Space): add new component space (#10857)
* feat(Space): add new component space

* feat(Space): improve functions,documents and add test
2022-07-31 18:20:16 +08:00
10 changed files with 1058 additions and 0 deletions

View File

@ -0,0 +1,130 @@
# 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">Button</van-button>
<van-button type="primary">Button</van-button>
<van-button type="primary">Button</van-button>
<van-button type="primary">Button</van-button>
</van-space>
```
### Vertical
```html
<van-space direction="vertical" fill>
<van-button type="primary" block>Button</van-button>
<van-button type="primary" block>Button</van-button>
<van-button type="primary" block>Button</van-button>
</van-space>
```
### Custom Size
```html
<!-- 20px -->
<van-space :size="20">
<van-button type="primary">Button</van-button>
<van-button type="primary">Button</van-button>
<van-button type="primary">Button</van-button>
</van-space>
<!-- 2rem -->
<van-space size="2rem">
<van-button type="primary">Button</van-button>
<van-button type="primary">Button</van-button>
<van-button type="primary">Button</van-button>
</van-space>
```
### Alignment
```html
<van-radio-group
v-model="align"
direction="horizontal"
style="margin-bottom: 16px"
>
<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>
<van-space :align="align" style="padding: 16px; background: #f3f2f5">
<van-button type="primary">{{ align }}</van-button>
<div style="padding: 40px 20px; background: #fff">Block</div>
</van-space>
```
```js
import { ref } from 'vue';
export default {
setup() {
const align = ref('center');
return { align };
},
};
```
### Auto Wrap
```html
<van-space wrap>
<van-button type="primary" block>Button</van-button>
<van-button type="primary" block>Button</van-button>
<van-button type="primary" block>Button</van-button>
<van-button type="primary" block>Button</van-button>
<van-button type="primary" block>Button</van-button>
<van-button type="primary" block>Button</van-button>
<van-button type="primary" block>Button</van-button>
<van-button type="primary" block>Button</van-button>
</van-space>
```
## API
### Props
| Attribute | Description | Type | Default |
| --- | --- | --- | --- |
| direction | Spacing direction | _vertical \| horizontal_ | `horizontal` |
| size | Spacing size, such as `20px` `2em`. The default unit is px, supports using array to set horizontal and vertical spacing | _number \| string \| number[] \| string[]_ | `8px` |
| align | Spacing alignment | _start \| end \| center \| baseline_ | - |
| wrap | Whether to wrap automatically, only for horizontal alignment | _boolean_ | `false` |
| fill | Whether to render Space as a block element and fill the parent element | _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,140 @@
# Space 间距
### 介绍
设置元素之间的间距。
### 引入
通过以下方式来全局注册组件,更多注册方式请参考[组件注册](#/zh-CN/advanced-usage#zu-jian-zhu-ce)。
```js
import { createApp } from 'vue';
import { Space } from 'vant';
const app = createApp();
app.use(Space);
```
## 代码演示
### 基础用法
Space 组件会在各个子组件之间设置一定的间距,默认间距为 `8px`
```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-space>
```
### 垂直排列
`direction` 属性设置为 `vertical`,可以设置垂直方向排列的间距。
```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` 的值来控制间距的大小。传入 `number` 类型时,会默认使用 `px` 单位;也可以传入 `string` 类型,比如 `2rem``5vw` 等带有单位的值。
```html
<!-- 20px -->
<van-space :size="20">
<van-button type="primary">按钮</van-button>
<van-button type="primary">按钮</van-button>
<van-button type="primary">按钮</van-button>
</van-space>
<!-- 2rem -->
<van-space size="2rem">
<van-button type="primary">按钮</van-button>
<van-button type="primary">按钮</van-button>
<van-button type="primary">按钮</van-button>
</van-space>
```
### 对齐方式
通过调整 `align` 的值来设置子元素的对齐方式, 可选值为 `start`, `center` ,`end` ,`baseline`,在水平模式下的默认值为 `center`
```html
<van-radio-group
v-model="align"
direction="horizontal"
style="margin-bottom: 16px"
>
<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>
<van-space :align="align" style="padding: 16px; background: #f3f2f5">
<van-button type="primary">{{ align }}</van-button>
<div style="padding: 40px 20px; background: #fff">Block</div>
</van-space>
```
```js
import { ref } from 'vue';
export default {
setup() {
const align = ref('center');
return { align };
},
};
```
### 自动换行
在水平模式下, 通过 `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-space>
```
## API
### Props
| 参数 | 说明 | 类型 | 默认值 |
| --- | --- | --- | --- |
| direction | 间距方向 | _vertical \| horizontal_ | `horizontal` |
| size | 间距大小,如 `20px` `2em`,默认单位为 `px`,支持数组形式来分别设置横向和纵向间距 | _number \| string \| number[] \| string[]_ | `8px` |
| align | 对齐方式 | _start \| end \| center \| baseline_ | - |
| wrap | 是否自动换行,仅适用于水平方向排列 | _boolean_ | `false` |
| fill | 是否让 Space 变为一个块级元素,填充整个父元素 | _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,102 @@
<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': {
vertical: '垂直排列',
customSize: '自定义间距',
align: '对齐方式',
wrap: '自动换行',
},
'en-US': {
vertical: 'Vertical',
customSize: 'Custom Size',
align: 'Alignment',
wrap: 'Auto Wrap',
},
});
const align = ref('center');
</script>
<template>
<demo-block :title="t('basicUsage')">
<van-space>
<van-button type="primary">{{ t('button') }}</van-button>
<van-button type="primary">{{ t('button') }}</van-button>
<van-button type="primary">{{ t('button') }}</van-button>
<van-button type="primary">{{ t('button') }}</van-button>
</van-space>
</demo-block>
<demo-block :title="t('vertical')">
<van-space direction="vertical" fill>
<van-button type="primary" block>{{ t('button') }}</van-button>
<van-button type="primary" block>{{ t('button') }}</van-button>
<van-button type="primary" block>{{ t('button') }}</van-button>
</van-space>
</demo-block>
<demo-block :title="t('customSize')">
<van-space :size="20" style="margin-bottom: 16px">
<van-button type="primary">{{ t('button') }}</van-button>
<van-button type="primary">{{ t('button') }}</van-button>
<van-button type="primary">{{ t('button') }}</van-button>
</van-space>
<van-space size="3rem">
<van-button type="primary">{{ t('button') }}</van-button>
<van-button type="primary">{{ t('button') }}</van-button>
<van-button type="primary">{{ t('button') }}</van-button>
</van-space>
</demo-block>
<demo-block :title="t('align')">
<van-radio-group
v-model="align"
direction="horizontal"
style="margin-bottom: 16px"
>
<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>
<van-space :align="align" style="padding: 16px; background: #f3f2f5">
<van-button type="primary">{{ align }}</van-button>
<div style="padding: 40px 20px; background: #fff">Block</div>
</van-space>
</demo-block>
<demo-block :title="t('wrap')">
<van-space wrap>
<van-button type="primary" block>{{ t('button') }}</van-button>
<van-button type="primary" block>{{ t('button') }}</van-button>
<van-button type="primary" block>{{ t('button') }}</van-button>
<van-button type="primary" block>{{ t('button') }}</van-button>
<van-button type="primary" block>{{ t('button') }}</van-button>
<van-button type="primary" block>{{ t('button') }}</van-button>
<van-button type="primary" block>{{ t('button') }}</van-button>
<van-button type="primary" block>{{ t('button') }}</van-button>
</van-space>
</demo-block>
</template>
<style lang="less">
.demo-space {
background: var(--van-background-color-light);
.van-doc-demo-block {
padding: 0 var(--van-padding-md);
}
.van-doc-demo-block__title {
padding-left: 0;
}
}
</style>

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,370 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`should render demo and match snapshot 1`] = `
<div>
<div class="van-space van-space--horizontal van-space--align-center">
<div class="van-space-item"
style="margin-right: 8px;"
>
<button type="button"
class="van-button van-button--primary van-button--normal"
>
<div class="van-button__content">
<span class="van-button__text">
Button
</span>
</div>
</button>
</div>
<div class="van-space-item"
style="margin-right: 8px;"
>
<button type="button"
class="van-button van-button--primary van-button--normal"
>
<div class="van-button__content">
<span class="van-button__text">
Button
</span>
</div>
</button>
</div>
<div class="van-space-item"
style="margin-right: 8px;"
>
<button type="button"
class="van-button van-button--primary van-button--normal"
>
<div class="van-button__content">
<span class="van-button__text">
Button
</span>
</div>
</button>
</div>
<div class="van-space-item">
<button type="button"
class="van-button van-button--primary van-button--normal"
>
<div class="van-button__content">
<span class="van-button__text">
Button
</span>
</div>
</button>
</div>
</div>
</div>
<div>
<div class="van-space van-space--vertical van-space--fill">
<div class="van-space-item"
style="margin-bottom: 8px;"
>
<button type="button"
class="van-button van-button--primary van-button--normal van-button--block"
>
<div class="van-button__content">
<span class="van-button__text">
Button
</span>
</div>
</button>
</div>
<div class="van-space-item"
style="margin-bottom: 8px;"
>
<button type="button"
class="van-button van-button--primary van-button--normal van-button--block"
>
<div class="van-button__content">
<span class="van-button__text">
Button
</span>
</div>
</button>
</div>
<div class="van-space-item">
<button type="button"
class="van-button van-button--primary van-button--normal van-button--block"
>
<div class="van-button__content">
<span class="van-button__text">
Button
</span>
</div>
</button>
</div>
</div>
</div>
<div>
<div class="van-space van-space--horizontal van-space--align-center"
style="margin-bottom: 16px;"
>
<div class="van-space-item"
style="margin-right: 20px;"
>
<button type="button"
class="van-button van-button--primary van-button--normal"
>
<div class="van-button__content">
<span class="van-button__text">
Button
</span>
</div>
</button>
</div>
<div class="van-space-item"
style="margin-right: 20px;"
>
<button type="button"
class="van-button van-button--primary van-button--normal"
>
<div class="van-button__content">
<span class="van-button__text">
Button
</span>
</div>
</button>
</div>
<div class="van-space-item">
<button type="button"
class="van-button van-button--primary van-button--normal"
>
<div class="van-button__content">
<span class="van-button__text">
Button
</span>
</div>
</button>
</div>
</div>
<div class="van-space van-space--horizontal van-space--align-center">
<div class="van-space-item"
style="margin-right: 3rem;"
>
<button type="button"
class="van-button van-button--primary van-button--normal"
>
<div class="van-button__content">
<span class="van-button__text">
Button
</span>
</div>
</button>
</div>
<div class="van-space-item"
style="margin-right: 3rem;"
>
<button type="button"
class="van-button van-button--primary van-button--normal"
>
<div class="van-button__content">
<span class="van-button__text">
Button
</span>
</div>
</button>
</div>
<div class="van-space-item">
<button type="button"
class="van-button van-button--primary van-button--normal"
>
<div class="van-button__content">
<span class="van-button__text">
Button
</span>
</div>
</button>
</div>
</div>
</div>
<div>
<div class="van-radio-group van-radio-group--horizontal"
role="radiogroup"
style="margin-bottom: 16px;"
>
<div role="radio"
class="van-radio van-radio--horizontal"
tabindex="0"
aria-checked="false"
>
<div class="van-radio__icon van-radio__icon--round">
<i class="van-badge__wrapper van-icon van-icon-success">
</i>
</div>
<span class="van-radio__label">
start
</span>
</div>
<div role="radio"
class="van-radio van-radio--horizontal"
tabindex="0"
aria-checked="true"
>
<div class="van-radio__icon van-radio__icon--round van-radio__icon--checked">
<i class="van-badge__wrapper van-icon van-icon-success">
</i>
</div>
<span class="van-radio__label">
center
</span>
</div>
<div role="radio"
class="van-radio van-radio--horizontal"
tabindex="0"
aria-checked="false"
>
<div class="van-radio__icon van-radio__icon--round">
<i class="van-badge__wrapper van-icon van-icon-success">
</i>
</div>
<span class="van-radio__label">
end
</span>
</div>
<div role="radio"
class="van-radio van-radio--horizontal"
tabindex="0"
aria-checked="false"
>
<div class="van-radio__icon van-radio__icon--round">
<i class="van-badge__wrapper van-icon van-icon-success">
</i>
</div>
<span class="van-radio__label">
baseline
</span>
</div>
</div>
<div class="van-space van-space--horizontal van-space--align-center"
style="padding: 16px; background: rgb(243, 242, 245);"
>
<div class="van-space-item"
style="margin-right: 8px;"
>
<button type="button"
class="van-button van-button--primary van-button--normal"
>
<div class="van-button__content">
<span class="van-button__text">
center
</span>
</div>
</button>
</div>
<div class="van-space-item">
<div style="padding: 40px 20px; background: rgb(255, 255, 255);">
Block
</div>
</div>
</div>
</div>
<div>
<div class="van-space van-space--horizontal van-space--align-center van-space--wrap">
<div class="van-space-item"
style="margin-right: 8px; margin-bottom: 8px;"
>
<button type="button"
class="van-button van-button--primary van-button--normal van-button--block"
>
<div class="van-button__content">
<span class="van-button__text">
Button
</span>
</div>
</button>
</div>
<div class="van-space-item"
style="margin-right: 8px; margin-bottom: 8px;"
>
<button type="button"
class="van-button van-button--primary van-button--normal van-button--block"
>
<div class="van-button__content">
<span class="van-button__text">
Button
</span>
</div>
</button>
</div>
<div class="van-space-item"
style="margin-right: 8px; margin-bottom: 8px;"
>
<button type="button"
class="van-button van-button--primary van-button--normal van-button--block"
>
<div class="van-button__content">
<span class="van-button__text">
Button
</span>
</div>
</button>
</div>
<div class="van-space-item"
style="margin-right: 8px; margin-bottom: 8px;"
>
<button type="button"
class="van-button van-button--primary van-button--normal van-button--block"
>
<div class="van-button__content">
<span class="van-button__text">
Button
</span>
</div>
</button>
</div>
<div class="van-space-item"
style="margin-right: 8px; margin-bottom: 8px;"
>
<button type="button"
class="van-button van-button--primary van-button--normal van-button--block"
>
<div class="van-button__content">
<span class="van-button__text">
Button
</span>
</div>
</button>
</div>
<div class="van-space-item"
style="margin-right: 8px; margin-bottom: 8px;"
>
<button type="button"
class="van-button van-button--primary van-button--normal van-button--block"
>
<div class="van-button__content">
<span class="van-button__text">
Button
</span>
</div>
</button>
</div>
<div class="van-space-item"
style="margin-right: 8px; margin-bottom: 8px;"
>
<button type="button"
class="van-button van-button--primary van-button--normal van-button--block"
>
<div class="van-button__content">
<span class="van-button__text">
Button
</span>
</div>
</button>
</div>
<div class="van-space-item"
style="margin-bottom: 8px;"
>
<button type="button"
class="van-button van-button--primary van-button--normal van-button--block"
>
<div class="van-button__content">
<span class="van-button__text">
Button
</span>
</div>
</button>
</div>
</div>
</div>
`;

View File

@ -0,0 +1,4 @@
import Demo from '../demo/index.vue';
import { snapshotDemo } from '../../../test/demo';
snapshotDemo(Demo);

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