1
0

index.tsx 5.7 KB

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