123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286 |
- import React, { isValidElement, useEffect, useState } from 'react';
- import cls from 'classnames';
- import { isEqual, noop, pick } from 'lodash';
- import { IconFilter } from '@douyinfe/semi-icons';
- import { cssClasses } from '@douyinfe/semi-foundation/table/constants';
- import Dropdown, { DropdownProps } from '../dropdown';
- import { Radio } from '../radio';
- import { Checkbox } from '../checkbox';
- import {
- FilterIcon,
- Filter,
- OnFilterDropdownVisibleChange,
- RenderFilterDropdownItem
- } from './interface';
- function renderDropdown(props: RenderDropdownProps, nestedElem: React.ReactNode = null, level = 0) {
- const {
- filterMultiple = true,
- filters = [],
- filteredValue = [],
- filterDropdownVisible,
- onSelect = noop,
- onFilterDropdownVisibleChange = noop,
- trigger = 'click',
- position = 'bottom',
- renderFilterDropdown,
- renderFilterDropdownItem,
- } = props ?? {};
- const renderFilterDropdownProps: RenderFilterDropdownProps = pick(props, ['tempFilteredValue', 'setTempFilteredValue', 'confirm', 'clear', 'close', 'filters']);
- const render = typeof renderFilterDropdown === 'function' ? renderFilterDropdown(renderFilterDropdownProps) : (
- <Dropdown.Menu>
- {Array.isArray(filters) &&
- filters.map((filter, index) => {
- const changeFn = (e: React.MouseEvent<HTMLLIElement>) => {
- const domEvent = e && e.nativeEvent;
- if (domEvent) {
- // Block this event to prevent the pop-up layer from closing
- domEvent.stopImmediatePropagation();
- // Prevent bubbling and default events to prevent label click events from triggering twice
- domEvent.stopPropagation();
- domEvent.preventDefault();
- }
- let values = [...filteredValue];
- const included = values.includes(filter.value);
- const idx = values.indexOf(filter.value);
- if (idx > -1) {
- values.splice(idx, 1);
- } else if (filterMultiple) {
- values.push(filter.value);
- } else {
- values = [filter.value];
- }
- return onSelect({
- value: filter.value,
- filteredValue: values,
- included: !included,
- domEvent,
- });
- };
- const checked = filteredValue.includes(filter.value);
- const { text } = filter;
- const { value } = filter;
- const key = `${level}_${index}`;
- const dropdownItem =
- typeof renderFilterDropdownItem === 'function' ?
- renderFilterDropdownItem({
- onChange: changeFn,
- filterMultiple,
- value,
- text,
- checked,
- filteredValue,
- level,
- }) :
- null;
- let item =
- dropdownItem && React.isValidElement(dropdownItem) ? (
- React.cloneElement(dropdownItem, { key })
- ) : (
- <Dropdown.Item key={key} onClick={changeFn}>
- {filterMultiple ? (
- <Checkbox checked={checked}>{text}</Checkbox>
- ) : (
- <Radio checked={checked}>{text}</Radio>
- )}
- </Dropdown.Item>
- );
- if (Array.isArray(filter.children) && filter.children.length) {
- const childrenDropdownProps = {
- ...props,
- filters: filter.children,
- trigger: 'hover' as const,
- position: 'right' as const,
- };
- delete childrenDropdownProps.filterDropdownVisible;
- item = renderDropdown(childrenDropdownProps, item, level + 1);
- }
- return item;
- })}
- </Dropdown.Menu>
- );
- const dropdownProps: DropdownProps = {
- ...props,
- onVisibleChange: (visible: boolean) => onFilterDropdownVisibleChange(visible),
- trigger,
- position,
- render,
- };
- if (filterDropdownVisible != null) {
- dropdownProps.visible = filterDropdownVisible;
- }
- return (
- <Dropdown {...dropdownProps} key={`Dropdown_level_${level}`} className={`${cssClasses.PREFIX}-column-filter-dropdown`}>
- {nestedElem}
- </Dropdown>
- );
- }
- export default function ColumnFilter(props: ColumnFilterProps = {}): React.ReactElement {
- const {
- prefixCls = cssClasses.PREFIX,
- filteredValue,
- filterIcon = 'filter',
- filterDropdownProps,
- onSelect,
- filterDropdownVisible,
- renderFilterDropdown,
- onFilterDropdownVisibleChange
- } = props;
- let { filterDropdown = null } = props;
-
- // custom filter related status
- const isFilterDropdownVisibleControlled = typeof filterDropdownVisible !== 'undefined';
- const isCustomFilterDropdown = typeof renderFilterDropdown === 'function';
- const isCustomDropdownVisible = !isFilterDropdownVisibleControlled && isCustomFilterDropdown;
- const [tempFilteredValue, setTempFilteredValue] = useState<any[]>(filteredValue);
- const dropdownVisibleInitValue = isCustomDropdownVisible ? false : filterDropdownVisible;
- const [dropdownVisible, setDropdownVisible] = useState<boolean | undefined>(dropdownVisibleInitValue);
- useEffect(() => {
- if (typeof filterDropdownVisible !== 'undefined') {
- setDropdownVisible(filterDropdownVisible);
- }
- }, [filterDropdownVisible]);
- useEffect(() => {
- setTempFilteredValue(filteredValue);
- }, [filteredValue]);
- const confirm: RenderFilterDropdownProps['confirm'] = (props = {}) => {
- const newFilteredValue = props?.filteredValue || tempFilteredValue;
- if (!isEqual(newFilteredValue, filteredValue)) {
- onSelect({ filteredValue: newFilteredValue });
- }
- if (props.closeDropdown) {
- setDropdownVisible(false);
- }
- };
- const clear: RenderFilterDropdownProps['clear'] = (props: { closeDropdown?: boolean } = {}) => {
- setTempFilteredValue([]);
- onSelect({ filteredValue: [] });
- if (props.closeDropdown) {
- setDropdownVisible(false);
- }
- };
- const close: RenderFilterDropdownProps['close'] = () => {
- setDropdownVisible(false);
- };
- const handleFilterDropdownVisibleChange = (visible: boolean) => {
- if (isCustomDropdownVisible) {
- setDropdownVisible(visible);
- }
- onFilterDropdownVisibleChange(visible);
- };
- const renderFilterDropdownProps: RenderFilterDropdownProps = {
- tempFilteredValue,
- setTempFilteredValue,
- confirm,
- clear,
- close
- };
- const finalCls = cls(`${prefixCls}-column-filter`, {
- on: Array.isArray(filteredValue) && filteredValue.length,
- });
- let iconElem;
- if (typeof filterIcon === 'function') {
- iconElem = filterIcon(Array.isArray(filteredValue) && filteredValue.length > 0);
- } else if (isValidElement(filterIcon)) {
- iconElem = filterIcon;
- } else {
- iconElem = (
- <div className={finalCls}>
- {'\u200b'/* ZWSP(zero-width space) */}
- <IconFilter
- role="button"
- aria-label="Filter data with this column"
- aria-haspopup="listbox"
- tabIndex={-1}
- size="default"
- />
- </div>
- );
- }
- const renderProps = {
- ...props,
- ...filterDropdownProps,
- ...renderFilterDropdownProps,
- filterDropdownVisible: isFilterDropdownVisibleControlled ? filterDropdownVisible : dropdownVisible,
- onFilterDropdownVisibleChange: handleFilterDropdownVisibleChange,
- };
- filterDropdown = React.isValidElement<ColumnFilterProps>(filterDropdown) ?
- filterDropdown :
- renderDropdown(renderProps, iconElem);
- return filterDropdown;
- }
- export interface ColumnFilterProps extends Omit<RenderDropdownProps, keyof RenderFilterDropdownProps> {
- prefixCls?: string;
- filteredValue?: any[];
- filterIcon?: FilterIcon;
- filterDropdown?: React.ReactElement;
- filterDropdownProps?: FilterDropdownProps;
- filters?: Filter[]
- }
- export interface RenderDropdownProps extends FilterDropdownProps, RenderFilterDropdownProps {
- filterMultiple?: boolean;
- filters?: Filter[];
- filteredValue?: any[];
- filterDropdownVisible?: boolean;
- onSelect?: (data: OnSelectData) => void;
- onFilterDropdownVisibleChange?: OnFilterDropdownVisibleChange;
- renderFilterDropdown?: (props?: RenderFilterDropdownProps) => React.ReactNode;
- renderFilterDropdownItem?: RenderFilterDropdownItem
- }
- export interface FilterDropdownProps extends Omit<DropdownProps, 'render' | 'onVisibleChange'> {}
- export interface OnSelectData {
- value?: any;
- /** only this value is used now */
- filteredValue: any;
- included?: boolean;
- domEvent?: React.MouseEvent<HTMLElement>
- }
- export interface RenderFilterDropdownProps {
- /** temporary filteredValue */
- tempFilteredValue: any[];
- /** set temporary filteredValue */
- setTempFilteredValue: (tempFilteredValue: any[]) => void;
- /** set tempFilteredValue to filteredValue. You can also pass filteredValue to directly set the filteredValue */
- confirm: (props?: { closeDropdown?: boolean; filteredValue?: any[] }) => void;
- /** clear tempFilteredValue and filteredValue */
- clear: (props?: { closeDropdown?: boolean }) => void;
- /** close dropdown */
- close: () => void;
- /** column filters */
- filters?: RenderDropdownProps['filters']
- }
|