mirror of
https://gitee.com/vant-contrib/vant.git
synced 2025-04-06 03:57:59 +08:00
feat: Skeleton component
This commit is contained in:
parent
ee8bb3df58
commit
ca0b7ff028
62
src-next/skeleton/README.md
Normal file
62
src-next/skeleton/README.md
Normal file
@ -0,0 +1,62 @@
|
||||
# Skeleton
|
||||
|
||||
### Install
|
||||
|
||||
```js
|
||||
import Vue from 'vue';
|
||||
import { Skeleton } from 'vant';
|
||||
|
||||
Vue.use(Skeleton);
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
### Basic Usage
|
||||
|
||||
```html
|
||||
<van-skeleton title :row="3" />
|
||||
```
|
||||
|
||||
### Show Avatar
|
||||
|
||||
```html
|
||||
<van-skeleton title avatar :row="3" />
|
||||
```
|
||||
|
||||
### Show Children
|
||||
|
||||
```html
|
||||
<van-skeleton title avatar :row="3" :loading="loading">
|
||||
<div>Content</div>
|
||||
</van-skeleton>
|
||||
```
|
||||
|
||||
```js
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
loading: true,
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
this.loading = false;
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
## API
|
||||
|
||||
### Props
|
||||
|
||||
| Attribute | Description | Type | Default |
|
||||
| --- | --- | --- | --- |
|
||||
| row | Row count | _number \| string_ | `0` |
|
||||
| row-width | Row width, can be array | _number \| string \|<br>(number \| string)[]_ | `100%` |
|
||||
| title | Whether to show title placeholder | _boolean_ | `false` |
|
||||
| avatar | Whether to show avatar placeholder | _boolean_ | `false` |
|
||||
| loading | Whether to show skeleton,pass `false` to show child component | _boolean_ | `true` |
|
||||
| animate | Whether to enable animation | _boolean_ | `true` |
|
||||
| round `v2.8.5` | Whether to show round title and row | _boolean_ | `false` |
|
||||
| title-width | Title width | _number \| string_ | `40%` |
|
||||
| avatar-size | Size of avatar placeholder | _number \| string_ | `32px` |
|
||||
| avatar-shape | Shape of avatar placeholder,can be set to `square` | _string_ | `round` |
|
68
src-next/skeleton/README.zh-CN.md
Normal file
68
src-next/skeleton/README.zh-CN.md
Normal file
@ -0,0 +1,68 @@
|
||||
# Skeleton 骨架屏
|
||||
|
||||
### 引入
|
||||
|
||||
```js
|
||||
import Vue from 'vue';
|
||||
import { Skeleton } from 'vant';
|
||||
|
||||
Vue.use(Skeleton);
|
||||
```
|
||||
|
||||
## 代码演示
|
||||
|
||||
### 基础用法
|
||||
|
||||
通过`title`属性显示标题占位图,通过`row`属性配置占位段落行数
|
||||
|
||||
```html
|
||||
<van-skeleton title :row="3" />
|
||||
```
|
||||
|
||||
### 显示头像
|
||||
|
||||
通过`avatar`属性显示头像占位图
|
||||
|
||||
```html
|
||||
<van-skeleton title avatar :row="3" />
|
||||
```
|
||||
|
||||
### 展示子组件
|
||||
|
||||
将`loading`属性设置成`false`表示内容加载完成,此时会隐藏占位图,并显示`Skeleton`的子组件
|
||||
|
||||
```html
|
||||
<van-skeleton title avatar :row="3" :loading="loading">
|
||||
<div>实际内容</div>
|
||||
</van-skeleton>
|
||||
```
|
||||
|
||||
```js
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
loading: true,
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
this.loading = false;
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
## API
|
||||
|
||||
### Props
|
||||
|
||||
| 参数 | 说明 | 类型 | 默认值 |
|
||||
| --- | --- | --- | --- |
|
||||
| row | 段落占位图行数 | _number \| string_ | `0` |
|
||||
| row-width | 段落占位图宽度,可传数组来设置每一行的宽度 | _number \| string \|<br>(number \| string)[]_ | `100%` |
|
||||
| title | 是否显示标题占位图 | _boolean_ | `false` |
|
||||
| avatar | 是否显示头像占位图 | _boolean_ | `false` |
|
||||
| loading | 是否显示骨架屏,传 `false` 时会展示子组件内容 | _boolean_ | `true` |
|
||||
| animate | 是否开启动画 | _boolean_ | `true` |
|
||||
| round `v2.8.5` | 是否将标题和段落显示为圆角风格 | _boolean_ | `false` |
|
||||
| title-width | 标题占位图宽度 | _number \| string_ | `40%` |
|
||||
| avatar-size | 头像占位图大小 | _number \| string_ | `32px` |
|
||||
| avatar-shape | 头像占位图形状,可选值为`square` | _string_ | `round` |
|
90
src-next/skeleton/demo/index.vue
Normal file
90
src-next/skeleton/demo/index.vue
Normal file
@ -0,0 +1,90 @@
|
||||
<template>
|
||||
<demo-section>
|
||||
<demo-block :title="t('basicUsage')">
|
||||
<van-skeleton title :row="3" />
|
||||
</demo-block>
|
||||
|
||||
<demo-block :title="t('showAvatar')">
|
||||
<van-skeleton title avatar :row="3" />
|
||||
</demo-block>
|
||||
|
||||
<demo-block :title="t('showChildren')">
|
||||
<van-switch v-model="show" size="24px" />
|
||||
<van-skeleton title avatar :row="3" :loading="!show">
|
||||
<div class="demo-preview">
|
||||
<img src="https://img.yzcdn.cn/vant/logo.png" />
|
||||
<div class="demo-content">
|
||||
<h3>{{ t('title') }}</h3>
|
||||
<p>{{ t('desc') }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</van-skeleton>
|
||||
</demo-block>
|
||||
</demo-section>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
i18n: {
|
||||
'zh-CN': {
|
||||
showAvatar: '显示头像',
|
||||
showChildren: '显示子组件',
|
||||
title: '关于 Vant',
|
||||
desc:
|
||||
'Vant 是一套轻量、可靠的移动端 Vue 组件库,提供了丰富的基础组件和业务组件,帮助开发者快速搭建移动应用。',
|
||||
},
|
||||
'en-US': {
|
||||
showAvatar: 'Show Avatar',
|
||||
showChildren: 'Show Children',
|
||||
title: 'About Vant',
|
||||
desc: 'Vant is a set of Mobile UI Components built on Vue.',
|
||||
},
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
show: false,
|
||||
};
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
@import '../../style/var';
|
||||
|
||||
.demo-skeleton {
|
||||
background-color: @white;
|
||||
|
||||
.van-switch {
|
||||
margin: 0 @padding-md @padding-xs;
|
||||
}
|
||||
|
||||
.demo-preview {
|
||||
display: flex;
|
||||
padding: 0 @padding-md;
|
||||
|
||||
.demo-content {
|
||||
padding-top: 6px;
|
||||
|
||||
h3 {
|
||||
margin: 0;
|
||||
font-size: 18px;
|
||||
line-height: 20px;
|
||||
}
|
||||
|
||||
p {
|
||||
margin: 13px 0 0;
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
img {
|
||||
flex-shrink: 0;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
margin-right: @padding-md;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
110
src-next/skeleton/index.js
Normal file
110
src-next/skeleton/index.js
Normal file
@ -0,0 +1,110 @@
|
||||
import { createNamespace, addUnit } from '../utils';
|
||||
|
||||
const [createComponent, bem] = createNamespace('skeleton');
|
||||
const DEFAULT_ROW_WIDTH = '100%';
|
||||
const DEFAULT_LAST_ROW_WIDTH = '60%';
|
||||
|
||||
export default createComponent({
|
||||
props: {
|
||||
title: Boolean,
|
||||
round: Boolean,
|
||||
avatar: Boolean,
|
||||
row: {
|
||||
type: [Number, String],
|
||||
default: 0,
|
||||
},
|
||||
loading: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
animate: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
avatarSize: {
|
||||
type: String,
|
||||
default: '32px',
|
||||
},
|
||||
avatarShape: {
|
||||
type: String,
|
||||
default: 'round',
|
||||
},
|
||||
titleWidth: {
|
||||
type: [Number, String],
|
||||
default: '40%',
|
||||
},
|
||||
rowWidth: {
|
||||
type: [Number, String, Array],
|
||||
default: DEFAULT_ROW_WIDTH,
|
||||
},
|
||||
},
|
||||
|
||||
setup(props, { slots }) {
|
||||
return function () {
|
||||
if (!props.loading) {
|
||||
return slots.default && slots.default();
|
||||
}
|
||||
|
||||
function Title() {
|
||||
if (props.title) {
|
||||
return (
|
||||
<h3
|
||||
class={bem('title')}
|
||||
style={{ width: addUnit(props.titleWidth) }}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function Rows() {
|
||||
const Rows = [];
|
||||
const { rowWidth } = props;
|
||||
|
||||
function getRowWidth(index) {
|
||||
if (rowWidth === DEFAULT_ROW_WIDTH && index === +props.row - 1) {
|
||||
return DEFAULT_LAST_ROW_WIDTH;
|
||||
}
|
||||
|
||||
if (Array.isArray(rowWidth)) {
|
||||
return rowWidth[index];
|
||||
}
|
||||
|
||||
return rowWidth;
|
||||
}
|
||||
|
||||
for (let i = 0; i < props.row; i++) {
|
||||
Rows.push(
|
||||
<div
|
||||
class={bem('row')}
|
||||
style={{ width: addUnit(getRowWidth(i)) }}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return Rows;
|
||||
}
|
||||
|
||||
function Avatar() {
|
||||
if (props.avatar) {
|
||||
const size = addUnit(props.avatarSize);
|
||||
return (
|
||||
<div
|
||||
class={bem('avatar', props.avatarShape)}
|
||||
style={{ width: size, height: size }}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div class={bem({ animate: props.animate, round: props.round })}>
|
||||
{Avatar()}
|
||||
<div class={bem('content')}>
|
||||
{Title()}
|
||||
{Rows()}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
},
|
||||
});
|
62
src-next/skeleton/index.less
Normal file
62
src-next/skeleton/index.less
Normal file
@ -0,0 +1,62 @@
|
||||
@import '../style/var';
|
||||
|
||||
.van-skeleton {
|
||||
display: flex;
|
||||
padding: 0 @padding-md;
|
||||
|
||||
&__avatar {
|
||||
flex-shrink: 0;
|
||||
margin-right: @padding-md;
|
||||
background-color: @skeleton-avatar-background-color;
|
||||
|
||||
&--round {
|
||||
border-radius: @border-radius-max;
|
||||
}
|
||||
}
|
||||
|
||||
&__content {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
&__avatar + &__content {
|
||||
padding-top: @padding-xs;
|
||||
}
|
||||
|
||||
&__row,
|
||||
&__title {
|
||||
height: @skeleton-row-height;
|
||||
background-color: @skeleton-row-background-color;
|
||||
}
|
||||
|
||||
&__title {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
&__row {
|
||||
&:not(:first-child) {
|
||||
margin-top: @skeleton-row-margin-top;
|
||||
}
|
||||
}
|
||||
|
||||
&__title + &__row {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
&--animate {
|
||||
animation: van-skeleton-blink @skeleton-animation-duration ease-in-out
|
||||
infinite;
|
||||
}
|
||||
|
||||
&--round {
|
||||
.van-skeleton__row,
|
||||
.van-skeleton__title {
|
||||
border-radius: @border-radius-max;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes van-skeleton-blink {
|
||||
50% {
|
||||
opacity: 0.6;
|
||||
}
|
||||
}
|
41
src-next/skeleton/test/__snapshots__/demo.spec.js.snap
Normal file
41
src-next/skeleton/test/__snapshots__/demo.spec.js.snap
Normal file
@ -0,0 +1,41 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`renders demo correctly 1`] = `
|
||||
<div>
|
||||
<div>
|
||||
<div class="van-skeleton van-skeleton--animate">
|
||||
<div class="van-skeleton__content">
|
||||
<h3 class="van-skeleton__title" style="width: 40%;"></h3>
|
||||
<div class="van-skeleton__row" style="width: 100%;"></div>
|
||||
<div class="van-skeleton__row" style="width: 100%;"></div>
|
||||
<div class="van-skeleton__row" style="width: 60%;"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="van-skeleton van-skeleton--animate">
|
||||
<div class="van-skeleton__avatar van-skeleton__avatar--round" style="width: 32px; height: 32px;"></div>
|
||||
<div class="van-skeleton__content">
|
||||
<h3 class="van-skeleton__title" style="width: 40%;"></h3>
|
||||
<div class="van-skeleton__row" style="width: 100%;"></div>
|
||||
<div class="van-skeleton__row" style="width: 100%;"></div>
|
||||
<div class="van-skeleton__row" style="width: 60%;"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div role="switch" aria-checked="false" class="van-switch" style="font-size: 24px;">
|
||||
<div class="van-switch__node"></div>
|
||||
</div>
|
||||
<div class="van-skeleton van-skeleton--animate">
|
||||
<div class="van-skeleton__avatar van-skeleton__avatar--round" style="width: 32px; height: 32px;"></div>
|
||||
<div class="van-skeleton__content">
|
||||
<h3 class="van-skeleton__title" style="width: 40%;"></h3>
|
||||
<div class="van-skeleton__row" style="width: 100%;"></div>
|
||||
<div class="van-skeleton__row" style="width: 100%;"></div>
|
||||
<div class="van-skeleton__row" style="width: 60%;"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
38
src-next/skeleton/test/__snapshots__/index.spec.js.snap
Normal file
38
src-next/skeleton/test/__snapshots__/index.spec.js.snap
Normal file
@ -0,0 +1,38 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`avatar shape 1`] = `
|
||||
<div class="van-skeleton van-skeleton--animate">
|
||||
<div class="van-skeleton__avatar van-skeleton__avatar--square" style="width: 20px; height: 20px;"></div>
|
||||
<div class="van-skeleton__content"></div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`disable animate 1`] = `
|
||||
<div class="van-skeleton van-skeleton--animate">
|
||||
<div class="van-skeleton__content">
|
||||
<div class="van-skeleton__row" style="width: 60%;"></div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`render chidren 1`] = `<div>Content</div>`;
|
||||
|
||||
exports[`round prop 1`] = `
|
||||
<div class="van-skeleton van-skeleton--animate van-skeleton--round">
|
||||
<div class="van-skeleton__avatar van-skeleton__avatar--round" style="width: 32px; height: 32px;"></div>
|
||||
<div class="van-skeleton__content">
|
||||
<h3 class="van-skeleton__title" style="width: 40%;"></h3>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`row-width array 1`] = `
|
||||
<div class="van-skeleton van-skeleton--animate">
|
||||
<div class="van-skeleton__content">
|
||||
<div class="van-skeleton__row" style="width: 100%;"></div>
|
||||
<div class="van-skeleton__row" style="width: 30px;"></div>
|
||||
<div class="van-skeleton__row" style="width: 5rem;"></div>
|
||||
<div class="van-skeleton__row"></div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
4
src-next/skeleton/test/demo.spec.js
Normal file
4
src-next/skeleton/test/demo.spec.js
Normal file
@ -0,0 +1,4 @@
|
||||
import Demo from '../demo';
|
||||
import { snapshotDemo } from '../../../test/demo';
|
||||
|
||||
snapshotDemo(Demo);
|
56
src-next/skeleton/test/index.spec.js
Normal file
56
src-next/skeleton/test/index.spec.js
Normal file
@ -0,0 +1,56 @@
|
||||
import { mount } from '../../../test';
|
||||
import Skeleton from '..';
|
||||
|
||||
test('row-width array', () => {
|
||||
const wrapper = mount(Skeleton, {
|
||||
propsData: {
|
||||
row: 4,
|
||||
rowWidth: ['100%', 30, '5rem'],
|
||||
},
|
||||
});
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('render chidren', () => {
|
||||
const wrapper = mount({
|
||||
template: `
|
||||
<skeleton :loading="false">
|
||||
<div>Content</div>
|
||||
</skeleton>
|
||||
`,
|
||||
components: { Skeleton },
|
||||
});
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('avatar shape', () => {
|
||||
const wrapper = mount(Skeleton, {
|
||||
propsData: {
|
||||
avatar: true,
|
||||
avatarSize: 20,
|
||||
avatarShape: 'square',
|
||||
},
|
||||
});
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('round prop', () => {
|
||||
const wrapper = mount(Skeleton, {
|
||||
propsData: {
|
||||
title: true,
|
||||
round: true,
|
||||
avatar: true,
|
||||
},
|
||||
});
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('disable animate', () => {
|
||||
const wrapper = mount(Skeleton, {
|
||||
propsData: {
|
||||
row: 1,
|
||||
aniamte: false,
|
||||
},
|
||||
});
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
@ -261,10 +261,10 @@ module.exports = {
|
||||
// path: 'progress',
|
||||
// title: 'Progress 进度条',
|
||||
// },
|
||||
// {
|
||||
// path: 'skeleton',
|
||||
// title: 'Skeleton 骨架屏',
|
||||
// },
|
||||
{
|
||||
path: 'skeleton',
|
||||
title: 'Skeleton 骨架屏',
|
||||
},
|
||||
// {
|
||||
// path: 'steps',
|
||||
// title: 'Steps 步骤条',
|
||||
@ -595,10 +595,10 @@ module.exports = {
|
||||
// path: 'progress',
|
||||
// title: 'Progress',
|
||||
// },
|
||||
// {
|
||||
// path: 'skeleton',
|
||||
// title: 'Skeleton',
|
||||
// },
|
||||
{
|
||||
path: 'skeleton',
|
||||
title: 'Skeleton',
|
||||
},
|
||||
// {
|
||||
// path: 'steps',
|
||||
// title: 'Steps',
|
||||
|
Loading…
x
Reference in New Issue
Block a user