ColumnFilter.tsx 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209
  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';
  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. onFilterDropdownVisibleChange?: OnFilterDropdownVisibleChange;
  125. onSelect?: (data: OnSelectData) => void;
  126. }
  127. export default function ColumnFilter(props: ColumnFilterProps = {}): React.ReactElement {
  128. const {
  129. prefixCls = cssClasses.PREFIX,
  130. filteredValue,
  131. filterIcon = 'filter',
  132. renderFilterDropdown,
  133. filterDropdownProps,
  134. } = props;
  135. let { filterDropdown = null } = props;
  136. const finalCls = cls(`${prefixCls}-column-filter`, {
  137. on: Array.isArray(filteredValue) && filteredValue.length,
  138. });
  139. let iconElem;
  140. if (typeof filterIcon === 'function') {
  141. iconElem = filterIcon(Array.isArray(filteredValue) && filteredValue.length > 0);
  142. } else if (isValidElement(filterIcon)) {
  143. iconElem = filterIcon;
  144. } else {
  145. iconElem = (
  146. <div className={finalCls}>
  147. <IconFilter
  148. role="button"
  149. aria-label="Filter data with this column"
  150. aria-haspopup="listbox"
  151. tabIndex={-1}
  152. size="small"
  153. />
  154. </div>
  155. );
  156. }
  157. const renderProps = {
  158. ...props,
  159. ...filterDropdownProps,
  160. };
  161. filterDropdown = React.isValidElement<ColumnFilterProps>(filterDropdown) ?
  162. filterDropdown :
  163. typeof renderFilterDropdown === 'function' ?
  164. renderFilterDropdown(renderProps, { iconElem }) :
  165. renderDropdown(renderProps, iconElem);
  166. return filterDropdown;
  167. }
  168. export interface OnSelectData {
  169. value: any;
  170. filteredValue: any;
  171. included: boolean;
  172. domEvent: React.MouseEvent<HTMLElement>;
  173. }
  174. export interface RenderDropdownProps {
  175. filterMultiple?: boolean;
  176. filters?: Filter[];
  177. filteredValue?: any[];
  178. filterDropdownVisible?: boolean;
  179. onSelect?: (data: OnSelectData) => void;
  180. onFilterDropdownVisibleChange?: OnFilterDropdownVisibleChange;
  181. trigger?: Trigger;
  182. position?: Position;
  183. renderFilterDropdownItem?: RenderFilterDropdownItem;
  184. }