ColumnFilter.tsx 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201
  1. /* eslint-disable no-nested-ternary */
  2. /* eslint-disable eqeqeq */
  3. import React, { isValidElement } from 'react';
  4. import cls from 'classnames';
  5. import { noop } from 'lodash-es';
  6. import { IconFilter } from '@douyinfe/semi-icons';
  7. import { cssClasses } from '@douyinfe/semi-foundation/table/constants';
  8. import Dropdown, { DropdownProps } from '../dropdown';
  9. import { Trigger, Position } from '../tooltip';
  10. import { Radio } from '../radio';
  11. import { Checkbox } from '../checkbox';
  12. import {
  13. FilterIcon,
  14. Filter,
  15. OnFilterDropdownVisibleChange,
  16. RenderFilterDropdownItem
  17. } from './interface';
  18. function renderDropdown(props: RenderDropdownProps = {}, nestedElem: React.ReactNode = null, level = 0) {
  19. const {
  20. filterMultiple = true,
  21. filters = [],
  22. filteredValue = [],
  23. filterDropdownVisible,
  24. onSelect = noop,
  25. onFilterDropdownVisibleChange = noop,
  26. trigger = 'click',
  27. position = 'bottom',
  28. renderFilterDropdownItem,
  29. } = props;
  30. const dropdownProps: DropdownProps = {
  31. ...props,
  32. onVisibleChange: (visible: boolean) => onFilterDropdownVisibleChange(visible),
  33. trigger,
  34. position,
  35. render: (
  36. <Dropdown.Menu>
  37. {Array.isArray(filters) &&
  38. filters.map((filter, index) => {
  39. const changeFn = (e: React.MouseEvent<HTMLLIElement>) => {
  40. const domEvent = e && e.nativeEvent;
  41. if (domEvent) {
  42. // Block this event to prevent the pop-up layer from closing
  43. domEvent.stopImmediatePropagation();
  44. // Prevent bubbling and default events to prevent label click events from triggering twice
  45. domEvent.stopPropagation();
  46. domEvent.preventDefault();
  47. }
  48. let values = [...filteredValue];
  49. const included = values.includes(filter.value);
  50. const idx = values.indexOf(filter.value);
  51. if (idx > -1) {
  52. values.splice(idx, 1);
  53. } else if (filterMultiple) {
  54. values.push(filter.value);
  55. } else {
  56. values = [filter.value];
  57. }
  58. return onSelect({
  59. value: filter.value,
  60. filteredValue: values,
  61. included: !included,
  62. domEvent,
  63. });
  64. };
  65. const checked = filteredValue.includes(filter.value);
  66. const { text } = filter;
  67. const { value } = filter;
  68. const key = `${level}_${index}`;
  69. const dropdownItem =
  70. typeof renderFilterDropdownItem === 'function' ?
  71. renderFilterDropdownItem({
  72. onChange: changeFn,
  73. filterMultiple,
  74. value,
  75. text,
  76. checked,
  77. filteredValue,
  78. level,
  79. }) :
  80. null;
  81. let item =
  82. dropdownItem && React.isValidElement(dropdownItem) ? (
  83. React.cloneElement(dropdownItem, { key })
  84. ) : (
  85. <Dropdown.Item key={key} onClick={changeFn}>
  86. {filterMultiple ? (
  87. <Checkbox checked={checked}>{text}</Checkbox>
  88. ) : (
  89. <Radio checked={checked}>{text}</Radio>
  90. )}
  91. </Dropdown.Item>
  92. );
  93. if (Array.isArray(filter.children) && filter.children.length) {
  94. const childrenDropdownProps = {
  95. ...props,
  96. filters: filter.children,
  97. trigger: 'hover' as const,
  98. position: 'right' as const,
  99. };
  100. delete childrenDropdownProps.filterDropdownVisible;
  101. item = renderDropdown(childrenDropdownProps, item, level + 1);
  102. }
  103. return item;
  104. })}
  105. </Dropdown.Menu>
  106. ),
  107. };
  108. if (filterDropdownVisible != null) {
  109. dropdownProps.visible = filterDropdownVisible;
  110. }
  111. return (
  112. <Dropdown {...dropdownProps} key={`Dropdown_level_${level}`}>
  113. {nestedElem}
  114. </Dropdown>
  115. );
  116. }
  117. export interface ColumnFilterProps {
  118. prefixCls?: string;
  119. filteredValue?: any[];
  120. filterIcon?: FilterIcon;
  121. filterDropdown?: React.ReactElement;
  122. renderFilterDropdown?: (props: RenderDropdownProps, options: { iconElem: React.ReactNode }) => React.ReactElement;
  123. filterDropdownProps?: DropdownProps;
  124. }
  125. export default function ColumnFilter(props: ColumnFilterProps = {}): React.ReactElement {
  126. const {
  127. prefixCls = cssClasses.PREFIX,
  128. filteredValue,
  129. filterIcon = 'filter',
  130. renderFilterDropdown,
  131. filterDropdownProps,
  132. } = props;
  133. let { filterDropdown = null } = props;
  134. const finalCls = cls(`${prefixCls}-column-filter`, {
  135. on: Array.isArray(filteredValue) && filteredValue.length,
  136. });
  137. let iconElem;
  138. if (typeof filterIcon === 'function') {
  139. iconElem = filterIcon(Array.isArray(filteredValue) && filteredValue.length > 0);
  140. } else if (isValidElement(filterIcon)) {
  141. iconElem = filterIcon;
  142. } else {
  143. iconElem = (
  144. <div className={finalCls}>
  145. <IconFilter size="small" />
  146. </div>
  147. );
  148. }
  149. const renderProps = {
  150. ...props,
  151. ...filterDropdownProps,
  152. };
  153. filterDropdown = React.isValidElement<ColumnFilterProps>(filterDropdown) ?
  154. filterDropdown :
  155. typeof renderFilterDropdown === 'function' ?
  156. renderFilterDropdown(renderProps, { iconElem }) :
  157. renderDropdown(renderProps, iconElem);
  158. return filterDropdown;
  159. }
  160. export interface OnSelectData {
  161. value: any;
  162. filteredValue: any;
  163. included: boolean;
  164. domEvent: React.MouseEvent<HTMLElement>;
  165. }
  166. export interface RenderDropdownProps {
  167. filterMultiple?: boolean;
  168. filters?: Filter[];
  169. filteredValue?: any[];
  170. filterDropdownVisible?: boolean;
  171. onSelect?: (data: OnSelectData) => void;
  172. onFilterDropdownVisibleChange?: OnFilterDropdownVisibleChange;
  173. trigger?: Trigger;
  174. position?: Position;
  175. renderFilterDropdownItem?: RenderFilterDropdownItem;
  176. }