feat: loading component

This commit is contained in:
chenjiahan 2020-07-05 16:08:04 +08:00
parent e671e22ea5
commit e0f9382123
10 changed files with 435 additions and 8 deletions

View File

@ -0,0 +1,60 @@
# Loading
### Install
```js
import Vue from 'vue';
import { Loading } from 'vant';
Vue.use(Loading);
```
## Usage
### Type
```html
<van-loading /> <van-loading type="spinner" />
```
### Color
```html
<van-loading color="#1989fa" /> <van-loading type="spinner" color="#1989fa" />
```
### Size
```html
<van-loading size="24" /> <van-loading type="spinner" size="24px" />
```
### Text
```html
<van-loading size="24px">Loading...</van-loading>
```
### Vertical
```html
<van-loading size="24px" vertical>Loading...</van-loading>
```
## API
### Props
| Attribute | Description | Type | Default |
| --- | --- | --- | --- |
| color | Loading color | _string_ | `#c9c9c9` |
| type | Can be set to `spinner` | _string_ | `circular` |
| size | Icon size | _number \| string_ | `30px` |
| text-size | Text font size | _number \| string_ | `14px` |
| vertical | Whether to arrange icons and text content vertically | _boolean_ | `false` |
### Slots
| Name | Description |
| ------- | ------------ |
| default | Loading text |

View File

@ -0,0 +1,70 @@
# Loading 加载
### 引入
```js
import Vue from 'vue';
import { Loading } from 'vant';
Vue.use(Loading);
```
## 代码演示
### 加载类型
通过`type`属性可以设置加载图标的类型,默认为`circular`,可选值为`spinner`
```html
<van-loading /> <van-loading type="spinner" />
```
### 自定义颜色
通过`color`属性设置加载图标的颜色
```html
<van-loading color="#1989fa" /> <van-loading type="spinner" color="#1989fa" />
```
### 自定义大小
通过`size`属性设置加载图标的大小,默认单位为`px`
```html
<van-loading size="24" /> <van-loading type="spinner" size="24px" />
```
### 加载文案
可以使用默认插槽在图标的右侧插入加载文案
```html
<van-loading size="24px">加载中...</van-loading>
```
### 垂直排列
设置`vertical`属性后,图标和文案会垂直排列
```html
<van-loading size="24px" vertical>加载中...</van-loading>
```
## API
### Props
| 参数 | 说明 | 类型 | 默认值 |
| --------- | ---------------------------- | ------------------ | ---------- |
| color | 颜色 | _string_ | `#c9c9c9` |
| type | 类型,可选值为 `spinner` | _string_ | `circular` |
| size | 加载图标大小,默认单位为`px` | _number \| string_ | `30px` |
| text-size | 文字大小,默认单位为`px` | _number \| string_ | `14px` |
| vertical | 是否垂直排列图标和文字内容 | _boolean_ | `false` |
### Slots
| 名称 | 说明 |
| ------- | -------- |
| default | 加载文案 |

View File

@ -0,0 +1,68 @@
<template>
<demo-section>
<demo-block :title="t('type')">
<van-loading />
<van-loading type="spinner" />
</demo-block>
<demo-block :title="t('color')">
<van-loading color="#1989fa" />
<van-loading type="spinner" color="#1989fa" />
</demo-block>
<demo-block :title="t('size')">
<van-loading size="24" />
<van-loading type="spinner" size="24" />
</demo-block>
<demo-block :title="t('text')">
<van-loading size="24px">
{{ t('loading') }}
</van-loading>
</demo-block>
<demo-block :title="t('vertical')">
<van-loading size="24px" vertical>
{{ t('loading') }}
</van-loading>
</demo-block>
</demo-section>
</template>
<script>
export default {
i18n: {
'zh-CN': {
type: '加载类型',
text: '加载文案',
size: '自定义大小',
color: '自定义颜色',
vertical: '垂直排列',
},
'en-US': {
type: 'Type',
text: 'Text',
size: 'Size',
color: 'Color',
vertical: 'Vertical',
},
},
};
</script>
<style lang="less">
@import '../../style/var';
.demo-loading {
background: @white;
.van-loading {
display: inline-block;
margin: 5px 0 5px 20px;
&--vertical {
display: inline-flex;
}
}
}
</style>

