fix(SingleEntitySelectMenuItems): extract Add New button from entitiesToSelect (#8474)

# Description
Closes #8169

Extract Add New button from entitiesToSelect and add it as a separate
element .
There doesn't seem to be a point in having Add New as part of a list, it
seems better off in its own component, apart from list items

## Rationale
There already is #8353 addressing the same issue, but it seems it
doesn't really remove the duplicate "Add New" in the list, leaving a
duplicate "Add New" in `SingleEntitySelect`

---------

Co-authored-by: Félix Malfait <felix@twenty.com>
This commit is contained in:
Nicolas Rouanne 2024-11-16 06:49:55 +01:00 committed by GitHub
parent 9b2853bb01
commit dc42315f12
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 52 additions and 83 deletions

View File

@ -140,11 +140,13 @@ export const MultiRecordSelect = ({
<DropdownMenu ref={containerRef} data-select-disable>
{dropdownPlacement?.includes('end') && (
<>
<DropdownMenuItemsContainer>
{createNewButton}
</DropdownMenuItemsContainer>
{isDefined(onCreate) && (
<DropdownMenuItemsContainer>
{createNewButton}
</DropdownMenuItemsContainer>
)}
<DropdownMenuSeparator />
{results}
{objectRecordsIdsMultiSelect?.length > 0 && results}
{recordMultiSelectIsLoading && !relationPickerSearchFilter && (
<>
<DropdownMenuSkeletonItem />
@ -171,13 +173,11 @@ export const MultiRecordSelect = ({
<DropdownMenuSeparator />
</>
)}
{results}
{objectRecordsIdsMultiSelect?.length > 0 && results}
{objectRecordsIdsMultiSelect?.length > 0 && (
<DropdownMenuSeparator />
)}
<DropdownMenuItemsContainer>
{createNewButton}
</DropdownMenuItemsContainer>
{isDefined(onCreate) && <div>{createNewButton}</div>}
</>
)}
</DropdownMenu>

View File

@ -1,15 +1,13 @@
import { isNonEmptyString } from '@sniptt/guards';
import { Fragment, useRef } from 'react';
import { useRef } from 'react';
import { useRecoilValue } from 'recoil';
import { Key } from 'ts-key-enum';
import { IconComponent, IconPlus, MenuItemSelect } from 'twenty-ui';
import { IconComponent, MenuItemSelect } from 'twenty-ui';
import { SelectableMenuItemSelect } from '@/object-record/relation-picker/components/SelectableMenuItemSelect';
import { SINGLE_ENTITY_SELECT_BASE_LIST } from '@/object-record/relation-picker/constants/SingleEntitySelectBaseList';
import { CreateNewButton } from '@/ui/input/relation-picker/components/CreateNewButton';
import { DropdownMenuSkeletonItem } from '@/ui/input/relation-picker/components/skeletons/DropdownMenuSkeletonItem';
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownMenuSeparator';
import { SelectableList } from '@/ui/layout/selectable-list/components/SelectableList';
import { useSelectableList } from '@/ui/layout/selectable-list/hooks/useSelectableList';
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
@ -26,8 +24,6 @@ export type SingleEntitySelectMenuItemsProps = {
onCancel?: () => void;
onEntitySelected: (entity?: EntityForSelect) => void;
selectedEntity?: EntityForSelect;
onCreate?: () => void;
showCreateButton?: boolean;
SelectAllIcon?: IconComponent;
selectAllLabel?: string;
isAllEntitySelected?: boolean;
@ -46,8 +42,6 @@ export const SingleEntitySelectMenuItems = ({
onCancel,
onEntitySelected,
selectedEntity,
onCreate,
showCreateButton,
SelectAllIcon,
selectAllLabel,
isAllEntitySelected,
@ -59,14 +53,6 @@ export const SingleEntitySelectMenuItems = ({
}: SingleEntitySelectMenuItemsProps) => {
const containerRef = useRef<HTMLDivElement>(null);
const createNewRecord = showCreateButton
? {
__typename: '',
id: 'add-new',
name: 'Add New',
}
: null;
const selectNone = emptyLabel
? {
__typename: '',
@ -88,7 +74,6 @@ export const SingleEntitySelectMenuItems = ({
selectNone,
selectedEntity,
...entitiesToSelect,
createNewRecord,
].filter(
(entity): entity is EntityForSelect =>
isDefined(entity) && isNonEmptyString(entity.name),
@ -98,10 +83,6 @@ export const SingleEntitySelectMenuItems = ({
SINGLE_ENTITY_SELECT_BASE_LIST,
);
const isSelectedAddNewButton = useRecoilValue(
isSelectedItemIdSelector('add-new'),
);
const isSelectedSelectNoneButton = useRecoilValue(
isSelectedItemIdSelector('select-none'),
);
@ -129,14 +110,10 @@ export const SingleEntitySelectMenuItems = ({
selectableItemIdArray={selectableItemIds}
hotkeyScope={hotkeyScope}
onEnter={(itemId) => {
if (itemId === 'add-new' && showCreateButton === true) {
onCreate?.();
} else {
const entityIndex = entitiesInDropdown.findIndex(
(entity) => entity.id === itemId,
);
onEntitySelected(entitiesInDropdown[entityIndex]);
}
const entityIndex = entitiesInDropdown.findIndex(
(entity) => entity.id === itemId,
);
onEntitySelected(entitiesInDropdown[entityIndex]);
resetSelectedItem();
}}
>
@ -146,32 +123,10 @@ export const SingleEntitySelectMenuItems = ({
) : entitiesInDropdown.length === 0 &&
!isAllEntitySelectShown &&
!loading ? (
<>
{entitiesToSelect.length > 0 && <DropdownMenuSeparator />}
<CreateNewButton
key="add-new"
onClick={onCreate}
LeftIcon={IconPlus}
text="Add New"
hovered={isSelectedAddNewButton}
/>
</>
<></>
) : (
entitiesInDropdown?.map((entity) => {
switch (entity.id) {
case 'add-new': {
return (
<Fragment key={entity.id}>
{entitiesToSelect.length > 0 && <DropdownMenuSeparator />}
<CreateNewButton
onClick={onCreate}
LeftIcon={IconPlus}
text="Add New"
hovered={isSelectedAddNewButton}
/>
</Fragment>
);
}
case 'select-none': {
return (
emptyLabel && (

View File

@ -4,9 +4,12 @@ import {
} from '@/object-record/relation-picker/components/SingleEntitySelectMenuItems';
import { useEntitySelectSearch } from '@/object-record/relation-picker/hooks/useEntitySelectSearch';
import { useRelationPickerEntitiesOptions } from '@/object-record/relation-picker/hooks/useRelationPickerEntitiesOptions';
import { CreateNewButton } from '@/ui/input/relation-picker/components/CreateNewButton';
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 { Placement } from '@floating-ui/react';
import { IconPlus } from 'twenty-ui';
import { isDefined } from '~/utils/isDefined';
import { isUndefinedOrNull } from '~/utils/isUndefinedOrNull';
@ -49,21 +52,13 @@ export const SingleEntitySelectMenuItemsWithSearch = ({
excludedRelationRecordIds,
});
const showCreateButton = isDefined(onCreate);
let onCreateWithInput = undefined;
if (isDefined(onCreate)) {
onCreateWithInput = () => {
if (onCreate.length > 0) {
(onCreate as (searchInput?: string) => void)(
relationPickerSearchFilter,
);
} else {
(onCreate as () => void)();
}
};
}
const createNewButton = isDefined(onCreate) && (
<CreateNewButton
onClick={() => onCreate?.(relationPickerSearchFilter)}
LeftIcon={IconPlus}
text="Add New"
/>
);
const results = (
<SingleEntitySelectMenuItems
@ -76,14 +71,12 @@ export const SingleEntitySelectMenuItemsWithSearch = ({
}
shouldSelectEmptyOption={selectedRelationRecordIds?.length === 0}
hotkeyScope={relationPickerScopeId}
onCreate={onCreateWithInput}
isFiltered={!!relationPickerSearchFilter}
{...{
EmptyIcon,
emptyLabel,
onCancel,
onEntitySelected,
showCreateButton,
}}
/>
);
@ -92,7 +85,11 @@ export const SingleEntitySelectMenuItemsWithSearch = ({
<>
{dropdownPlacement?.includes('end') && (
<>
{results}
<DropdownMenuItemsContainer>
{createNewButton}
</DropdownMenuItemsContainer>
{entities.entitiesToSelect.length > 0 && <DropdownMenuSeparator />}
{entities.entitiesToSelect.length > 0 && results}
<DropdownMenuSeparator />
</>
)}
@ -101,7 +98,15 @@ export const SingleEntitySelectMenuItemsWithSearch = ({
isUndefinedOrNull(dropdownPlacement)) && (
<>
<DropdownMenuSeparator />
{results}
{entities.entitiesToSelect.length > 0 && results}
{entities.entitiesToSelect.length > 0 && isDefined(onCreate) && (
<DropdownMenuSeparator />
)}
{isDefined(onCreate) && (
<DropdownMenuItemsContainer>
{createNewButton}
</DropdownMenuItemsContainer>
)}
</>
)}
</>

View File

@ -7,7 +7,7 @@ import {
size,
useFloating,
} from '@floating-ui/react';
import { MouseEvent, ReactNode, useRef } from 'react';
import { MouseEvent, ReactNode, useEffect, useRef } from 'react';
import { Keys } from 'react-hotkeys-hook';
import { Key } from 'ts-key-enum';
@ -65,8 +65,13 @@ export const Dropdown = ({
}: DropdownProps) => {
const containerRef = useRef<HTMLDivElement>(null);
const { isDropdownOpen, toggleDropdown, closeDropdown, dropdownWidth } =
useDropdown(dropdownId);
const {
isDropdownOpen,
toggleDropdown,
closeDropdown,
dropdownWidth,
setDropdownPlacement,
} = useDropdown(dropdownId);
const offsetMiddlewares = [];
@ -78,7 +83,7 @@ export const Dropdown = ({
offsetMiddlewares.push(offset({ mainAxis: dropdownOffset.y }));
}
const { refs, floatingStyles } = useFloating({
const { refs, floatingStyles, placement } = useFloating({
placement: dropdownPlacement,
middleware: [
flip(),
@ -100,6 +105,10 @@ export const Dropdown = ({
strategy: dropdownStrategy,
});
useEffect(() => {
setDropdownPlacement(placement);
}, [placement, setDropdownPlacement]);
const handleHotkeyTriggered = () => {
toggleDropdown();
};