diff --git a/packages/twenty-front/src/modules/activities/inline-cell/hooks/useOpenActivityTargetCellEditMode.ts b/packages/twenty-front/src/modules/activities/inline-cell/hooks/useOpenActivityTargetCellEditMode.ts
index fd8cb2893..cfadbfc69 100644
--- a/packages/twenty-front/src/modules/activities/inline-cell/hooks/useOpenActivityTargetCellEditMode.ts
+++ b/packages/twenty-front/src/modules/activities/inline-cell/hooks/useOpenActivityTargetCellEditMode.ts
@@ -1,6 +1,7 @@
import { ActivityTargetWithTargetRecord } from '@/activities/types/ActivityTargetObject';
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
+import { useMultipleRecordPickerOpen } from '@/object-record/record-picker/multiple-record-picker/hooks/useMultipleRecordPickerOpen';
import { useMultipleRecordPickerPerformSearch } from '@/object-record/record-picker/multiple-record-picker/hooks/useMultipleRecordPickerPerformSearch';
import { multipleRecordPickerPickableMorphItemsComponentState } from '@/object-record/record-picker/multiple-record-picker/states/multipleRecordPickerPickableMorphItemsComponentState';
import { multipleRecordPickerSearchFilterComponentState } from '@/object-record/record-picker/multiple-record-picker/states/multipleRecordPickerSearchFilterComponentState';
@@ -19,6 +20,7 @@ type OpenActivityTargetCellEditModeProps = {
export const useOpenActivityTargetCellEditMode = () => {
const { performSearch: multipleRecordPickerPerformSearch } =
useMultipleRecordPickerPerformSearch();
+ const { openMultipleRecordPicker } = useMultipleRecordPickerOpen();
const { pushFocusItemToFocusStack } = usePushFocusItemToFocusStack();
@@ -70,6 +72,8 @@ export const useOpenActivityTargetCellEditMode = () => {
'',
);
+ openMultipleRecordPicker(recordPickerInstanceId);
+
multipleRecordPickerPerformSearch({
multipleRecordPickerInstanceId: recordPickerInstanceId,
forceSearchFilter: '',
@@ -97,7 +101,11 @@ export const useOpenActivityTargetCellEditMode = () => {
memoizeKey: recordPickerInstanceId,
});
},
- [multipleRecordPickerPerformSearch, pushFocusItemToFocusStack],
+ [
+ multipleRecordPickerPerformSearch,
+ openMultipleRecordPicker,
+ pushFocusItemToFocusStack,
+ ],
);
return { openActivityTargetCellEditMode };
diff --git a/packages/twenty-front/src/modules/object-record/record-field/meta-types/input/hooks/useOpenRelationFromManyFieldInput.tsx b/packages/twenty-front/src/modules/object-record/record-field/meta-types/input/hooks/useOpenRelationFromManyFieldInput.tsx
index 10504aa69..1f1d8128c 100644
--- a/packages/twenty-front/src/modules/object-record/record-field/meta-types/input/hooks/useOpenRelationFromManyFieldInput.tsx
+++ b/packages/twenty-front/src/modules/object-record/record-field/meta-types/input/hooks/useOpenRelationFromManyFieldInput.tsx
@@ -4,6 +4,7 @@ import {
FieldRelationFromManyValue,
FieldRelationValue,
} from '@/object-record/record-field/types/FieldMetadata';
+import { useMultipleRecordPickerOpen } from '@/object-record/record-picker/multiple-record-picker/hooks/useMultipleRecordPickerOpen';
import { useMultipleRecordPickerPerformSearch } from '@/object-record/record-picker/multiple-record-picker/hooks/useMultipleRecordPickerPerformSearch';
import { multipleRecordPickerPickableMorphItemsComponentState } from '@/object-record/record-picker/multiple-record-picker/states/multipleRecordPickerPickableMorphItemsComponentState';
import { multipleRecordPickerSearchableObjectMetadataItemsComponentState } from '@/object-record/record-picker/multiple-record-picker/states/multipleRecordPickerSearchableObjectMetadataItemsComponentState';
@@ -17,6 +18,7 @@ import { useRecoilCallback } from 'recoil';
export const useOpenRelationFromManyFieldInput = () => {
const { performSearch } = useMultipleRecordPickerPerformSearch();
+ const { openMultipleRecordPicker } = useMultipleRecordPickerOpen();
const { pushFocusItemToFocusStack } = usePushFocusItemToFocusStack();
@@ -58,6 +60,8 @@ export const useOpenRelationFromManyFieldInput = () => {
return;
}
+ openMultipleRecordPicker(recordPickerInstanceId);
+
const pickableMorphItems: RecordPickerPickableMorphItem[] =
fieldValue.map((record) => {
return {
@@ -105,7 +109,7 @@ export const useOpenRelationFromManyFieldInput = () => {
memoizeKey: recordPickerInstanceId,
});
},
- [performSearch, pushFocusItemToFocusStack],
+ [openMultipleRecordPicker, performSearch, pushFocusItemToFocusStack],
);
return { openRelationFromManyFieldInput };
diff --git a/packages/twenty-front/src/modules/object-record/record-field/meta-types/input/hooks/useOpenRelationToOneFieldInput.tsx b/packages/twenty-front/src/modules/object-record/record-field/meta-types/input/hooks/useOpenRelationToOneFieldInput.tsx
index b163f83c8..170d72524 100644
--- a/packages/twenty-front/src/modules/object-record/record-field/meta-types/input/hooks/useOpenRelationToOneFieldInput.tsx
+++ b/packages/twenty-front/src/modules/object-record/record-field/meta-types/input/hooks/useOpenRelationToOneFieldInput.tsx
@@ -3,6 +3,7 @@ import {
FieldRelationToOneValue,
FieldRelationValue,
} from '@/object-record/record-field/types/FieldMetadata';
+import { useSingleRecordPickerOpen } from '@/object-record/record-picker/single-record-picker/hooks/useSingleRecordPickerOpen';
import { singleRecordPickerSelectedIdComponentState } from '@/object-record/record-picker/single-record-picker/states/singleRecordPickerSelectedIdComponentState';
import { recordStoreFamilySelector } from '@/object-record/record-store/states/selectors/recordStoreFamilySelector';
import { DropdownHotkeyScope } from '@/ui/layout/dropdown/constants/DropdownHotkeyScope';
@@ -13,6 +14,7 @@ import { isDefined } from 'twenty-shared/utils';
export const useOpenRelationToOneFieldInput = () => {
const { pushFocusItemToFocusStack } = usePushFocusItemToFocusStack();
+ const { openSingleRecordPicker } = useSingleRecordPickerOpen();
const openRelationToOneFieldInput = useRecoilCallback(
({ set, snapshot }) =>
@@ -39,6 +41,8 @@ export const useOpenRelationToOneFieldInput = () => {
);
}
+ openSingleRecordPicker(recordPickerInstanceId);
+
pushFocusItemToFocusStack({
focusId: recordPickerInstanceId,
component: {
@@ -50,7 +54,7 @@ export const useOpenRelationToOneFieldInput = () => {
memoizeKey: recordPickerInstanceId,
});
},
- [pushFocusItemToFocusStack],
+ [openSingleRecordPicker, pushFocusItemToFocusStack],
);
return { openRelationToOneFieldInput };
diff --git a/packages/twenty-front/src/modules/object-record/record-picker/components/RecordPickerInitialLoadingEmptyContainer.tsx b/packages/twenty-front/src/modules/object-record/record-picker/components/RecordPickerInitialLoadingEmptyContainer.tsx
new file mode 100644
index 000000000..b7e895c1a
--- /dev/null
+++ b/packages/twenty-front/src/modules/object-record/record-picker/components/RecordPickerInitialLoadingEmptyContainer.tsx
@@ -0,0 +1,8 @@
+import styled from '@emotion/styled';
+
+const StyledRecordPickerInitialLoadingEmptyContainer = styled.div`
+ height: 320px;
+ width: 100%;
+`;
+
+export { StyledRecordPickerInitialLoadingEmptyContainer as RecordPickerInitialLoadingEmptyContainer };
diff --git a/packages/twenty-front/src/modules/object-record/record-picker/components/RecordPickerLoadingSkeletonList.tsx b/packages/twenty-front/src/modules/object-record/record-picker/components/RecordPickerLoadingSkeletonList.tsx
new file mode 100644
index 000000000..64c47ccbc
--- /dev/null
+++ b/packages/twenty-front/src/modules/object-record/record-picker/components/RecordPickerLoadingSkeletonList.tsx
@@ -0,0 +1,13 @@
+import { DropdownMenuSkeletonItem } from '@/ui/input/relation-picker/components/skeletons/DropdownMenuSkeletonItem';
+
+export const RecordPickerLoadingSkeletonList = () => {
+ return (
+ <>
+
+
+
+
+
+ >
+ );
+};
diff --git a/packages/twenty-front/src/modules/object-record/record-picker/multiple-record-picker/components/MultipleRecordPicker.tsx b/packages/twenty-front/src/modules/object-record/record-picker/multiple-record-picker/components/MultipleRecordPicker.tsx
index c2cb1b64a..b2884318f 100644
--- a/packages/twenty-front/src/modules/object-record/record-picker/multiple-record-picker/components/MultipleRecordPicker.tsx
+++ b/packages/twenty-front/src/modules/object-record/record-picker/multiple-record-picker/components/MultipleRecordPicker.tsx
@@ -12,23 +12,16 @@ import { CreateNewButton } from '@/ui/input/relation-picker/components/CreateNew
import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent';
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
import { DropdownHotkeyScope } from '@/ui/layout/dropdown/constants/DropdownHotkeyScope';
-import { SelectableListItem } from '@/ui/layout/selectable-list/components/SelectableListItem';
import { useSelectableList } from '@/ui/layout/selectable-list/hooks/useSelectableList';
import { useHotkeysOnFocusedElement } from '@/ui/utilities/hotkey/hooks/useHotkeysOnFocusedElement';
import { usePreviousHotkeyScope } from '@/ui/utilities/hotkey/hooks/usePreviousHotkeyScope';
import { useRecoilComponentCallbackStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackStateV2';
-import styled from '@emotion/styled';
import { useRef } from 'react';
import { useRecoilCallback } from 'recoil';
import { Key } from 'ts-key-enum';
import { isDefined } from 'twenty-shared/utils';
import { IconPlus } from 'twenty-ui/display';
-export const StyledSelectableItem = styled(SelectableListItem)`
- height: 100%;
- width: 100%;
-`;
-
type MultipleRecordPickerProps = {
onChange?: (morphItem: RecordPickerPickableMorphItem) => void;
onSubmit?: () => void;
diff --git a/packages/twenty-front/src/modules/object-record/record-picker/multiple-record-picker/components/MultipleRecordPickerFetchMoreLoader.tsx b/packages/twenty-front/src/modules/object-record/record-picker/multiple-record-picker/components/MultipleRecordPickerFetchMoreLoader.tsx
index 7f4de8a5a..0c087c744 100644
--- a/packages/twenty-front/src/modules/object-record/record-picker/multiple-record-picker/components/MultipleRecordPickerFetchMoreLoader.tsx
+++ b/packages/twenty-front/src/modules/object-record/record-picker/multiple-record-picker/components/MultipleRecordPickerFetchMoreLoader.tsx
@@ -1,10 +1,16 @@
import { useMultipleRecordPickerPerformSearch } from '@/object-record/record-picker/multiple-record-picker/hooks/useMultipleRecordPickerPerformSearch';
import { MultipleRecordPickerComponentInstanceContext } from '@/object-record/record-picker/multiple-record-picker/states/contexts/MultipleRecordPickerComponentInstanceContext';
+
+import { multipleRecordPickerIsFetchingMoreComponentState } from '@/object-record/record-picker/multiple-record-picker/states/multipleRecordPickerIsFetchingMoreComponentState';
import { multipleRecordPickerIsLoadingComponentState } from '@/object-record/record-picker/multiple-record-picker/states/multipleRecordPickerIsLoadingComponentState';
+
import { multipleRecordPickerPaginationState } from '@/object-record/record-picker/multiple-record-picker/states/multipleRecordPickerPaginationState';
import { multipleRecordPickerSearchFilterComponentState } from '@/object-record/record-picker/multiple-record-picker/states/multipleRecordPickerSearchFilterComponentState';
+import { multipleRecordPickerShouldShowInitialLoadingComponentState } from '@/object-record/record-picker/multiple-record-picker/states/multipleRecordPickerShouldShowInitialLoadingComponentState';
+import { multipleRecordPickerShouldShowSkeletonComponentState } from '@/object-record/record-picker/multiple-record-picker/states/multipleRecordPickerShouldShowSkeletonComponentState';
import { multipleRecordPickerPaginationSelector } from '@/object-record/record-picker/multiple-record-picker/states/selectors/multipleRecordPickerPaginationSelector';
import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow';
+import { useRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentStateV2';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import styled from '@emotion/styled';
import { useCallback } from 'react';
@@ -23,10 +29,17 @@ const StyledText = styled.div`
`;
const StyledIntersectionObserver = styled.div`
- height: 1px;
+ height: 0px;
`;
export const MultipleRecordPickerFetchMoreLoader = () => {
+ const [
+ multipleRecordPickerIsFetchingMore,
+ setMultipleRecordPickerIsFetchingMore,
+ ] = useRecoilComponentStateV2(
+ multipleRecordPickerIsFetchingMoreComponentState,
+ );
+
const componentInstanceId = useAvailableComponentInstanceIdOrThrow(
MultipleRecordPickerComponentInstanceContext,
);
@@ -46,6 +59,15 @@ export const MultipleRecordPickerFetchMoreLoader = () => {
componentInstanceId,
);
+ const multipleRecordPickerShouldShowInitialLoading =
+ useRecoilComponentValueV2(
+ multipleRecordPickerShouldShowInitialLoadingComponentState,
+ );
+
+ const multipleRecordPickerShouldShowSkeleton = useRecoilComponentValueV2(
+ multipleRecordPickerShouldShowSkeletonComponentState,
+ );
+
const { performSearch } = useMultipleRecordPickerPerformSearch();
const fetchMore = useRecoilCallback(
@@ -63,7 +85,7 @@ export const MultipleRecordPickerFetchMoreLoader = () => {
return;
}
- performSearch({
+ await performSearch({
multipleRecordPickerInstanceId: componentInstanceId,
forceSearchFilter: searchFilter,
loadMore: true,
@@ -74,23 +96,34 @@ export const MultipleRecordPickerFetchMoreLoader = () => {
const { ref } = useInView({
onChange: useCallback(
- (inView: boolean) => {
+ async (inView: boolean) => {
if (inView) {
- fetchMore();
+ setMultipleRecordPickerIsFetchingMore(true);
+
+ await fetchMore();
+
+ setMultipleRecordPickerIsFetchingMore(false);
}
},
- [fetchMore],
+ [fetchMore, setMultipleRecordPickerIsFetchingMore],
),
});
- if (!paginationState.hasNextPage) {
+ if (
+ !paginationState.hasNextPage ||
+ multipleRecordPickerShouldShowInitialLoading ||
+ multipleRecordPickerShouldShowSkeleton ||
+ (isLoading && !multipleRecordPickerIsFetchingMore)
+ ) {
return null;
}
return (
-
+ <>
- {isLoading && Loading more...}
-
+ {multipleRecordPickerIsFetchingMore && (
+ Loading more...
+ )}
+ >
);
};
diff --git a/packages/twenty-front/src/modules/object-record/record-picker/multiple-record-picker/components/MultipleRecordPickerItemsDisplay.tsx b/packages/twenty-front/src/modules/object-record/record-picker/multiple-record-picker/components/MultipleRecordPickerItemsDisplay.tsx
index de5a6b6ae..d095dd247 100644
--- a/packages/twenty-front/src/modules/object-record/record-picker/multiple-record-picker/components/MultipleRecordPickerItemsDisplay.tsx
+++ b/packages/twenty-front/src/modules/object-record/record-picker/multiple-record-picker/components/MultipleRecordPickerItemsDisplay.tsx
@@ -1,12 +1,7 @@
+import { MultipleRecordPickerLoadingEffect } from '@/object-record/record-picker/multiple-record-picker/components/MultipleRecordPickerLoadingEffect';
import { MultipleRecordPickerMenuItems } from '@/object-record/record-picker/multiple-record-picker/components/MultipleRecordPickerMenuItems';
-import { MultipleRecordPickerComponentInstanceContext } from '@/object-record/record-picker/multiple-record-picker/states/contexts/MultipleRecordPickerComponentInstanceContext';
-import { multipleRecordPickerIsLoadingComponentState } from '@/object-record/record-picker/multiple-record-picker/states/multipleRecordPickerIsLoadingComponentState';
-import { multipleRecordPickerPickableMorphItemsLengthComponentSelector } from '@/object-record/record-picker/multiple-record-picker/states/selectors/multipleRecordPickerPickableMorphItemsLengthComponentSelector';
import { RecordPickerPickableMorphItem } from '@/object-record/record-picker/types/RecordPickerPickableMorphItem';
-import { DropdownMenuSkeletonItem } from '@/ui/input/relation-picker/components/skeletons/DropdownMenuSkeletonItem';
import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownMenuSeparator';
-import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow';
-import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
export const MultipleRecordPickerItemsDisplay = ({
onChange,
@@ -15,28 +10,11 @@ export const MultipleRecordPickerItemsDisplay = ({
onChange?: (morphItem: RecordPickerPickableMorphItem) => void;
focusId: string;
}) => {
- const componentInstanceId = useAvailableComponentInstanceIdOrThrow(
- MultipleRecordPickerComponentInstanceContext,
- );
-
- const isLoading = useRecoilComponentValueV2(
- multipleRecordPickerIsLoadingComponentState,
- componentInstanceId,
- );
-
- const itemsLength = useRecoilComponentValueV2(
- multipleRecordPickerPickableMorphItemsLengthComponentSelector,
- componentInstanceId,
- );
-
return (
<>
+
- {isLoading && itemsLength === 0 ? (
-
- ) : (
-
- )}
+
>
);
diff --git a/packages/twenty-front/src/modules/object-record/record-picker/multiple-record-picker/components/MultipleRecordPickerLoadingEffect.tsx b/packages/twenty-front/src/modules/object-record/record-picker/multiple-record-picker/components/MultipleRecordPickerLoadingEffect.tsx
new file mode 100644
index 000000000..22738daf5
--- /dev/null
+++ b/packages/twenty-front/src/modules/object-record/record-picker/multiple-record-picker/components/MultipleRecordPickerLoadingEffect.tsx
@@ -0,0 +1,52 @@
+import { multipleRecordPickerIsFetchingMoreComponentState } from '@/object-record/record-picker/multiple-record-picker/states/multipleRecordPickerIsFetchingMoreComponentState';
+import { multipleRecordPickerIsLoadingComponentState } from '@/object-record/record-picker/multiple-record-picker/states/multipleRecordPickerIsLoadingComponentState';
+import { multipleRecordPickerShouldShowSkeletonComponentState } from '@/object-record/record-picker/multiple-record-picker/states/multipleRecordPickerShouldShowSkeletonComponentState';
+import { useRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentStateV2';
+import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
+import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2';
+import { useEffect, useState } from 'react';
+import { useDebouncedCallback } from 'use-debounce';
+
+export const MultipleRecordPickerLoadingEffect = () => {
+ const [previousLoading, setPreviousLoading] = useState(false);
+
+ const loading = useRecoilComponentValueV2(
+ multipleRecordPickerIsLoadingComponentState,
+ );
+
+ const setMultipleRecordPickerShowSkeleton = useSetRecoilComponentStateV2(
+ multipleRecordPickerShouldShowSkeletonComponentState,
+ );
+
+ const [multipleRecordPickerIsFetchingMore] = useRecoilComponentStateV2(
+ multipleRecordPickerIsFetchingMoreComponentState,
+ );
+
+ const debouncedShowPickerSearchSkeleton = useDebouncedCallback(
+ () => setMultipleRecordPickerShowSkeleton(true),
+ 350,
+ );
+
+ useEffect(() => {
+ if (previousLoading !== loading) {
+ setPreviousLoading(loading);
+
+ if (loading) {
+ if (!multipleRecordPickerIsFetchingMore) {
+ debouncedShowPickerSearchSkeleton();
+ }
+ } else {
+ debouncedShowPickerSearchSkeleton.cancel();
+ setMultipleRecordPickerShowSkeleton(false);
+ }
+ }
+ }, [
+ loading,
+ previousLoading,
+ setMultipleRecordPickerShowSkeleton,
+ multipleRecordPickerIsFetchingMore,
+ debouncedShowPickerSearchSkeleton,
+ ]);
+
+ return null;
+};
diff --git a/packages/twenty-front/src/modules/object-record/record-picker/multiple-record-picker/components/MultipleRecordPickerMenuItems.tsx b/packages/twenty-front/src/modules/object-record/record-picker/multiple-record-picker/components/MultipleRecordPickerMenuItems.tsx
index ede0533e3..d5ad65ab4 100644
--- a/packages/twenty-front/src/modules/object-record/record-picker/multiple-record-picker/components/MultipleRecordPickerMenuItems.tsx
+++ b/packages/twenty-front/src/modules/object-record/record-picker/multiple-record-picker/components/MultipleRecordPickerMenuItems.tsx
@@ -1,28 +1,24 @@
-import styled from '@emotion/styled';
-
+import { RecordPickerInitialLoadingEmptyContainer } from '@/object-record/record-picker/components/RecordPickerInitialLoadingEmptyContainer';
+import { RecordPickerLoadingSkeletonList } from '@/object-record/record-picker/components/RecordPickerLoadingSkeletonList';
import { RecordPickerNoRecordFoundMenuItem } from '@/object-record/record-picker/components/RecordPickerNoRecordFoundMenuItem';
import { MultipleRecordPickerFetchMoreLoader } from '@/object-record/record-picker/multiple-record-picker/components/MultipleRecordPickerFetchMoreLoader';
import { MultipleRecordPickerMenuItem } from '@/object-record/record-picker/multiple-record-picker/components/MultipleRecordPickerMenuItem';
import { MultipleRecordPickerComponentInstanceContext } from '@/object-record/record-picker/multiple-record-picker/states/contexts/MultipleRecordPickerComponentInstanceContext';
import { multipleRecordPickerPickableMorphItemsComponentState } from '@/object-record/record-picker/multiple-record-picker/states/multipleRecordPickerPickableMorphItemsComponentState';
+import { multipleRecordPickerShouldShowInitialLoadingComponentState } from '@/object-record/record-picker/multiple-record-picker/states/multipleRecordPickerShouldShowInitialLoadingComponentState';
+import { multipleRecordPickerShouldShowSkeletonComponentState } from '@/object-record/record-picker/multiple-record-picker/states/multipleRecordPickerShouldShowSkeletonComponentState';
import { multipleRecordPickerPickableRecordIdsMatchingSearchComponentSelector } from '@/object-record/record-picker/multiple-record-picker/states/selectors/multipleRecordPickerPickableRecordIdsMatchingSearchComponentSelector';
import { getMultipleRecordPickerSelectableListId } from '@/object-record/record-picker/multiple-record-picker/utils/getMultipleRecordPickerSelectableListId';
import { RecordPickerPickableMorphItem } from '@/object-record/record-picker/types/RecordPickerPickableMorphItem';
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
import { DropdownHotkeyScope } from '@/ui/layout/dropdown/constants/DropdownHotkeyScope';
import { SelectableList } from '@/ui/layout/selectable-list/components/SelectableList';
-import { SelectableListItem } from '@/ui/layout/selectable-list/components/SelectableListItem';
import { useSelectableList } from '@/ui/layout/selectable-list/hooks/useSelectableList';
import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow';
import { useRecoilComponentCallbackStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackStateV2';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import { useRecoilCallback } from 'recoil';
-export const StyledSelectableItem = styled(SelectableListItem)`
- height: 100%;
- width: 100%;
-`;
-
type MultipleRecordPickerMenuItemsProps = {
onChange?: (morphItem: RecordPickerPickableMorphItem) => void;
focusId: string;
@@ -79,9 +75,24 @@ export const MultipleRecordPickerMenuItems = ({
[multipleRecordPickerPickableMorphItemsState],
);
+ const multipleRecordPickerShouldShowInitialLoading =
+ useRecoilComponentValueV2(
+ multipleRecordPickerShouldShowInitialLoadingComponentState,
+ );
+
+ const multipleRecordPickerShouldShowSkeleton = useRecoilComponentValueV2(
+ multipleRecordPickerShouldShowSkeletonComponentState,
+ );
+
+ const searchHasNoResults = pickableRecordIds.length === 0;
+
return (
- {pickableRecordIds.length === 0 ? (
+ {multipleRecordPickerShouldShowInitialLoading ? (
+
+ ) : multipleRecordPickerShouldShowSkeleton ? (
+
+ ) : searchHasNoResults ? (
) : (
{
+ const openMultipleRecordPicker = useRecoilCallback(
+ ({ set }) =>
+ (recordPickerComponentInstanceId: string) => {
+ set(
+ multipleRecordPickerShouldShowInitialLoadingComponentState.atomFamily(
+ {
+ instanceId: recordPickerComponentInstanceId,
+ },
+ ),
+ true,
+ );
+ set(
+ multipleRecordPickerShouldShowSkeletonComponentState.atomFamily({
+ instanceId: recordPickerComponentInstanceId,
+ }),
+ true,
+ );
+ setTimeout(() => {
+ set(
+ multipleRecordPickerShouldShowInitialLoadingComponentState.atomFamily(
+ {
+ instanceId: recordPickerComponentInstanceId,
+ },
+ ),
+ false,
+ );
+ }, 100);
+ },
+ [],
+ );
+
+ return {
+ openMultipleRecordPicker,
+ };
+};
diff --git a/packages/twenty-front/src/modules/object-record/record-picker/multiple-record-picker/states/multipleRecordPickerIsFetchingMoreComponentState.ts b/packages/twenty-front/src/modules/object-record/record-picker/multiple-record-picker/states/multipleRecordPickerIsFetchingMoreComponentState.ts
new file mode 100644
index 000000000..d07837ebe
--- /dev/null
+++ b/packages/twenty-front/src/modules/object-record/record-picker/multiple-record-picker/states/multipleRecordPickerIsFetchingMoreComponentState.ts
@@ -0,0 +1,9 @@
+import { MultipleRecordPickerComponentInstanceContext } from '@/object-record/record-picker/multiple-record-picker/states/contexts/MultipleRecordPickerComponentInstanceContext';
+import { createComponentStateV2 } from '@/ui/utilities/state/component-state/utils/createComponentStateV2';
+
+export const multipleRecordPickerIsFetchingMoreComponentState =
+ createComponentStateV2({
+ key: 'multipleRecordPickerIsFetchingMoreComponentState',
+ defaultValue: false,
+ componentInstanceContext: MultipleRecordPickerComponentInstanceContext,
+ });
diff --git a/packages/twenty-front/src/modules/object-record/record-picker/multiple-record-picker/states/multipleRecordPickerShouldShowInitialLoadingComponentState.ts b/packages/twenty-front/src/modules/object-record/record-picker/multiple-record-picker/states/multipleRecordPickerShouldShowInitialLoadingComponentState.ts
new file mode 100644
index 000000000..e7d2f3a11
--- /dev/null
+++ b/packages/twenty-front/src/modules/object-record/record-picker/multiple-record-picker/states/multipleRecordPickerShouldShowInitialLoadingComponentState.ts
@@ -0,0 +1,9 @@
+import { MultipleRecordPickerComponentInstanceContext } from '@/object-record/record-picker/multiple-record-picker/states/contexts/MultipleRecordPickerComponentInstanceContext';
+import { createComponentStateV2 } from '@/ui/utilities/state/component-state/utils/createComponentStateV2';
+
+export const multipleRecordPickerShouldShowInitialLoadingComponentState =
+ createComponentStateV2({
+ key: 'multipleRecordPickerShouldShowInitialLoadingComponentState',
+ defaultValue: false,
+ componentInstanceContext: MultipleRecordPickerComponentInstanceContext,
+ });
diff --git a/packages/twenty-front/src/modules/object-record/record-picker/multiple-record-picker/states/multipleRecordPickerShouldShowSkeletonComponentState.ts b/packages/twenty-front/src/modules/object-record/record-picker/multiple-record-picker/states/multipleRecordPickerShouldShowSkeletonComponentState.ts
new file mode 100644
index 000000000..431cd5f05
--- /dev/null
+++ b/packages/twenty-front/src/modules/object-record/record-picker/multiple-record-picker/states/multipleRecordPickerShouldShowSkeletonComponentState.ts
@@ -0,0 +1,9 @@
+import { MultipleRecordPickerComponentInstanceContext } from '@/object-record/record-picker/multiple-record-picker/states/contexts/MultipleRecordPickerComponentInstanceContext';
+import { createComponentStateV2 } from '@/ui/utilities/state/component-state/utils/createComponentStateV2';
+
+export const multipleRecordPickerShouldShowSkeletonComponentState =
+ createComponentStateV2({
+ key: 'multipleRecordPickerShouldShowSkeletonComponentState',
+ defaultValue: false,
+ componentInstanceContext: MultipleRecordPickerComponentInstanceContext,
+ });
diff --git a/packages/twenty-front/src/modules/object-record/record-picker/single-record-picker/components/SingleRecordPickerLoadingEffect.tsx b/packages/twenty-front/src/modules/object-record/record-picker/single-record-picker/components/SingleRecordPickerLoadingEffect.tsx
new file mode 100644
index 000000000..ed6256c19
--- /dev/null
+++ b/packages/twenty-front/src/modules/object-record/record-picker/single-record-picker/components/SingleRecordPickerLoadingEffect.tsx
@@ -0,0 +1,40 @@
+import { singleRecordPickerShouldShowSkeletonComponentState } from '@/object-record/record-picker/single-record-picker/states/singleRecordPickerShouldShowSkeletonComponentState';
+import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2';
+import { useEffect, useState } from 'react';
+import { useDebouncedCallback } from 'use-debounce';
+
+export const SingleRecordPickerLoadingEffect = ({
+ loading,
+}: {
+ loading: boolean;
+}) => {
+ const [previousLoading, setPreviousLoading] = useState(false);
+
+ const setSingleRecordPickerShouldShowSkeleton = useSetRecoilComponentStateV2(
+ singleRecordPickerShouldShowSkeletonComponentState,
+ );
+
+ const debouncedShowPickerSearchSkeleton = useDebouncedCallback(() => {
+ setSingleRecordPickerShouldShowSkeleton(true);
+ }, 350);
+
+ useEffect(() => {
+ if (previousLoading !== loading) {
+ setPreviousLoading(loading);
+
+ if (loading) {
+ debouncedShowPickerSearchSkeleton();
+ } else {
+ debouncedShowPickerSearchSkeleton.cancel();
+ setSingleRecordPickerShouldShowSkeleton(false);
+ }
+ }
+ }, [
+ loading,
+ previousLoading,
+ debouncedShowPickerSearchSkeleton,
+ setSingleRecordPickerShouldShowSkeleton,
+ ]);
+
+ return null;
+};
diff --git a/packages/twenty-front/src/modules/object-record/record-picker/single-record-picker/components/SingleRecordPickerMenuItems.tsx b/packages/twenty-front/src/modules/object-record/record-picker/single-record-picker/components/SingleRecordPickerMenuItems.tsx
index fbda69791..2588a24cd 100644
--- a/packages/twenty-front/src/modules/object-record/record-picker/single-record-picker/components/SingleRecordPickerMenuItems.tsx
+++ b/packages/twenty-front/src/modules/object-record/record-picker/single-record-picker/components/SingleRecordPickerMenuItems.tsx
@@ -1,10 +1,12 @@
import { isNonEmptyString, isUndefined } from '@sniptt/guards';
import { Key } from 'ts-key-enum';
-import { DropdownMenuSkeletonItem } from '@/ui/input/relation-picker/components/skeletons/DropdownMenuSkeletonItem';
import { SelectableList } from '@/ui/layout/selectable-list/components/SelectableList';
import { useSelectableList } from '@/ui/layout/selectable-list/hooks/useSelectableList';
+import { RecordPickerInitialLoadingEmptyContainer } from '@/object-record/record-picker/components/RecordPickerInitialLoadingEmptyContainer';
+import { RecordPickerLoadingSkeletonList } from '@/object-record/record-picker/components/RecordPickerLoadingSkeletonList';
+import { RecordPickerNoRecordFoundMenuItem } from '@/object-record/record-picker/components/RecordPickerNoRecordFoundMenuItem';
import { SingleRecordPickerMenuItem } from '@/object-record/record-picker/single-record-picker/components/SingleRecordPickerMenuItem';
import { SingleRecordPickerComponentInstanceContext } from '@/object-record/record-picker/single-record-picker/states/contexts/SingleRecordPickerComponentInstanceContext';
import { singleRecordPickerSelectedIdComponentState } from '@/object-record/record-picker/single-record-picker/states/singleRecordPickerSelectedIdComponentState';
@@ -17,44 +19,35 @@ import { useHotkeysOnFocusedElement } from '@/ui/utilities/hotkey/hooks/useHotke
import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow';
import { useRecoilComponentFamilyValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentFamilyValueV2';
import { useRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentStateV2';
+import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import { isDefined } from 'twenty-shared/utils';
import { IconComponent } from 'twenty-ui/display';
import { MenuItemSelect } from 'twenty-ui/navigation';
+import { singleRecordPickerShouldShowInitialLoadingComponentState } from '@/object-record/record-picker/single-record-picker/states/singleRecordPickerShouldShowInitialLoadingComponentState';
+import { singleRecordPickerShouldShowSkeletonComponentState } from '@/object-record/record-picker/single-record-picker/states/singleRecordPickerShouldShowSkeletonComponentState';
export type SingleRecordPickerMenuItemsProps = {
EmptyIcon?: IconComponent;
emptyLabel?: string;
recordsToSelect: SingleRecordPickerRecord[];
- loading?: boolean;
onCancel?: () => void;
onRecordSelected: (entity?: SingleRecordPickerRecord) => void;
selectedRecord?: SingleRecordPickerRecord;
focusId: string;
+ filteredSelectedRecords: SingleRecordPickerRecord[];
};
export const SingleRecordPickerMenuItems = ({
EmptyIcon,
emptyLabel,
recordsToSelect,
- loading,
onCancel,
onRecordSelected,
+ filteredSelectedRecords,
selectedRecord,
focusId,
}: SingleRecordPickerMenuItemsProps) => {
- const selectNone = emptyLabel
- ? {
- __typename: '',
- id: 'select-none',
- name: emptyLabel,
- }
- : null;
-
- const recordsInDropdown = [
- selectNone,
- selectedRecord,
- ...recordsToSelect,
- ].filter(
+ const recordsInDropdown = [selectedRecord, ...recordsToSelect].filter(
(entity): entity is SingleRecordPickerRecord =>
isDefined(entity) && isNonEmptyString(entity.name),
);
@@ -93,6 +86,17 @@ export const SingleRecordPickerMenuItems = ({
singleRecordPickerSelectedIdComponentState,
);
+ const singleRecordPickerShouldShowSkeleton = useRecoilComponentValueV2(
+ singleRecordPickerShouldShowSkeletonComponentState,
+ );
+
+ const singleRecordPickerShouldShowInitialLoading = useRecoilComponentValueV2(
+ singleRecordPickerShouldShowInitialLoadingComponentState,
+ );
+
+ const searchHasNoResults =
+ recordsToSelect.length === 0 && filteredSelectedRecords?.length === 0;
+
return (
- {loading ? (
-
- ) : (
- recordsInDropdown?.map((record) => {
- switch (record.id) {
- case 'select-none': {
- return (
- emptyLabel && (
- {
- setSelectedRecordId(undefined);
- onRecordSelected();
- }}
- >
- {
- setSelectedRecordId(undefined);
- onRecordSelected();
- }}
- LeftIcon={EmptyIcon}
- text={emptyLabel}
- selected={isUndefined(selectedRecordId)}
- focused={isSelectedSelectNoneButton}
- />
-
- )
- );
- }
- default: {
- return (
-
- );
- }
- }
- })
+ {emptyLabel && (
+ {
+ setSelectedRecordId(undefined);
+ onRecordSelected();
+ }}
+ >
+ {
+ setSelectedRecordId(undefined);
+ onRecordSelected();
+ }}
+ LeftIcon={EmptyIcon}
+ text={emptyLabel}
+ selected={isUndefined(selectedRecordId)}
+ focused={isSelectedSelectNoneButton}
+ />
+
)}
+ {singleRecordPickerShouldShowInitialLoading ? (
+
+ ) : singleRecordPickerShouldShowSkeleton ? (
+
+ ) : (
+ recordsInDropdown?.map((record) => (
+
+ ))
+ )}
+ {searchHasNoResults && }
);
};
diff --git a/packages/twenty-front/src/modules/object-record/record-picker/single-record-picker/components/SingleRecordPickerMenuItemsWithSearch.tsx b/packages/twenty-front/src/modules/object-record/record-picker/single-record-picker/components/SingleRecordPickerMenuItemsWithSearch.tsx
index 01e1b7452..b419f869c 100644
--- a/packages/twenty-front/src/modules/object-record/record-picker/single-record-picker/components/SingleRecordPickerMenuItemsWithSearch.tsx
+++ b/packages/twenty-front/src/modules/object-record/record-picker/single-record-picker/components/SingleRecordPickerMenuItemsWithSearch.tsx
@@ -1,6 +1,6 @@
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
import { useObjectPermissionsForObject } from '@/object-record/hooks/useObjectPermissionsForObject';
-import { RecordPickerNoRecordFoundMenuItem } from '@/object-record/record-picker/components/RecordPickerNoRecordFoundMenuItem';
+import { SingleRecordPickerLoadingEffect } from '@/object-record/record-picker/single-record-picker/components/SingleRecordPickerLoadingEffect';
import {
SingleRecordPickerMenuItems,
SingleRecordPickerMenuItemsProps,
@@ -16,7 +16,6 @@ import { DropdownMenuSearchInput } from '@/ui/layout/dropdown/components/Dropdow
import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownMenuSeparator';
import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
-import { isNonEmptyString } from '@sniptt/guards';
import { isDefined } from 'twenty-shared/utils';
import { IconPlus } from 'twenty-ui/display';
@@ -73,18 +72,13 @@ export const SingleRecordPickerMenuItemsWithSearch = ({
const hasObjectUpdatePermissions = objectPermissions.canUpdateObjectRecords;
- const searchHasNoResults =
- isNonEmptyString(recordPickerSearchFilter) &&
- records.recordsToSelect.length === 0 &&
- records.filteredSelectedRecords.length === 0 &&
- !records.loading;
-
const handleCreateNew = () => {
onCreate?.(recordPickerSearchFilter);
};
return (
<>
+
{layoutDirection === 'search-bar-on-bottom' && (
<>
{isDefined(onCreate) && hasObjectUpdatePermissions && (
@@ -101,12 +95,11 @@ export const SingleRecordPickerMenuItemsWithSearch = ({
)}
- {searchHasNoResults && }
- {searchHasNoResults && }
{isDefined(onCreate) && hasObjectUpdatePermissions && (
<>
diff --git a/packages/twenty-front/src/modules/object-record/record-picker/single-record-picker/hooks/useSingleRecordPickerOpen.ts b/packages/twenty-front/src/modules/object-record/record-picker/single-record-picker/hooks/useSingleRecordPickerOpen.ts
new file mode 100644
index 000000000..6a7990c0c
--- /dev/null
+++ b/packages/twenty-front/src/modules/object-record/record-picker/single-record-picker/hooks/useSingleRecordPickerOpen.ts
@@ -0,0 +1,38 @@
+import { singleRecordPickerShouldShowInitialLoadingComponentState } from '@/object-record/record-picker/single-record-picker/states/singleRecordPickerShouldShowInitialLoadingComponentState';
+import { singleRecordPickerShouldShowSkeletonComponentState } from '@/object-record/record-picker/single-record-picker/states/singleRecordPickerShouldShowSkeletonComponentState';
+import { useRecoilCallback } from 'recoil';
+
+export const useSingleRecordPickerOpen = () => {
+ const openSingleRecordPicker = useRecoilCallback(
+ ({ set }) =>
+ (recordPickerComponentInstanceId: string) => {
+ set(
+ singleRecordPickerShouldShowInitialLoadingComponentState.atomFamily({
+ instanceId: recordPickerComponentInstanceId,
+ }),
+ true,
+ );
+ set(
+ singleRecordPickerShouldShowSkeletonComponentState.atomFamily({
+ instanceId: recordPickerComponentInstanceId,
+ }),
+ true,
+ );
+ setTimeout(() => {
+ set(
+ singleRecordPickerShouldShowInitialLoadingComponentState.atomFamily(
+ {
+ instanceId: recordPickerComponentInstanceId,
+ },
+ ),
+ false,
+ );
+ }, 100);
+ },
+ [],
+ );
+
+ return {
+ openSingleRecordPicker,
+ };
+};
diff --git a/packages/twenty-front/src/modules/object-record/record-picker/single-record-picker/hooks/useSingleRecordPickerSearch.ts b/packages/twenty-front/src/modules/object-record/record-picker/single-record-picker/hooks/useSingleRecordPickerSearch.ts
index 16aa4a28d..dc24e964d 100644
--- a/packages/twenty-front/src/modules/object-record/record-picker/single-record-picker/hooks/useSingleRecordPickerSearch.ts
+++ b/packages/twenty-front/src/modules/object-record/record-picker/single-record-picker/hooks/useSingleRecordPickerSearch.ts
@@ -23,6 +23,7 @@ export const useSingleRecordPickerSearch = (
singleRecordPickerSelectedIdComponentState,
recordPickerComponentInstanceId,
);
+
const debouncedSetSearchFilter = useDebouncedCallback(
setRecordPickerSearchFilter,
100,
diff --git a/packages/twenty-front/src/modules/object-record/record-picker/single-record-picker/states/singleRecordPickerShouldShowInitialLoadingComponentState.ts b/packages/twenty-front/src/modules/object-record/record-picker/single-record-picker/states/singleRecordPickerShouldShowInitialLoadingComponentState.ts
new file mode 100644
index 000000000..81dd2f46e
--- /dev/null
+++ b/packages/twenty-front/src/modules/object-record/record-picker/single-record-picker/states/singleRecordPickerShouldShowInitialLoadingComponentState.ts
@@ -0,0 +1,9 @@
+import { SingleRecordPickerComponentInstanceContext } from '@/object-record/record-picker/single-record-picker/states/contexts/SingleRecordPickerComponentInstanceContext';
+import { createComponentStateV2 } from '@/ui/utilities/state/component-state/utils/createComponentStateV2';
+
+export const singleRecordPickerShouldShowInitialLoadingComponentState =
+ createComponentStateV2({
+ key: 'singleRecordPickerShouldShowInitialLoadingComponentState',
+ defaultValue: false,
+ componentInstanceContext: SingleRecordPickerComponentInstanceContext,
+ });
diff --git a/packages/twenty-front/src/modules/object-record/record-picker/single-record-picker/states/singleRecordPickerShouldShowSkeletonComponentState.ts b/packages/twenty-front/src/modules/object-record/record-picker/single-record-picker/states/singleRecordPickerShouldShowSkeletonComponentState.ts
new file mode 100644
index 000000000..073c86517
--- /dev/null
+++ b/packages/twenty-front/src/modules/object-record/record-picker/single-record-picker/states/singleRecordPickerShouldShowSkeletonComponentState.ts
@@ -0,0 +1,9 @@
+import { SingleRecordPickerComponentInstanceContext } from '@/object-record/record-picker/single-record-picker/states/contexts/SingleRecordPickerComponentInstanceContext';
+import { createComponentStateV2 } from '@/ui/utilities/state/component-state/utils/createComponentStateV2';
+
+export const singleRecordPickerShouldShowSkeletonComponentState =
+ createComponentStateV2({
+ key: 'singleRecordPickerShouldShowSkeletonComponentState',
+ defaultValue: false,
+ componentInstanceContext: SingleRecordPickerComponentInstanceContext,
+ });
diff --git a/packages/twenty-front/src/modules/object-record/record-show/record-detail-section/components/RecordDetailRelationSectionDropdownToMany.tsx b/packages/twenty-front/src/modules/object-record/record-show/record-detail-section/components/RecordDetailRelationSectionDropdownToMany.tsx
index 979635b2a..21cad55d8 100644
--- a/packages/twenty-front/src/modules/object-record/record-show/record-detail-section/components/RecordDetailRelationSectionDropdownToMany.tsx
+++ b/packages/twenty-front/src/modules/object-record/record-show/record-detail-section/components/RecordDetailRelationSectionDropdownToMany.tsx
@@ -7,6 +7,7 @@ import { useAddNewRecordAndOpenRightDrawer } from '@/object-record/record-field/
import { useUpdateRelationFromManyFieldInput } from '@/object-record/record-field/meta-types/input/hooks/useUpdateRelationFromManyFieldInput';
import { FieldRelationMetadata } from '@/object-record/record-field/types/FieldMetadata';
import { MultipleRecordPicker } from '@/object-record/record-picker/multiple-record-picker/components/MultipleRecordPicker';
+import { useMultipleRecordPickerOpen } from '@/object-record/record-picker/multiple-record-picker/hooks/useMultipleRecordPickerOpen';
import { useMultipleRecordPickerPerformSearch } from '@/object-record/record-picker/multiple-record-picker/hooks/useMultipleRecordPickerPerformSearch';
import { multipleRecordPickerPickableMorphItemsComponentState } from '@/object-record/record-picker/multiple-record-picker/states/multipleRecordPickerPickableMorphItemsComponentState';
import { multipleRecordPickerSearchFilterComponentState } from '@/object-record/record-picker/multiple-record-picker/states/multipleRecordPickerSearchFilterComponentState';
@@ -71,6 +72,8 @@ export const RecordDetailRelationSectionDropdownToMany = () => {
const { performSearch: multipleRecordPickerPerformSearch } =
useMultipleRecordPickerPerformSearch();
+ const { openMultipleRecordPicker } = useMultipleRecordPickerOpen();
+
const handleCloseRelationPickerDropdown = useCallback(() => {
setMultipleRecordPickerSearchFilter('');
}, [setMultipleRecordPickerSearchFilter]);
@@ -99,6 +102,8 @@ export const RecordDetailRelationSectionDropdownToMany = () => {
})),
);
+ openMultipleRecordPicker(dropdownId);
+
multipleRecordPickerPerformSearch({
multipleRecordPickerInstanceId: dropdownId,
forceSearchFilter: '',
diff --git a/packages/twenty-front/src/modules/object-record/record-show/record-detail-section/components/RecordDetailRelationSectionDropdownToOne.tsx b/packages/twenty-front/src/modules/object-record/record-show/record-detail-section/components/RecordDetailRelationSectionDropdownToOne.tsx
index f6f5c0589..2343e32ad 100644
--- a/packages/twenty-front/src/modules/object-record/record-show/record-detail-section/components/RecordDetailRelationSectionDropdownToOne.tsx
+++ b/packages/twenty-front/src/modules/object-record/record-show/record-detail-section/components/RecordDetailRelationSectionDropdownToOne.tsx
@@ -8,6 +8,7 @@ import { usePersistField } from '@/object-record/record-field/hooks/usePersistFi
import { useAddNewRecordAndOpenRightDrawer } from '@/object-record/record-field/meta-types/input/hooks/useAddNewRecordAndOpenRightDrawer';
import { FieldRelationMetadata } from '@/object-record/record-field/types/FieldMetadata';
import { SingleRecordPicker } from '@/object-record/record-picker/single-record-picker/components/SingleRecordPicker';
+import { useSingleRecordPickerOpen } from '@/object-record/record-picker/single-record-picker/hooks/useSingleRecordPickerOpen';
import { singleRecordPickerSearchFilterComponentState } from '@/object-record/record-picker/single-record-picker/states/singleRecordPickerSearchFilterComponentState';
import { singleRecordPickerSelectedIdComponentState } from '@/object-record/record-picker/single-record-picker/states/singleRecordPickerSelectedIdComponentState';
import { SingleRecordPickerRecord } from '@/object-record/record-picker/single-record-picker/types/SingleRecordPickerRecord';
@@ -87,8 +88,12 @@ export const RecordDetailRelationSectionDropdownToOne = () => {
recordId,
});
+ const { openSingleRecordPicker } = useSingleRecordPickerOpen();
+
const handleOpenRelationPickerDropdown = () => {
setSingleRecordPickerSearchFilter('');
+ openSingleRecordPicker(dropdownId);
+
if (relationRecords.length > 0) {
setSingleRecordPickerSelectedId(relationRecords[0]?.id);
}
diff --git a/packages/twenty-front/src/modules/ui/input/relation-picker/components/skeletons/DropdownMenuSkeletonItem.tsx b/packages/twenty-front/src/modules/ui/input/relation-picker/components/skeletons/DropdownMenuSkeletonItem.tsx
index 820831f7c..6f495fa0b 100644
--- a/packages/twenty-front/src/modules/ui/input/relation-picker/components/skeletons/DropdownMenuSkeletonItem.tsx
+++ b/packages/twenty-front/src/modules/ui/input/relation-picker/components/skeletons/DropdownMenuSkeletonItem.tsx
@@ -1,4 +1,5 @@
import { SKELETON_LOADER_HEIGHT_SIZES } from '@/activities/components/SkeletonLoader';
+import { CSSWidth } from '@/ui/types/CSSWidth';
import { useTheme } from '@emotion/react';
import styled from '@emotion/styled';
import Skeleton, { SkeletonTheme } from 'react-loading-skeleton';
@@ -6,15 +7,24 @@ import Skeleton, { SkeletonTheme } from 'react-loading-skeleton';
const StyledDropdownMenuSkeletonContainer = styled.div`
--horizontal-padding: ${({ theme }) => theme.spacing(1)};
--vertical-padding: ${({ theme }) => theme.spacing(2)};
- align-items: center;
+
border-radius: ${({ theme }) => theme.border.radius.sm};
gap: ${({ theme }) => theme.spacing(2)};
- height: calc(32px - 2 * var(--vertical-padding));
- padding: var(--vertical-padding) var(--horizontal-padding);
- width: calc(100% - 2 * var(--horizontal-padding));
+ box-sizing: border-box;
+
+ flex-shrink: 0;
+
+ padding-left: var(--horizontal-padding);
+ padding-right: var(--horizontal-padding);
+
+ height: ${({ theme }) => theme.spacing(8)};
`;
-export const DropdownMenuSkeletonItem = () => {
+export const DropdownMenuSkeletonItem = ({
+ width = '100%',
+}: {
+ width?: CSSWidth;
+}) => {
const theme = useTheme();
return (
@@ -22,7 +32,11 @@ export const DropdownMenuSkeletonItem = () => {
baseColor={theme.background.quaternary}
highlightColor={theme.background.secondary}
>
-
+
);
diff --git a/packages/twenty-front/src/modules/ui/layout/dropdown/components/__stories__/Dropdown.stories.tsx b/packages/twenty-front/src/modules/ui/layout/dropdown/components/__stories__/Dropdown.stories.tsx
index cec0b9c4e..e5cbb3273 100644
--- a/packages/twenty-front/src/modules/ui/layout/dropdown/components/__stories__/Dropdown.stories.tsx
+++ b/packages/twenty-front/src/modules/ui/layout/dropdown/components/__stories__/Dropdown.stories.tsx
@@ -6,6 +6,7 @@ import { useState } from 'react';
import { DropdownMenuSkeletonItem } from '@/ui/input/relation-picker/components/skeletons/DropdownMenuSkeletonItem';
+import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent';
import { DropdownMenuHeaderLeftComponent } from '@/ui/layout/dropdown/components/DropdownMenuHeader/internal/DropdownMenuHeaderLeftComponent';
import { Modal } from '@/ui/layout/modal/components/Modal';
import { ModalHotkeyScope } from '@/ui/layout/modal/components/types/ModalHotkeyScope';
@@ -84,7 +85,9 @@ const StyledEmptyDropdownContent = styled.div`
export const Empty: Story = {
args: {
dropdownComponents: (
-
+
+
+
),
},
play: async () => {
@@ -155,26 +158,28 @@ const FakeSelectableMenuItemList = ({ hasAvatar }: { hasAvatar?: boolean }) => {
const [selectedItem, setSelectedItem] = useState(null);
return (
- <>
- {optionsMock.map((item) => (
- setSelectedItem(item.id)}
- avatar={
- hasAvatar ? (
-
- ) : undefined
- }
- text={item.name}
- />
- ))}
- >
+
+
+ {optionsMock.map((item) => (
+ setSelectedItem(item.id)}
+ avatar={
+ hasAvatar ? (
+
+ ) : undefined
+ }
+ text={item.name}
+ />
+ ))}
+
+
);
};
@@ -184,31 +189,33 @@ const FakeCheckableMenuItemList = ({ hasAvatar }: { hasAvatar?: boolean }) => {
>({});
return (
- <>
- {optionsMock.map((item) => (
-
- setSelectedItemsById((previous) => ({
- ...previous,
- [item.id]: checked,
- }))
- }
- avatar={
- hasAvatar ? (
-
- ) : undefined
- }
- text={item.name}
- />
- ))}
- >
+
+
+ {optionsMock.map((item) => (
+
+ setSelectedItemsById((previous) => ({
+ ...previous,
+ [item.id]: checked,
+ }))
+ }
+ avatar={
+ hasAvatar ? (
+
+ ) : undefined
+ }
+ text={item.name}
+ />
+ ))}
+
+
);
};
@@ -227,7 +234,7 @@ export const WithHeaders: Story = {
decorators: [WithContentBelowDecorator],
args: {
dropdownComponents: (
- <>
+
@@ -250,7 +257,7 @@ export const WithHeaders: Story = {
))}
- >
+
),
},
play: playInteraction,
@@ -260,13 +267,13 @@ export const SearchWithLoadingMenu: Story = {
decorators: [WithContentBelowDecorator],
args: {
dropdownComponents: (
- <>
+
- >
+
),
},
play: async () => {
@@ -292,7 +299,7 @@ export const WithInput: Story = {
decorators: [WithContentBelowDecorator],
args: {
dropdownComponents: (
- <>
+
@@ -300,7 +307,7 @@ export const WithInput: Story = {
))}
- >
+
),
},
play: playInteraction,
@@ -309,11 +316,7 @@ export const WithInput: Story = {
export const SelectableMenuItemWithAvatar: Story = {
decorators: [WithContentBelowDecorator],
args: {
- dropdownComponents: (
-
-
-
- ),
+ dropdownComponents: ,
},
play: playInteraction,
};
@@ -321,11 +324,7 @@ export const SelectableMenuItemWithAvatar: Story = {
export const CheckableMenuItemWithAvatar: Story = {
decorators: [WithContentBelowDecorator],
args: {
- dropdownComponents: (
-
-
-
- ),
+ dropdownComponents: ,
},
play: playInteraction,
};
@@ -354,11 +353,9 @@ const ModalWithDropdown = () => {
dropdownId="modal-dropdown-test"
isDropdownInModal={true}
dropdownComponents={
-
-
-
-
-
+
+
+
}
/>
diff --git a/packages/twenty-front/src/modules/ui/types/CSSWidth.ts b/packages/twenty-front/src/modules/ui/types/CSSWidth.ts
new file mode 100644
index 000000000..1f2524b16
--- /dev/null
+++ b/packages/twenty-front/src/modules/ui/types/CSSWidth.ts
@@ -0,0 +1 @@
+export type CSSWidth = `${number}%` | `${number}px`;