import React, { useRef, useCallback, ChangeEvent } from 'react'; import Input from '../input'; import Checkbox from '../checkbox'; import useReducer from 'utils/useReducerHelper'; import useRotate from 'styles/animation/useRotate'; import { animated } from 'react-spring'; import { ClearFilter, CollapseTransition, SelectedItemTransition } from './helpers'; import * as S from './style'; import { ISelect, ISelectInput } from './types'; import { useClickOutSide } from 'utils/hooks'; const Down = animated(S.DownIcon); export const Select = function({ options, onChange, value, width, filter = false, checkbox = false, checkAllLabel = '全部帐号', multiple, disabled, placeholder, defaultValue, noFocus, ...rest }: ISelect) { if (value && !Array.isArray(value) && multiple) { throw new Error('多选模式的Select指定的value必须是数组'); } const [state, dispatch] = useReducer({ isOpen: false, checkAll: false, setDefault: false, selected: '', multiSelected: [], selectedIndex: -1, filterInput: '', }); let controlledValue = false; const onFilterFocusBlur = ClearFilter(filter, dispatch); if ('value' in arguments[0]) { controlledValue = true; if (!multiple) { const find = options.findIndex(o => o.value === value); if (~find && state.selected !== options[find].label) { dispatch({ filterInput: options[find].label, selected: options[find].label, selectedIndex: find }); } } else if (state.multiSelected.join() !== (value as any[]).join()) { dispatch({ multiSelected: value }); } } if (!state.setDefault && 'defaultValue' in arguments[0]) { if (!multiple) { const find = options.findIndex(o => o.value === defaultValue); if (~find && state.selected !== options[find].label) { dispatch({ selected: options[find].label, setDefault: true, selectedIndex: find }); } } else { dispatch({ multiSelected: defaultValue, setDefault: true }); } } const multiRef: any = useRef(); const inputRef: any = useRef(); const onClick = useCallback(() => { !disabled && dispatch({ isOpen: multiple || !state.isOpen }); setTimeout(() => { if (multiple && multiRef.current && multiRef.current.input) { multiRef.current.input.focus(); } }, 0); }, [state.isOpen, disabled, multiple]); const rotate = useRotate(state.isOpen); const ref = useRef() as any; let filteredOptions: any = filter ? options.filter(o => o.label.includes(state.filterInput)) : options; if (filteredOptions.length === 0 && filter) { filteredOptions = ( 无匹配结果 ); } useClickOutSide(ref, () => { dispatch({ isOpen: false, filterInput: '' }); }); const height = Array.isArray(filteredOptions) && filteredOptions.length ? (filteredOptions.length + (checkbox ? 1 : 0)) * 36 + 12 : 'auto'; const collapse = CollapseTransition(state.isOpen + '_' + height, height); const multiSelectedItems = SelectedItemTransition(state.multiSelected); return ( { const { key } = e; if (key === 'Escape') { dispatch({ isOpen: false }); if (inputRef.current && inputRef.current.input) { inputRef.current.input.blur(); } e.preventDefault(); e.stopPropagation(); } if (multiple && key === 'Backspace' && !state.filterInput && state.multiSelected.length) { const multiSelected = [...state.multiSelected]; multiSelected.pop(); dispatch({ multiSelected }); onChange && onChange(multiSelected); setTimeout(() => { if (inputRef.current && inputRef.current.input) { inputRef.current.input.focus(); } }, 0); } }} > {multiple && state.multiSelected.length ? ( <> {multiSelectedItems.map(({ item: value, props }: any, index) => ( { e.preventDefault(); }} > {(options.find((o: any) => o.value === value) as any).label} { const multiSelected = JSON.parse(JSON.stringify(state.multiSelected)); multiSelected.splice(index, 1); dispatch({ multiSelected, filterInput: '' }); }} /> ))} ) => { dispatch({ filterInput: value }); }} onFocus={onFilterFocusBlur} /> { dispatch({ isOpen: false, multiSelected: [], checkAll: false }); onChange && onChange([]); e.stopPropagation(); }} /> ) : ( ) => { dispatch({ filterInput: value }); }} onFocus={onFilterFocusBlur} /> )} {!(multiple && state.multiSelected.length) && ( )} {collapse.map(({ item, key, props }) => { const show = JSON.parse(item.split('_')[0]); return ( show && ( {checkbox && ( 0 && state.multiSelected.length < filteredOptions.length } onChange={(e: boolean) => { if (e && Array.isArray(filteredOptions)) { dispatch({ multiSelected: filteredOptions.map(e => e.value), isOpen: false, checkAll: true, }); onChange && onChange(filteredOptions.map(e => e.value)); } else { dispatch({ multiSelected: [], checkAll: false }); onChange && onChange([]); } }} > {checkAllLabel} )} {Array.isArray(filteredOptions) ? filteredOptions.map((o, index) => ( { const payload: any = { multiSelected: JSON.parse(JSON.stringify(state.multiSelected)), }; if (multiple) { const mIndex = state.multiSelected.indexOf(o.value); if (~mIndex) { payload.multiSelected.splice(mIndex, 1); } else { payload.multiSelected.push(o.value); } payload.filterInput = ''; onChange && onChange(payload.multiSelected); } else { payload.isOpen = false; payload.selected = o.label; payload.selectedIndex = index; onChange && onChange(o.value); } if (!controlledValue) { dispatch(payload); } !controlledValue && filter && !multiple && dispatch({ filterInput: o.label }); }} > {multiple && !checkbox && state.multiSelected.includes(o.value) && ( )} {checkbox ? ( {o.label} ) : ( o.label )} )) : filteredOptions} ) ); })} ); }; Select.defaultProps = { width: 158, }; Select.SelectInput = SelectInput; export default Select; export function SelectInput({ input, select }: ISelectInput) { return ( ); }