import * as React from 'react'; import * as PropTypes from 'prop-types'; import classNames from 'classnames'; import { SpinProps } from '../spin'; import { ConfigConsumer, ConfigConsumerProps, RenderEmptyHandler } from '../config-provider'; import Spin from '../spin'; import Pagination, { PaginationConfig } from '../pagination'; import { Row } from '../grid'; import Item from './Item'; export { ListItemProps, ListItemMetaProps } from './Item'; export type ColumnCount = 1 | 2 | 3 | 4 | 6 | 8 | 12 | 24; export type ColumnType = 'gutter' | 'column' | 'xs' | 'sm' | 'md' | 'lg' | 'xl' | 'xxl'; export interface ListGridType { gutter?: number; column?: ColumnCount; xs?: ColumnCount; sm?: ColumnCount; md?: ColumnCount; lg?: ColumnCount; xl?: ColumnCount; xxl?: ColumnCount; } export type ListSize = 'small' | 'default' | 'large'; export type ListItemLayout = 'horizontal' | 'vertical'; export interface ListProps { bordered?: boolean; className?: string; children?: React.ReactNode; dataSource: T[]; extra?: React.ReactNode; grid?: ListGridType; id?: string; itemLayout?: ListItemLayout; loading?: boolean | SpinProps; loadMore?: React.ReactNode; pagination?: PaginationConfig | false; prefixCls?: string; rowKey?: any; renderItem: (item: T, index: number) => React.ReactNode; size?: ListSize; split?: boolean; header?: React.ReactNode; footer?: React.ReactNode; locale?: ListLocale; } export interface ListLocale { emptyText: React.ReactNode | (() => React.ReactNode); } interface ListState { paginationCurrent: number; paginationSize: number; } export default class List extends React.Component, ListState> { static Item: typeof Item = Item; static childContextTypes = { grid: PropTypes.any, itemLayout: PropTypes.string, }; static defaultProps = { dataSource: [], bordered: false, split: true, loading: false, pagination: false as ListProps['pagination'], }; defaultPaginationProps = { current: 1, total: 0, }; private keys: { [key: string]: string } = {}; private onPaginationChange = this.triggerPaginationEvent('onChange'); private onPaginationShowSizeChange = this.triggerPaginationEvent('onShowSizeChange'); constructor(props: ListProps) { super(props); const { pagination } = props; const paginationObj = typeof pagination === 'object' ? pagination : {}; this.state = { paginationCurrent: paginationObj.defaultCurrent || 1, paginationSize: paginationObj.defaultPageSize || 10, }; } getChildContext() { return { grid: this.props.grid, itemLayout: this.props.itemLayout, }; } triggerPaginationEvent(eventName: string) { return (page: number, pageSize: number) => { const { pagination } = this.props; this.setState({ paginationCurrent: page, paginationSize: pageSize, }); if (pagination && (pagination as any)[eventName]) { (pagination as any)[eventName](page, pageSize); } }; } renderItem = (item: any, index: number) => { const { renderItem, rowKey } = this.props; let key; if (typeof rowKey === 'function') { key = rowKey(item); } else if (typeof rowKey === 'string') { key = item[rowKey]; } else { key = item.key; } if (!key) { key = `list-item-${index}`; } this.keys[index] = key; return renderItem(item, index); }; isSomethingAfterLastItem() { const { loadMore, pagination, footer } = this.props; return !!(loadMore || pagination || footer); } renderEmpty = (prefixCls: string, renderEmpty: RenderEmptyHandler) => { const { locale } = this.props; return (
{(locale && locale.emptyText) || renderEmpty('List')}
); }; renderList = ({ getPrefixCls, renderEmpty }: ConfigConsumerProps) => { const { paginationCurrent, paginationSize } = this.state; const { prefixCls: customizePrefixCls, bordered, split, className, children, itemLayout, loadMore, pagination, grid, dataSource, size, rowKey, renderItem, header, footer, loading, locale, ...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'; 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(), }); const paginationProps = { ...this.defaultPaginationProps, total: dataSource.length, current: paginationCurrent, pageSize: paginationSize, ...(pagination || {}), }; const largestPage = Math.ceil(paginationProps.total / paginationProps.pageSize); if (paginationProps.current > largestPage) { paginationProps.current = largestPage; } const paginationContent = pagination ? (
) : null; let splitDataSource = [...dataSource]; if (pagination) { if (dataSource.length > (paginationProps.current - 1) * paginationProps.pageSize) { splitDataSource = [...dataSource].splice( (paginationProps.current - 1) * paginationProps.pageSize, paginationProps.pageSize, ); } } let childrenContent; childrenContent = isLoading &&
; if (splitDataSource.length > 0) { const items = splitDataSource.map((item: any, index: number) => this.renderItem(item, index)); const childrenList: Array = []; React.Children.forEach(items, (child: any, index) => { childrenList.push( React.cloneElement(child, { key: this.keys[index], }), ); }); childrenContent = grid ? {childrenList} : childrenList; } else if (!children && !isLoading) { childrenContent = this.renderEmpty(prefixCls, renderEmpty); } const paginationPosition = paginationProps.position || 'bottom'; return (
{(paginationPosition === 'top' || paginationPosition === 'both') && paginationContent} {header &&
{header}
} {childrenContent} {children} {footer &&
{footer}
} {loadMore || ((paginationPosition === 'bottom' || paginationPosition === 'both') && paginationContent)}
); }; render() { return {this.renderList}; } }