import { useTheme } from '@emotion/react'; import styled from '@emotion/styled'; import { autoUpdate, flip, offset, size, useFloating, } from '@floating-ui/react'; import React, { useCallback, useRef, useState } from 'react'; import { createPortal } from 'react-dom'; import { AppTooltip, MenuItem, MenuItemSelect } from 'twenty-ui'; import { ReadonlyDeep } from 'type-fest'; import { useDebouncedCallback } from 'use-debounce'; import { SelectOption } from '@/spreadsheet-import/types'; import { DropdownMenu } from '@/ui/layout/dropdown/components/DropdownMenu'; import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer'; import { DropdownMenuSearchInput } from '@/ui/layout/dropdown/components/DropdownMenuSearchInput'; import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownMenuSeparator'; import { OverlayContainer } from '@/ui/layout/overlay/components/OverlayContainer'; import { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside'; import { useUpdateEffect } from '~/hooks/useUpdateEffect'; const StyledFloatingDropdown = styled.div` z-index: ${({ theme }) => theme.lastLayerZIndex}; `; interface MatchColumnSelectProps { onChange: (value: ReadonlyDeep | null) => void; value?: ReadonlyDeep; options: readonly ReadonlyDeep[]; placeholder?: string; name?: string; } export const MatchColumnSelect = ({ onChange, value, options: initialOptions, placeholder, }: MatchColumnSelectProps) => { 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.toLowerCase().includes(text.toLowerCase()), ), ); }, [initialOptions], ); const debouncedHandleSearchFilter = useDebouncedCallback( 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); }, listenerId: 'match-column-select', }); 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, )} ); };