List:add infinite & detail optimize (#7445)

* List:add infinite & detail optimize

* React.children.map -> context

* update snapshots & demo order

* remove Item.Action _id

* update List demo

* List:add infinite & detail optimize

* React.children.map -> context

* 更新 snap

* update snapshots
This commit is contained in:
niko 2017-09-06 15:53:25 +08:00 committed by GitHub
parent 78f2b0aa5d
commit bceb15c4e6
14 changed files with 2121 additions and 1363 deletions

View File

@ -70,10 +70,13 @@ export default class Item extends React.Component<ListItemProps, any> {
xl: PropTypes.oneOf(GridColumns),
};
_id: number = new Date().getTime();
static contextTypes = {
grid: PropTypes.any,
};
render() {
const { prefixCls = 'ant-list', children, actions, extra, className, grid, ...others } = this.props;
const { grid } = this.context;
const { prefixCls = 'ant-list', children, actions, extra, className, ...others } = this.props;
const classString = classNames(`${prefixCls}-item`, className);
const metaContent: React.ReactElement<any>[] = [];
@ -87,8 +90,11 @@ export default class Item extends React.Component<ListItemProps, any> {
}
});
const contentClassString = classNames(`${prefixCls}-item-content`, {
[`${prefixCls}-item-content-single`]: (metaContent.length < 1),
});
const content = (
<div className={`${prefixCls}-item-content`}>
<div className={contentClassString}>
{otherContent}
</div>
);
@ -96,7 +102,7 @@ export default class Item extends React.Component<ListItemProps, any> {
let actionsContent;
if (actions && actions.length > 0) {
const actionsContentItem = (action, i) => (
<li key={`${prefixCls}-item-action-${this._id}-${i}`}>
<li key={`${prefixCls}-item-action-${i}`}>
{action}
{i !== (actions.length - 1) && <em className={`${prefixCls}-item-action-split`}/>}
</li>

File diff suppressed because it is too large Load Diff

View File

@ -1,4 +1,3 @@
import demoTest from '../../../tests/shared/demoTest';
demoTest('list');

View File

@ -1,5 +1,5 @@
---
order: 0
order: 2
title:
zh-CN: 基础列表
en-US: Basic

View File

@ -1,5 +1,5 @@
---
order: 0
order: 4
title:
zh-CN: 栅格列表
en-US: Grid

View File

@ -0,0 +1,83 @@
---
order: 6
title:
zh-CN: 无限加载
en-US: infinite load
---
## zh-CN
无限加载样例。
## en-US
The example of infinite load.
````jsx
import { List, Card, message } from 'antd';
let countId = 1;
function mockData() {
const data = [];
for (let i = 0; i < 5; i++) {
const id = countId;
data.push({
id: `id-${id}`,
title: `List Item Title ${id}`,
content: `List Item Content ${id}`,
});
countId++;
}
return data;
}
class InfiniteList extends React.Component {
state = {
data: mockData(),
loading: false,
}
handleInfiniteOnLoad = (done) => {
let data = this.state.data;
if (data.length > 15) {
message.warning('Loaded All');
return;
}
this.setState({
loading: true,
});
setTimeout(() => {
data = data.concat(mockData());
this.setState({
data,
loading: false,
});
// reset the infinite onLoad callback flag
// so can trigger onLoad callback again
done();
}, 1000);
}
render() {
return (
<List
infinite={{
loading: this.state.loading,
onLoad: this.handleInfiniteOnLoad,
offset: -20,
}}
grid={{ gutter: 16, column: 4 }}
>
{
this.state.data.map(item => (
<List.Item key={item.id}>
<Card title={item.title}>{item.content}</Card>
</List.Item>
))
}
</List>
);
}
}
ReactDOM.render(<InfiniteList />, mountNode);
````

View File

@ -0,0 +1,53 @@
---
order: 1
title:
zh-CN: 带有头像和描述的列表
en-US: List with avatar and description.
---
## zh-CN
带有头像和描述的列表。
## en-US
List with avatar and description.
````jsx
import { List, Avatar } from 'antd';
ReactDOM.render(
<List
itemLayout="horizontal"
>
<List.Item>
<List.Item.Meta
avatar={<Avatar src="https://zos.alipayobjects.com/rmsportal/ODTLcjxAfvqbxHnVXCYX.png" />}
title={<a href="https://ant.design">Ant design</a>}
description="Ant Design, a design language for background applications, is refined by Ant UED Team"
/>
</List.Item>
<List.Item>
<List.Item.Meta
avatar={<Avatar src="https://zos.alipayobjects.com/rmsportal/ODTLcjxAfvqbxHnVXCYX.png" />}
title={<a href="https://ant.design">Ant design</a>}
description="Ant Design, a design language for background applications, is refined by Ant UED Team"
/>
</List.Item>
<List.Item>
<List.Item.Meta
avatar={<Avatar src="https://zos.alipayobjects.com/rmsportal/ODTLcjxAfvqbxHnVXCYX.png" />}
title={<a href="https://ant.design">Ant design</a>}
description="Ant Design, a design language for background applications, is refined by Ant UED Team"
/>
</List.Item>
<List.Item>
<List.Item.Meta
avatar={<Avatar src="https://zos.alipayobjects.com/rmsportal/ODTLcjxAfvqbxHnVXCYX.png" />}
title={<a href="https://ant.design">Ant design</a>}
description="Ant Design, a design language for background applications, is refined by Ant UED Team"
/>
</List.Item>
</List>
, mountNode);
````

View File

@ -1,5 +1,5 @@
---
order: 0
order: 5
title:
zh-CN: 响应式的栅格列表
en-US: Grid

View File

@ -0,0 +1,30 @@
---
order: 0
title:
zh-CN: 简单列表
en-US: Simple
---
## zh-CN
最简单的列表。
## en-US
Simple List.
````jsx
import { List } from 'antd';
ReactDOM.render(
<List
itemLayout="horizontal"
>
<List.Item>Racing car sprays burning fuel into crowd.</List.Item>
<List.Item>Japanese princess to wed commoner.</List.Item>
<List.Item>Australian walks 100km after outback crash.</List.Item>
<List.Item>Man charged over missing wedding girl.</List.Item>
<List.Item>Los Angeles battles huge wildfires.</List.Item>
</List>
, mountNode);
````

View File

@ -1,5 +1,5 @@
---
order: 1
order: 3
title:
zh-CN: 竖排列表样式
en-US: Layout Vertical

View File

@ -25,6 +25,7 @@ A list can be used to display content related to a single subject. The content c
| onMoreClick | -| function | - |
| pagination | - | boolean \| object | false |
| grid | - | object | - |
| infinite | - | object | - |
### List grid props
| Property | Description | Type | Default |
@ -37,6 +38,13 @@ A list can be used to display content related to a single subject. The content c
| lg | `≥1200px` - | number | - |
| xl | `≥1600px` - | number | - |
### List infinite props
| Property | Description | Type | Default |
---------|-------------|------|---------
| onLoad | - | function | - |
| offset | - | number | 0 |
| loading | - | boolean | - |
### List.Item
| Property | Description | Type | Default |

View File

@ -1,11 +1,14 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames';
import addEventListener from 'rc-util/lib/Dom/addEventListener';
import Spin from '../spin';
import Icon from '../icon';
import Pagination from '../pagination';
import Button from '../button';
import { Row } from '../grid';
import { throttleByAnimationFrameDecorator } from '../_util/throttleByAnimationFrame';
import Item from './Item';
@ -19,6 +22,12 @@ export interface ListGridType {
xl?: 1 | 2 | 3 | 4 | 6 | 8 | 12 | 24;
}
export interface InfinitePropType {
onLoad?: any;
loading?: boolean;
offset?: number;
}
export interface ListProps {
bordered?: boolean;
className?: string;
@ -33,11 +42,144 @@ export interface ListProps {
pagination?: any;
prefixCls?: string;
grid?: ListGridType;
infinite?: InfinitePropType;
}
function getDefaultTarget() {
return typeof window !== 'undefined' ?
window : null;
}
function getOffsetTop(element?: HTMLElement): number {
if (!element) {
return 0;
}
if (!element.getClientRects().length) {
return 0;
}
const rect = element.getBoundingClientRect();
if (rect.width || rect.height) {
const doc = element.ownerDocument;
const docElem = doc.documentElement;
return rect.top - docElem.clientTop;
}
return rect.top;
}
function getViewportHeight() {
const win = getDefaultTarget();
if (!win) {
return 0;
}
return Math.max(win.document.documentElement.clientHeight, win.innerHeight || 0);
}
export default class List extends Component<ListProps> {
static Item: typeof Item = Item;
static childContextTypes = {
grid: PropTypes.any,
};
scrollEvent: any;
resizeEvent: any;
timeout: any;
node: any;
events = [
'resize',
'scroll',
'touchstart',
'touchmove',
'touchend',
'pageshow',
'load',
];
infiniteLoaded = false;
eventHandlers = {};
getChildContext() {
return {
grid: this.props.grid,
};
}
componentDidMount() {
if (!this.isInfinite()) {
return;
}
const target = getDefaultTarget;
// Wait for parent component ref has its value
this.timeout = setTimeout(() => {
this.setTargetEventListeners(target);
});
}
componentWillUnmount() {
if (!this.isInfinite()) {
return;
}
this.clearEventListeners();
clearTimeout(this.timeout);
(this.infiniteLoad as any).cancel();
}
getNode = (n) => {
this.node = n;
}
isInfinite() {
const { infinite } = this.props;
return infinite && (typeof infinite.onLoad === 'function');
}
setTargetEventListeners(getTarget) {
const target = getTarget();
if (!target) {
return;
}
this.clearEventListeners();
this.events.forEach(eventName => {
this.eventHandlers[eventName] = addEventListener(target, eventName, this.infiniteLoad);
});
}
clearEventListeners() {
this.events.forEach(eventName => {
const handler = this.eventHandlers[eventName];
if (handler && handler.remove) {
handler.remove();
}
});
}
@throttleByAnimationFrameDecorator()
infiniteLoad() {
const { infinite = {} } = this.props;
const targetElement = this.node;
if (this.infiniteLoaded || !targetElement) {
return;
}
const viewportHeight = getViewportHeight();
const eleOffsetTop = getOffsetTop(targetElement);
const bottomPositionY = eleOffsetTop + targetElement.offsetHeight - viewportHeight + (infinite.offset || 0);
if (bottomPositionY < 0) {
this.infiniteLoaded = true;
infinite.onLoad(() => {
this.infiniteLoaded = false;
});
}
}
render() {
const {
bordered = true,
@ -52,6 +194,7 @@ export default class List extends Component<ListProps> {
pagination = false,
prefixCls = 'ant-list',
grid,
infinite = {},
...rest,
} = this.props;
@ -60,6 +203,7 @@ export default class List extends Component<ListProps> {
[`${prefixCls}-bordered`]: bordered,
[`${prefixCls}-loading`]: loading,
[`${prefixCls}-grid`]: grid,
[`${prefixCls}-infinite`]: infinite.onLoad,
});
const moreButton = (
@ -81,24 +225,28 @@ export default class List extends Component<ListProps> {
</div>
);
const loadingContent = (
<div className={`${prefixCls}-spin`}>
<Spin size="large"/>
const childrenContent = grid ? (
<Row gutter={grid.gutter}>{children}</Row>
) : children;
const content = loading ? (
<Spin size="large">
{childrenContent}
{showLoadMore && moreContent}
{(!showLoadMore && pagination) && paginationContent}
</Spin>
) : (
<div>
{childrenContent}
{showLoadMore && moreContent}
{(!showLoadMore && pagination) && paginationContent}
{infinite.loading && (<div className={`${prefixCls}-spin`}><Spin /></div>)}
</div>
);
const childrenContent = grid ? (
<Row gutter={grid.gutter}>
{React.Children.map(children, (element: React.ReactElement<any>) => React.cloneElement(element, { grid }))}
</Row>
) : children;
return (
<div className={classString} {...rest}>
{loading && loadingContent}
{!loading && childrenContent}
{!loading && showLoadMore && moreContent}
{(!showLoadMore && pagination) && paginationContent}
<div className={classString} {...rest} ref={this.getNode}>
{content}
</div>
);
}

View File

@ -26,6 +26,7 @@ cols: 1
| onMoreClick | 点击 more 按钮的回调 | function | - |
| pagination | 对应的 pagination 配置, 设置 false 不显示 | boolean \| object | false |
| grid | 列表栅格 | object | - |
| infinite | 无限加载配置 | object | - |
### List grid props
| 参数 | 说明 | 类型 | 默认值 |
@ -38,6 +39,13 @@ cols: 1
| lg | `≥1200px` 展示的列数 | number | - |
| xl | `≥1600px` 展示的列数 | number | - |
### List infinite props
| 参数 | 说明 | 类型 | 默认值 |
---------|-------------|------|---------
| onLoad | 无限加载的回调函数 | function | - |
| offset | 触发加载位置的偏移量 | number | 0 |
| loading | 显示无限加载时的 loading | boolean | - |
### List.Item
| 参数 | 说明 | 类型 | 默认值 |

View File

@ -57,6 +57,9 @@
flex: 1;
justify-content: flex-end;
}
&-content-single {
justify-content: flex-start;
}
&-action {
font-size: 0;
flex: 0 0 auto;