import React, { useRef, useCallback, useState, useMemo, useEffect } from 'react'; import { Button, useDisclosure, Box, Flex, useOutsideClick, Checkbox, css, Menu, MenuButton, MenuList } from '@chakra-ui/react'; import { ListItemType, MultipleArraySelectProps, MultipleSelectProps } from './type'; import EmptyTip from '../EmptyTip'; import { useTranslation } from 'next-i18next'; import MyIcon from '../../common/Icon'; export const MultipleRowSelect = ({ placeholder, label, value = [], list, emptyTip, maxH = 300, onSelect, ButtonProps, changeOnEverySelect = false, rowMinWidth = 'autp' }: MultipleSelectProps & { rowMinWidth?: string; }) => { const { t } = useTranslation(); const ButtonRef = useRef(null); const { isOpen, onOpen, onClose } = useDisclosure(); const [cloneValue, setCloneValue] = useState(value); const MenuRef = useRef<(HTMLDivElement | null)[]>([]); const SelectedItemRef = useRef<(HTMLDivElement | null)[]>([]); useEffect(() => { if (isOpen) { for (let i = 0; i < MenuRef.current.length; i++) { const menu = MenuRef.current[i]; const selectedItem = SelectedItemRef.current[i]; if (menu && selectedItem) { menu.scrollTop = selectedItem.offsetTop - menu.offsetTop - 100; } } } }, [isOpen]); const minWidth = `${MenuRef.current?.[0]?.offsetWidth || 0}px`; const RenderList = useCallback( ({ index, list }: { index: number; list: MultipleSelectProps['list'] }) => { const selectedValue = cloneValue[index]; const selectedIndex = list.findIndex((item) => item.value === selectedValue); const children = list[selectedIndex]?.children || []; // Store current scroll position before update const currentScrollTop = MenuRef.current[index]?.scrollTop; // Use useEffect to restore scroll position after render useEffect(() => { if (currentScrollTop !== undefined && MenuRef.current[index]) { MenuRef.current[index]!.scrollTop = currentScrollTop; } }, [cloneValue, currentScrollTop]); return ( <> { MenuRef.current[index] = ref; }} className="nowheel" flex={'1 0 auto'} px={2} borderLeft={index !== 0 ? 'base' : 'none'} minW={index !== 0 ? minWidth : rowMinWidth} maxH={`${maxH}px`} overflowY={'auto'} whiteSpace={'nowrap'} > {list.map((item) => { const hasChildren = item.children && item.children.length > 0; return ( { if (item.value === selectedValue) { SelectedItemRef.current[index] = ref; } }} py={1.5} _notLast={{ mb: 1 }} cursor={'pointer'} px={1.5} borderRadius={'sm'} _hover={{ bg: 'primary.50' }} onClick={() => { const newValue = [...cloneValue]; if (item.value === selectedValue) { for (let i = index; i < newValue.length; i++) { newValue[i] = undefined; } setCloneValue(newValue); onSelect(newValue); } else { newValue[index] = item.value; setCloneValue(newValue); if (changeOnEverySelect || !hasChildren) { onSelect(newValue); } if (!hasChildren) { onClose(); } } }} {...(item.value === selectedValue ? { bg: 'primary.50', color: 'primary.600' } : {})} > {item.label} ); })} {list.length === 0 && ( )} {children.length > 0 && } ); }, [cloneValue] ); const onOpenSelect = useCallback(() => { setCloneValue(Array.isArray(value) ? value : []); onOpen(); }, [value, onOpen]); return ( } variant={'whitePrimaryOutline'} size={'lg'} 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' } : {})} {...ButtonProps} > {label ?? placeholder} { const w = ButtonRef.current?.clientWidth; if (w) { return `${w}px !important`; } const width = ButtonProps?.width; return Array.isArray(width) ? width.map((item) => `${item} !important`) : `${width} !important`; })()} w={'auto'} 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'} display={'flex'} userSelect={'none'} > ); }; export const MultipleRowArraySelect = ({ placeholder, label, value = [], list, emptyTip, maxH = 300, onSelect, popDirection = 'bottom', ButtonProps }: MultipleArraySelectProps) => { const { t } = useTranslation(); const ref = useRef(null); const { isOpen, onOpen, onClose } = useDisclosure(); const [navigationPath, setNavigationPath] = useState([]); // Make sure the value is an array of arrays const formatValue = useMemo(() => { return Array.isArray(value) ? value.filter((v) => Array.isArray(v)) : []; }, [value]); // Close when clicking outside useOutsideClick({ ref: ref, handler: onClose }); const onChange = useCallback( (val: any[][]) => { // Filter invalid value const validList = val.filter((item) => { const listItem = list.find((v) => v.value === item[0]); if (!listItem) return false; return listItem.children?.some((v) => v.value === item[1]); }); onSelect(validList); }, [onSelect] ); const RenderList = useCallback( ({ index, list }: { index: number; list: MultipleSelectProps['list'] }) => { const currentNavValue = navigationPath[index]; const selectedIndex = list.findIndex((item) => item.value === currentNavValue); const children = list[selectedIndex]?.children || []; const hasChildren = list.some((item) => item.children && item.children?.length > 0); const handleSelect = (item: ListItemType) => { // Has children, set parent value if (hasChildren) { const newPath = [...navigationPath]; newPath[index] = item.value; setNavigationPath(newPath); } else { const parentValue = navigationPath[0]; const newValues = [...formatValue]; const newValue = [parentValue, item.value]; if (newValues.some((v) => v[0] === parentValue && v[1] === item.value)) { onChange(newValues.filter((v) => !(v[0] === parentValue && v[1] === item.value))); } else { onChange([...newValues, newValue]); } } }; return ( <> {list.map((item) => { const isSelected = item.value === currentNavValue; const showCheckbox = !hasChildren; const isChecked = showCheckbox && formatValue.some((v) => v[1] === item.value && v[0] === navigationPath[0]); return ( handleSelect(item)} {...(isSelected ? { color: 'primary.600' } : {})} > {showCheckbox && } {item.label} ); })} {list.length === 0 && ( )} {children.length > 0 && } ); }, [navigationPath, formatValue, onSelect] ); const onOpenSelect = useCallback(() => { setNavigationPath([]); onOpen(); }, []); return ( {isOpen && ( )} ); }; export default React.memo(MultipleRowSelect);