Files
twenty/front/src/modules/spreadsheet-import/components/MatchColumnSelect.tsx
brendanlaschke 54042a7d8f Fix drag to select in dropdowns, context menu and action bar (#1704)
- fix drag to select disable at many locations
2023-09-21 14:22:13 -07:00

166 lines
5.1 KiB
TypeScript

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<SelectOption> | null) => void;
value?: ReadonlyDeep<SelectOption>;
options: readonly ReadonlyDeep<SelectOption>[];
placeholder?: string;
name?: string;
}
export const MatchColumnSelect = ({
onChange,
value,
options: initialOptions,
placeholder,
}: Props) => {
const theme = useTheme();
const dropdownContainerRef = useRef<HTMLDivElement>(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<HTMLInputElement>) => {
const value = event.currentTarget.value;
setSearchFilter(value);
debouncedHandleSearchFilter(value);
};
const handleDropdownItemClick = () => {
setIsOpen(true);
};
const handleChange = (option: ReadonlyDeep<SelectOption>) => {
onChange(option);
setIsOpen(false);
};
useListenClickOutside({
refs: [dropdownContainerRef],
callback: () => {
setIsOpen(false);
},
});
useUpdateEffect(() => {
setOptions(initialOptions);
}, [initialOptions]);
return (
<>
<div ref={refs.setReference}>
<MenuItem
LeftIcon={value?.icon}
onClick={handleDropdownItemClick}
text={value?.label ?? placeholder ?? ''}
accent={value?.label ? 'default' : 'placeholder'}
/>
</div>
{isOpen &&
createPortal(
<StyledFloatingDropdown ref={refs.setFloating} style={floatingStyles}>
<StyledDropdownMenu
data-select-disable
ref={dropdownContainerRef}
width={refs.domReference.current?.clientWidth}
>
<DropdownMenuSearchInput
value={searchFilter}
onChange={handleFilterChange}
autoFocus
/>
<StyledDropdownMenuSeparator />
<StyledDropdownMenuItemsContainer hasMaxHeight>
{options?.map((option) => (
<>
<MenuItemSelect
key={option.label}
selected={value?.label === option.label}
onClick={() => handleChange(option)}
disabled={
option.disabled && value?.value !== option.value
}
LeftIcon={option?.icon}
text={option.label}
/>
{option.disabled &&
value?.value !== option.value &&
createPortal(
<AppTooltip
key={option.value}
anchorSelect={`#${option.value}`}
content="You are already importing this column."
place="right"
offset={-20}
/>,
document.body,
)}
</>
))}
{options?.length === 0 && <MenuItem text="No result" />}
</StyledDropdownMenuItemsContainer>
</StyledDropdownMenu>
</StyledFloatingDropdown>,
document.body,
)}
</>
);
};