mirror of
https://github.com/lingble/twenty.git
synced 2025-10-29 03:42:30 +00:00
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:
@@ -140,11 +140,13 @@ export const MultiRecordSelect = ({
|
|||||||
<DropdownMenu ref={containerRef} data-select-disable>
|
<DropdownMenu ref={containerRef} data-select-disable>
|
||||||
{dropdownPlacement?.includes('end') && (
|
{dropdownPlacement?.includes('end') && (
|
||||||
<>
|
<>
|
||||||
<DropdownMenuItemsContainer>
|
{isDefined(onCreate) && (
|
||||||
{createNewButton}
|
<DropdownMenuItemsContainer>
|
||||||
</DropdownMenuItemsContainer>
|
{createNewButton}
|
||||||
|
</DropdownMenuItemsContainer>
|
||||||
|
)}
|
||||||
<DropdownMenuSeparator />
|
<DropdownMenuSeparator />
|
||||||
{results}
|
{objectRecordsIdsMultiSelect?.length > 0 && results}
|
||||||
{recordMultiSelectIsLoading && !relationPickerSearchFilter && (
|
{recordMultiSelectIsLoading && !relationPickerSearchFilter && (
|
||||||
<>
|
<>
|
||||||
<DropdownMenuSkeletonItem />
|
<DropdownMenuSkeletonItem />
|
||||||
@@ -171,13 +173,11 @@ export const MultiRecordSelect = ({
|
|||||||
<DropdownMenuSeparator />
|
<DropdownMenuSeparator />
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
{results}
|
{objectRecordsIdsMultiSelect?.length > 0 && results}
|
||||||
{objectRecordsIdsMultiSelect?.length > 0 && (
|
{objectRecordsIdsMultiSelect?.length > 0 && (
|
||||||
<DropdownMenuSeparator />
|
<DropdownMenuSeparator />
|
||||||
)}
|
)}
|
||||||
<DropdownMenuItemsContainer>
|
{isDefined(onCreate) && <div>{createNewButton}</div>}
|
||||||
{createNewButton}
|
|
||||||
</DropdownMenuItemsContainer>
|
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</DropdownMenu>
|
</DropdownMenu>
|
||||||
|
|||||||
@@ -1,15 +1,13 @@
|
|||||||
import { isNonEmptyString } from '@sniptt/guards';
|
import { isNonEmptyString } from '@sniptt/guards';
|
||||||
import { Fragment, useRef } from 'react';
|
import { useRef } from 'react';
|
||||||
import { useRecoilValue } from 'recoil';
|
import { useRecoilValue } from 'recoil';
|
||||||
import { Key } from 'ts-key-enum';
|
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 { SelectableMenuItemSelect } from '@/object-record/relation-picker/components/SelectableMenuItemSelect';
|
||||||
import { SINGLE_ENTITY_SELECT_BASE_LIST } from '@/object-record/relation-picker/constants/SingleEntitySelectBaseList';
|
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 { DropdownMenuSkeletonItem } from '@/ui/input/relation-picker/components/skeletons/DropdownMenuSkeletonItem';
|
||||||
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
|
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 { SelectableList } from '@/ui/layout/selectable-list/components/SelectableList';
|
||||||
import { useSelectableList } from '@/ui/layout/selectable-list/hooks/useSelectableList';
|
import { useSelectableList } from '@/ui/layout/selectable-list/hooks/useSelectableList';
|
||||||
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
|
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
|
||||||
@@ -26,8 +24,6 @@ export type SingleEntitySelectMenuItemsProps = {
|
|||||||
onCancel?: () => void;
|
onCancel?: () => void;
|
||||||
onEntitySelected: (entity?: EntityForSelect) => void;
|
onEntitySelected: (entity?: EntityForSelect) => void;
|
||||||
selectedEntity?: EntityForSelect;
|
selectedEntity?: EntityForSelect;
|
||||||
onCreate?: () => void;
|
|
||||||
showCreateButton?: boolean;
|
|
||||||
SelectAllIcon?: IconComponent;
|
SelectAllIcon?: IconComponent;
|
||||||
selectAllLabel?: string;
|
selectAllLabel?: string;
|
||||||
isAllEntitySelected?: boolean;
|
isAllEntitySelected?: boolean;
|
||||||
@@ -46,8 +42,6 @@ export const SingleEntitySelectMenuItems = ({
|
|||||||
onCancel,
|
onCancel,
|
||||||
onEntitySelected,
|
onEntitySelected,
|
||||||
selectedEntity,
|
selectedEntity,
|
||||||
onCreate,
|
|
||||||
showCreateButton,
|
|
||||||
SelectAllIcon,
|
SelectAllIcon,
|
||||||
selectAllLabel,
|
selectAllLabel,
|
||||||
isAllEntitySelected,
|
isAllEntitySelected,
|
||||||
@@ -59,14 +53,6 @@ export const SingleEntitySelectMenuItems = ({
|
|||||||
}: SingleEntitySelectMenuItemsProps) => {
|
}: SingleEntitySelectMenuItemsProps) => {
|
||||||
const containerRef = useRef<HTMLDivElement>(null);
|
const containerRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
const createNewRecord = showCreateButton
|
|
||||||
? {
|
|
||||||
__typename: '',
|
|
||||||
id: 'add-new',
|
|
||||||
name: 'Add New',
|
|
||||||
}
|
|
||||||
: null;
|
|
||||||
|
|
||||||
const selectNone = emptyLabel
|
const selectNone = emptyLabel
|
||||||
? {
|
? {
|
||||||
__typename: '',
|
__typename: '',
|
||||||
@@ -88,7 +74,6 @@ export const SingleEntitySelectMenuItems = ({
|
|||||||
selectNone,
|
selectNone,
|
||||||
selectedEntity,
|
selectedEntity,
|
||||||
...entitiesToSelect,
|
...entitiesToSelect,
|
||||||
createNewRecord,
|
|
||||||
].filter(
|
].filter(
|
||||||
(entity): entity is EntityForSelect =>
|
(entity): entity is EntityForSelect =>
|
||||||
isDefined(entity) && isNonEmptyString(entity.name),
|
isDefined(entity) && isNonEmptyString(entity.name),
|
||||||
@@ -98,10 +83,6 @@ export const SingleEntitySelectMenuItems = ({
|
|||||||
SINGLE_ENTITY_SELECT_BASE_LIST,
|
SINGLE_ENTITY_SELECT_BASE_LIST,
|
||||||
);
|
);
|
||||||
|
|
||||||
const isSelectedAddNewButton = useRecoilValue(
|
|
||||||
isSelectedItemIdSelector('add-new'),
|
|
||||||
);
|
|
||||||
|
|
||||||
const isSelectedSelectNoneButton = useRecoilValue(
|
const isSelectedSelectNoneButton = useRecoilValue(
|
||||||
isSelectedItemIdSelector('select-none'),
|
isSelectedItemIdSelector('select-none'),
|
||||||
);
|
);
|
||||||
@@ -129,14 +110,10 @@ export const SingleEntitySelectMenuItems = ({
|
|||||||
selectableItemIdArray={selectableItemIds}
|
selectableItemIdArray={selectableItemIds}
|
||||||
hotkeyScope={hotkeyScope}
|
hotkeyScope={hotkeyScope}
|
||||||
onEnter={(itemId) => {
|
onEnter={(itemId) => {
|
||||||
if (itemId === 'add-new' && showCreateButton === true) {
|
const entityIndex = entitiesInDropdown.findIndex(
|
||||||
onCreate?.();
|
(entity) => entity.id === itemId,
|
||||||
} else {
|
);
|
||||||
const entityIndex = entitiesInDropdown.findIndex(
|
onEntitySelected(entitiesInDropdown[entityIndex]);
|
||||||
(entity) => entity.id === itemId,
|
|
||||||
);
|
|
||||||
onEntitySelected(entitiesInDropdown[entityIndex]);
|
|
||||||
}
|
|
||||||
resetSelectedItem();
|
resetSelectedItem();
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@@ -146,32 +123,10 @@ export const SingleEntitySelectMenuItems = ({
|
|||||||
) : entitiesInDropdown.length === 0 &&
|
) : entitiesInDropdown.length === 0 &&
|
||||||
!isAllEntitySelectShown &&
|
!isAllEntitySelectShown &&
|
||||||
!loading ? (
|
!loading ? (
|
||||||
<>
|
<></>
|
||||||
{entitiesToSelect.length > 0 && <DropdownMenuSeparator />}
|
|
||||||
<CreateNewButton
|
|
||||||
key="add-new"
|
|
||||||
onClick={onCreate}
|
|
||||||
LeftIcon={IconPlus}
|
|
||||||
text="Add New"
|
|
||||||
hovered={isSelectedAddNewButton}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
) : (
|
) : (
|
||||||
entitiesInDropdown?.map((entity) => {
|
entitiesInDropdown?.map((entity) => {
|
||||||
switch (entity.id) {
|
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': {
|
case 'select-none': {
|
||||||
return (
|
return (
|
||||||
emptyLabel && (
|
emptyLabel && (
|
||||||
|
|||||||
@@ -4,9 +4,12 @@ import {
|
|||||||
} from '@/object-record/relation-picker/components/SingleEntitySelectMenuItems';
|
} from '@/object-record/relation-picker/components/SingleEntitySelectMenuItems';
|
||||||
import { useEntitySelectSearch } from '@/object-record/relation-picker/hooks/useEntitySelectSearch';
|
import { useEntitySelectSearch } from '@/object-record/relation-picker/hooks/useEntitySelectSearch';
|
||||||
import { useRelationPickerEntitiesOptions } from '@/object-record/relation-picker/hooks/useRelationPickerEntitiesOptions';
|
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 { DropdownMenuSearchInput } from '@/ui/layout/dropdown/components/DropdownMenuSearchInput';
|
||||||
import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownMenuSeparator';
|
import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownMenuSeparator';
|
||||||
import { Placement } from '@floating-ui/react';
|
import { Placement } from '@floating-ui/react';
|
||||||
|
import { IconPlus } from 'twenty-ui';
|
||||||
import { isDefined } from '~/utils/isDefined';
|
import { isDefined } from '~/utils/isDefined';
|
||||||
import { isUndefinedOrNull } from '~/utils/isUndefinedOrNull';
|
import { isUndefinedOrNull } from '~/utils/isUndefinedOrNull';
|
||||||
|
|
||||||
@@ -49,21 +52,13 @@ export const SingleEntitySelectMenuItemsWithSearch = ({
|
|||||||
excludedRelationRecordIds,
|
excludedRelationRecordIds,
|
||||||
});
|
});
|
||||||
|
|
||||||
const showCreateButton = isDefined(onCreate);
|
const createNewButton = isDefined(onCreate) && (
|
||||||
|
<CreateNewButton
|
||||||
let onCreateWithInput = undefined;
|
onClick={() => onCreate?.(relationPickerSearchFilter)}
|
||||||
|
LeftIcon={IconPlus}
|
||||||
if (isDefined(onCreate)) {
|
text="Add New"
|
||||||
onCreateWithInput = () => {
|
/>
|
||||||
if (onCreate.length > 0) {
|
);
|
||||||
(onCreate as (searchInput?: string) => void)(
|
|
||||||
relationPickerSearchFilter,
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
(onCreate as () => void)();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const results = (
|
const results = (
|
||||||
<SingleEntitySelectMenuItems
|
<SingleEntitySelectMenuItems
|
||||||
@@ -76,14 +71,12 @@ export const SingleEntitySelectMenuItemsWithSearch = ({
|
|||||||
}
|
}
|
||||||
shouldSelectEmptyOption={selectedRelationRecordIds?.length === 0}
|
shouldSelectEmptyOption={selectedRelationRecordIds?.length === 0}
|
||||||
hotkeyScope={relationPickerScopeId}
|
hotkeyScope={relationPickerScopeId}
|
||||||
onCreate={onCreateWithInput}
|
|
||||||
isFiltered={!!relationPickerSearchFilter}
|
isFiltered={!!relationPickerSearchFilter}
|
||||||
{...{
|
{...{
|
||||||
EmptyIcon,
|
EmptyIcon,
|
||||||
emptyLabel,
|
emptyLabel,
|
||||||
onCancel,
|
onCancel,
|
||||||
onEntitySelected,
|
onEntitySelected,
|
||||||
showCreateButton,
|
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
@@ -92,7 +85,11 @@ export const SingleEntitySelectMenuItemsWithSearch = ({
|
|||||||
<>
|
<>
|
||||||
{dropdownPlacement?.includes('end') && (
|
{dropdownPlacement?.includes('end') && (
|
||||||
<>
|
<>
|
||||||
{results}
|
<DropdownMenuItemsContainer>
|
||||||
|
{createNewButton}
|
||||||
|
</DropdownMenuItemsContainer>
|
||||||
|
{entities.entitiesToSelect.length > 0 && <DropdownMenuSeparator />}
|
||||||
|
{entities.entitiesToSelect.length > 0 && results}
|
||||||
<DropdownMenuSeparator />
|
<DropdownMenuSeparator />
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
@@ -101,7 +98,15 @@ export const SingleEntitySelectMenuItemsWithSearch = ({
|
|||||||
isUndefinedOrNull(dropdownPlacement)) && (
|
isUndefinedOrNull(dropdownPlacement)) && (
|
||||||
<>
|
<>
|
||||||
<DropdownMenuSeparator />
|
<DropdownMenuSeparator />
|
||||||
{results}
|
{entities.entitiesToSelect.length > 0 && results}
|
||||||
|
{entities.entitiesToSelect.length > 0 && isDefined(onCreate) && (
|
||||||
|
<DropdownMenuSeparator />
|
||||||
|
)}
|
||||||
|
{isDefined(onCreate) && (
|
||||||
|
<DropdownMenuItemsContainer>
|
||||||
|
{createNewButton}
|
||||||
|
</DropdownMenuItemsContainer>
|
||||||
|
)}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import {
|
|||||||
size,
|
size,
|
||||||
useFloating,
|
useFloating,
|
||||||
} from '@floating-ui/react';
|
} from '@floating-ui/react';
|
||||||
import { MouseEvent, ReactNode, useRef } from 'react';
|
import { MouseEvent, ReactNode, useEffect, useRef } from 'react';
|
||||||
import { Keys } from 'react-hotkeys-hook';
|
import { Keys } from 'react-hotkeys-hook';
|
||||||
import { Key } from 'ts-key-enum';
|
import { Key } from 'ts-key-enum';
|
||||||
|
|
||||||
@@ -65,8 +65,13 @@ export const Dropdown = ({
|
|||||||
}: DropdownProps) => {
|
}: DropdownProps) => {
|
||||||
const containerRef = useRef<HTMLDivElement>(null);
|
const containerRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
const { isDropdownOpen, toggleDropdown, closeDropdown, dropdownWidth } =
|
const {
|
||||||
useDropdown(dropdownId);
|
isDropdownOpen,
|
||||||
|
toggleDropdown,
|
||||||
|
closeDropdown,
|
||||||
|
dropdownWidth,
|
||||||
|
setDropdownPlacement,
|
||||||
|
} = useDropdown(dropdownId);
|
||||||
|
|
||||||
const offsetMiddlewares = [];
|
const offsetMiddlewares = [];
|
||||||
|
|
||||||
@@ -78,7 +83,7 @@ export const Dropdown = ({
|
|||||||
offsetMiddlewares.push(offset({ mainAxis: dropdownOffset.y }));
|
offsetMiddlewares.push(offset({ mainAxis: dropdownOffset.y }));
|
||||||
}
|
}
|
||||||
|
|
||||||
const { refs, floatingStyles } = useFloating({
|
const { refs, floatingStyles, placement } = useFloating({
|
||||||
placement: dropdownPlacement,
|
placement: dropdownPlacement,
|
||||||
middleware: [
|
middleware: [
|
||||||
flip(),
|
flip(),
|
||||||
@@ -100,6 +105,10 @@ export const Dropdown = ({
|
|||||||
strategy: dropdownStrategy,
|
strategy: dropdownStrategy,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setDropdownPlacement(placement);
|
||||||
|
}, [placement, setDropdownPlacement]);
|
||||||
|
|
||||||
const handleHotkeyTriggered = () => {
|
const handleHotkeyTriggered = () => {
|
||||||
toggleDropdown();
|
toggleDropdown();
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user