index.tsx 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173
  1. import React from 'react';
  2. import cls from 'classnames';
  3. import PropTypes from 'prop-types';
  4. import { cssClasses, strings } from '@douyinfe/semi-foundation/list/constants';
  5. import { noop } from 'lodash-es';
  6. import '@douyinfe/semi-foundation/list/list.scss';
  7. import LocaleConsumer from '../locale/localeConsumer';
  8. import { Locale } from '../locale/interface';
  9. import ListItem from './item';
  10. import { Row } from '../grid';
  11. import Spin from '../spin';
  12. import ListContext, { Grid } from './list-context';
  13. import BaseComponent from '../_base/baseComponent';
  14. export { ListItemProps } from './item';
  15. export interface ListProps {
  16. style?: React.CSSProperties;
  17. className?: string;
  18. bordered?: boolean;
  19. footer?: React.ReactNode;
  20. header?: React.ReactNode;
  21. layout?: 'vertical' | 'horizontal';
  22. size?: 'small' | 'large' | 'default';
  23. split?: boolean;
  24. emptyContent?: React.ReactNode;
  25. dataSource?: any[];
  26. renderItem?: (item: any, ind: number) => React.ReactNode;
  27. grid?: Grid;
  28. loading?: boolean;
  29. loadMore?: React.ReactNode;
  30. onClick?: React.MouseEventHandler<HTMLLIElement>;
  31. onRightClick?: React.MouseEventHandler<HTMLLIElement>;
  32. }
  33. const prefixCls = cssClasses.PREFIX;
  34. class List extends BaseComponent<ListProps, void> {
  35. static Item = ListItem;
  36. static propTypes = {
  37. style: PropTypes.object,
  38. className: PropTypes.string,
  39. bordered: PropTypes.bool,
  40. footer: PropTypes.node,
  41. header: PropTypes.node,
  42. layout: PropTypes.oneOf(strings.LAYOUT),
  43. size: PropTypes.oneOf(strings.SIZE),
  44. split: PropTypes.bool,
  45. emptyContent: PropTypes.node,
  46. dataSource: PropTypes.array,
  47. renderItem: PropTypes.func,
  48. grid: PropTypes.object,
  49. loading: PropTypes.bool,
  50. loadMore: PropTypes.node,
  51. onRightClick: PropTypes.func,
  52. onClick: PropTypes.func,
  53. };
  54. static defaultProps = {
  55. bordered: false,
  56. split: true,
  57. loading: false,
  58. layout: 'vertical',
  59. size: 'default',
  60. onRightClick: noop,
  61. onClick: noop,
  62. };
  63. renderEmpty = () => {
  64. const { emptyContent } = this.props;
  65. if (emptyContent) {
  66. return (<div className={`${cssClasses.PREFIX}-empty`}>{emptyContent}</div>);
  67. } else {
  68. return (
  69. <LocaleConsumer componentName="List">
  70. {
  71. (locale: Locale['List']) => (
  72. <div className={`${cssClasses.PREFIX}-empty`}>{locale.emptyText}</div>
  73. )
  74. }
  75. </LocaleConsumer>
  76. );
  77. }
  78. };
  79. wrapChildren(childrenList: React.ReactNode, children: React.ReactNode) {
  80. const { grid } = this.props;
  81. if (grid) {
  82. const rowProps = {};
  83. ['align', 'gutter', 'justify', 'type'].forEach(key => {
  84. if (key in grid) {
  85. rowProps[key] = grid[key];
  86. }
  87. });
  88. return (
  89. <Row type="flex" {...rowProps}>
  90. {childrenList ? childrenList : null}
  91. {children}
  92. </Row>
  93. );
  94. }
  95. return (
  96. <ul className={`${prefixCls}-items`}>
  97. {childrenList ? childrenList : null}
  98. {children}
  99. </ul>
  100. );
  101. }
  102. render() {
  103. const {
  104. style,
  105. className,
  106. header,
  107. loading,
  108. onRightClick,
  109. onClick,
  110. footer,
  111. layout,
  112. grid,
  113. size,
  114. split,
  115. loadMore,
  116. bordered,
  117. dataSource,
  118. renderItem,
  119. children
  120. } = this.props;
  121. const wrapperCls = cls(prefixCls, className, {
  122. [`${prefixCls}-flex`]: layout === 'horizontal',
  123. [`${prefixCls}-${size}`]: size,
  124. [`${prefixCls}-grid`]: grid,
  125. [`${prefixCls}-split`]: split,
  126. [`${prefixCls}-bordered`]: bordered,
  127. });
  128. let childrenList;
  129. if (dataSource && dataSource.length) {
  130. childrenList = [];
  131. const items = renderItem ? dataSource.map((item, index) => renderItem(item, index)) : [];
  132. React.Children.forEach(items as any, (child, index) => {
  133. const itemKey = child.key || `list-item-${index}`;
  134. childrenList.push(
  135. React.cloneElement(child, {
  136. key: itemKey,
  137. })
  138. );
  139. });
  140. } else if (!children && !loading) {
  141. childrenList = this.renderEmpty();
  142. }
  143. return (
  144. <div className={wrapperCls} style={style}>
  145. {header ? <div className={`${cssClasses.PREFIX}-header`}>{header}</div> : null}
  146. <ListContext.Provider
  147. value={{
  148. grid,
  149. onRightClick,
  150. onClick
  151. }}
  152. >
  153. <Spin spinning={loading} size="large">
  154. {this.wrapChildren(childrenList, children)}
  155. </Spin>
  156. </ListContext.Provider>
  157. {footer ? <div className={`${cssClasses.PREFIX}-footer`}>{footer}</div> : null}
  158. {loadMore ? loadMore : null}
  159. </div>
  160. );
  161. }
  162. }
  163. export default List;