TableHeaderRow.tsx 9.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259
  1. import React from 'react';
  2. import classnames from 'classnames';
  3. import PropTypes from 'prop-types';
  4. import { get, noop, map, set, omit, findIndex } from 'lodash';
  5. import { cssClasses } from '@douyinfe/semi-foundation/table/constants';
  6. import {
  7. arrayAdd,
  8. isFirstFixedRight,
  9. isLastLeftFixed,
  10. isFixedLeft,
  11. isFixedRight,
  12. sliceColumnsByLevel,
  13. getRTLAlign
  14. } from '@douyinfe/semi-foundation/table/utils';
  15. import BaseComponent from '../_base/baseComponent';
  16. import TableContext, { TableContextProps } from './table-context';
  17. import { TableComponents, OnHeaderRow, Fixed, TableLocale } from './interface';
  18. import type { TableHeaderCell } from './TableHeader';
  19. import Tooltip from '../tooltip';
  20. import LocaleConsumer from '../locale/localeConsumer';
  21. import { getNextSortOrder } from './utils';
  22. export interface TableHeaderRowProps {
  23. components?: TableComponents;
  24. row?: TableHeaderCell[];
  25. prefixCls?: string;
  26. onHeaderRow?: OnHeaderRow<any>;
  27. index?: number;
  28. style?: React.CSSProperties;
  29. columns?: any[];
  30. fixed?: Fixed;
  31. selectedRowKeysSet: Set<any>
  32. }
  33. export default class TableHeaderRow extends BaseComponent<TableHeaderRowProps, Record<string, any>> {
  34. static contextType = TableContext;
  35. static propTypes = {
  36. components: PropTypes.object,
  37. row: PropTypes.array,
  38. prefixCls: PropTypes.string,
  39. onHeaderRow: PropTypes.func,
  40. index: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
  41. style: PropTypes.object,
  42. columns: PropTypes.array,
  43. fixed: PropTypes.oneOfType([PropTypes.bool, PropTypes.string]),
  44. selectedRowKeysSet: PropTypes.instanceOf(Set).isRequired,
  45. };
  46. static defaultProps = {
  47. onHeaderRow: noop,
  48. prefixCls: cssClasses.PREFIX,
  49. columns: [] as [],
  50. components: {
  51. header: {
  52. wrapper: 'thead',
  53. row: 'tr',
  54. cell: 'th',
  55. },
  56. },
  57. };
  58. get adapter() {
  59. return {
  60. ...super.adapter,
  61. };
  62. }
  63. headerNode: HTMLElement;
  64. context: TableContextProps;
  65. constructor(props: TableHeaderRowProps) {
  66. super(props);
  67. this.headerNode = null;
  68. }
  69. cacheRef = (node: HTMLElement) => {
  70. this.headerNode = node;
  71. if (node && this.context.setHeadWidths) {
  72. const { prefixCls, row, index } = this.props;
  73. const cellSelector = `.${prefixCls}-row-head`;
  74. const heads = node && node.querySelectorAll && node.querySelectorAll(cellSelector);
  75. this.context.setHeadWidths(
  76. map(heads, (head, headIndex) => {
  77. let configWidth = get(row, [headIndex, 'column', 'width']);
  78. const key = get(row, [headIndex, 'column', 'key']) as any;
  79. if (typeof configWidth !== 'number') {
  80. configWidth = (head && head.getBoundingClientRect().width) || 0;
  81. }
  82. return { width: configWidth, key };
  83. }),
  84. index
  85. );
  86. }
  87. };
  88. componentDidUpdate(prevProps: TableHeaderRowProps) {
  89. if (prevProps.columns !== this.props.columns && this.headerNode) {
  90. this.cacheRef(this.headerNode);
  91. }
  92. }
  93. render() {
  94. const { components, row, prefixCls, onHeaderRow, index, style, columns } = this.props;
  95. const { getCellWidths, direction } = this.context;
  96. const isRTL = direction === 'rtl';
  97. const slicedColumns = sliceColumnsByLevel(columns, index);
  98. const headWidths = getCellWidths(slicedColumns);
  99. const HeaderRow = get(components, 'header.row', 'tr');
  100. const HeaderCell = get(components, 'header.cell', 'th');
  101. const rowProps = onHeaderRow(columns, index) || {};
  102. set(rowProps, 'className', classnames(get(rowProps, 'className'), `${prefixCls}-row`));
  103. const cells = map(row, (cell, cellIndex) => {
  104. const { column, ...cellProps } = cell;
  105. const customProps =
  106. typeof column.onHeaderCell === 'function' ? column.onHeaderCell(column, cellIndex, index) : {};
  107. let cellStyle = { ...customProps.style };
  108. if (column.align) {
  109. const textAlign = getRTLAlign(column.align, direction);
  110. cellStyle = { ...cellStyle, textAlign };
  111. customProps.className = classnames(customProps.className, column.className, {
  112. [`${prefixCls}-align-${textAlign}`]: Boolean(textAlign),
  113. });
  114. }
  115. let fixedLeft, fixedRight, fixedLeftLast, fixedRightFirst;
  116. if (isRTL) {
  117. fixedLeft = isFixedRight(column);
  118. fixedRight = isFixedLeft(column);
  119. fixedLeftLast = isFirstFixedRight(slicedColumns, column);
  120. fixedRightFirst = isLastLeftFixed(slicedColumns, column);
  121. } else {
  122. fixedLeft = isFixedLeft(column);
  123. fixedRight = isFixedRight(column);
  124. fixedLeftLast = isLastLeftFixed(slicedColumns, column);
  125. fixedRightFirst = isFirstFixedRight(slicedColumns, column);
  126. }
  127. customProps.className = classnames(
  128. `${prefixCls}-row-head`,
  129. column.className,
  130. customProps.className,
  131. // `${prefixCls}-fixed-columns`,
  132. {
  133. [`${prefixCls}-cell-fixed-left`]: fixedLeft,
  134. [`${prefixCls}-cell-fixed-left-last`]: fixedLeftLast,
  135. [`${prefixCls}-cell-fixed-right`]: fixedRight,
  136. [`${prefixCls}-cell-fixed-right-first`]: fixedRightFirst,
  137. [`${prefixCls}-row-head-ellipsis`]: column.ellipsis,
  138. [`${prefixCls}-row-head-clickSort`]: column.clickToSort
  139. }
  140. );
  141. if (headWidths.length && slicedColumns.length) {
  142. const indexOfSlicedColumns = findIndex(
  143. slicedColumns,
  144. item => item && item.key != null && item.key === column.key
  145. );
  146. if (indexOfSlicedColumns > -1) {
  147. if (isFixedLeft(column)) {
  148. const xPositionKey = isRTL ? 'right' : 'left';
  149. cellStyle = {
  150. ...cellStyle,
  151. position: 'sticky',
  152. [xPositionKey]: arrayAdd(headWidths, 0, indexOfSlicedColumns),
  153. };
  154. } else if (isFixedRight(column)) {
  155. const xPositionKey = isRTL ? 'left' : 'right';
  156. cellStyle = {
  157. ...cellStyle,
  158. position: 'sticky',
  159. [xPositionKey]: arrayAdd(headWidths, indexOfSlicedColumns + 1),
  160. };
  161. }
  162. }
  163. }
  164. Object.assign(cellProps, { resize: column.resize });
  165. const props = omit({ ...cellProps, ...customProps }, [
  166. 'colStart',
  167. 'colEnd',
  168. 'hasSubColumns',
  169. 'parents',
  170. 'level',
  171. ]);
  172. const { rowSpan, colSpan } = props;
  173. if (rowSpan === 0 || colSpan === 0) {
  174. return null;
  175. }
  176. if (typeof column.clickToSort === 'function') {
  177. if (props.onClick) {
  178. const onClick = props.onClick;
  179. props.onClick = (e: any) => {
  180. onClick(e);
  181. column.clickToSort(e);
  182. };
  183. } else {
  184. props.onClick = column.clickToSort;
  185. }
  186. }
  187. if (typeof column.mouseDown === 'function') {
  188. if (props.onMouseDown) {
  189. const onMouseDown = props.onMouseDown;
  190. props.onMouseDown = (e: any) => {
  191. onMouseDown(e);
  192. column.mouseDown(e);
  193. };
  194. } else {
  195. props.onMouseDown = column.mouseDown;
  196. }
  197. }
  198. const headerCellNode = (<HeaderCell
  199. role="columnheader"
  200. aria-colindex={cellIndex + 1}
  201. {...props}
  202. style={cellStyle}
  203. key={column.key || column.dataIndex || cellIndex}
  204. />);
  205. if (typeof column.clickToSort === 'function' && column.showSortTip === true) {
  206. let content = getNextSortOrder(column.sortOrder);
  207. return (<LocaleConsumer
  208. componentName="Table"
  209. key={column.key || column.dataIndex || cellIndex}
  210. >
  211. {(locale: TableLocale, localeCode: string) => (
  212. <Tooltip content={locale[content]}>
  213. {headerCellNode}
  214. </Tooltip>
  215. )}
  216. </LocaleConsumer>);
  217. }
  218. return headerCellNode;
  219. });
  220. return (
  221. // @ts-ignore no need to do complex ts type checking and qualification
  222. <HeaderRow
  223. role="row"
  224. aria-rowindex={index + 1}
  225. {...rowProps}
  226. style={style}
  227. ref={this.cacheRef}
  228. >
  229. {cells}
  230. </HeaderRow>
  231. );
  232. }
  233. }