mirror of
https://github.com/ant-design/ant-design.git
synced 2024-11-27 12:39:49 +08:00
New Component Skeleton (#11226)
* Add new component Skeleton * Add related doc * Add sample * Add test case ref: https://github.com/ant-design/ant-design/issues/10308
This commit is contained in:
parent
cc2bd76595
commit
796b56dbc4
@ -41,6 +41,7 @@ Array [
|
||||
"Rate",
|
||||
"Row",
|
||||
"Select",
|
||||
"Skeleton",
|
||||
"Slider",
|
||||
"Spin",
|
||||
"Steps",
|
||||
|
@ -372,174 +372,83 @@ exports[`renders ./components/card/demo/inner.md correctly 1`] = `
|
||||
|
||||
exports[`renders ./components/card/demo/loading.md correctly 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="ant-card ant-card-loading ant-card-bordered"
|
||||
<span
|
||||
class="ant-switch"
|
||||
tabindex="0"
|
||||
>
|
||||
<span
|
||||
class="ant-switch-inner"
|
||||
/>
|
||||
</span>
|
||||
<div
|
||||
class="ant-card ant-card-bordered"
|
||||
style="width:300px;margin-top:16px"
|
||||
>
|
||||
<div
|
||||
class="ant-card-head"
|
||||
>
|
||||
<div
|
||||
class="ant-card-head-wrapper"
|
||||
>
|
||||
<div
|
||||
class="ant-card-head-title"
|
||||
>
|
||||
Card title
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="ant-card-body"
|
||||
>
|
||||
<div
|
||||
class="ant-card-loading-content"
|
||||
class="ant-skeleton ant-skeleton-with-avatar ant-skeleton-active"
|
||||
>
|
||||
<div
|
||||
class="ant-row"
|
||||
style="margin-left:-4px;margin-right:-4px"
|
||||
class="ant-skeleton-header"
|
||||
>
|
||||
<div
|
||||
class="ant-col-22"
|
||||
style="padding-left:4px;padding-right:4px"
|
||||
>
|
||||
<div
|
||||
class="ant-card-loading-block"
|
||||
/>
|
||||
</div>
|
||||
<span
|
||||
class="ant-skeleton-avatar ant-skeleton-avatar-lg ant-skeleton-avatar-circle"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
class="ant-row"
|
||||
style="margin-left:-4px;margin-right:-4px"
|
||||
class="ant-skeleton-content"
|
||||
>
|
||||
<div
|
||||
class="ant-col-8"
|
||||
style="padding-left:4px;padding-right:4px"
|
||||
<h3
|
||||
class="ant-skeleton-title"
|
||||
style="width:50%"
|
||||
/>
|
||||
<ul
|
||||
class="ant-skeleton-paragraph"
|
||||
>
|
||||
<div
|
||||
class="ant-card-loading-block"
|
||||
<li
|
||||
style="width:100%"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
class="ant-col-15"
|
||||
style="padding-left:4px;padding-right:4px"
|
||||
>
|
||||
<div
|
||||
class="ant-card-loading-block"
|
||||
<li
|
||||
style="width:100%"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="ant-row"
|
||||
style="margin-left:-4px;margin-right:-4px"
|
||||
>
|
||||
<div
|
||||
class="ant-col-6"
|
||||
style="padding-left:4px;padding-right:4px"
|
||||
>
|
||||
<div
|
||||
class="ant-card-loading-block"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
class="ant-col-18"
|
||||
style="padding-left:4px;padding-right:4px"
|
||||
>
|
||||
<div
|
||||
class="ant-card-loading-block"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="ant-row"
|
||||
style="margin-left:-4px;margin-right:-4px"
|
||||
>
|
||||
<div
|
||||
class="ant-col-13"
|
||||
style="padding-left:4px;padding-right:4px"
|
||||
>
|
||||
<div
|
||||
class="ant-card-loading-block"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
class="ant-col-9"
|
||||
style="padding-left:4px;padding-right:4px"
|
||||
>
|
||||
<div
|
||||
class="ant-card-loading-block"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="ant-row"
|
||||
style="margin-left:-4px;margin-right:-4px"
|
||||
>
|
||||
<div
|
||||
class="ant-col-4"
|
||||
style="padding-left:4px;padding-right:4px"
|
||||
>
|
||||
<div
|
||||
class="ant-card-loading-block"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
class="ant-col-3"
|
||||
style="padding-left:4px;padding-right:4px"
|
||||
>
|
||||
<div
|
||||
class="ant-card-loading-block"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
class="ant-col-16"
|
||||
style="padding-left:4px;padding-right:4px"
|
||||
>
|
||||
<div
|
||||
class="ant-card-loading-block"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="ant-row"
|
||||
style="margin-left:-4px;margin-right:-4px"
|
||||
>
|
||||
<div
|
||||
class="ant-col-8"
|
||||
style="padding-left:4px;padding-right:4px"
|
||||
>
|
||||
<div
|
||||
class="ant-card-loading-block"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
class="ant-col-6"
|
||||
style="padding-left:4px;padding-right:4px"
|
||||
>
|
||||
<div
|
||||
class="ant-card-loading-block"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
class="ant-col-8"
|
||||
style="padding-left:4px;padding-right:4px"
|
||||
>
|
||||
<div
|
||||
class="ant-card-loading-block"
|
||||
/>
|
||||
</div>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<ul
|
||||
class="ant-card-actions"
|
||||
>
|
||||
<li
|
||||
style="width:33.333333333333336%"
|
||||
>
|
||||
<span>
|
||||
<i
|
||||
class="anticon anticon-setting"
|
||||
/>
|
||||
</span>
|
||||
</li>
|
||||
<li
|
||||
style="width:33.333333333333336%"
|
||||
>
|
||||
<span>
|
||||
<i
|
||||
class="anticon anticon-edit"
|
||||
/>
|
||||
</span>
|
||||
</li>
|
||||
<li
|
||||
style="width:33.333333333333336%"
|
||||
>
|
||||
<span>
|
||||
<i
|
||||
class="anticon anticon-ellipsis"
|
||||
/>
|
||||
</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<button
|
||||
class="ant-btn"
|
||||
style="margin-top:16px"
|
||||
type="button"
|
||||
>
|
||||
<span>
|
||||
Toggle loading
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
`;
|
||||
|
||||
|
@ -14,30 +14,42 @@ title:
|
||||
Shows a loading indicator while the contents of the card is being fetched.
|
||||
|
||||
````jsx
|
||||
import { Card, Button } from 'antd';
|
||||
import { Skeleton, Switch, Card, Icon, Avatar } from 'antd';
|
||||
|
||||
class LoadingCard extends React.Component {
|
||||
const { Meta } = Card;
|
||||
|
||||
class App extends React.Component {
|
||||
state = {
|
||||
loading: true,
|
||||
}
|
||||
|
||||
handleClick = () => {
|
||||
this.setState({ loading: !this.state.loading });
|
||||
onChange = (checked) => {
|
||||
this.setState({ loading: !checked });
|
||||
}
|
||||
|
||||
render() {
|
||||
const { loading } = this.state;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Card loading={this.state.loading} title="Card title">
|
||||
Whatever content
|
||||
<Switch checked={!loading} onChange={this.onChange} />
|
||||
|
||||
<Card
|
||||
style={{ width: 300, marginTop: 16 }}
|
||||
actions={[<Icon type="setting" />, <Icon type="edit" />, <Icon type="ellipsis" />]}
|
||||
>
|
||||
<Skeleton loading={loading} avatar active>
|
||||
<Meta
|
||||
avatar={<Avatar src="https://zos.alipayobjects.com/rmsportal/ODTLcjxAfvqbxHnVXCYX.png" />}
|
||||
title="Card title"
|
||||
description="This is the description"
|
||||
/>
|
||||
</Skeleton>
|
||||
</Card>
|
||||
<Button onClick={this.handleClick} style={{ marginTop: 16 }}>Toggle loading</Button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
ReactDOM.render(
|
||||
<LoadingCard />,
|
||||
mountNode);
|
||||
ReactDOM.render(<App />, mountNode);
|
||||
````
|
||||
|
@ -91,6 +91,8 @@ export { default as Row } from './row';
|
||||
|
||||
export { default as Select } from './select';
|
||||
|
||||
export { default as Skeleton } from './skeleton';
|
||||
|
||||
export { default as Slider } from './slider';
|
||||
|
||||
export { default as Spin } from './spin';
|
||||
|
@ -365,7 +365,7 @@ exports[`renders ./components/list/demo/infinite-virtualized-load.md correctly 1
|
||||
|
||||
exports[`renders ./components/list/demo/loadmore.md correctly 1`] = `
|
||||
<div
|
||||
class="ant-list demo-loadmore-list ant-list-split ant-list-loading ant-list-something-after-last-item"
|
||||
class="ant-list demo-loadmore-list ant-list-split ant-list-loading"
|
||||
>
|
||||
<div
|
||||
class="ant-spin-nested-loading"
|
||||
@ -392,18 +392,6 @@ exports[`renders ./components/list/demo/loadmore.md correctly 1`] = `
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
style="text-align:center;margin-top:12px;height:32px;line-height:32px"
|
||||
>
|
||||
<button
|
||||
class="ant-btn"
|
||||
type="button"
|
||||
>
|
||||
<span>
|
||||
loading more
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
|
@ -14,25 +14,27 @@ title:
|
||||
Load more list with `loadMore` property.
|
||||
|
||||
````jsx
|
||||
import { List, Avatar, Button, Spin } from 'antd';
|
||||
import { List, Avatar, Button, Skeleton } from 'antd';
|
||||
|
||||
import reqwest from 'reqwest';
|
||||
|
||||
const fakeDataUrl = 'https://randomuser.me/api/?results=5&inc=name,gender,email,nat&noinfo';
|
||||
const count = 3;
|
||||
const fakeDataUrl = `https://randomuser.me/api/?results=${count}&inc=name,gender,email,nat&noinfo`;
|
||||
|
||||
class LoadMoreList extends React.Component {
|
||||
state = {
|
||||
loading: true,
|
||||
loadingMore: false,
|
||||
showLoadingMore: true,
|
||||
initLoading: true,
|
||||
loading: false,
|
||||
data: [],
|
||||
list: [],
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.getData((res) => {
|
||||
this.setState({
|
||||
loading: false,
|
||||
initLoading: false,
|
||||
data: res.results,
|
||||
list: res.results,
|
||||
});
|
||||
});
|
||||
}
|
||||
@ -51,13 +53,15 @@ class LoadMoreList extends React.Component {
|
||||
|
||||
onLoadMore = () => {
|
||||
this.setState({
|
||||
loadingMore: true,
|
||||
loading: true,
|
||||
list: this.state.data.concat([...new Array(count)].map(() => ({ loading: true, name: {} }))),
|
||||
});
|
||||
this.getData((res) => {
|
||||
const data = this.state.data.concat(res.results);
|
||||
this.setState({
|
||||
data,
|
||||
loadingMore: false,
|
||||
list: data,
|
||||
loading: false,
|
||||
}, () => {
|
||||
// Resetting window's offsetTop so as to display react-virtualized demo underfloor.
|
||||
// In real scene, you can using public method of react-virtualized:
|
||||
@ -68,28 +72,30 @@ class LoadMoreList extends React.Component {
|
||||
}
|
||||
|
||||
render() {
|
||||
const { loading, loadingMore, showLoadingMore, data } = this.state;
|
||||
const loadMore = showLoadingMore ? (
|
||||
const { initLoading, loading, list } = this.state;
|
||||
const loadMore = !initLoading && !loading ? (
|
||||
<div style={{ textAlign: 'center', marginTop: 12, height: 32, lineHeight: '32px' }}>
|
||||
{loadingMore && <Spin />}
|
||||
{!loadingMore && <Button onClick={this.onLoadMore}>loading more</Button>}
|
||||
<Button onClick={this.onLoadMore}>loading more</Button>
|
||||
</div>
|
||||
) : null;
|
||||
|
||||
return (
|
||||
<List
|
||||
className="demo-loadmore-list"
|
||||
loading={loading}
|
||||
loading={initLoading}
|
||||
itemLayout="horizontal"
|
||||
loadMore={loadMore}
|
||||
dataSource={data}
|
||||
dataSource={list}
|
||||
renderItem={item => (
|
||||
<List.Item actions={[<a>edit</a>, <a>more</a>]}>
|
||||
<List.Item.Meta
|
||||
avatar={<Avatar src="https://zos.alipayobjects.com/rmsportal/ODTLcjxAfvqbxHnVXCYX.png" />}
|
||||
title={<a href="https://ant.design">{item.name.last}</a>}
|
||||
description="Ant Design, a design language for background applications, is refined by Ant UED Team"
|
||||
/>
|
||||
<div>content</div>
|
||||
<Skeleton avatar title={false} loading={item.loading} active>
|
||||
<List.Item.Meta
|
||||
avatar={<Avatar src="https://zos.alipayobjects.com/rmsportal/ODTLcjxAfvqbxHnVXCYX.png" />}
|
||||
title={<a href="https://ant.design">{item.name.last}</a>}
|
||||
description="Ant Design, a design language for background applications, is refined by Ant UED Team"
|
||||
/>
|
||||
<div>content</div>
|
||||
</Skeleton>
|
||||
</List.Item>
|
||||
)}
|
||||
/>
|
||||
|
37
components/skeleton/Avatar.tsx
Normal file
37
components/skeleton/Avatar.tsx
Normal file
@ -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;
|
78
components/skeleton/Paragraph.tsx
Normal file
78
components/skeleton/Paragraph.tsx
Normal file
@ -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;
|
28
components/skeleton/Title.tsx
Normal file
28
components/skeleton/Title.tsx
Normal file
@ -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;
|
204
components/skeleton/__tests__/__snapshots__/demo.test.js.snap
Normal file
204
components/skeleton/__tests__/__snapshots__/demo.test.js.snap
Normal file
@ -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>
|
||||
`;
|
279
components/skeleton/__tests__/__snapshots__/index.test.js.snap
Normal file
279
components/skeleton/__tests__/__snapshots__/index.test.js.snap
Normal file
@ -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>
|
||||
`;
|
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');
|
50
components/skeleton/__tests__/index.test.js
Normal file
50
components/skeleton/__tests__/index.test.js
Normal file
@ -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();
|
||||
});
|
||||
});
|
||||
});
|
22
components/skeleton/demo/active.md
Normal file
22
components/skeleton/demo/active.md
Normal file
@ -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);
|
||||
````
|
22
components/skeleton/demo/basic.md
Normal file
22
components/skeleton/demo/basic.md
Normal file
@ -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);
|
||||
````
|
58
components/skeleton/demo/children.md
Normal file
58
components/skeleton/demo/children.md
Normal file
@ -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>
|
86
components/skeleton/demo/list.md
Normal file
86
components/skeleton/demo/list.md
Normal file
@ -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>
|
44
components/skeleton/index.en-US.md
Normal file
44
components/skeleton/index.en-US.md
Normal file
@ -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> | - |
|
155
components/skeleton/index.tsx
Normal file
155
components/skeleton/index.tsx
Normal file
@ -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;
|
46
components/skeleton/index.zh-CN.md
Normal file
46
components/skeleton/index.zh-CN.md
Normal file
@ -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> | - |
|
113
components/skeleton/style/index.less
Normal file
113
components/skeleton/style/index.less
Normal file
@ -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%;
|
||||
}
|
||||
}
|
2
components/skeleton/style/index.tsx
Normal file
2
components/skeleton/style/index.tsx
Normal file
@ -0,0 +1,2 @@
|
||||
import '../../style/index.less';
|
||||
import './index.less';
|
@ -484,6 +484,10 @@
|
||||
@collapse-content-padding: @padding-md;
|
||||
@collapse-content-bg: @component-background;
|
||||
|
||||
// Skeleton
|
||||
// ---
|
||||
@skeleton-color: #f2f2f2;
|
||||
|
||||
// Message
|
||||
// ---
|
||||
@message-notice-content-padding: 10px 16px;
|
||||
|
@ -41,6 +41,7 @@ Array [
|
||||
"Rate",
|
||||
"Row",
|
||||
"Select",
|
||||
"Skeleton",
|
||||
"Slider",
|
||||
"Spin",
|
||||
"Steps",
|
||||
|
Loading…
Reference in New Issue
Block a user