Antd Select组件 DropdownRender扩展

先上效果图~

 

import React, {
  useState,
  useCallback,
  useMemo,
  useEffect,
  useRef,
} from 'react';
import {
  Select,
  SelectProps,
  Space,
  Checkbox,
  Input,
  Empty,
  Tooltip,
} from 'antd';
import { debounce, isArray, isEmpty, isFunction } from 'lodash-es';
import classnames from 'classnames';
import { CheckboxChangeEvent } from 'antd/lib/checkbox';
import { useIntl } from 'umi';


type Key = string | number;

type OptionType = {
  label: string | React.ReactNode;
  value: Key;
};

type TypeOptMap = {
  [key: string]: OptionType;
};

type CustomTagProps = {
  label: React.ReactNode;
  value: any;
  disabled?: boolean;
  onClose?: (event?: React.MouseEvent<HTMLElement, MouseEvent>) => void;
  closable?: boolean;
};

type PluralSelectProps = Omit<
  SelectProps,
  'onChange' | 'options' | 'value' | 'mode'
> & {
  onChange?: (value: Key[]) => void;
  options?: OptionType[];
  value?: Key[];
  showSearch?: boolean;
};

const transformMap = (params: OptionType[]) => {
  const result: { [key: string]: OptionType } = {};
  if (isEmpty(params) || !isArray(params)) return result;
  params.forEach((item) => {
    result[item.value] = item;
  });
  return result;
};

const labelVisible = (searchValue: string, label: string) => {
  return !searchValue || (searchValue && label.indexOf(searchValue) > -1);
};

const PluralSelect: React.FC<PluralSelectProps> = ({
  options,
  maxTagCount = 'responsive',
  value = [],
  onChange,
  placeholder,
  className,
  dropdownClassName,
  showSearch = true,
  ...props
}) => {
  const { formatMessage } = useIntl();
  const [currentValues, setCurrentValues] = useState<Key[]>(value);
  const [searchValue, setSearchValue] = useState<string>('');
  const searchInputRef: any = useRef();
  const valueCacheRef = useRef<string>();

  const optMap: TypeOptMap = useMemo(() => {
    return transformMap(options || []) || {};
  }, [options]);

  const allValues = useMemo<Key[]>(() => {
    return options?.map((opt) => opt?.value) || [];
  }, [options]);

  // 半选状态
  const indeterminate = useMemo((): boolean => {
    return (
      Boolean(currentValues?.length) &&
      isArray(options) &&
      currentValues?.length < options.length
    );
  }, [currentValues, options]);

  // 全选状态
  const checkedAll = useMemo((): boolean => {
    return Boolean(
      options?.length && currentValues?.length === options?.length,
    );
  }, [currentValues, options]);

  // 选择checkbox
  const handleChange = useCallback(
    (event: CheckboxChangeEvent) => {
      const {
        target: { value, checked },
      } = event;
      let mergedValues: Key[] = [];
      if (checked) {
        mergedValues = [...currentValues, value];
        setCurrentValues(mergedValues);
      } else {
        mergedValues = currentValues.filter(
          (each) => String(each) !== String(value),
        );
        setCurrentValues(mergedValues);
      }
      isFunction(onChange) && onChange?.(mergedValues);
    },
    [currentValues, onChange],
  );

  // 输入搜索条件
  const handleInputKeyword = useCallback(() => {
    const value = searchInputRef.current?.input?.value || '';
    setSearchValue(value);
  }, []);

  // 搜索(防抖)
  const debounceSearch = useMemo(() => debounce(handleInputKeyword, 200), [
    handleInputKeyword,
  ]);

  // 全选或者全不选
  const handleCheckAllChange = useCallback(
    (event: CheckboxChangeEvent) => {
      const {
        target: { checked },
      } = event;
      isFunction(onChange) && onChange?.(checked ? allValues : []);
      setCurrentValues(checked ? allValues : []);
    },
    [onChange, allValues],
  );

  // select 变化
  const handleSelectChange = useCallback(
    (values: Key[]) => {
      setCurrentValues(values);
      isFunction(onChange) && onChange?.(values);
    },
    [onChange],
  );

  useEffect(() => {
    // 当 value 发生变化时,更新 currentValues
    const currentValueStr = value ? JSON.stringify(value) : '';
    if (currentValueStr === valueCacheRef.current) return;
    valueCacheRef.current = currentValueStr;
    setCurrentValues(value);
  }, [value]);

  // 卸载时清空
  useEffect(() => {
    return () => {
      setSearchValue('');
    };
  }, []);

  const renderTitle = (title: string | React.ReactNode) => {
    if (!searchValue) {
      return <span>{title}</span>;
    }

    const strTitle = String(title);
    const index = strTitle.indexOf(searchValue);
    const beforeStr = strTitle.substring(0, index);
    const afterStr = strTitle.substring(index + searchValue.length);
    // 高亮搜索关键字
    const currentTitle =
      index > -1 ? (
        <>
          {beforeStr}
          <span className="highlight-value">{searchValue}</span>
          {afterStr}
        </>
      ) : (
        strTitle
      );

    return <span>{currentTitle}</span>;
  };

  const CheckboxOptions = () => {
    if (isEmpty(options) || !isArray(options))
      return <Empty image={Empty.PRESENTED_IMAGE_SIMPLE} />;
    return (
      <Space direction="vertical" className="content-inner-wapper">
        <Checkbox
          indeterminate={indeterminate}
          onChange={handleCheckAllChange}
          checked={checkedAll}
        >
          {formatMessage({ id: 'ACTION_SELECT_ALL' })}
        </Checkbox>
        <Checkbox.Group value={currentValues}>
          <Space direction="vertical">
            {isArray(options) &&
              options.map(
                ({ label, value }, idx) =>
                  labelVisible(searchValue, String(label)) && (
                    <Checkbox
                      onChange={handleChange}
                      value={value}
                      key={`${idx}_${value}`}
                    >
                      {renderTitle(label)}
                    </Checkbox>
                  ),
              )}
          </Space>
        </Checkbox.Group>
      </Space>
    );
  };

  const tagRender = (props: CustomTagProps) => {
    const { value } = props;
    const { label } = optMap[value];
    return (
      <Tooltip placement="bottom" title={label}>
        <span
          className="ant-select-selection-item"
          style={{ maxWidth: '80px' }}
        >
          {label}
        </span>
      </Tooltip>
    );
  };

  const dropdownRender = () => (
    <div className="customer-select-wrapper">
      {showSearch && (
        <Input
          ref={searchInputRef}
          className="customer-select-input"
          placeholder={formatMessage({ id: 'SEARCH' })}
          onChange={debounceSearch}
          onPressEnter={debounceSearch}
        />
      )}
      <CheckboxOptions />
    </div>
  );

  return (
    <Select
      placeholder={placeholder || formatMessage({ id: 'PLEASE_SELECT' })}
      tagRender={tagRender}
      maxTagCount={maxTagCount}
      dropdownRender={dropdownRender}
      showArrow
      allowClear
      dropdownMatchSelectWidth
      {...props}
      mode="multiple"
      showSearch={false}
      value={currentValues}
      onChange={handleSelectChange}
      className={classnames(['checkbox-selector', className])}
      dropdownClassName={classnames([
        'checkbox-selector-dropdown',
        dropdownClassName,
      ])}
    />
  );
};

export default PluralSelect;

  • 7
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

superTiger_y

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值