64
src-next/loading/index.js Normal file
View File

@ -0,0 +1,64 @@
// Utils
import { createNamespace, addUnit } from '../utils';
const [createComponent, bem] = createNamespace('loading');
const SpinIcon = [];
for (let i = 0; i < 12; i++) {
SpinIcon.push(<i />);
}
const CircularIcon = (
<svg class={bem('circular')} viewBox="25 25 50 50">
<circle cx="50" cy="50" r="20" fill="none" />
</svg>
);
export default createComponent({
props: {
color: String,
size: [Number, String],
vertical: Boolean,
textSize: [Number, String],
type: {
type: String,
default: 'circular',
},
},
methods: {
genLoadingText() {
if (this.$slots.default) {
const style = this.textSize && {
fontSize: addUnit(this.textSize),
};
return (
<span class={bem('text')} style={style}>
{this.$slots.default()}
</span>
);
}
},
},
render() {
const { color, size, type, vertical } = this;
const style = { color };
if (size) {
const iconSize = addUnit(size);
style.width = iconSize;
style.height = iconSize;
}
return (
<div class={bem([type, { vertical }])}>
<span class={bem('spinner', type)} style={style}>
{type === 'spinner' ? SpinIcon : CircularIcon}
</span>
{this.genLoadingText()}
</div>
);
},
});

103
src-next/loading/index.less Normal file
View File

@ -0,0 +1,103 @@
@import '../style/var';
.van-loading {
position: relative;
color: @loading-spinner-color;
font-size: 0;
vertical-align: middle;
&__spinner {
position: relative;
display: inline-block;
width: @loading-spinner-size;
// compatible for 1.x, users may set width or height in root element
max-width: 100%;
height: @loading-spinner-size;
max-height: 100%;
vertical-align: middle;
animation: van-rotate @loading-spinner-animation-duration linear infinite;
&--spinner {
animation-timing-function: steps(12);
i {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
&::before {
display: block;
width: 2px;
height: 25%;
margin: 0 auto;
background-color: currentColor;
border-radius: 40%;
content: ' ';
}
}
}
&--circular {
animation-duration: 2s;
}
}
&__circular {
display: block;
width: 100%;
height: 100%;
circle {
animation: van-circular 1.5s ease-in-out infinite;
stroke: currentColor;
stroke-width: 3;
stroke-linecap: round;
}
}
&__text {
display: inline-block;
margin-left: @padding-xs;
color: @loading-text-color;
font-size: @loading-text-font-size;
vertical-align: middle;
}
&--vertical {
display: flex;
flex-direction: column;
align-items: center;
.van-loading__text {
margin: @padding-xs 0 0;
}
}
}
@keyframes van-circular {
0% {
stroke-dasharray: 1, 200;
stroke-dashoffset: 0;
}
50% {
stroke-dasharray: 90, 150;
stroke-dashoffset: -40;
}
100% {
stroke-dasharray: 90, 150;
stroke-dashoffset: -120;
}
}
.generate-spinner(@n, @i: 1) when (@i =< @n) {
.van-loading__spinner--spinner i:nth-of-type(@{i}) {
transform: rotate(@i * 30deg);
opacity: 1 - (0.75 / 12) * (@i - 1);
}
.generate-spinner(@n, (@i + 1));
}
.generate-spinner(12);

View File

