import React, { CSSProperties, useEffect, useMemo, useRef, useState } from 'react' import { NormalSizes } from '../utils/prop-types' import useTheme from '../use-theme' import useClickAway from '../utils/use-click-away' import useCurrentState from '../utils/use-current-state' import { pickChildByProps } from '../utils/collections' import SelectIcon from './select-icon' import SelectOption from './select-option' import SelectDropdown from './select-dropdown' import SelectMultipleValue from './select-multiple-value' import Grid from '../grid' import { SelectContext, SelectConfig } from './select-context' import { getSizes } from './styles' import Ellipsis from '../shared/ellipsis' interface Props { disabled?: boolean size?: NormalSizes value?: string | string[] initialValue?: string | string[] placeholder?: React.ReactNode | string icon?: React.ComponentType onChange?: (value: string | string[]) => void pure?: boolean multiple?: boolean clearable?: boolean className?: string width?: string dropdownClassName?: string dropdownStyle?: CSSProperties disableMatchWidth?: boolean getPopupContainer?: () => HTMLElement | null } const defaultProps = { disabled: false, size: 'medium' as NormalSizes, icon: SelectIcon as React.ComponentType, pure: false, multiple: false, clearable: true, width: 'initial', className: '', disableMatchWidth: false, } type NativeAttrs = Omit, keyof Props> export type SelectProps = Props & typeof defaultProps & NativeAttrs const Select: React.FC> = ({ children, size, disabled, initialValue: init, value: customValue, icon: Icon, onChange, pure, multiple, clearable, placeholder, width, className, dropdownClassName, dropdownStyle, disableMatchWidth, getPopupContainer, ...props }) => { const theme = useTheme() const ref = useRef(null) const [visible, setVisible] = useState(false) const [value, setValue, valueRef] = useCurrentState( () => { if (!multiple) return init if (Array.isArray(init)) return init return typeof init === 'undefined' ? [] : [init] }, ) const isEmpty = useMemo(() => { if (!Array.isArray(value)) return !value return value.length === 0 }, [value]) const sizes = useMemo(() => getSizes(theme, size), [theme, size]) const updateVisible = (next: boolean) => setVisible(next) const updateValue = (next: string) => { setValue(last => { if (!Array.isArray(last)) return next if (!last.includes(next)) return [...last, next] return last.filter(item => item !== next) }) onChange && onChange(valueRef.current as string | string[]) if (!multiple) { setVisible(false) } } const initialValue: SelectConfig = useMemo( () => ({ value, visible, updateValue, updateVisible, size, ref, disableAll: disabled, }), [visible, size, disabled, ref, value, multiple], ) const clickHandler = (event: React.MouseEvent) => { event.stopPropagation() event.nativeEvent.stopImmediatePropagation() event.preventDefault() if (disabled) return setVisible(!visible) } useClickAway(ref, () => setVisible(false)) useEffect(() => { if (customValue === undefined) return setValue(customValue) }, [customValue]) const selectedChild = useMemo(() => { const [, optionChildren] = pickChildByProps(children, 'value', value) return React.Children.map(optionChildren, child => { if (!React.isValidElement(child)) return null const el = React.cloneElement(child, { preventAllEvents: true }) if (!multiple) return el return ( updateValue(child.props.value) : null}> {el} ) }) }, [value, children, multiple]) return (
{isEmpty && ( {placeholder} )} {value && !multiple && {selectedChild}} {value && multiple && {selectedChild}} {children} {!pure && (
)}
) } type SelectComponent

= React.FC

& { Option: typeof SelectOption } type ComponentProps = Partial & Omit & NativeAttrs ;(Select as SelectComponent).defaultProps = defaultProps export default Select as SelectComponent