|
|
@ -1,9 +1,8 @@ |
|
|
|
import * as React from 'react'; |
|
|
|
import * as PropTypes from 'prop-types'; |
|
|
|
import classNames from 'classnames'; |
|
|
|
import omit from 'omit.js'; |
|
|
|
import Spin, { SpinProps } from '../spin'; |
|
|
|
import { ConfigConsumer, ConfigConsumerProps, RenderEmptyHandler } from '../config-provider'; |
|
|
|
import { RenderEmptyHandler, ConfigContext } from '../config-provider'; |
|
|
|
|
|
|
|
import Pagination, { PaginationConfig } from '../pagination'; |
|
|
|
import { Row } from '../grid'; |
|
|
@ -58,73 +57,49 @@ export interface ListLocale { |
|
|
|
emptyText: React.ReactNode | (() => React.ReactNode); |
|
|
|
} |
|
|
|
|
|
|
|
interface ListState { |
|
|
|
paginationCurrent: number; |
|
|
|
paginationSize: number; |
|
|
|
export interface ListConsumerProps { |
|
|
|
grid?: any; |
|
|
|
itemLayout?: string; |
|
|
|
} |
|
|
|
|
|
|
|
export default class List<T> extends React.Component<ListProps<T>, ListState> { |
|
|
|
static Item: typeof Item = Item; |
|
|
|
export const ListContext = React.createContext<ListConsumerProps>({}); |
|
|
|
|
|
|
|
static childContextTypes = { |
|
|
|
grid: PropTypes.any, |
|
|
|
itemLayout: PropTypes.string, |
|
|
|
}; |
|
|
|
export const ListConsumer = ListContext.Consumer; |
|
|
|
|
|
|
|
static defaultProps = { |
|
|
|
dataSource: [], |
|
|
|
bordered: false, |
|
|
|
split: true, |
|
|
|
loading: false, |
|
|
|
pagination: false as ListProps<any>['pagination'], |
|
|
|
}; |
|
|
|
function List<T>({ pagination, ...props }: ListProps<T>) { |
|
|
|
const paginationObj = pagination && typeof pagination === 'object' ? pagination : {}; |
|
|
|
|
|
|
|
const [paginationCurrent, setPaginationCurrent] = React.useState( |
|
|
|
paginationObj.defaultCurrent || 1, |
|
|
|
); |
|
|
|
const [paginationSize, setPaginationSize] = React.useState(paginationObj.defaultPageSize || 10); |
|
|
|
|
|
|
|
defaultPaginationProps = { |
|
|
|
const { getPrefixCls, renderEmpty, direction } = React.useContext(ConfigContext); |
|
|
|
|
|
|
|
const defaultPaginationProps = { |
|
|
|
current: 1, |
|
|
|
total: 0, |
|
|
|
}; |
|
|
|
|
|
|
|
private keys: { [key: string]: string } = {}; |
|
|
|
|
|
|
|
private onPaginationChange = this.triggerPaginationEvent('onChange'); |
|
|
|
|
|
|
|
private onPaginationShowSizeChange = this.triggerPaginationEvent('onShowSizeChange'); |
|
|
|
|
|
|
|
constructor(props: ListProps<T>) { |
|
|
|
super(props); |
|
|
|
|
|
|
|
const { pagination } = props; |
|
|
|
const paginationObj = pagination && typeof pagination === 'object' ? pagination : {}; |
|
|
|
|
|
|
|
this.state = { |
|
|
|
paginationCurrent: paginationObj.defaultCurrent || 1, |
|
|
|
paginationSize: paginationObj.defaultPageSize || 10, |
|
|
|
}; |
|
|
|
} |
|
|
|
|
|
|
|
getChildContext() { |
|
|
|
return { |
|
|
|
grid: this.props.grid, |
|
|
|
itemLayout: this.props.itemLayout, |
|
|
|
}; |
|
|
|
} |
|
|
|
const keys: { [key: string]: string } = {}; |
|
|
|
|
|
|
|
triggerPaginationEvent(eventName: string) { |
|
|
|
const triggerPaginationEvent = (eventName: string) => { |
|
|
|
return (page: number, pageSize: number) => { |
|
|
|
const { pagination } = this.props; |
|
|
|
this.setState({ |
|
|
|
paginationCurrent: page, |
|
|
|
paginationSize: pageSize, |
|
|
|
}); |
|
|
|
setPaginationCurrent(page); |
|
|
|
setPaginationSize(pageSize); |
|
|
|
if (pagination && (pagination as any)[eventName]) { |
|
|
|
(pagination as any)[eventName](page, pageSize); |
|
|
|
} |
|
|
|
}; |
|
|
|
} |
|
|
|
}; |
|
|
|
|
|
|
|
const onPaginationChange = triggerPaginationEvent('onChange'); |
|
|
|
|
|
|
|
const onPaginationShowSizeChange = triggerPaginationEvent('onShowSizeChange'); |
|
|
|
|
|
|
|
renderItem = (item: any, index: number) => { |
|
|
|
const { renderItem, rowKey } = this.props; |
|
|
|
if (!renderItem) return null; |
|
|
|
const renderItem = (item: any, index: number) => { |
|
|
|
const { rowKey } = props; |
|
|
|
if (!props.renderItem) return null; |
|
|
|
|
|
|
|
let key; |
|
|
|
|
|
|
@ -140,138 +115,136 @@ export default class List<T> extends React.Component<ListProps<T>, ListState> { |
|
|
|
key = `list-item-${index}`; |
|
|
|
} |
|
|
|
|
|
|
|
this.keys[index] = key; |
|
|
|
keys[index] = key; |
|
|
|
|
|
|
|
return renderItem(item, index); |
|
|
|
return props.renderItem(item, index); |
|
|
|
}; |
|
|
|
|
|
|
|
isSomethingAfterLastItem() { |
|
|
|
const { loadMore, pagination, footer } = this.props; |
|
|
|
const isSomethingAfterLastItem = () => { |
|
|
|
const { loadMore, footer } = props; |
|
|
|
return !!(loadMore || pagination || footer); |
|
|
|
} |
|
|
|
}; |
|
|
|
|
|
|
|
renderEmpty = (prefixCls: string, renderEmpty: RenderEmptyHandler) => { |
|
|
|
const { locale } = this.props; |
|
|
|
const renderEmptyFunc = (prefixCls: string, renderEmptyHandler: RenderEmptyHandler) => { |
|
|
|
const { locale } = props; |
|
|
|
|
|
|
|
return ( |
|
|
|
<div className={`${prefixCls}-empty-text`}> |
|
|
|
{(locale && locale.emptyText) || renderEmpty('List')} |
|
|
|
{(locale && locale.emptyText) || renderEmptyHandler('List')} |
|
|
|
</div> |
|
|
|
); |
|
|
|
}; |
|
|
|
|
|
|
|
renderList = ({ getPrefixCls, renderEmpty, direction }: ConfigConsumerProps) => { |
|
|
|
const { paginationCurrent, paginationSize } = this.state; |
|
|
|
const { |
|
|
|
prefixCls: customizePrefixCls, |
|
|
|
bordered, |
|
|
|
split, |
|
|
|
className, |
|
|
|
children, |
|
|
|
itemLayout, |
|
|
|
loadMore, |
|
|
|
pagination, |
|
|
|
grid, |
|
|
|
dataSource = [], |
|
|
|
size, |
|
|
|
header, |
|
|
|
footer, |
|
|
|
loading, |
|
|
|
...rest |
|
|
|
} = this.props; |
|
|
|
|
|
|
|
const prefixCls = getPrefixCls('list', customizePrefixCls); |
|
|
|
let loadingProp = loading; |
|
|
|
if (typeof loadingProp === 'boolean') { |
|
|
|
loadingProp = { |
|
|
|
spinning: loadingProp, |
|
|
|
}; |
|
|
|
} |
|
|
|
const isLoading = loadingProp && loadingProp.spinning; |
|
|
|
|
|
|
|
// large => lg
|
|
|
|
// small => sm
|
|
|
|
let sizeCls = ''; |
|
|
|
switch (size) { |
|
|
|
case 'large': |
|
|
|
sizeCls = 'lg'; |
|
|
|
break; |
|
|
|
case 'small': |
|
|
|
sizeCls = 'sm'; |
|
|
|
break; |
|
|
|
default: |
|
|
|
break; |
|
|
|
} |
|
|
|
|
|
|
|
const classString = classNames(prefixCls, className, { |
|
|
|
[`${prefixCls}-vertical`]: itemLayout === 'vertical', |
|
|
|
[`${prefixCls}-${sizeCls}`]: sizeCls, |
|
|
|
[`${prefixCls}-split`]: split, |
|
|
|
[`${prefixCls}-bordered`]: bordered, |
|
|
|
[`${prefixCls}-loading`]: isLoading, |
|
|
|
[`${prefixCls}-grid`]: grid, |
|
|
|
[`${prefixCls}-something-after-last-item`]: this.isSomethingAfterLastItem(), |
|
|
|
[`${prefixCls}-rtl`]: direction === 'rtl', |
|
|
|
}); |
|
|
|
|
|
|
|
const paginationProps = { |
|
|
|
...this.defaultPaginationProps, |
|
|
|
total: dataSource.length, |
|
|
|
current: paginationCurrent, |
|
|
|
pageSize: paginationSize, |
|
|
|
...(pagination || {}), |
|
|
|
const { |
|
|
|
prefixCls: customizePrefixCls, |
|
|
|
bordered, |
|
|
|
split, |
|
|
|
className, |
|
|
|
children, |
|
|
|
itemLayout, |
|
|
|
loadMore, |
|
|
|
grid, |
|
|
|
dataSource = [], |
|
|
|
size, |
|
|
|
header, |
|
|
|
footer, |
|
|
|
loading, |
|
|
|
...rest |
|
|
|
} = props; |
|
|
|
|
|
|
|
const prefixCls = getPrefixCls('list', customizePrefixCls); |
|
|
|
let loadingProp = loading; |
|
|
|
if (typeof loadingProp === 'boolean') { |
|
|
|
loadingProp = { |
|
|
|
spinning: loadingProp, |
|
|
|
}; |
|
|
|
} |
|
|
|
const isLoading = loadingProp && loadingProp.spinning; |
|
|
|
|
|
|
|
// large => lg
|
|
|
|
// small => sm
|
|
|
|
let sizeCls = ''; |
|
|
|
switch (size) { |
|
|
|
case 'large': |
|
|
|
sizeCls = 'lg'; |
|
|
|
break; |
|
|
|
case 'small': |
|
|
|
sizeCls = 'sm'; |
|
|
|
break; |
|
|
|
default: |
|
|
|
break; |
|
|
|
} |
|
|
|
|
|
|
|
const largestPage = Math.ceil(paginationProps.total / paginationProps.pageSize); |
|
|
|
if (paginationProps.current > largestPage) { |
|
|
|
paginationProps.current = largestPage; |
|
|
|
} |
|
|
|
const paginationContent = pagination ? ( |
|
|
|
<div className={`${prefixCls}-pagination`}> |
|
|
|
<Pagination |
|
|
|
{...paginationProps} |
|
|
|
onChange={this.onPaginationChange} |
|
|
|
onShowSizeChange={this.onPaginationShowSizeChange} |
|
|
|
/> |
|
|
|
</div> |
|
|
|
) : null; |
|
|
|
|
|
|
|
let splitDataSource = [...dataSource]; |
|
|
|
if (pagination) { |
|
|
|
if (dataSource.length > (paginationProps.current - 1) * paginationProps.pageSize) { |
|
|
|
splitDataSource = [...dataSource].splice( |
|
|
|
(paginationProps.current - 1) * paginationProps.pageSize, |
|
|
|
paginationProps.pageSize, |
|
|
|
); |
|
|
|
} |
|
|
|
} |
|
|
|
const classString = classNames(prefixCls, className, { |
|
|
|
[`${prefixCls}-vertical`]: itemLayout === 'vertical', |
|
|
|
[`${prefixCls}-${sizeCls}`]: sizeCls, |
|
|
|
[`${prefixCls}-split`]: split, |
|
|
|
[`${prefixCls}-bordered`]: bordered, |
|
|
|
[`${prefixCls}-loading`]: isLoading, |
|
|
|
[`${prefixCls}-grid`]: grid, |
|
|
|
[`${prefixCls}-something-after-last-item`]: isSomethingAfterLastItem(), |
|
|
|
[`${prefixCls}-rtl`]: direction === 'rtl', |
|
|
|
}); |
|
|
|
|
|
|
|
const paginationProps = { |
|
|
|
...defaultPaginationProps, |
|
|
|
total: dataSource.length, |
|
|
|
current: paginationCurrent, |
|
|
|
pageSize: paginationSize, |
|
|
|
...(pagination || {}), |
|
|
|
}; |
|
|
|
|
|
|
|
let childrenContent; |
|
|
|
childrenContent = isLoading && <div style={{ minHeight: 53 }} />; |
|
|
|
if (splitDataSource.length > 0) { |
|
|
|
const items = splitDataSource.map((item: any, index: number) => this.renderItem(item, index)); |
|
|
|
|
|
|
|
const childrenList: Array<React.ReactNode> = []; |
|
|
|
React.Children.forEach(items, (child: any, index) => { |
|
|
|
childrenList.push( |
|
|
|
React.cloneElement(child, { |
|
|
|
key: this.keys[index], |
|
|
|
}), |
|
|
|
); |
|
|
|
}); |
|
|
|
|
|
|
|
childrenContent = grid ? ( |
|
|
|
<Row gutter={grid.gutter}>{childrenList}</Row> |
|
|
|
) : ( |
|
|
|
<ul className={`${prefixCls}-items`}>{childrenList}</ul> |
|
|
|
const largestPage = Math.ceil(paginationProps.total / paginationProps.pageSize); |
|
|
|
if (paginationProps.current > largestPage) { |
|
|
|
paginationProps.current = largestPage; |
|
|
|
} |
|
|
|
const paginationContent = pagination ? ( |
|
|
|
<div className={`${prefixCls}-pagination`}> |
|
|
|
<Pagination |
|
|
|
{...paginationProps} |
|
|
|
onChange={onPaginationChange} |
|
|
|
onShowSizeChange={onPaginationShowSizeChange} |
|
|
|
/> |
|
|
|
</div> |
|
|
|
) : null; |
|
|
|
|
|
|
|
let splitDataSource = [...dataSource]; |
|
|
|
if (pagination) { |
|
|
|
if (dataSource.length > (paginationProps.current - 1) * paginationProps.pageSize) { |
|
|
|
splitDataSource = [...dataSource].splice( |
|
|
|
(paginationProps.current - 1) * paginationProps.pageSize, |
|
|
|
paginationProps.pageSize, |
|
|
|
); |
|
|
|
} else if (!children && !isLoading) { |
|
|
|
childrenContent = this.renderEmpty(prefixCls, renderEmpty); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
let childrenContent; |
|
|
|
childrenContent = isLoading && <div style={{ minHeight: 53 }} />; |
|
|
|
if (splitDataSource.length > 0) { |
|
|
|
const items = splitDataSource.map((item: any, index: number) => renderItem(item, index)); |
|
|
|
|
|
|
|
const childrenList: Array<React.ReactNode> = []; |
|
|
|
React.Children.forEach(items, (child: any, index) => { |
|
|
|
childrenList.push( |
|
|
|
React.cloneElement(child, { |
|
|
|
key: keys[index], |
|
|
|
}), |
|
|
|
); |
|
|
|
}); |
|
|
|
|
|
|
|
childrenContent = grid ? ( |
|
|
|
<Row gutter={grid.gutter}>{childrenList}</Row> |
|
|
|
) : ( |
|
|
|
<ul className={`${prefixCls}-items`}>{childrenList}</ul> |
|
|
|
); |
|
|
|
} else if (!children && !isLoading) { |
|
|
|
childrenContent = renderEmptyFunc(prefixCls, renderEmpty); |
|
|
|
} |
|
|
|
|
|
|
|
const paginationPosition = paginationProps.position || 'bottom'; |
|
|
|
const paginationPosition = paginationProps.position || 'bottom'; |
|
|
|
|
|
|
|
return ( |
|
|
|
return ( |
|
|
|
<ListContext.Provider value={{ grid: props.grid, itemLayout: props.itemLayout }}> |
|
|
|
<div className={classString} {...omit(rest, ['rowKey', 'renderItem', 'locale'])}> |
|
|
|
{(paginationPosition === 'top' || paginationPosition === 'both') && paginationContent} |
|
|
|
{header && <div className={`${prefixCls}-header`}>{header}</div>} |
|
|
@ -283,10 +256,18 @@ export default class List<T> extends React.Component<ListProps<T>, ListState> { |
|
|
|
{loadMore || |
|
|
|
((paginationPosition === 'bottom' || paginationPosition === 'both') && paginationContent)} |
|
|
|
</div> |
|
|
|
); |
|
|
|
}; |
|
|
|
|
|
|
|
render() { |
|
|
|
return <ConfigConsumer>{this.renderList}</ConfigConsumer>; |
|
|
|
} |
|
|
|
</ListContext.Provider> |
|
|
|
); |
|
|
|
} |
|
|
|
|
|
|
|
List.defaultProps = { |
|
|
|
dataSource: [], |
|
|
|
bordered: false, |
|
|
|
split: true, |
|
|
|
loading: false, |
|
|
|
pagination: false as ListProps<any>['pagination'], |
|
|
|
}; |
|
|
|
|
|
|
|
List.Item = Item; |
|
|
|
|
|
|
|
export default List; |
|
|
|