import type { FlexProps } from '@chakra-ui/react'; import { Box, Button, type ButtonProps, Checkbox, Flex, Input, Menu, MenuButton, MenuItem, type MenuItemProps, MenuList, useDisclosure } from '@chakra-ui/react'; import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import MyTag from '../Tag/index'; import MyIcon from '../Icon'; import MyAvatar from '../Avatar'; import { useTranslation } from 'next-i18next'; import type { useScrollPagination } from '../../../hooks/useScrollPagination'; import MyDivider from '../MyDivider'; import { shadowLight } from '../../../styles/theme'; import { isArray } from 'lodash'; const menuItemStyles: MenuItemProps = { borderRadius: 'sm', py: 2, display: 'flex', alignItems: 'center', _hover: { backgroundColor: 'myGray.100' }, _notLast: { mb: 2 } }; export type SelectProps = { list: { icon?: string; label: string | React.ReactNode; value: T; }[]; value: T[]; isSelectAll: boolean; setIsSelectAll?: React.Dispatch>; placeholder?: string; itemWrap?: boolean; onSelect: (val: T[]) => void; closeable?: boolean; isDisabled?: boolean; ScrollData?: ReturnType['ScrollData']; formLabel?: string; formLabelFontSize?: string; inputValue?: string; setInputValue?: (val: string) => void; tagStyle?: FlexProps; } & Omit; const MultipleSelect = ({ value = [], placeholder, list = [], onSelect, closeable = false, itemWrap = true, ScrollData, isSelectAll, setIsSelectAll, isDisabled = false, formLabel, formLabelFontSize = 'sm', inputValue, setInputValue, tagStyle, ...props }: SelectProps) => { const ref = useRef(null); const SearchInputRef = useRef(null); const tagsContainerRef = useRef(null); const { t } = useTranslation(); const { isOpen, onOpen, onClose } = useDisclosure(); const canInput = setInputValue !== undefined; type SelectedItemType = { icon?: string; label: string | React.ReactNode; value: T; }; const [visibleItems, setVisibleItems] = useState([]); const [overflowItems, setOverflowItems] = useState([]); const selectedItems = useMemo(() => { if (!value || !isArray(value)) return []; return value.map((val) => { const listItem = list.find((item) => item.value === val); return listItem || { value: val, label: String(val) }; }); }, [value, list]); const handleKeyDown = useCallback( (e: React.KeyboardEvent) => { if (e.key === 'Backspace' && (!inputValue || inputValue === '')) { const newValue = [...value]; newValue.pop(); onSelect(newValue); } }, [inputValue, value, isSelectAll, onSelect] ); useEffect(() => { if (!isOpen) { setInputValue?.(''); } }, [isOpen]); useEffect(() => { const getWidth = (w: any) => typeof w === 'number' ? w : typeof w === 'string' ? parseInt(w) : 0; const totalWidth = getWidth(props.w) || 200; const tagWidth = getWidth(tagStyle?.w) || 60; const formLabelWidth = formLabel ? formLabel.length * 8 + 20 : 0; const availableWidth = totalWidth - formLabelWidth - 40; const overflowWidth = 30; if (availableWidth <= 0) { setVisibleItems(selectedItems.length > 0 ? [selectedItems[0]] : []); setOverflowItems(selectedItems.slice(1)); return; } const { count } = selectedItems.reduce( (acc, item, i) => { const remain = selectedItems.length - i - 1; const needOverflow = remain > 0 ? overflowWidth : 0; if (acc.used + tagWidth + needOverflow <= availableWidth) { return { used: acc.used + tagWidth, count: i + 1 }; } return acc; }, { used: 0, count: 0 } ); setVisibleItems(selectedItems.slice(0, count)); setOverflowItems(selectedItems.slice(count)); }, [selectedItems, isOpen, props.w, tagStyle, formLabel]); const onclickItem = useCallback( (val: T) => { if (isSelectAll) { onSelect(list.map((item) => item.value).filter((i) => i !== val)); setIsSelectAll?.(false); return; } if (value.includes(val)) { onSelect(value.filter((i) => i !== val)); } else { onSelect([...value, val]); } }, [value, isSelectAll, onSelect, setIsSelectAll] ); const onSelectAll = useCallback(() => { const hasSelected = isSelectAll || value.length > 0; onSelect(hasSelected ? [] : list.map((item) => item.value)); setIsSelectAll?.((state) => !state); }, [value, list, setIsSelectAll, onSelect]); const ListRender = useMemo(() => { return ( <> {list.map((item, i) => { const isSelected = isSelectAll || value.includes(item.value); return ( { e.stopPropagation(); e.preventDefault(); onclickItem(item.value); }} whiteSpace={'pre-wrap'} fontSize={'sm'} gap={2} {...menuItemStyles} > {item.icon && } {item.label} ); })} ); }, [value, list, isSelectAll]); return ( {formLabel && ( {formLabel} )} {value.length === 0 && placeholder ? ( {placeholder} ) : ( {(!isOpen || !canInput) && (isSelectAll ? ( {t('common:All')} ) : ( <> {visibleItems.map((item, i) => ( {item.label} {closeable && ( { e.stopPropagation(); e.preventDefault(); onclickItem(item.value); }} /> )} ))} {overflowItems.length > 0 && ( +{overflowItems.length} )} ))} {canInput && isOpen && ( setInputValue?.(e.target.value)} onKeyDown={handleKeyDown} ref={SearchInputRef} autoFocus onBlur={() => { setTimeout(() => { SearchInputRef?.current?.focus(); }, 0); }} h={6} variant={'unstyled'} border={'none'} /> )} )} { e.stopPropagation(); e.preventDefault(); onSelectAll(); }} whiteSpace={'pre-wrap'} fontSize={'sm'} gap={2} mb={1} {...menuItemStyles} > {t('common:All')} {ScrollData ? {ListRender} : ListRender} ); }; export default MultipleSelect; export const useMultipleSelect = (defaultValue: T[] = [], defaultSelectAll = false) => { const [value, setValue] = useState(defaultValue); const [isSelectAll, setIsSelectAll] = useState(defaultSelectAll); return { value, setValue, isSelectAll, setIsSelectAll }; };