fix glitch for relation picker search (#8040)

Fix for #7957

---------

Co-authored-by: Félix Malfait <felix@twenty.com>
This commit is contained in:
Sanskar Jain
2024-10-25 20:21:52 +05:30
committed by GitHub
parent f633f0d330
commit 9c923ba8d5
13 changed files with 162 additions and 92 deletions

View File

@ -31,7 +31,7 @@ const StyledContainer = styled.div`
const StyledColumnContainer = styled.div`
display: flex;
& > *:not(:first-child) {
& > *:not(:first-of-type) {
border-left: 1px solid ${({ theme }) => theme.border.color.light};
}
`;

View File

@ -18,7 +18,7 @@ const StyledHeaderContainer = styled.div`
top: 0;
}
& > *:not(:first-child) {
& > *:not(:first-of-type) {
border-left: 1px solid ${({ theme }) => theme.border.color.light};
}
`;

View File

@ -10,6 +10,7 @@ import { FieldRelationMetadata } from '@/object-record/record-field/types/FieldM
import { MultiRecordSelect } from '@/object-record/relation-picker/components/MultiRecordSelect';
import { useAddNewRecordAndOpenRightDrawer } from '@/object-record/relation-picker/hooks/useAddNewRecordAndOpenRightDrawer';
import { RelationPickerScope } from '@/object-record/relation-picker/scopes/RelationPickerScope';
import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown';
type RelationFromManyFieldInputProps = {
onSubmit?: FieldInputEvent;
@ -50,6 +51,8 @@ export const RelationFromManyFieldInput = ({
recordId,
});
const { dropdownPlacement } = useDropdown(relationPickerScopeId);
return (
<>
<RelationPickerScope relationPickerScopeId={relationPickerScopeId}>
@ -58,6 +61,7 @@ export const RelationFromManyFieldInput = ({
onSubmit={handleSubmit}
onChange={updateRelation}
onCreate={createNewRecordAndOpenRightDrawer}
dropdownPlacement={dropdownPlacement}
/>
</RelationPickerScope>
</>

View File

@ -82,7 +82,8 @@ export const RecordDetailRelationSection = ({
const dropdownId = `record-field-card-relation-picker-${fieldDefinition.label}-${recordId}`;
const { closeDropdown, isDropdownOpen } = useDropdown(dropdownId);
const { closeDropdown, isDropdownOpen, dropdownPlacement } =
useDropdown(dropdownId);
const { setRelationPickerSearchFilter } = useRelationPicker({
relationPickerScopeId: dropdownId,
@ -183,7 +184,7 @@ export const RecordDetailRelationSection = ({
<DropdownScope dropdownScopeId={dropdownId}>
<StyledAddDropdown
dropdownId={dropdownId}
dropdownPlacement="right-start"
dropdownPlacement="left-start"
onClose={handleCloseRelationPickerDropdown}
clickableComponent={
<LightIconButton
@ -204,6 +205,7 @@ export const RecordDetailRelationSection = ({
}
relationPickerScopeId={dropdownId}
onCreate={createNewRecordAndOpenRightDrawer}
dropdownPlacement={dropdownPlacement}
/>
) : (
<>
@ -212,6 +214,7 @@ export const RecordDetailRelationSection = ({
onCreate={createNewRecordAndOpenRightDrawer}
onChange={updateRelation}
onSubmit={closeDropdown}
dropdownPlacement={dropdownPlacement}
/>
</>
)}

View File

@ -5,6 +5,7 @@ import { MULTI_OBJECT_RECORD_SELECT_SELECTABLE_LIST_ID } from '@/object-record/r
import { useRelationPickerScopedStates } from '@/object-record/relation-picker/hooks/internal/useRelationPickerScopedStates';
import { RelationPickerScopeInternalContext } from '@/object-record/relation-picker/scopes/scope-internal-context/RelationPickerScopeInternalContext';
import { CreateNewButton } from '@/ui/input/relation-picker/components/CreateNewButton';
import { DropdownMenuSkeletonItem } from '@/ui/input/relation-picker/components/skeletons/DropdownMenuSkeletonItem';
import { DropdownMenu } from '@/ui/layout/dropdown/components/DropdownMenu';
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
import { DropdownMenuSearchInput } from '@/ui/layout/dropdown/components/DropdownMenuSearchInput';
@ -18,11 +19,12 @@ import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
import { useSetHotkeyScope } from '@/ui/utilities/hotkey/hooks/useSetHotkeyScope';
import { useAvailableScopeIdOrThrow } from '@/ui/utilities/recoil-scope/scopes-internal/hooks/useAvailableScopeId';
import styled from '@emotion/styled';
import { Placement } from '@floating-ui/react';
import { useCallback, useEffect, useRef } from 'react';
import { useRecoilValue, useSetRecoilState } from 'recoil';
import { Key } from 'ts-key-enum';
import { IconPlus, isDefined } from 'twenty-ui';
import { useDebouncedCallback } from 'use-debounce';
import { isUndefinedOrNull } from '~/utils/isUndefinedOrNull';
export const StyledSelectableItem = styled(SelectableItem)`
height: 100%;
@ -33,10 +35,12 @@ export const MultiRecordSelect = ({
onChange,
onSubmit,
onCreate,
dropdownPlacement,
}: {
onChange?: (changedRecordForSelectId: string) => void;
onSubmit?: () => void;
onCreate?: ((searchInput?: string) => void) | (() => void);
dropdownPlacement?: Placement | null;
}) => {
const containerRef = useRef<HTMLDivElement>(null);
const setHotkeyScope = useSetHotkeyScope();
@ -55,6 +59,7 @@ export const MultiRecordSelect = ({
const recordMultiSelectIsLoading = useRecoilValue(
recordMultiSelectIsLoadingState,
);
const objectRecordsIdsMultiSelect = useRecoilValue(
objectRecordsIdsMultiSelectState,
);
@ -67,9 +72,6 @@ export const MultiRecordSelect = ({
const relationPickerSearchFilter = useRecoilValue(
relationPickerSearchFilterState,
);
const debouncedSetSearchFilter = useDebouncedCallback(setSearchFilter, 100, {
leading: true,
});
useEffect(() => {
setHotkeyScope(relationPickerScopedId);
@ -86,16 +88,53 @@ export const MultiRecordSelect = ({
[onSubmit, goBackToPreviousHotkeyScope, resetSelectedItem],
);
const debouncedOnCreate = useDebouncedCallback(
() => onCreate?.(relationPickerSearchFilter),
500,
);
const handleFilterChange = useCallback(
(event: React.ChangeEvent<HTMLInputElement>) => {
debouncedSetSearchFilter(event.currentTarget.value);
setSearchFilter(event.currentTarget.value);
},
[debouncedSetSearchFilter],
[setSearchFilter],
);
const results = (
<DropdownMenuItemsContainer hasMaxHeight>
<SelectableList
selectableListId={MULTI_OBJECT_RECORD_SELECT_SELECTABLE_LIST_ID}
selectableItemIdArray={objectRecordsIdsMultiSelect}
hotkeyScope={relationPickerScopedId}
onEnter={(selectedId) => {
onChange?.(selectedId);
resetSelectedItem();
}}
>
{objectRecordsIdsMultiSelect?.map((recordId) => {
return (
<MultipleObjectRecordSelectItem
key={recordId}
objectRecordId={recordId}
onChange={(recordId) => {
onChange?.(recordId);
resetSelectedItem();
}}
/>
);
})}
</SelectableList>
{objectRecordsIdsMultiSelect?.length === 0 &&
!recordMultiSelectIsLoading && <MenuItem text="No result" />}
</DropdownMenuItemsContainer>
);
const createNewButton = isDefined(onCreate) && (
<>
<DropdownMenuSeparator />
<DropdownMenuItemsContainer>
<CreateNewButton
onClick={() => onCreate?.(relationPickerSearchFilter)}
LeftIcon={IconPlus}
text="Add New"
/>
</DropdownMenuItemsContainer>
</>
);
return (
@ -107,55 +146,30 @@ export const MultiRecordSelect = ({
}}
/>
<DropdownMenu ref={containerRef} data-select-disable>
{dropdownPlacement?.includes('end') && (
<>
{createNewButton}
{results}
{recordMultiSelectIsLoading && !relationPickerSearchFilter && (
<DropdownMenuSkeletonItem />
)}
<DropdownMenuSeparator />
</>
)}
<DropdownMenuSearchInput
value={relationPickerSearchFilter}
onChange={handleFilterChange}
autoFocus
/>
<DropdownMenuSeparator />
<DropdownMenuItemsContainer hasMaxHeight hasMinHeight>
{recordMultiSelectIsLoading ? (
<MenuItem text="Loading..." />
) : (
<>
<SelectableList
selectableListId={MULTI_OBJECT_RECORD_SELECT_SELECTABLE_LIST_ID}
selectableItemIdArray={objectRecordsIdsMultiSelect}
hotkeyScope={relationPickerScopedId}
onEnter={(selectedId) => {
onChange?.(selectedId);
resetSelectedItem();
}}
>
{objectRecordsIdsMultiSelect?.map((recordId) => {
return (
<MultipleObjectRecordSelectItem
key={recordId}
objectRecordId={recordId}
onChange={(recordId) => {
onChange?.(recordId);
resetSelectedItem();
}}
/>
);
})}
</SelectableList>
{objectRecordsIdsMultiSelect?.length === 0 && (
<MenuItem text="No result" />
)}
</>
)}
</DropdownMenuItemsContainer>
{isDefined(onCreate) && (
{(dropdownPlacement?.includes('start') ||
isUndefinedOrNull(dropdownPlacement)) && (
<>
<DropdownMenuSeparator />
<DropdownMenuItemsContainer>
<CreateNewButton
onClick={debouncedOnCreate}
LeftIcon={IconPlus}
text="Add New"
/>
</DropdownMenuItemsContainer>
{recordMultiSelectIsLoading && !relationPickerSearchFilter && (
<DropdownMenuSkeletonItem />
)}
{results}
{createNewButton}
</>
)}
</DropdownMenu>

View File

@ -36,6 +36,7 @@ export type SingleEntitySelectMenuItemsProps = {
isAllEntitySelectShown?: boolean;
onAllEntitySelected?: () => void;
hotkeyScope?: string;
isFiltered: boolean;
};
export const SingleEntitySelectMenuItems = ({
@ -54,6 +55,7 @@ export const SingleEntitySelectMenuItems = ({
isAllEntitySelectShown,
onAllEntitySelected,
hotkeyScope = RelationPickerHotkeyScope.RelationPicker,
isFiltered,
}: SingleEntitySelectMenuItemsProps) => {
const containerRef = useRef<HTMLDivElement>(null);
@ -139,9 +141,11 @@ export const SingleEntitySelectMenuItems = ({
}}
>
<DropdownMenuItemsContainer hasMaxHeight>
{loading ? (
{loading && !isFiltered ? (
<DropdownMenuSkeletonItem />
) : entitiesInDropdown.length === 0 && !isAllEntitySelectShown ? (
) : entitiesInDropdown.length === 0 &&
!isAllEntitySelectShown &&
!loading ? (
<>
<MenuItem text="No result" />
{entitiesToSelect.length > 0 && <DropdownMenuSeparator />}

View File

@ -6,7 +6,9 @@ import { useEntitySelectSearch } from '@/object-record/relation-picker/hooks/use
import { useRelationPickerEntitiesOptions } from '@/object-record/relation-picker/hooks/useRelationPickerEntitiesOptions';
import { DropdownMenuSearchInput } from '@/ui/layout/dropdown/components/DropdownMenuSearchInput';
import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownMenuSeparator';
import { Placement } from '@floating-ui/react';
import { isDefined } from '~/utils/isDefined';
import { isUndefinedOrNull } from '~/utils/isUndefinedOrNull';
export type SingleEntitySelectMenuItemsWithSearchProps = {
excludedRelationRecordIds?: string[];
@ -14,6 +16,7 @@ export type SingleEntitySelectMenuItemsWithSearchProps = {
relationObjectNameSingular: string;
relationPickerScopeId?: string;
selectedRelationRecordIds: string[];
dropdownPlacement?: Placement | null;
} & Pick<
SingleEntitySelectMenuItemsProps,
| 'EmptyIcon'
@ -34,6 +37,7 @@ export const SingleEntitySelectMenuItemsWithSearch = ({
relationPickerScopeId = 'relation-picker',
selectedEntity,
selectedRelationRecordIds,
dropdownPlacement,
}: SingleEntitySelectMenuItemsWithSearchProps) => {
const { handleSearchFilterChange } = useEntitySelectSearch({
relationPickerScopeId,
@ -62,29 +66,45 @@ export const SingleEntitySelectMenuItemsWithSearch = ({
};
}
const results = (
<SingleEntitySelectMenuItems
entitiesToSelect={entities.entitiesToSelect}
loading={entities.loading}
selectedEntity={
selectedEntity ??
(entities.selectedEntities.length === 1
? entities.selectedEntities[0]
: undefined)
}
hotkeyScope={relationPickerScopeId}
onCreate={onCreateWithInput}
isFiltered={!!relationPickerSearchFilter}
{...{
EmptyIcon,
emptyLabel,
onCancel,
onEntitySelected,
showCreateButton,
}}
/>
);
return (
<>
{dropdownPlacement?.includes('end') && (
<>
{results}
<DropdownMenuSeparator />
</>
)}
<DropdownMenuSearchInput onChange={handleSearchFilterChange} autoFocus />
<DropdownMenuSeparator />
<SingleEntitySelectMenuItems
entitiesToSelect={entities.entitiesToSelect}
loading={entities.loading}
selectedEntity={
selectedEntity ??
(entities.selectedEntities.length === 1
? entities.selectedEntities[0]
: undefined)
}
hotkeyScope={relationPickerScopeId}
onCreate={onCreateWithInput}
{...{
EmptyIcon,
emptyLabel,
onCancel,
onEntitySelected,
showCreateButton,
}}
/>
{(dropdownPlacement?.includes('start') ||
isUndefinedOrNull(dropdownPlacement)) && (
<>
<DropdownMenuSeparator />
{results}
</>
)}
</>
);
};