Files
twenty_crm/packages/twenty-front/src/modules/ui/input/components/SelectInput.tsx
gitstart-app[bot] 6264d509bd Migrate to twenty-ui - navigation/menu-item (#8213)
This PR was created by [GitStart](https://gitstart.com/) to address the
requirements from this ticket:
[TWNTY-7536](https://clients.gitstart.com/twenty/5449/tickets/TWNTY-7536).

 --- 

### Description

Migrate all menu items components to twenty ui and update imports.

```typescript
MenuItem
MenuItemAvata
MenuItemCommand
MenuItemCommandHotKeys
MenuItemDraggable
MenuItemMultiSelect
MenuItemMultiSelectAvatar
MenuItemMultiSelectTag
MenuItemNavigate
MenuItemSelect
MenuItemSelectAvatar
MenuItemSelectColor
MenuItemSelectTag
MenuItemSuggestion
MenuItemToggle
```

\
Also migrate all other dependent components and utilities like
`Checkbox` & `Toggle`\
\
Fixes twentyhq/private-issues#82

---------

Co-authored-by: gitstart-twenty <gitstart-twenty@users.noreply.github.com>
Co-authored-by: gitstart-twenty <140154534+gitstart-twenty@users.noreply.github.com>
Co-authored-by: Lucas Bordeau <bordeau.lucas@gmail.com>
2024-11-07 16:51:39 +00:00

174 lines
4.9 KiB
TypeScript

import styled from '@emotion/styled';
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 { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
import { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside';
import { useTheme } from '@emotion/react';
import {
ReferenceType,
autoUpdate,
flip,
offset,
size,
useFloating,
} from '@floating-ui/react';
import { useEffect, useMemo, useRef, useState } from 'react';
import { Key } from 'ts-key-enum';
import { MenuItemSelectTag, TagColor, isDefined } from 'twenty-ui';
const StyledRelationPickerContainer = styled.div`
left: -1px;
position: absolute;
top: -1px;
z-index: ${({ theme }) => theme.lastLayerZIndex};
`;
interface SelectInputProps {
onOptionSelected: (selectedOption: SelectOption) => void;
options: SelectOption[];
onCancel?: () => void;
defaultOption?: SelectOption;
parentRef?: ReferenceType | null | undefined;
onFilterChange?: (filteredOptions: SelectOption[]) => void;
onClear?: () => void;
clearLabel?: string;
hotkeyScope: string;
}
export const SelectInput = ({
onOptionSelected,
onClear,
clearLabel,
options,
onCancel,
defaultOption,
parentRef,
onFilterChange,
hotkeyScope,
}: SelectInputProps) => {
const containerRef = useRef<HTMLDivElement>(null);
const theme = useTheme();
const [searchFilter, setSearchFilter] = useState('');
const [selectedOption, setSelectedOption] = useState<
SelectOption | undefined
>(defaultOption);
const optionsToSelect = useMemo(
() =>
options.filter((option) => {
return (
option.value !== selectedOption?.value &&
option.label.toLowerCase().includes(searchFilter.toLowerCase())
);
}) || [],
[options, searchFilter, selectedOption?.value],
);
const optionsInDropDown = useMemo(
() =>
selectedOption ? [selectedOption, ...optionsToSelect] : optionsToSelect,
[optionsToSelect, selectedOption],
);
const handleOptionChange = (option: SelectOption) => {
setSelectedOption(option);
onOptionSelected(option);
};
const { refs, floatingStyles } = useFloating({
elements: { reference: parentRef },
strategy: 'absolute',
middleware: [
offset(() => {
return parseInt(theme.spacing(2), 10);
}),
flip(),
size(),
],
whileElementsMounted: autoUpdate,
open: true,
placement: 'bottom-start',
});
useEffect(() => {
onFilterChange?.(optionsInDropDown);
}, [onFilterChange, optionsInDropDown]);
useListenClickOutside({
refs: [refs.floating],
callback: (event) => {
event.stopImmediatePropagation();
const weAreNotInAnHTMLInput = !(
event.target instanceof HTMLInputElement &&
event.target.tagName === 'INPUT'
);
if (weAreNotInAnHTMLInput && isDefined(onCancel)) {
onCancel();
}
},
});
useScopedHotkeys(
Key.Enter,
() => {
const selectedOption = optionsInDropDown.find((option) =>
option.label.toLowerCase().includes(searchFilter.toLowerCase()),
);
if (isDefined(selectedOption)) {
handleOptionChange(selectedOption);
}
},
hotkeyScope,
[searchFilter, optionsInDropDown],
);
return (
<StyledRelationPickerContainer
ref={refs.setFloating}
style={floatingStyles}
>
<DropdownMenu ref={containerRef} data-select-disable>
<DropdownMenuSearchInput
value={searchFilter}
onChange={(e) => setSearchFilter(e.target.value)}
autoFocus
/>
<DropdownMenuSeparator />
<DropdownMenuItemsContainer hasMaxHeight>
{onClear && clearLabel && (
<MenuItemSelectTag
key={`No ${clearLabel}`}
selected={false}
text={`No ${clearLabel}`}
color="transparent"
variant="outline"
onClick={() => {
setSelectedOption(undefined);
onClear();
}}
/>
)}
{optionsInDropDown.map((option) => {
return (
<MenuItemSelectTag
key={option.value}
selected={selectedOption?.value === option.value}
text={option.label}
color={option.color as TagColor}
onClick={() => handleOptionChange(option)}
/>
);
})}
</DropdownMenuItemsContainer>
</DropdownMenu>
</StyledRelationPickerContainer>
);
};