Use SelectableList in RelationPicker, SingleEntitySelectBase and MultipleEntitySelect (#2949)
* 2747-fix: conditional updation of selectedItemId * 2747-fix: bug in toggling * 2747-feat: SingleEntitySelectBase list changed to SelectableList * 2747-feat: MultipleEntitySelect use SelectableList * Fix lint * 2747-fix: onEnter property fix for SingleEntitySelectBase * 2747-fix: onEnter property fix for MultipleEntitySelect * yarn fix in twenty-front --------- Co-authored-by: Charles Bochet <charles@twenty.com>
This commit is contained in:
@ -1,11 +1,16 @@
|
||||
/* eslint-disable react-hooks/rules-of-hooks */
|
||||
import { useRef } from 'react';
|
||||
import { isNonEmptyString } from '@sniptt/guards';
|
||||
import debounce from 'lodash.debounce';
|
||||
|
||||
import { RelationPickerHotkeyScope } from '@/object-record/relation-picker/types/RelationPickerHotkeyScope';
|
||||
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 { SelectableItem } from '@/ui/layout/selectable-list/components/SelectableItem';
|
||||
import { SelectableList } from '@/ui/layout/selectable-list/components/SelectableList';
|
||||
import { useSelectableList } from '@/ui/layout/selectable-list/hooks/useSelectableList';
|
||||
import { MenuItem } from '@/ui/navigation/menu-item/components/MenuItem';
|
||||
import { MenuItemMultiSelectAvatar } from '@/ui/navigation/menu-item/components/MenuItemMultiSelectAvatar';
|
||||
import { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside';
|
||||
@ -71,6 +76,8 @@ export const MultipleEntitySelect = <
|
||||
},
|
||||
});
|
||||
|
||||
const selectableItemIds = entitiesInDropdown.map((entity) => entity.id);
|
||||
|
||||
return (
|
||||
<DropdownMenu ref={containerRef} data-select-disable>
|
||||
<DropdownMenuSearchInput
|
||||
@ -80,25 +87,46 @@ export const MultipleEntitySelect = <
|
||||
/>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItemsContainer hasMaxHeight>
|
||||
{entitiesInDropdown?.map((entity) => (
|
||||
<MenuItemMultiSelectAvatar
|
||||
key={entity.id}
|
||||
selected={value[entity.id]}
|
||||
onSelectChange={(newCheckedValue) =>
|
||||
onChange({ ...value, [entity.id]: newCheckedValue })
|
||||
<SelectableList
|
||||
selectableListId="multiple-entity-select-list"
|
||||
selectableItemIds={[selectableItemIds]}
|
||||
hotkeyScope={RelationPickerHotkeyScope.RelationPicker}
|
||||
onEnter={(_itemId) => {
|
||||
if (_itemId in value === false || value[_itemId] === false) {
|
||||
onChange({ ...value, [_itemId]: true });
|
||||
} else {
|
||||
onChange({ ...value, [_itemId]: false });
|
||||
}
|
||||
avatar={
|
||||
<Avatar
|
||||
avatarUrl={entity.avatarUrl}
|
||||
colorId={entity.id}
|
||||
placeholder={entity.name}
|
||||
size="md"
|
||||
type={entity.avatarType ?? 'rounded'}
|
||||
}}
|
||||
>
|
||||
{entitiesInDropdown?.map((entity) => (
|
||||
<SelectableItem itemId={entity.id} key={entity.id}>
|
||||
<MenuItemMultiSelectAvatar
|
||||
key={entity.id}
|
||||
isKeySelected={
|
||||
useSelectableList({
|
||||
selectableListId: 'multiple-entity-select-list',
|
||||
itemId: entity.id,
|
||||
}).isSelectedItemId
|
||||
}
|
||||
selected={value[entity.id]}
|
||||
onSelectChange={(newCheckedValue) =>
|
||||
onChange({ ...value, [entity.id]: newCheckedValue })
|
||||
}
|
||||
avatar={
|
||||
<Avatar
|
||||
avatarUrl={entity.avatarUrl}
|
||||
colorId={entity.id}
|
||||
placeholder={entity.name}
|
||||
size="md"
|
||||
type={entity.avatarType ?? 'rounded'}
|
||||
/>
|
||||
}
|
||||
text={entity.name}
|
||||
/>
|
||||
}
|
||||
text={entity.name}
|
||||
/>
|
||||
))}
|
||||
</SelectableItem>
|
||||
))}
|
||||
</SelectableList>
|
||||
{entitiesInDropdown?.length === 0 && <MenuItem text="No result" />}
|
||||
</DropdownMenuItemsContainer>
|
||||
</DropdownMenu>
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
/* eslint-disable react-hooks/rules-of-hooks */
|
||||
import { useRef } from 'react';
|
||||
import { isNonEmptyString } from '@sniptt/guards';
|
||||
import { Key } from 'ts-key-enum';
|
||||
@ -6,6 +7,9 @@ import { IconPlus } from '@/ui/display/icon';
|
||||
import { IconComponent } from '@/ui/display/icon/types/IconComponent';
|
||||
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
|
||||
import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownMenuSeparator';
|
||||
import { SelectableItem } from '@/ui/layout/selectable-list/components/SelectableItem';
|
||||
import { SelectableList } from '@/ui/layout/selectable-list/components/SelectableList';
|
||||
import { useSelectableList } from '@/ui/layout/selectable-list/hooks/useSelectableList';
|
||||
import { MenuItem } from '@/ui/navigation/menu-item/components/MenuItem';
|
||||
import { MenuItemSelect } from '@/ui/navigation/menu-item/components/MenuItemSelect';
|
||||
import { MenuItemSelectAvatar } from '@/ui/navigation/menu-item/components/MenuItemSelectAvatar';
|
||||
@ -64,7 +68,7 @@ export const SingleEntitySelectBase = <
|
||||
assertNotNull(entity) && isNonEmptyString(entity.name),
|
||||
);
|
||||
|
||||
const { preselectedOptionId, resetScroll } = useEntitySelectScroll({
|
||||
const { preselectedOptionId } = useEntitySelectScroll({
|
||||
selectableOptionIds: [
|
||||
EmptyButtonId,
|
||||
...entitiesInDropdown.map((item) => item.id),
|
||||
@ -73,24 +77,6 @@ export const SingleEntitySelectBase = <
|
||||
containerRef,
|
||||
});
|
||||
|
||||
useScopedHotkeys(
|
||||
Key.Enter,
|
||||
() => {
|
||||
if (showCreateButton && preselectedOptionId === CreateButtonId) {
|
||||
onCreate?.();
|
||||
} else {
|
||||
const entity = entitiesInDropdown.findIndex(
|
||||
(entity) => entity.id === preselectedOptionId,
|
||||
);
|
||||
onEntitySelected(entitiesInDropdown[entity]);
|
||||
}
|
||||
|
||||
resetScroll();
|
||||
},
|
||||
RelationPickerHotkeyScope.RelationPicker,
|
||||
[entitiesInDropdown, preselectedOptionId, onEntitySelected],
|
||||
);
|
||||
|
||||
useScopedHotkeys(
|
||||
Key.Escape,
|
||||
() => {
|
||||
@ -100,6 +86,8 @@ export const SingleEntitySelectBase = <
|
||||
[onCancel],
|
||||
);
|
||||
|
||||
const selectableItemIds = entitiesInDropdown.map((entity) => entity.id);
|
||||
|
||||
return (
|
||||
<div ref={containerRef}>
|
||||
<DropdownMenuItemsContainer hasMaxHeight>
|
||||
@ -130,23 +118,49 @@ export const SingleEntitySelectBase = <
|
||||
/>
|
||||
)}
|
||||
{entitiesInDropdown?.map((entity) => (
|
||||
<MenuItemSelectAvatar
|
||||
key={entity.id}
|
||||
testId="menu-item"
|
||||
selected={selectedEntity?.id === entity.id}
|
||||
onClick={() => onEntitySelected(entity)}
|
||||
text={entity.name}
|
||||
hovered={preselectedOptionId === entity.id}
|
||||
avatar={
|
||||
<Avatar
|
||||
avatarUrl={entity.avatarUrl}
|
||||
colorId={entity.id}
|
||||
placeholder={entity.name}
|
||||
size="md"
|
||||
type={entity.avatarType ?? 'rounded'}
|
||||
<SelectableList
|
||||
selectableListId="single-entity-select-base-list"
|
||||
selectableItemIds={[selectableItemIds]}
|
||||
hotkeyScope={RelationPickerHotkeyScope.RelationPicker}
|
||||
onEnter={(_itemId) => {
|
||||
if (
|
||||
showCreateButton &&
|
||||
preselectedOptionId === CreateButtonId
|
||||
) {
|
||||
onCreate?.();
|
||||
} else {
|
||||
const entity = entitiesInDropdown.findIndex(
|
||||
(entity) => entity.id === _itemId,
|
||||
);
|
||||
onEntitySelected(entitiesInDropdown[entity]);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<SelectableItem itemId={entity.id} key={entity.id}>
|
||||
<MenuItemSelectAvatar
|
||||
key={entity.id}
|
||||
testId="menu-item"
|
||||
onClick={() => onEntitySelected(entity)}
|
||||
text={entity.name}
|
||||
selected={selectedEntity?.id === entity.id}
|
||||
hovered={
|
||||
useSelectableList({
|
||||
selectableListId: 'single-entity-select-base-list',
|
||||
itemId: entity.id,
|
||||
}).isSelectedItemId
|
||||
}
|
||||
avatar={
|
||||
<Avatar
|
||||
avatarUrl={entity.avatarUrl}
|
||||
colorId={entity.id}
|
||||
placeholder={entity.name}
|
||||
size="md"
|
||||
type={entity.avatarType ?? 'rounded'}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</SelectableItem>
|
||||
</SelectableList>
|
||||
))}
|
||||
</>
|
||||
)}
|
||||
|
||||
@ -115,7 +115,6 @@ export const turnFiltersIntoObjectRecordFilters = (
|
||||
);
|
||||
}
|
||||
|
||||
// eslint-disable-next-line no-case-declarations
|
||||
const parsedRecordIds = JSON.parse(rawUIFilter.value) as string[];
|
||||
|
||||
if (parsedRecordIds.length > 0) {
|
||||
|
||||
Reference in New Issue
Block a user