mirror of
https://gitee.com/ant-design-vue/ant-design-vue.git
synced 2024-11-30 02:57:50 +08:00
feat: add skeleton
This commit is contained in:
parent
a04bdc6acf
commit
1859364a8a
@ -122,6 +122,8 @@ import { default as version } from './version'
|
||||
|
||||
import { default as Drawer } from './drawer'
|
||||
|
||||
import { default as Skeleton } from './skeleton'
|
||||
|
||||
const components = [
|
||||
Affix,
|
||||
Anchor,
|
||||
@ -174,6 +176,7 @@ const components = [
|
||||
Tooltip,
|
||||
Upload,
|
||||
Drawer,
|
||||
Skeleton,
|
||||
]
|
||||
|
||||
const install = function (Vue) {
|
||||
@ -251,6 +254,7 @@ export {
|
||||
Tooltip,
|
||||
Upload,
|
||||
Drawer,
|
||||
Skeleton,
|
||||
}
|
||||
|
||||
export default {
|
||||
|
37
components/skeleton/Avatar.jsx
Normal file
37
components/skeleton/Avatar.jsx
Normal file
@ -0,0 +1,37 @@
|
||||
import classNames from 'classnames'
|
||||
import PropTypes from '../_util/vue-types'
|
||||
import { initDefaultProps } from '../_util/props-util'
|
||||
|
||||
const skeletonAvatarProps = {
|
||||
prefixCls: PropTypes.string,
|
||||
size: PropTypes.oneOf(['large', 'small', 'default']),
|
||||
shape: PropTypes.oneOf(['circle', 'square']),
|
||||
}
|
||||
|
||||
export const SkeletonAvatarProps = PropTypes.shape(skeletonAvatarProps).loose
|
||||
|
||||
const Avatar = {
|
||||
props: initDefaultProps(skeletonAvatarProps, {
|
||||
prefixCls: 'ant-skeleton-avatar',
|
||||
size: 'large',
|
||||
}),
|
||||
render () {
|
||||
const { prefixCls, size, shape } = this.$props
|
||||
|
||||
const sizeCls = classNames({
|
||||
[`${prefixCls}-lg`]: size === 'large',
|
||||
[`${prefixCls}-sm`]: size === 'small',
|
||||
})
|
||||
|
||||
const shapeCls = classNames({
|
||||
[`${prefixCls}-circle`]: shape === 'circle',
|
||||
[`${prefixCls}-square`]: shape === 'square',
|
||||
})
|
||||
|
||||
return (
|
||||
<span class={classNames(prefixCls, sizeCls, shapeCls)} />
|
||||
)
|
||||
},
|
||||
}
|
||||
|
||||
export default Avatar
|
53
components/skeleton/Paragraph.jsx
Normal file
53
components/skeleton/Paragraph.jsx
Normal file
@ -0,0 +1,53 @@
|
||||
import PropTypes from '../_util/vue-types'
|
||||
import { initDefaultProps } from '../_util/props-util'
|
||||
|
||||
const widthUnit = PropTypes.oneOfType([
|
||||
PropTypes.number,
|
||||
PropTypes.string,
|
||||
])
|
||||
|
||||
const skeletonParagraphProps = {
|
||||
prefixCls: PropTypes.string,
|
||||
width: PropTypes.oneOfType([
|
||||
widthUnit,
|
||||
PropTypes.arrayOf(widthUnit),
|
||||
]),
|
||||
rows: PropTypes.number,
|
||||
}
|
||||
|
||||
export const SkeletonParagraphProps = PropTypes.shape(skeletonParagraphProps)
|
||||
|
||||
const Paragraph = {
|
||||
props: initDefaultProps(skeletonParagraphProps, {
|
||||
prefixCls: 'ant-skeleton-paragraph',
|
||||
}),
|
||||
methods: {
|
||||
getWidth (index) {
|
||||
const { width, rows = 2 } = this
|
||||
if (Array.isArray(width)) {
|
||||
return width[index]
|
||||
}
|
||||
// last paragraph
|
||||
if (rows - 1 === index) {
|
||||
return width
|
||||
}
|
||||
return undefined
|
||||
},
|
||||
},
|
||||
render () {
|
||||
const { prefixCls, rows } = this.$props
|
||||
const rowList = [...Array(rows)].map((_, index) => {
|
||||
const width = this.getWidth(index)
|
||||
return <li key={index} style={{ width: typeof width === 'number' ? `${width}px` : width }} />
|
||||
})
|
||||
return (
|
||||
<ul
|
||||
class={prefixCls}
|
||||
>
|
||||
{rowList}
|
||||
</ul>
|
||||
)
|
||||
},
|
||||
}
|
||||
|
||||
export default Paragraph
|
30
components/skeleton/Title.jsx
Normal file
30
components/skeleton/Title.jsx
Normal file
@ -0,0 +1,30 @@
|
||||
import PropTypes from '../_util/vue-types'
|
||||
import { initDefaultProps } from '../_util/props-util'
|
||||
|
||||
const skeletonTitleProps = {
|
||||
prefixCls: PropTypes.string,
|
||||
width: PropTypes.oneOfType([
|
||||
PropTypes.number,
|
||||
PropTypes.string,
|
||||
]),
|
||||
}
|
||||
|
||||
export const SkeletonTitleProps = PropTypes.shape(skeletonTitleProps)
|
||||
|
||||
const Title = {
|
||||
props: initDefaultProps(skeletonTitleProps, {
|
||||
prefixCls: 'ant-skeleton-title',
|
||||
}),
|
||||
render () {
|
||||
const { prefixCls, width } = this.$props
|
||||
const zWidth = typeof width === 'number' ? `${width}px` : width
|
||||
return (
|
||||
<h3
|
||||
class={prefixCls}
|
||||
style={{ width: zWidth }}
|
||||
/>
|
||||
)
|
||||
},
|
||||
}
|
||||
|
||||
export default Title
|
104
components/skeleton/__tests__/__snapshots__/demo.test.js.snap
Normal file
104
components/skeleton/__tests__/__snapshots__/demo.test.js.snap
Normal file
@ -0,0 +1,104 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`renders ./components/skeleton/demo/active.md correctly 1`] = `
|
||||
<div class="ant-skeleton ant-skeleton-active">
|
||||
<div class="ant-skeleton-content">
|
||||
<h3 class="ant-skeleton-title" style="width: 38%;"></h3>
|
||||
<ul class="ant-skeleton-paragraph">
|
||||
<li></li>
|
||||
<li></li>
|
||||
<li style="width: 61%;"></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`renders ./components/skeleton/demo/basic.md correctly 1`] = `
|
||||
<div class="ant-skeleton">
|
||||
<div class="ant-skeleton-content">
|
||||
<h3 class="ant-skeleton-title" style="width: 38%;"></h3>
|
||||
<ul class="ant-skeleton-paragraph">
|
||||
<li></li>
|
||||
<li></li>
|
||||
<li style="width: 61%;"></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`renders ./components/skeleton/demo/children.md correctly 1`] = `
|
||||
<div class="article">
|
||||
<div>
|
||||
<h4>Ant Design Vue, a design language</h4>
|
||||
<p>We supply a series of design principles, practical patterns and high quality design resources (Sketch and Axure), to help people create their product prototypes beautifully and efficiently.</p>
|
||||
</div> <button type="button" class="ant-btn ant-btn-default"><span>Show Skeleton</span></button>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`renders ./components/skeleton/demo/complex.md correctly 1`] = `
|
||||
<div class="ant-skeleton ant-skeleton-with-avatar">
|
||||
<div class="ant-skeleton-header"><span class="ant-skeleton-avatar ant-skeleton-avatar-lg ant-skeleton-avatar-circle"></span></div>
|
||||
<div class="ant-skeleton-content">
|
||||
<h3 class="ant-skeleton-title" style="width: 50%;"></h3>
|
||||
<ul class="ant-skeleton-paragraph">
|
||||
<li></li>
|
||||
<li></li>
|
||||
<li></li>
|
||||
<li></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`renders ./components/skeleton/demo/list.md correctly 1`] = `
|
||||
<div><span tabindex="0" class="ant-switch"><span class="ant-switch-inner"></span></span>
|
||||
<div class="ant-list ant-list-vertical ant-list-lg ant-list-split">
|
||||
<div class="ant-spin-nested-loading">
|
||||
<div class="ant-spin-container">
|
||||
<div class="ant-list-item">
|
||||
<div class="ant-list-item-content ant-list-item-content-single">
|
||||
<div class="ant-skeleton ant-skeleton-with-avatar ant-skeleton-active">
|
||||
<div class="ant-skeleton-header"><span class="ant-skeleton-avatar ant-skeleton-avatar-lg ant-skeleton-avatar-circle"></span></div>
|
||||
<div class="ant-skeleton-content">
|
||||
<h3 class="ant-skeleton-title" style="width: 50%;"></h3>
|
||||
<ul class="ant-skeleton-paragraph">
|
||||
<li></li>
|
||||
<li></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ant-list-item">
|
||||
<div class="ant-list-item-content ant-list-item-content-single">
|
||||
<div class="ant-skeleton ant-skeleton-with-avatar ant-skeleton-active">
|
||||
<div class="ant-skeleton-header"><span class="ant-skeleton-avatar ant-skeleton-avatar-lg ant-skeleton-avatar-circle"></span></div>
|
||||
<div class="ant-skeleton-content">
|
||||
<h3 class="ant-skeleton-title" style="width: 50%;"></h3>
|
||||
<ul class="ant-skeleton-paragraph">
|
||||
<li></li>
|
||||
<li></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ant-list-item">
|
||||
<div class="ant-list-item-content ant-list-item-content-single">
|
||||
<div class="ant-skeleton ant-skeleton-with-avatar ant-skeleton-active">
|
||||
<div class="ant-skeleton-header"><span class="ant-skeleton-avatar ant-skeleton-avatar-lg ant-skeleton-avatar-circle"></span></div>
|
||||
<div class="ant-skeleton-content">
|
||||
<h3 class="ant-skeleton-title" style="width: 50%;"></h3>
|
||||
<ul class="ant-skeleton-paragraph">
|
||||
<li></li>
|
||||
<li></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
120
components/skeleton/__tests__/__snapshots__/index.test.js.snap
Normal file
120
components/skeleton/__tests__/__snapshots__/index.test.js.snap
Normal file
@ -0,0 +1,120 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`Skeleton avatar shape 1`] = `
|
||||
<div class="ant-skeleton ant-skeleton-with-avatar">
|
||||
<div class="ant-skeleton-header"><span class="ant-skeleton-avatar ant-skeleton-avatar-lg ant-skeleton-avatar-circle"></span></div>
|
||||
<div class="ant-skeleton-content">
|
||||
<h3 class="ant-skeleton-title" style="width: 50%;"></h3>
|
||||
<ul class="ant-skeleton-paragraph">
|
||||
<li></li>
|
||||
<li></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`Skeleton avatar shape 2`] = `
|
||||
<div class="ant-skeleton ant-skeleton-with-avatar">
|
||||
<div class="ant-skeleton-header"><span class="ant-skeleton-avatar ant-skeleton-avatar-lg ant-skeleton-avatar-square"></span></div>
|
||||
<div class="ant-skeleton-content">
|
||||
<h3 class="ant-skeleton-title" style="width: 50%;"></h3>
|
||||
<ul class="ant-skeleton-paragraph">
|
||||
<li></li>
|
||||
<li></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`Skeleton avatar size 1`] = `
|
||||
<div class="ant-skeleton ant-skeleton-with-avatar">
|
||||
<div class="ant-skeleton-header"><span class="ant-skeleton-avatar ant-skeleton-avatar-sm ant-skeleton-avatar-circle"></span></div>
|
||||
<div class="ant-skeleton-content">
|
||||
<h3 class="ant-skeleton-title" style="width: 50%;"></h3>
|
||||
<ul class="ant-skeleton-paragraph">
|
||||
<li></li>
|
||||
<li></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`Skeleton avatar size 2`] = `
|
||||
<div class="ant-skeleton ant-skeleton-with-avatar">
|
||||
<div class="ant-skeleton-header"><span class="ant-skeleton-avatar ant-skeleton-avatar-circle"></span></div>
|
||||
<div class="ant-skeleton-content">
|
||||
<h3 class="ant-skeleton-title" style="width: 50%;"></h3>
|
||||
<ul class="ant-skeleton-paragraph">
|
||||
<li></li>
|
||||
<li></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`Skeleton avatar size 3`] = `
|
||||
<div class="ant-skeleton ant-skeleton-with-avatar">
|
||||
<div class="ant-skeleton-header"><span class="ant-skeleton-avatar ant-skeleton-avatar-lg ant-skeleton-avatar-circle"></span></div>
|
||||
<div class="ant-skeleton-content">
|
||||
<h3 class="ant-skeleton-title" style="width: 50%;"></h3>
|
||||
<ul class="ant-skeleton-paragraph">
|
||||
<li></li>
|
||||
<li></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`Skeleton paragraph rows 1`] = `
|
||||
<div class="ant-skeleton">
|
||||
<div class="ant-skeleton-content">
|
||||
<h3 class="ant-skeleton-title" style="width: 38%;"></h3>
|
||||
<ul class="ant-skeleton-paragraph">
|
||||
<li></li>
|
||||
<li></li>
|
||||
<li></li>
|
||||
<li></li>
|
||||
<li style="width: 61%;"></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`Skeleton paragraph width 1`] = `
|
||||
<div class="ant-skeleton">
|
||||
<div class="ant-skeleton-content">
|
||||
<h3 class="ant-skeleton-title" style="width: 38%;"></h3>
|
||||
<ul class="ant-skeleton-paragraph">
|
||||
<li></li>
|
||||
<li></li>
|
||||
<li style="width: 93%;"></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`Skeleton paragraph width 2`] = `
|
||||
<div class="ant-skeleton">
|
||||
<div class="ant-skeleton-content">
|
||||
<h3 class="ant-skeleton-title" style="width: 38%;"></h3>
|
||||
<ul class="ant-skeleton-paragraph">
|
||||
<li style="width: 28%;"></li>
|
||||
<li style="width: 93%;"></li>
|
||||
<li></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`Skeleton title width 1`] = `
|
||||
<div class="ant-skeleton">
|
||||
<div class="ant-skeleton-content">
|
||||
<h3 class="ant-skeleton-title" style="width: 93%;"></h3>
|
||||
<ul class="ant-skeleton-paragraph">
|
||||
<li></li>
|
||||
<li></li>
|
||||
<li style="width: 61%;"></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
3
components/skeleton/__tests__/demo.test.js
Normal file
3
components/skeleton/__tests__/demo.test.js
Normal file
@ -0,0 +1,3 @@
|
||||
import demoTest from '../../../tests/shared/demoTest'
|
||||
|
||||
demoTest('skeleton')
|
82
components/skeleton/__tests__/index.test.js
Normal file
82
components/skeleton/__tests__/index.test.js
Normal file
@ -0,0 +1,82 @@
|
||||
import { mount } from '@vue/test-utils'
|
||||
import { asyncExpect } from '@/tests/utils'
|
||||
import Skeleton from '..'
|
||||
|
||||
describe('Skeleton', () => {
|
||||
const genSkeleton = props => {
|
||||
const skeletonProps = {
|
||||
propsData: {
|
||||
loading: true,
|
||||
...props,
|
||||
},
|
||||
slots: {
|
||||
default: 'Bamboo',
|
||||
},
|
||||
sync: false,
|
||||
}
|
||||
return mount(Skeleton, skeletonProps)
|
||||
}
|
||||
|
||||
describe('avatar', () => {
|
||||
it('size', async () => {
|
||||
const wrapperSmall = genSkeleton({ avatar: { size: 'small' }})
|
||||
await asyncExpect(() => {
|
||||
expect(wrapperSmall.html()).toMatchSnapshot()
|
||||
})
|
||||
|
||||
const wrapperDefault = genSkeleton({ avatar: { size: 'default' }})
|
||||
|
||||
await asyncExpect(() => {
|
||||
expect(wrapperDefault.html()).toMatchSnapshot()
|
||||
})
|
||||
|
||||
const wrapperLarge = genSkeleton({ avatar: { size: 'large' }})
|
||||
|
||||
await asyncExpect(() => {
|
||||
expect(wrapperLarge.html()).toMatchSnapshot()
|
||||
})
|
||||
})
|
||||
|
||||
it('shape', async () => {
|
||||
const wrapperCircle = genSkeleton({ avatar: { shape: 'circle' }})
|
||||
await asyncExpect(() => {
|
||||
expect(wrapperCircle.html()).toMatchSnapshot()
|
||||
})
|
||||
|
||||
const wrapperSquare = genSkeleton({ avatar: { shape: 'square' }})
|
||||
await asyncExpect(() => {
|
||||
expect(wrapperSquare.html()).toMatchSnapshot()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('title', () => {
|
||||
it('width', async () => {
|
||||
const wrapper = genSkeleton({ title: { width: '93%' }})
|
||||
await asyncExpect(() => {
|
||||
expect(wrapper.html()).toMatchSnapshot()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('paragraph', () => {
|
||||
it('rows', async () => {
|
||||
const wrapper = genSkeleton({ paragraph: { rows: 5 }})
|
||||
await asyncExpect(() => {
|
||||
expect(wrapper.html()).toMatchSnapshot()
|
||||
})
|
||||
})
|
||||
|
||||
it('width', async () => {
|
||||
const wrapperPure = genSkeleton({ paragraph: { width: '93%' }})
|
||||
await asyncExpect(() => {
|
||||
expect(wrapperPure.html()).toMatchSnapshot()
|
||||
})
|
||||
|
||||
const wrapperList = genSkeleton({ paragraph: { width: ['28%', '93%'] }})
|
||||
await asyncExpect(() => {
|
||||
expect(wrapperList.html()).toMatchSnapshot()
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
16
components/skeleton/demo/active.md
Normal file
16
components/skeleton/demo/active.md
Normal file
@ -0,0 +1,16 @@
|
||||
<cn>
|
||||
#### 动画效果
|
||||
显示动画效果。
|
||||
</cn>
|
||||
|
||||
<us>
|
||||
#### Active Animation
|
||||
Display active animation.
|
||||
</us>
|
||||
|
||||
```html
|
||||
<template>
|
||||
<a-skeleton active />
|
||||
</template>
|
||||
```
|
||||
|
15
components/skeleton/demo/basic.md
Normal file
15
components/skeleton/demo/basic.md
Normal file
@ -0,0 +1,15 @@
|
||||
<cn>
|
||||
#### 基本
|
||||
最简单的占位效果。
|
||||
</cn>
|
||||
|
||||
<us>
|
||||
#### Basic
|
||||
Simplest Skeleton usage.
|
||||
</us>
|
||||
|
||||
```html
|
||||
<template>
|
||||
<a-skeleton />
|
||||
</template>
|
||||
```
|
51
components/skeleton/demo/children.md
Normal file
51
components/skeleton/demo/children.md
Normal file
@ -0,0 +1,51 @@
|
||||
<cn>
|
||||
#### 包含子组件
|
||||
加载占位图包含子组件。
|
||||
</cn>
|
||||
|
||||
<us>
|
||||
#### Contains sub component
|
||||
Skeleton contains sub component.
|
||||
</us>
|
||||
|
||||
```html
|
||||
<template>
|
||||
<div class="article">
|
||||
<a-skeleton :loading="loading">
|
||||
<div>
|
||||
<h4>Ant Design Vue, a design language</h4>
|
||||
<p>We supply a series of design principles, practical patterns and high quality design resources (Sketch and Axure), to help people create their product prototypes beautifully and efficiently.</p>
|
||||
</div>
|
||||
</a-skeleton>
|
||||
<a-button @click="showSkeleton" :disabled="loading">
|
||||
Show Skeleton
|
||||
</a-button>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
loading: false,
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
showSkeleton() {
|
||||
this.loading = true
|
||||
setTimeout(() => {
|
||||
this.loading = false
|
||||
}, 3000);
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style>
|
||||
.article h4 {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
.article button {
|
||||
margin-top: 16px;
|
||||
}
|
||||
</style>
|
||||
```
|
||||
|
16
components/skeleton/demo/complex.md
Normal file
16
components/skeleton/demo/complex.md
Normal file
@ -0,0 +1,16 @@
|
||||
<cn>
|
||||
#### 复杂的组合
|
||||
更复杂的组合。
|
||||
</cn>
|
||||
|
||||
<us>
|
||||
#### Complex combination
|
||||
Complex combination with avatar and multiple paragraphs.
|
||||
</us>
|
||||
|
||||
```html
|
||||
<template>
|
||||
<a-skeleton avatar :paragraph="{rows: 4}" />
|
||||
</template>
|
||||
```
|
||||
|
62
components/skeleton/demo/index.vue
Normal file
62
components/skeleton/demo/index.vue
Normal file
@ -0,0 +1,62 @@
|
||||
<script>
|
||||
import Basic from './basic.md'
|
||||
import Active from './active.md'
|
||||
import Children from './children.md'
|
||||
import Complex from './complex.md'
|
||||
import List from './list.md'
|
||||
import CN from '../index.zh-CN.md'
|
||||
import US from '../index.en-US.md'
|
||||
|
||||
const md = {
|
||||
cn: `# 加载占位图
|
||||
|
||||
在需要等待加载内容的位置提供一个占位图。
|
||||
|
||||
## 何时使用
|
||||
|
||||
- 网络较慢,需要长时间等待加载处理的情况下。
|
||||
- 图文信息内容较多的列表/卡片中。
|
||||
## 代码演示`,
|
||||
us: `# Skeleton
|
||||
|
||||
Provide a placeholder at the place which need waiting for loading.
|
||||
|
||||
# When To Use
|
||||
|
||||
- When resource needs long time to load, like low network speed.
|
||||
- The component contains much information. Such as List or Card.
|
||||
## Examples
|
||||
`,
|
||||
}
|
||||
export default {
|
||||
category: 'Components',
|
||||
subtitle: '加载占位图',
|
||||
type: 'Feedback',
|
||||
title: 'Skeleton',
|
||||
cols: 1,
|
||||
render () {
|
||||
return (
|
||||
<div>
|
||||
<md cn={md.cn} us={md.us}/>
|
||||
<br/>
|
||||
<Basic/>
|
||||
<br />
|
||||
<Complex />
|
||||
<br />
|
||||
<Active />
|
||||
<br />
|
||||
<Children />
|
||||
<br />
|
||||
<List />
|
||||
<br/>
|
||||
<api>
|
||||
<template slot='cn'>
|
||||
<CN/>
|
||||
</template>
|
||||
<US/>
|
||||
</api>
|
||||
</div>
|
||||
)
|
||||
},
|
||||
}
|
||||
</script>
|
79
components/skeleton/demo/list.md
Normal file
79
components/skeleton/demo/list.md
Normal file
@ -0,0 +1,79 @@
|
||||
<cn>
|
||||
#### 列表
|
||||
在列表组件中使用加载占位符。
|
||||
</cn>
|
||||
|
||||
<us>
|
||||
#### List
|
||||
Use skeleton in list component.
|
||||
</us>
|
||||
|
||||
```html
|
||||
<template>
|
||||
<div>
|
||||
<a-switch :checked="!loading" @change="onChange" />
|
||||
|
||||
<a-list
|
||||
itemLayout="vertical"
|
||||
size="large"
|
||||
:dataSource="listData"
|
||||
>
|
||||
<a-list-item slot="renderItem" slot-scope="item, index" key="item.title">
|
||||
<template v-if="!loading" slot="actions" v-for="{type, text} in actions">
|
||||
<span :key="type">
|
||||
<a-icon :type="type" style="margin-right: 8px" />
|
||||
{{text}}
|
||||
</span>
|
||||
</template>
|
||||
<img v-if="!loading" slot="extra" width="272" alt="logo" src="https://gw.alipayobjects.com/zos/rmsportal/mqaQswcyDLcXyDKnZfES.png" />
|
||||
<a-skeleton :loading="loading" active avatar>
|
||||
<a-list-item-meta
|
||||
:description="item.description"
|
||||
>
|
||||
<a slot="title" :href="item.href">{{item.title}}</a>
|
||||
<a-avatar slot="avatar" :src="item.avatar" />
|
||||
</a-list-item-meta>
|
||||
{{item.content}}
|
||||
</a-skeleton>
|
||||
</a-list-item>
|
||||
</a-list>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
const listData = [];
|
||||
for (let i = 0; i < 3; i++) {
|
||||
listData.push({
|
||||
href: 'https://vuecomponent.github.io/ant-design-vue/',
|
||||
title: `ant design vue part ${i}`,
|
||||
avatar: 'https://zos.alipayobjects.com/rmsportal/ODTLcjxAfvqbxHnVXCYX.png',
|
||||
description: 'Ant Design, a design language for background applications, is refined by Ant UED Team.',
|
||||
content: 'We supply a series of design principles, practical patterns and high quality design resources (Sketch and Axure), to help people create their product prototypes beautifully and efficiently.',
|
||||
});
|
||||
}
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
loading: true,
|
||||
listData,
|
||||
actions: [
|
||||
{ type: 'star-o', text: '156' },
|
||||
{ type: 'like-o', text: '156' },
|
||||
{ type: 'message', text: '2' },
|
||||
],
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onChange(checked) {
|
||||
this.loading = !checked
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style>
|
||||
.skeleton-demo {
|
||||
border: 1px solid #f4f4f4;
|
||||
}
|
||||
</style>
|
||||
```
|
||||
|
||||
|
31
components/skeleton/index.en-US.md
Normal file
31
components/skeleton/index.en-US.md
Normal file
@ -0,0 +1,31 @@
|
||||
## API
|
||||
|
||||
### Skeleton
|
||||
|
||||
| Property | Description | Type | Default |
|
||||
| --- | --- | --- | --- |
|
||||
| active | Show animation effect | boolean | false |
|
||||
| avatar | Show avatar placeholder | boolean \| [SkeletonAvatarProps](#SkeletonAvatarProps) | false |
|
||||
| loading | Display the skeleton when `true` | boolean | - |
|
||||
| paragraph | Show paragraph placeholder | boolean \| [SkeletonParagraphProps](#SkeletonParagraphProps) | true |
|
||||
| title | Show title placeholder | boolean \| [SkeletonTitleProps](#SkeletonTitleProps) | true |
|
||||
|
||||
### SkeletonAvatarProps
|
||||
|
||||
| Property | Description | Type | Default |
|
||||
| --- | --- | --- | --- |
|
||||
| size | Set the size of avatar | Enum{ 'large', 'small', 'default' } | - |
|
||||
| shape | Set the shape of avatar | Enum{ 'circle', 'square' } | - |
|
||||
|
||||
### SkeletonTitleProps
|
||||
|
||||
| Property | Description | Type | Default |
|
||||
| --- | --- | --- | --- |
|
||||
| width | Set the width of title | number \| string | - |
|
||||
|
||||
### SkeletonParagraphProps
|
||||
|
||||
| Property | Description | Type | Default |
|
||||
| --- | --- | --- | --- |
|
||||
| rows | Set the row count of paragraph | number | - |
|
||||
| width | Set the width of paragraph. When width is an Array, it can set the width of each row. Otherwise only set the last row width | number \| string \| Array<number \| string> | - |
|
171
components/skeleton/index.jsx
Normal file
171
components/skeleton/index.jsx
Normal file
@ -0,0 +1,171 @@
|
||||
import classNames from 'classnames'
|
||||
import PropTypes from '../_util/vue-types'
|
||||
import { initDefaultProps, hasProp } from '../_util/props-util'
|
||||
import Avatar, { SkeletonAvatarProps } from './Avatar'
|
||||
import Title, { SkeletonTitleProps } from './Title'
|
||||
import Paragraph, { SkeletonParagraphProps } from './Paragraph'
|
||||
|
||||
export const SkeletonProps = {
|
||||
active: PropTypes.bool,
|
||||
loading: PropTypes.bool,
|
||||
prefixCls: PropTypes.string,
|
||||
children: PropTypes.any,
|
||||
avatar: PropTypes.oneOfType([
|
||||
PropTypes.string,
|
||||
SkeletonAvatarProps,
|
||||
PropTypes.bool,
|
||||
]),
|
||||
title: PropTypes.oneOfType([
|
||||
PropTypes.bool,
|
||||
PropTypes.string,
|
||||
SkeletonTitleProps,
|
||||
]),
|
||||
paragraph: PropTypes.oneOfType([
|
||||
PropTypes.bool,
|
||||
PropTypes.string,
|
||||
SkeletonParagraphProps,
|
||||
]),
|
||||
}
|
||||
|
||||
function getComponentProps (prop) {
|
||||
if (prop && typeof prop === 'object') {
|
||||
return prop
|
||||
}
|
||||
return {}
|
||||
}
|
||||
|
||||
function getAvatarBasicProps (hasTitle, hasParagraph) {
|
||||
if (hasTitle && !hasParagraph) {
|
||||
return { shape: 'square' }
|
||||
}
|
||||
|
||||
return { shape: 'circle' }
|
||||
}
|
||||
|
||||
function getTitleBasicProps (hasAvatar, hasParagraph) {
|
||||
if (!hasAvatar && hasParagraph) {
|
||||
return { width: '38%' }
|
||||
}
|
||||
|
||||
if (hasAvatar && hasParagraph) {
|
||||
return { width: '50%' }
|
||||
}
|
||||
|
||||
return {}
|
||||
}
|
||||
|
||||
function getParagraphBasicProps (hasAvatar, hasTitle) {
|
||||
const basicProps = {}
|
||||
|
||||
// Width
|
||||
if (!hasAvatar || !hasTitle) {
|
||||
basicProps.width = '61%'
|
||||
}
|
||||
|
||||
// Rows
|
||||
if (!hasAvatar && hasTitle) {
|
||||
basicProps.rows = 3
|
||||
} else {
|
||||
basicProps.rows = 2
|
||||
}
|
||||
|
||||
return basicProps
|
||||
}
|
||||
|
||||
const Skeleton = {
|
||||
name: 'ASkeleton',
|
||||
props: initDefaultProps(SkeletonProps, {
|
||||
prefixCls: 'ant-skeleton',
|
||||
avatar: false,
|
||||
title: true,
|
||||
paragraph: true,
|
||||
}),
|
||||
render () {
|
||||
const {
|
||||
loading, prefixCls,
|
||||
avatar, title, paragraph, active,
|
||||
} = this.$props
|
||||
if (loading || !hasProp(this, 'loading')) {
|
||||
const hasAvatar = !!avatar || avatar === ''
|
||||
const hasTitle = !!title
|
||||
const hasParagraph = !!paragraph
|
||||
|
||||
// Avatar
|
||||
let avatarNode
|
||||
if (hasAvatar) {
|
||||
const avatarProps = {
|
||||
props: {
|
||||
...getAvatarBasicProps(hasTitle, hasParagraph),
|
||||
...getComponentProps(avatar),
|
||||
},
|
||||
}
|
||||
|
||||
avatarNode = (
|
||||
<div class={`${prefixCls}-header`}>
|
||||
<Avatar {...avatarProps} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
let contentNode
|
||||
if (hasTitle || hasParagraph) {
|
||||
// Title
|
||||
let $title
|
||||
if (hasTitle) {
|
||||
const titleProps = {
|
||||
props: {
|
||||
...getTitleBasicProps(hasAvatar, hasParagraph),
|
||||
...getComponentProps(title),
|
||||
},
|
||||
}
|
||||
|
||||
$title = (
|
||||
<Title {...titleProps} />
|
||||
)
|
||||
}
|
||||
|
||||
// Paragraph
|
||||
let paragraphNode
|
||||
if (hasParagraph) {
|
||||
const paragraphProps = {
|
||||
props: {
|
||||
...getParagraphBasicProps(hasAvatar, hasTitle),
|
||||
...getComponentProps(paragraph),
|
||||
},
|
||||
}
|
||||
|
||||
paragraphNode = (
|
||||
<Paragraph {...paragraphProps} />
|
||||
)
|
||||
}
|
||||
|
||||
contentNode = (
|
||||
<div class={`${prefixCls}-content`}>
|
||||
{$title}
|
||||
{paragraphNode}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const cls = classNames(
|
||||
prefixCls, {
|
||||
[`${prefixCls}-with-avatar`]: hasAvatar,
|
||||
[`${prefixCls}-active`]: active,
|
||||
},
|
||||
)
|
||||
|
||||
return (
|
||||
<div class={cls}>
|
||||
{avatarNode}
|
||||
{contentNode}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
return this.$slots.default && this.$slots.default[0]
|
||||
},
|
||||
}
|
||||
/* istanbul ignore next */
|
||||
Skeleton.install = function (Vue) {
|
||||
Vue.component(Skeleton.name, Skeleton)
|
||||
}
|
||||
export default Skeleton
|
31
components/skeleton/index.zh-CN.md
Normal file
31
components/skeleton/index.zh-CN.md
Normal file
@ -0,0 +1,31 @@
|
||||
## API
|
||||
|
||||
### Skeleton
|
||||
|
||||
| 属性 | 说明 | 类型 | 默认值 |
|
||||
| --- | --- | --- | --- |
|
||||
| active | 是否展示动画效果 | boolean | false |
|
||||
| avatar | 是否显示头像占位图 | boolean \| [SkeletonAvatarProps](#SkeletonAvatarProps) | false |
|
||||
| loading | 为 `true` 时,显示占位图。反之则直接展示子组件 | boolean | - |
|
||||
| paragraph | 是否显示段落占位图 | boolean \| [SkeletonParagraphProps](#SkeletonParagraphProps) | true |
|
||||
| title | 是否显示标题占位图 | boolean \| [SkeletonTitleProps](#SkeletonTitleProps) | true |
|
||||
|
||||
### SkeletonAvatarProps
|
||||
|
||||
| 属性 | 说明 | 类型 | 默认值 |
|
||||
| --- | --- | --- | --- |
|
||||
| size | 设置头像占位图的大小 | Enum{ 'large', 'small', 'default' } | - |
|
||||
| shape | 指定头像的形状 | Enum{ 'circle', 'square' } | - |
|
||||
|
||||
### SkeletonTitleProps
|
||||
|
||||
| 属性 | 说明 | 类型 | 默认值 |
|
||||
| --- | --- | --- | --- |
|
||||
| width | 设置标题占位图的宽度 | number \| string | - |
|
||||
|
||||
### SkeletonParagraphProps
|
||||
|
||||
| 属性 | 说明 | 类型 | 默认值 |
|
||||
| --- | --- | --- | --- |
|
||||
| rows | 设置段落占位图的行数 | number | - |
|
||||
| width | 设置段落占位图的宽度,若为数组时则为对应的每行宽度,反之则是最后一行的宽度 | number \| string \| Array<number \| string> | - |
|
2
components/skeleton/style/index.js
Normal file
2
components/skeleton/style/index.js
Normal file
@ -0,0 +1,2 @@
|
||||
import '../../style/index.less';
|
||||
import './index.less';
|
123
components/skeleton/style/index.less
Normal file
123
components/skeleton/style/index.less
Normal file
@ -0,0 +1,123 @@
|
||||
@import "../../style/themes/default";
|
||||
@import "../../style/mixins/index";
|
||||
|
||||
@skeleton-prefix-cls: ~"@{ant-prefix}-skeleton";
|
||||
@skeleton-avatar-prefix-cls: ~"@{skeleton-prefix-cls}-avatar";
|
||||
@skeleton-title-prefix-cls: ~"@{skeleton-prefix-cls}-title";
|
||||
@skeleton-paragraph-prefix-cls: ~"@{skeleton-prefix-cls}-paragraph";
|
||||
|
||||
@skeleton-to-color: shade(@skeleton-color, 5%);
|
||||
|
||||
.@{skeleton-prefix-cls} {
|
||||
display: table;
|
||||
width: 100%;
|
||||
|
||||
&-header {
|
||||
display: table-cell;
|
||||
vertical-align: top;
|
||||
padding-right: 16px;
|
||||
|
||||
// Avatar
|
||||
.@{skeleton-avatar-prefix-cls} {
|
||||
display: inline-block;
|
||||
vertical-align: top;
|
||||
background: @skeleton-color;
|
||||
|
||||
.avatar-size(@avatar-size-base);
|
||||
|
||||
&-lg {
|
||||
.avatar-size(@avatar-size-lg);
|
||||
}
|
||||
|
||||
&-sm {
|
||||
.avatar-size(@avatar-size-sm);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&-content {
|
||||
display: table-cell;
|
||||
vertical-align: top;
|
||||
width: 100%;
|
||||
|
||||
// Title
|
||||
.@{skeleton-title-prefix-cls} {
|
||||
margin-top: 16px;
|
||||
height: 16px;
|
||||
width: 100%;
|
||||
background: @skeleton-color;
|
||||
|
||||
+ .@{skeleton-paragraph-prefix-cls} {
|
||||
margin-top: 24px;
|
||||
}
|
||||
}
|
||||
|
||||
// paragraph
|
||||
.@{skeleton-paragraph-prefix-cls} {
|
||||
> li {
|
||||
height: 16px;
|
||||
background: @skeleton-color;
|
||||
list-style: none;
|
||||
width: 100%;
|
||||
|
||||
&:last-child:not(:first-child):not(:nth-child(2)) {
|
||||
width: 61%;
|
||||
}
|
||||
|
||||
+ li {
|
||||
margin-top: 16px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&-with-avatar &-content {
|
||||
// Title
|
||||
.@{skeleton-title-prefix-cls} {
|
||||
margin-top: 12px;
|
||||
|
||||
+ .@{skeleton-paragraph-prefix-cls} {
|
||||
margin-top: 28px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// With active animation
|
||||
&.@{skeleton-prefix-cls}-active {
|
||||
& .@{skeleton-prefix-cls}-content {
|
||||
.@{skeleton-title-prefix-cls},
|
||||
.@{skeleton-paragraph-prefix-cls} > li {
|
||||
.skeleton-color();
|
||||
}
|
||||
}
|
||||
|
||||
.@{skeleton-avatar-prefix-cls} {
|
||||
.skeleton-color();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.avatar-size(@size) {
|
||||
width: @size;
|
||||
height: @size;
|
||||
line-height: @size;
|
||||
|
||||
&.@{skeleton-avatar-prefix-cls}-circle {
|
||||
border-radius: 50%;
|
||||
}
|
||||
}
|
||||
|
||||
.skeleton-color() {
|
||||
background: linear-gradient(90deg, @skeleton-color 25%, @skeleton-to-color 37%, @skeleton-color 63%);
|
||||
animation: ~"@{skeleton-prefix-cls}-loading" 1.4s ease infinite;
|
||||
background-size: 400% 100%;
|
||||
}
|
||||
|
||||
@keyframes ~"@{skeleton-prefix-cls}-loading" {
|
||||
0% {
|
||||
background-position: 100% 50%;
|
||||
}
|
||||
100% {
|
||||
background-position: 0 50%;
|
||||
}
|
||||
}
|
@ -50,3 +50,4 @@ import './list/style'
|
||||
import './carousel/style'
|
||||
import './tree-select/style'
|
||||
import './drawer/style'
|
||||
import './skeleton/style'
|
||||
|
@ -354,6 +354,11 @@
|
||||
@tag-default-color: @text-color;
|
||||
@tag-font-size: @font-size-sm;
|
||||
|
||||
// Skeleton
|
||||
// ---
|
||||
@skeleton-color: #f2f2f2;
|
||||
|
||||
|
||||
// TimePicker
|
||||
// ---
|
||||
@time-picker-panel-column-width: 56px;
|
||||
|
@ -180,4 +180,4 @@
|
||||
"vue-ref": "^1.0.3",
|
||||
"warning": "^3.0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -55,6 +55,7 @@ import {
|
||||
Upload,
|
||||
// version,
|
||||
Drawer,
|
||||
Skeleton,
|
||||
} from 'ant-design-vue'
|
||||
|
||||
Vue.prototype.$message = message
|
||||
@ -117,6 +118,7 @@ Vue.use(TimePicker)
|
||||
Vue.use(Timeline)
|
||||
Vue.use(Tooltip)
|
||||
Vue.use(Upload)
|
||||
Vue.use(Skeleton)
|
||||
|
||||
/* v1.1.2 registration methods */
|
||||
// Vue.component(Affix.name, Affix) // a-affix
|
||||
|
Loading…
Reference in New Issue
Block a user