import React, { useRef, forwardRef, useMemo, useEffect, useImperativeHandle, ForwardedRef, useState } from 'react'; import { Menu, MenuList, MenuItem, Button, useDisclosure, MenuButton, Box, css, Flex, Input } from '@chakra-ui/react'; import type { ButtonProps, MenuItemProps } from '@chakra-ui/react'; import MyIcon from '../Icon'; import { useRequest2 } from '../../../hooks/useRequest'; import MyDivider from '../MyDivider'; import { useScrollPagination } from '../../../hooks/useScrollPagination'; /** 选择组件 Props 类型 * value: 选中的值 * placeholder: 占位符 * list: 列表数据 * isLoading: 是否加载中 * ScrollData: 分页滚动数据控制器 [useScrollPagination] 的返回值 * */ export type SelectProps = ButtonProps & { value?: T; placeholder?: string; isSearch?: boolean; list: { alias?: string; icon?: string; label: string | React.ReactNode; description?: string; value: T; showBorder?: boolean; }[]; isLoading?: boolean; onchange?: (val: T) => any | Promise; ScrollData?: ReturnType['ScrollData']; }; const MySelect = ( { placeholder, value, isSearch = false, width = '100%', list = [], onchange, isLoading = false, ScrollData, ...props }: SelectProps, ref: ForwardedRef<{ focus: () => void; }> ) => { const ButtonRef = useRef(null); const MenuListRef = useRef(null); const SelectedItemRef = useRef(null); const SearchInputRef = useRef(null); const menuItemStyles: MenuItemProps = { borderRadius: 'sm', py: 2, display: 'flex', alignItems: 'center', _hover: { backgroundColor: 'myGray.100' }, _notLast: { mb: 1 } }; const { isOpen, onOpen, onClose } = useDisclosure(); const selectItem = useMemo(() => list.find((item) => item.value === value), [list, value]); const [search, setSearch] = useState(''); const filterList = useMemo(() => { if (!isSearch || !search) { return list; } return list.filter((item) => { const text = `${item.label?.toString()}${item.alias}${item.value}`; const regx = new RegExp(search, 'gi'); return regx.test(text); }); }, [list, search, isSearch]); useImperativeHandle(ref, () => ({ focus() { onOpen(); } })); useEffect(() => { if (isOpen && MenuListRef.current && SelectedItemRef.current) { const menu = MenuListRef.current; const selectedItem = SelectedItemRef.current; menu.scrollTop = selectedItem.offsetTop - menu.offsetTop - 100; if (isSearch) { setSearch(''); } } }, [isSearch, isOpen]); const { runAsync: onChange, loading } = useRequest2((val: T) => onchange?.(val)); const ListRender = useMemo(() => { return ( <> {filterList.map((item, i) => ( { if (onChange && value !== item.value) { onChange(item.value); } }} whiteSpace={'pre-wrap'} fontSize={'sm'} display={'block'} > {item.icon && } {item.label} {item.description && ( {item.description} )} {item.showBorder && } ))} ); }, [filterList, value]); const isSelecting = loading || isLoading; return ( } variant={'whitePrimaryOutline'} size={'md'} fontSize={'sm'} textAlign={'left'} _active={{ transform: 'none' }} {...(isOpen ? { boxShadow: '0px 0px 0px 2.4px rgba(51, 112, 255, 0.15)', borderColor: 'primary.600', color: 'primary.700' } : {})} {...props} > {isSelecting && } {isSearch && isOpen ? ( setSearch(e.target.value)} placeholder={ selectItem?.alias || (typeof selectItem?.label === 'string' ? selectItem?.label : placeholder) } size={'sm'} w={'100%'} color={'myGray.700'} onBlur={() => { setTimeout(() => { SearchInputRef?.current?.focus(); }, 0); }} /> ) : ( <> {selectItem?.icon && } {selectItem?.alias || selectItem?.label || placeholder} )} { const w = ButtonRef.current?.clientWidth; if (w) { return `${w}px !important`; } return Array.isArray(width) ? width.map((item) => `${item} !important`) : `${width} !important`; })()} w={'auto'} px={'6px'} py={'6px'} border={'1px solid #fff'} boxShadow={ '0px 2px 4px rgba(161, 167, 179, 0.25), 0px 0px 1px rgba(121, 141, 159, 0.25);' } zIndex={99} maxH={'40vh'} overflowY={'auto'} > {ScrollData ? {ListRender} : ListRender} ); }; export default forwardRef(MySelect) as ( props: SelectProps & { ref?: React.Ref } ) => JSX.Element;