import React, { useCallback, useRef, useState } from 'react'; import { createPortal } from 'react-dom'; import { useTheme } from '@emotion/react'; import styled from '@emotion/styled'; import { autoUpdate, flip, offset, size, useFloating, } from '@floating-ui/react'; import debounce from 'lodash.debounce'; import { ReadonlyDeep } from 'type-fest'; import type { SelectOption } from '@/spreadsheet-import/types'; import { DropdownMenuSearchInput } from '@/ui/dropdown/components/DropdownMenuSearchInput'; import { StyledDropdownMenu } from '@/ui/dropdown/components/StyledDropdownMenu'; import { StyledDropdownMenuItemsContainer } from '@/ui/dropdown/components/StyledDropdownMenuItemsContainer'; import { StyledDropdownMenuSeparator } from '@/ui/dropdown/components/StyledDropdownMenuSeparator'; import { MenuItem } from '@/ui/menu-item/components/MenuItem'; import { MenuItemSelect } from '@/ui/menu-item/components/MenuItemSelect'; import { AppTooltip } from '@/ui/tooltip/AppTooltip'; import { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside'; import { useUpdateEffect } from '~/hooks/useUpdateEffect'; const StyledFloatingDropdown = styled.div` z-index: ${({ theme }) => theme.lastLayerZIndex}; `; interface Props { onChange: (value: ReadonlyDeep | null) => void; value?: ReadonlyDeep; options: readonly ReadonlyDeep[]; placeholder?: string; name?: string; } export const MatchColumnSelect = ({ onChange, value, options: initialOptions, placeholder, }: Props) => { const theme = useTheme(); const dropdownContainerRef = useRef(null); const [isOpen, setIsOpen] = useState(false); const [searchFilter, setSearchFilter] = useState(''); const [options, setOptions] = useState(initialOptions); const { refs, floatingStyles } = useFloating({ strategy: 'absolute', middleware: [ offset(() => { return parseInt(theme.spacing(2), 10); }), flip(), size(), ], whileElementsMounted: autoUpdate, open: isOpen, placement: 'bottom-start', }); const handleSearchFilterChange = useCallback( (text: string) => { setOptions( initialOptions.filter((option) => option.label.includes(text)), ); }, [initialOptions], ); const debouncedHandleSearchFilter = debounce(handleSearchFilterChange, 100, { leading: true, }); const handleFilterChange = (event: React.ChangeEvent) => { const value = event.currentTarget.value; setSearchFilter(value); debouncedHandleSearchFilter(value); }; const handleDropdownItemClick = () => { setIsOpen(true); }; const handleChange = (option: ReadonlyDeep) => { onChange(option); setIsOpen(false); }; useListenClickOutside({ refs: [dropdownContainerRef], callback: () => { setIsOpen(false); }, }); useUpdateEffect(() => { setOptions(initialOptions); }, [initialOptions]); return ( <>
{isOpen && createPortal( {options?.map((option) => ( <> handleChange(option)} disabled={ option.disabled && value?.value !== option.value } LeftIcon={option?.icon} text={option.label} /> {option.disabled && value?.value !== option.value && createPortal( , document.body, )} ))} {options?.length === 0 && } , document.body, )} ); };