Browse Source
* Add new component Skeleton * Add related doc * Add sample * Add test case ref: https://github.com/ant-design/ant-design/issues/10308pull/11884/head
zombieJ
6 years ago
committed by
GitHub
24 changed files with 1342 additions and 192 deletions
@ -0,0 +1,37 @@ |
|||
import * as React from 'react'; |
|||
import classNames from 'classnames'; |
|||
|
|||
export interface SkeletonAvatarProps { |
|||
prefixCls?: string; |
|||
className?: string; |
|||
style?: object; |
|||
size?: 'large' | 'small' | 'default'; |
|||
shape?: 'circle'| 'square'; |
|||
} |
|||
|
|||
class Title extends React.Component<SkeletonAvatarProps, any> { |
|||
static defaultProps: Partial<SkeletonAvatarProps> = { |
|||
prefixCls: 'ant-skeleton-avatar', |
|||
size: 'large', |
|||
}; |
|||
|
|||
render() { |
|||
const { prefixCls, className, style, 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 className={classNames(prefixCls, className, sizeCls, shapeCls)} style={style} /> |
|||
); |
|||
} |
|||
} |
|||
|
|||
export default Title; |
@ -0,0 +1,78 @@ |
|||
import * as React from 'react'; |
|||
import classNames from 'classnames'; |
|||
import { polyfill } from 'react-lifecycles-compat'; |
|||
|
|||
type widthUnit = number | string; |
|||
|
|||
export interface SkeletonParagraphProps { |
|||
prefixCls?: string; |
|||
className?: string; |
|||
style?: object; |
|||
width?: widthUnit | Array<widthUnit>; |
|||
rows?: number; |
|||
} |
|||
|
|||
interface SkeletonParagraphState { |
|||
prevProps: SkeletonParagraphProps; |
|||
widthList: Array<widthUnit>; |
|||
} |
|||
|
|||
class Paragraph extends React.Component<SkeletonParagraphProps, SkeletonParagraphState> { |
|||
static defaultProps: Partial<SkeletonParagraphProps> = { |
|||
prefixCls: 'ant-skeleton-paragraph', |
|||
}; |
|||
|
|||
static getDerivedStateFromProps( |
|||
props: SkeletonParagraphProps, |
|||
state: SkeletonParagraphState, |
|||
): Partial<SkeletonParagraphState> { |
|||
const { prevProps } = state; |
|||
const { width, rows = 2 } = props; |
|||
|
|||
const newState: Partial<SkeletonParagraphState> = { |
|||
prevProps: props, |
|||
}; |
|||
|
|||
if (rows !== prevProps.rows || width !== prevProps.width) { |
|||
// Parse width list
|
|||
let widthList = []; |
|||
if (width && Array.isArray(width)) { |
|||
widthList = width; |
|||
} else if (width && !Array.isArray(width)) { |
|||
widthList = []; |
|||
widthList[rows - 1] = width; |
|||
} |
|||
|
|||
newState.widthList = widthList; |
|||
} |
|||
|
|||
return newState; |
|||
} |
|||
|
|||
state: SkeletonParagraphState = { |
|||
prevProps: {}, |
|||
widthList: [], |
|||
}; |
|||
|
|||
render() { |
|||
const { widthList } = this.state; |
|||
const { prefixCls, className, style, rows } = this.props; |
|||
|
|||
const rowList = [...Array(rows)].map((_, index) => ( |
|||
<li key={index} style={{ width: widthList[index] || '100%' }} /> |
|||
)); |
|||
|
|||
return ( |
|||
<ul |
|||
className={classNames(prefixCls, className)} |
|||
style={style} |
|||
> |
|||
{rowList} |
|||
</ul> |
|||
); |
|||
} |
|||
} |
|||
|
|||
polyfill(Paragraph); |
|||
|
|||
export default Paragraph; |
@ -0,0 +1,28 @@ |
|||
import * as React from 'react'; |
|||
import classNames from 'classnames'; |
|||
|
|||
export interface SkeletonTitleProps { |
|||
prefixCls?: string; |
|||
className?: string; |
|||
style?: object; |
|||
width?: number | string; |
|||
} |
|||
|
|||
class Title extends React.Component<SkeletonTitleProps, any> { |
|||
static defaultProps: Partial<SkeletonTitleProps> = { |
|||
prefixCls: 'ant-skeleton-title', |
|||
}; |
|||
|
|||
render() { |
|||
const { prefixCls, className, width, style } = this.props; |
|||
|
|||
return ( |
|||
<h3 |
|||
className={classNames(prefixCls, className)} |
|||
style={{ width, ...style }} |
|||
/> |
|||
); |
|||
} |
|||
} |
|||
|
|||
export default Title; |
@ -0,0 +1,204 @@ |
|||
// 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%" |
|||
/> |
|||
<ul |
|||
class="ant-skeleton-paragraph" |
|||
> |
|||
<li |
|||
style="width:100%" |
|||
/> |
|||
<li |
|||
style="width:100%" |
|||
/> |
|||
<li |
|||
style="width:61%" |
|||
/> |
|||
</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%" |
|||
/> |
|||
<ul |
|||
class="ant-skeleton-paragraph" |
|||
> |
|||
<li |
|||
style="width:100%" |
|||
/> |
|||
<li |
|||
style="width:100%" |
|||
/> |
|||
<li |
|||
style="width:61%" |
|||
/> |
|||
</ul> |
|||
</div> |
|||
</div> |
|||
`; |
|||
|
|||
exports[`renders ./components/skeleton/demo/children.md correctly 1`] = ` |
|||
<div |
|||
class="article" |
|||
> |
|||
<div> |
|||
<h4> |
|||
Ant Design, 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 |
|||
class="ant-btn" |
|||
type="button" |
|||
> |
|||
<span> |
|||
Show Skeleton |
|||
</span> |
|||
</button> |
|||
</div> |
|||
`; |
|||
|
|||
exports[`renders ./components/skeleton/demo/list.md correctly 1`] = ` |
|||
<div> |
|||
<span |
|||
class="ant-switch" |
|||
tabindex="0" |
|||
> |
|||
<span |
|||
class="ant-switch-inner" |
|||
/> |
|||
</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-active" |
|||
> |
|||
<div |
|||
class="ant-skeleton-content" |
|||
> |
|||
<h3 |
|||
class="ant-skeleton-title" |
|||
style="width:38%" |
|||
/> |
|||
<ul |
|||
class="ant-skeleton-paragraph" |
|||
> |
|||
<li |
|||
style="width:100%" |
|||
/> |
|||
<li |
|||
style="width:100%" |
|||
/> |
|||
<li |
|||
style="width:61%" |
|||
/> |
|||
</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-active" |
|||
> |
|||
<div |
|||
class="ant-skeleton-content" |
|||
> |
|||
<h3 |
|||
class="ant-skeleton-title" |
|||
style="width:38%" |
|||
/> |
|||
<ul |
|||
class="ant-skeleton-paragraph" |
|||
> |
|||
<li |
|||
style="width:100%" |
|||
/> |
|||
<li |
|||
style="width:100%" |
|||
/> |
|||
<li |
|||
style="width:61%" |
|||
/> |
|||
</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-active" |
|||
> |
|||
<div |
|||
class="ant-skeleton-content" |
|||
> |
|||
<h3 |
|||
class="ant-skeleton-title" |
|||
style="width:38%" |
|||
/> |
|||
<ul |
|||
class="ant-skeleton-paragraph" |
|||
> |
|||
<li |
|||
style="width:100%" |
|||
/> |
|||
<li |
|||
style="width:100%" |
|||
/> |
|||
<li |
|||
style="width:61%" |
|||
/> |
|||
</ul> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
`; |
@ -0,0 +1,279 @@ |
|||
// 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" |
|||
/> |
|||
</div> |
|||
<div |
|||
class="ant-skeleton-content" |
|||
> |
|||
<h3 |
|||
class="ant-skeleton-title" |
|||
style="width: 50%;" |
|||
/> |
|||
<ul |
|||
class="ant-skeleton-paragraph" |
|||
> |
|||
<li |
|||
style="width: 100%;" |
|||
/> |
|||
<li |
|||
style="width: 100%;" |
|||
/> |
|||
</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" |
|||
/> |
|||
</div> |
|||
<div |
|||
class="ant-skeleton-content" |
|||
> |
|||
<h3 |
|||
class="ant-skeleton-title" |
|||
style="width: 50%;" |
|||
/> |
|||
<ul |
|||
class="ant-skeleton-paragraph" |
|||
> |
|||
<li |
|||
style="width: 100%;" |
|||
/> |
|||
<li |
|||
style="width: 100%;" |
|||
/> |
|||
</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" |
|||
/> |
|||
</div> |
|||
<div |
|||
class="ant-skeleton-content" |
|||
> |
|||
<h3 |
|||
class="ant-skeleton-title" |
|||
style="width: 50%;" |
|||
/> |
|||
<ul |
|||
class="ant-skeleton-paragraph" |
|||
> |
|||
<li |
|||
style="width: 100%;" |
|||
/> |
|||
<li |
|||
style="width: 100%;" |
|||
/> |
|||
</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" |
|||
/> |
|||
</div> |
|||
<div |
|||
class="ant-skeleton-content" |
|||
> |
|||
<h3 |
|||
class="ant-skeleton-title" |
|||
style="width: 50%;" |
|||
/> |
|||
<ul |
|||
class="ant-skeleton-paragraph" |
|||
> |
|||
<li |
|||
style="width: 100%;" |
|||
/> |
|||
<li |
|||
style="width: 100%;" |
|||
/> |
|||
</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" |
|||
/> |
|||
</div> |
|||
<div |
|||
class="ant-skeleton-content" |
|||
> |
|||
<h3 |
|||
class="ant-skeleton-title" |
|||
style="width: 50%;" |
|||
/> |
|||
<ul |
|||
class="ant-skeleton-paragraph" |
|||
> |
|||
<li |
|||
style="width: 100%;" |
|||
/> |
|||
<li |
|||
style="width: 100%;" |
|||
/> |
|||
</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%;" |
|||
/> |
|||
<ul |
|||
class="ant-skeleton-paragraph" |
|||
> |
|||
<li |
|||
style="width: 100%;" |
|||
/> |
|||
<li |
|||
style="width: 100%;" |
|||
/> |
|||
<li |
|||
style="width: 100%;" |
|||
/> |
|||
<li |
|||
style="width: 100%;" |
|||
/> |
|||
<li |
|||
style="width: 61%;" |
|||
/> |
|||
</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%;" |
|||
/> |
|||
<ul |
|||
class="ant-skeleton-paragraph" |
|||
> |
|||
<li |
|||
style="width: 100%;" |
|||
/> |
|||
<li |
|||
style="width: 100%;" |
|||
/> |
|||
<li |
|||
style="width: 93%;" |
|||
/> |
|||
</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%;" |
|||
/> |
|||
<ul |
|||
class="ant-skeleton-paragraph" |
|||
> |
|||
<li |
|||
style="width: 28%;" |
|||
/> |
|||
<li |
|||
style="width: 93%;" |
|||
/> |
|||
<li |
|||
style="width: 100%;" |
|||
/> |
|||
</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%;" |
|||
/> |
|||
<ul |
|||
class="ant-skeleton-paragraph" |
|||
> |
|||
<li |
|||
style="width: 100%;" |
|||
/> |
|||
<li |
|||
style="width: 100%;" |
|||
/> |
|||
<li |
|||
style="width: 61%;" |
|||
/> |
|||
</ul> |
|||
</div> |
|||
</div> |
|||
`; |
@ -0,0 +1,3 @@ |
|||
import demoTest from '../../../tests/shared/demoTest'; |
|||
|
|||
demoTest('skeleton'); |
@ -0,0 +1,50 @@ |
|||
import React from 'react'; |
|||
import { mount } from 'enzyme'; |
|||
import Skeleton from '..'; |
|||
|
|||
describe('Skeleton', () => { |
|||
const genSkeleton = props => mount( |
|||
<Skeleton loading {...props}> |
|||
Bamboo |
|||
</Skeleton> |
|||
); |
|||
|
|||
describe('avatar', () => { |
|||
it('size', () => { |
|||
const wrapperSmall = genSkeleton({ avatar: { size: 'small' } }); |
|||
expect(wrapperSmall.render()).toMatchSnapshot(); |
|||
const wrapperDefault = genSkeleton({ avatar: { size: 'default' } }); |
|||
expect(wrapperDefault.render()).toMatchSnapshot(); |
|||
const wrapperLarge = genSkeleton({ avatar: { size: 'large' } }); |
|||
expect(wrapperLarge.render()).toMatchSnapshot(); |
|||
}); |
|||
|
|||
it('shape', () => { |
|||
const wrapperCircle = genSkeleton({ avatar: { shape: 'circle' } }); |
|||
expect(wrapperCircle.render()).toMatchSnapshot(); |
|||
const wrapperSquare = genSkeleton({ avatar: { shape: 'square' } }); |
|||
expect(wrapperSquare.render()).toMatchSnapshot(); |
|||
}); |
|||
}); |
|||
|
|||
describe('title', () => { |
|||
it('width', () => { |
|||
const wrapper = genSkeleton({ title: { width: '93%' } }); |
|||
expect(wrapper.render()).toMatchSnapshot(); |
|||
}); |
|||
}); |
|||
|
|||
describe('paragraph', () => { |
|||
it('rows', () => { |
|||
const wrapper = genSkeleton({ paragraph: { rows: 5 } }); |
|||
expect(wrapper.render()).toMatchSnapshot(); |
|||
}); |
|||
|
|||
it('width', () => { |
|||
const wrapperPure = genSkeleton({ paragraph: { width: '93%' } }); |
|||
expect(wrapperPure.render()).toMatchSnapshot(); |
|||
const wrapperList = genSkeleton({ paragraph: { width: ['28%', '93%'] } }); |
|||
expect(wrapperList.render()).toMatchSnapshot(); |
|||
}); |
|||
}); |
|||
}); |
@ -0,0 +1,22 @@ |
|||
--- |
|||
order: 1 |
|||
title: |
|||
zh-CN: 动画效果 |
|||
en-US: Active Animation |
|||
--- |
|||
|
|||
## zh-CN |
|||
|
|||
显示动画效果。 |
|||
|
|||
## en-US |
|||
|
|||
Display active animation. |
|||
|
|||
````jsx |
|||
import { Skeleton } from 'antd'; |
|||
|
|||
ReactDOM.render( |
|||
<Skeleton active />, |
|||
mountNode); |
|||
```` |
@ -0,0 +1,22 @@ |
|||
--- |
|||
order: 0 |
|||
title: |
|||
zh-CN: 基本 |
|||
en-US: Basic |
|||
--- |
|||
|
|||
## zh-CN |
|||
|
|||
最简单的用法。 |
|||
|
|||
## en-US |
|||
|
|||
Basic usage. |
|||
|
|||
````jsx |
|||
import { Skeleton } from 'antd'; |
|||
|
|||
ReactDOM.render( |
|||
<Skeleton />, |
|||
mountNode); |
|||
```` |
@ -0,0 +1,58 @@ |
|||
--- |
|||
order: 2 |
|||
title: |
|||
zh-CN: 包含子组件 |
|||
en-US: Contains sub component |
|||
--- |
|||
|
|||
## zh-CN |
|||
|
|||
加载占位图包含子组件。 |
|||
|
|||
## en-US |
|||
|
|||
Skeleton contains sub component. |
|||
|
|||
````jsx |
|||
import { Skeleton, Button } from 'antd'; |
|||
|
|||
class Demo extends React.Component { |
|||
state = { |
|||
loading: false, |
|||
}; |
|||
|
|||
showSkeleton = () => { |
|||
this.setState({ loading: true }); |
|||
setTimeout(() => { |
|||
this.setState({ loading: false }); |
|||
}, 3000); |
|||
}; |
|||
|
|||
render() { |
|||
return ( |
|||
<div className="article"> |
|||
<Skeleton loading={this.state.loading}> |
|||
<div> |
|||
<h4>Ant Design, 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> |
|||
</Skeleton> |
|||
<Button onClick={this.showSkeleton} disabled={this.state.loading}> |
|||
Show Skeleton |
|||
</Button> |
|||
</div> |
|||
); |
|||
} |
|||
} |
|||
|
|||
ReactDOM.render(<Demo />, mountNode); |
|||
```` |
|||
|
|||
<style> |
|||
.article h4 { |
|||
margin-bottom: 16px; |
|||
} |
|||
.article button { |
|||
margin-top: 16px; |
|||
} |
|||
</style> |
@ -0,0 +1,86 @@ |
|||
--- |
|||
order: 3 |
|||
title: |
|||
zh-CN: 列表 |
|||
en-US: List |
|||
--- |
|||
|
|||
## zh-CN |
|||
|
|||
在列表组件中使用加载占位符。 |
|||
|
|||
## en-US |
|||
|
|||
Use skeleton in list component. |
|||
|
|||
````jsx |
|||
import { Skeleton, Switch, List, Avatar, Icon } from 'antd'; |
|||
|
|||
const listData = []; |
|||
for (let i = 0; i < 3; i++) { |
|||
listData.push({ |
|||
href: 'http://ant.design', |
|||
title: `ant design 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.', |
|||
}); |
|||
} |
|||
|
|||
const IconText = ({ type, text }) => ( |
|||
<span> |
|||
<Icon type={type} style={{ marginRight: 8 }} /> |
|||
{text} |
|||
</span> |
|||
); |
|||
|
|||
class App extends React.Component { |
|||
state = { |
|||
loading: true, |
|||
} |
|||
|
|||
onChange = (checked) => { |
|||
this.setState({ loading: !checked }); |
|||
} |
|||
|
|||
render() { |
|||
const { loading } = this.state; |
|||
|
|||
return ( |
|||
<div> |
|||
<Switch checked={!loading} onChange={this.onChange} /> |
|||
|
|||
<List |
|||
itemLayout="vertical" |
|||
size="large" |
|||
dataSource={listData} |
|||
renderItem={item => ( |
|||
<List.Item |
|||
key={item.title} |
|||
actions={!loading && [<IconText type="star-o" text="156" />, <IconText type="like-o" text="156" />, <IconText type="message" text="2" />]} |
|||
extra={!loading && <img width={272} alt="logo" src="https://gw.alipayobjects.com/zos/rmsportal/mqaQswcyDLcXyDKnZfES.png" />} |
|||
> |
|||
<Skeleton loading={loading} active> |
|||
<List.Item.Meta |
|||
avatar={<Avatar src={item.avatar} />} |
|||
title={<a href={item.href}>{item.title}</a>} |
|||
description={item.description} |
|||
/> |
|||
{item.content} |
|||
</Skeleton> |
|||
</List.Item> |
|||
)} |
|||
/> |
|||
</div> |
|||
); |
|||
} |
|||
} |
|||
|
|||
ReactDOM.render(<App />, mountNode); |
|||
```` |
|||
|
|||
<style> |
|||
.skeleton-demo { |
|||
border: 1px solid #f4f4f4; |
|||
} |
|||
</style> |
@ -0,0 +1,44 @@ |
|||
--- |
|||
category: Components |
|||
type: Data Entry |
|||
title: Skeleton |
|||
cols: 1 |
|||
--- |
|||
|
|||
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. |
|||
|
|||
## 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> | - | |
@ -0,0 +1,155 @@ |
|||
import * as React from 'react'; |
|||
import classNames from 'classnames'; |
|||
import Avatar, { SkeletonAvatarProps } from './Avatar'; |
|||
import Title, { SkeletonTitleProps } from './Title'; |
|||
import Paragraph, { SkeletonParagraphProps } from './Paragraph'; |
|||
|
|||
export interface SkeletonProps { |
|||
active?: boolean; |
|||
loading?: boolean; |
|||
prefixCls?: string; |
|||
className?: string; |
|||
children?: React.ReactNode; |
|||
avatar?: SkeletonAvatarProps | boolean; |
|||
title?: SkeletonTitleProps | boolean; |
|||
paragraph?: SkeletonParagraphProps | boolean; |
|||
} |
|||
|
|||
function getComponentProps<T>(prop: T | boolean | undefined): T | {} { |
|||
if (prop && typeof prop === 'object') { |
|||
return prop; |
|||
} |
|||
return {}; |
|||
} |
|||
|
|||
function getAvatarBasicProps(hasTitle: boolean, hasParagraph: boolean): SkeletonAvatarProps { |
|||
if (hasTitle && !hasParagraph) { |
|||
return { shape: 'square' }; |
|||
} |
|||
|
|||
return { shape: 'circle' }; |
|||
} |
|||
|
|||
function getTitleBasicProps(hasAvatar: boolean, hasParagraph: boolean): SkeletonTitleProps { |
|||
if (!hasAvatar && hasParagraph) { |
|||
return { width: '38%' }; |
|||
} |
|||
|
|||
if (hasAvatar && hasParagraph) { |
|||
return { width: '50%' }; |
|||
} |
|||
|
|||
return { width: '100%' }; |
|||
} |
|||
|
|||
function getParagraphBasicProps(hasAvatar: boolean, hasTitle: boolean): SkeletonParagraphProps { |
|||
const basicProps: SkeletonParagraphProps = {}; |
|||
|
|||
// Width
|
|||
if (hasAvatar && hasTitle) { |
|||
basicProps.width = '100%'; |
|||
} else { |
|||
basicProps.width = '61%'; |
|||
} |
|||
|
|||
// Rows
|
|||
if (!hasAvatar && hasTitle) { |
|||
basicProps.rows = 3; |
|||
} else { |
|||
basicProps.rows = 2; |
|||
} |
|||
|
|||
return basicProps; |
|||
} |
|||
|
|||
class Skeleton extends React.Component<SkeletonProps, any> { |
|||
static defaultProps: Partial<SkeletonProps> = { |
|||
prefixCls: 'ant-skeleton', |
|||
avatar: false, |
|||
title: true, |
|||
paragraph: true, |
|||
}; |
|||
|
|||
render() { |
|||
const { |
|||
loading, prefixCls, className, children, |
|||
avatar, title, paragraph, active, |
|||
} = this.props; |
|||
|
|||
if (loading || !('loading' in this.props)) { |
|||
const hasAvatar = !!avatar; |
|||
const hasTitle = !!title; |
|||
const hasParagraph = !!paragraph; |
|||
|
|||
// Avatar
|
|||
let avatarNode; |
|||
if (hasAvatar) { |
|||
const avatarProps: SkeletonAvatarProps = { |
|||
...getAvatarBasicProps(hasTitle, hasParagraph), |
|||
...getComponentProps(avatar), |
|||
}; |
|||
|
|||
avatarNode = ( |
|||
<div className={`${prefixCls}-header`}> |
|||
<Avatar {...avatarProps} /> |
|||
</div> |
|||
); |
|||
} |
|||
|
|||
let contentNode; |
|||
if (hasTitle || hasParagraph) { |
|||
// Title
|
|||
let $title; |
|||
if (hasTitle) { |
|||
const titleProps: SkeletonTitleProps = { |
|||
...getTitleBasicProps(hasAvatar, hasParagraph), |
|||
...getComponentProps(title), |
|||
}; |
|||
|
|||
$title = ( |
|||
<Title {...titleProps} /> |
|||
); |
|||
} |
|||
|
|||
// Paragraph
|
|||
let paragraphNode; |
|||
if (hasParagraph) { |
|||
const paragraphProps: SkeletonParagraphProps = { |
|||
...getParagraphBasicProps(hasAvatar, hasTitle), |
|||
...getComponentProps(paragraph), |
|||
}; |
|||
|
|||
paragraphNode = ( |
|||
<Paragraph {...paragraphProps} /> |
|||
); |
|||
} |
|||
|
|||
contentNode = ( |
|||
<div className={`${prefixCls}-content`}> |
|||
{$title} |
|||
{paragraphNode} |
|||
</div> |
|||
); |
|||
} |
|||
|
|||
const cls = classNames( |
|||
prefixCls, |
|||
className, { |
|||
[`${prefixCls}-with-avatar`]: hasAvatar, |
|||
[`${prefixCls}-active`]: active, |
|||
}, |
|||
); |
|||
|
|||
return ( |
|||
<div className={cls}> |
|||
{avatarNode} |
|||
{contentNode} |
|||
</div> |
|||
); |
|||
} |
|||
|
|||
return children; |
|||
} |
|||
} |
|||
|
|||
export default Skeleton; |
@ -0,0 +1,46 @@ |
|||
--- |
|||
category: Components |
|||
subtitle: 加载占位图 |
|||
type: Data Entry |
|||
title: Skeleton |
|||
cols: 1 |
|||
--- |
|||
|
|||
在需要等待加载内容的位置提供一个占位图。 |
|||
|
|||
## 何时使用 |
|||
|
|||
- 网络较慢,需要长时间等待加载处理的情况下。 |
|||
- 图文信息内容较多的列表/卡片中。 |
|||
|
|||
## 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> | - | |
@ -0,0 +1,113 @@ |
|||
@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; |
|||
|
|||
+ 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(); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
.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%; |
|||
} |
|||
} |
@ -0,0 +1,2 @@ |
|||
import '../../style/index.less'; |
|||
import './index.less'; |
Loading…
Reference in new issue