ColumnFilter.tsx 7.5 KB

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