@ -0,0 +1,28 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`renders demo correctly 1`] = `
<div>
<div>
<div class="van-loading van-loading--circular"><span class="van-loading__spinner van-loading__spinner--circular"><svg viewBox="25 25 50 50" class="van-loading__circular"><circle cx="50" cy="50" r="20" fill="none"></circle></svg></span></div>
<div class="van-loading van-loading--spinner"><span class="van-loading__spinner van-loading__spinner--spinner"><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i></span></div>
</div>
<div>
<div class="van-loading van-loading--circular"><span class="van-loading__spinner van-loading__spinner--circular" style="color: rgb(25, 137, 250);"><svg viewBox="25 25 50 50" class="van-loading__circular"><circle cx="50" cy="50" r="20" fill="none"></circle></svg></span></div>
<div class="van-loading van-loading--spinner"><span class="van-loading__spinner van-loading__spinner--spinner" style="color: rgb(25, 137, 250);"><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i></span></div>
</div>
<div>
<div class="van-loading van-loading--circular"><span class="van-loading__spinner van-loading__spinner--circular" style="width: 24px; height: 24px;"><svg viewBox="25 25 50 50" class="van-loading__circular"><circle cx="50" cy="50" r="20" fill="none"></circle></svg></span></div>
<div class="van-loading van-loading--spinner"><span class="van-loading__spinner van-loading__spinner--spinner" style="width: 24px; height: 24px;"><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i></span></div>
</div>
<div>
<div class="van-loading van-loading--circular"><span class="van-loading__spinner van-loading__spinner--circular" style="width: 24px; height: 24px;"><svg viewBox="25 25 50 50" class="van-loading__circular"><circle cx="50" cy="50" r="20" fill="none"></circle></svg></span><span class="van-loading__text">
加载中...
</span></div>
</div>
<div>
<div class="van-loading van-loading--circular van-loading--vertical"><span class="van-loading__spinner van-loading__spinner--circular" style="width: 24px; height: 24px;"><svg viewBox="25 25 50 50" class="van-loading__circular"><circle cx="50" cy="50" r="20" fill="none"></circle></svg></span><span class="van-loading__text">
加载中...
</span></div>
</div>
</div>
`;

View File

@ -0,0 +1,5 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`size prop 1`] = `<div class="van-loading van-loading--circular"><span class="van-loading__spinner van-loading__spinner--circular" style="width: 20px; height: 20px;"><svg viewBox="25 25 50 50" class="van-loading__circular"><circle cx="50" cy="50" r="20" fill="none"></circle></svg></span></div>`;
exports[`text-size prop 1`] = `<div class="van-loading van-loading--circular"><span class="van-loading__spinner van-loading__spinner--circular"><svg viewBox="25 25 50 50" class="van-loading__circular"><circle cx="50" cy="50" r="20" fill="none"></circle></svg></span><span class="van-loading__text" style="font-size: 20px;">Text</span></div>`;

View File

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

View File

@ -0,0 +1,25 @@
import { mount } from '../../../test';
import Loading from '..';
test('size prop', () => {
const wrapper = mount(Loading, {
propsData: {
size: 20,
},
});
expect(wrapper).toMatchSnapshot();
});
test('text-size prop', () => {
const wrapper = mount(Loading, {
propsData: {
textSize: 20,
},
scopedSlots: {
default: () => 'Text',
},
});
expect(wrapper).toMatchSnapshot();
});

View File

@ -192,10 +192,10 @@ module.exports = {
// path: 'dropdown-menu',
// title: 'DropdownMenu 下拉菜单',
// },
// {
// path: 'loading',
// title: 'Loading 加载',
// },
{
path: 'loading',
title: 'Loading 加载',
},
// {
// path: 'notify',
// title: 'Notify 消息通知',
@ -543,10 +543,10 @@ module.exports = {
// path: 'dropdown-menu',
// title: 'DropdownMenu',
// },
// {
// path: 'loading',
// title: 'Loading',
// },
{
path: 'loading',
title: 'Loading',
},
// {
// path: 'notify',
// title: 'Notify',