mirror of
https://github.com/ant-design/ant-design.git
synced 2025-08-05 23:46:28 +08:00
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:
parent
78f2b0aa5d
commit
bceb15c4e6
@ -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
@ -1,4 +1,3 @@
|
||||
import demoTest from '../../../tests/shared/demoTest';
|
||||
|
||||
demoTest('list');
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
---
|
||||
order: 0
|
||||
order: 2
|
||||
title:
|
||||
zh-CN: 基础列表
|
||||
en-US: Basic
|
||||
|
@ -1,5 +1,5 @@
|
||||
---
|
||||
order: 0
|
||||
order: 4
|
||||
title:
|
||||
zh-CN: 栅格列表
|
||||
en-US: Grid
|
||||
|
83
components/list/demo/infinite-load.md
Normal file
83
components/list/demo/infinite-load.md
Normal 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);
|
||||
````
|
53
components/list/demo/meta.md
Normal file
53
components/list/demo/meta.md
Normal 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);
|
||||
````
|
@ -1,5 +1,5 @@
|
||||
---
|
||||
order: 0
|
||||
order: 5
|
||||
title:
|
||||
zh-CN: 响应式的栅格列表
|
||||
en-US: Grid
|
||||
|
30
components/list/demo/simple.md
Normal file
30
components/list/demo/simple.md
Normal 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);
|
||||
````
|
@ -1,5 +1,5 @@
|
||||
---
|
||||
order: 1
|
||||
order: 3
|
||||
title:
|
||||
zh-CN: 竖排列表样式
|
||||
en-US: Layout Vertical
|
||||
|
@ -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 |
|
||||
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
@ -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
|
||||
|
||||
| 参数 | 说明 | 类型 | 默认值 |
|
||||
|
@ -57,6 +57,9 @@
|
||||
flex: 1;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
&-content-single {
|
||||
justify-content: flex-start;
|
||||
}
|
||||
&-action {
|
||||
font-size: 0;
|
||||
flex: 0 0 auto;
|
||||
|
Loading…
Reference in New Issue
Block a user