diff --git a/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/input/components/MorphRelationManyToOneFieldInput.tsx b/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/input/components/MorphRelationManyToOneFieldInput.tsx index d1f1901ccfd..d4c8174e769 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/input/components/MorphRelationManyToOneFieldInput.tsx +++ b/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/input/components/MorphRelationManyToOneFieldInput.tsx @@ -9,6 +9,7 @@ import { recordFieldInputLayoutDirectionLoadingComponentState } from '@/object-r import { isFieldMorphRelationManyToOne } from '@/object-record/record-field/ui/types/guards/isFieldMorphRelationManyToOne'; import { SingleRecordPicker } from '@/object-record/record-picker/single-record-picker/components/SingleRecordPicker'; import { type RecordPickerPickableMorphItem } from '@/object-record/record-picker/types/RecordPickerPickableMorphItem'; +import { GenericDropdownContentWidth } from '@/ui/layout/dropdown/constants/GenericDropdownContentWidth'; import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow'; import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue'; import { useContext } from 'react'; @@ -90,6 +91,7 @@ export const MorphRelationManyToOneFieldInput = () => { ? 'search-bar-on-top' : 'search-bar-on-bottom' } + dropdownWidth={GenericDropdownContentWidth.ExtraLarge} /> ); }; diff --git a/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/input/components/MorphRelationOneToManyFieldInput.tsx b/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/input/components/MorphRelationOneToManyFieldInput.tsx index a0e84a13b00..be242e7c1f1 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/input/components/MorphRelationOneToManyFieldInput.tsx +++ b/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/input/components/MorphRelationOneToManyFieldInput.tsx @@ -5,6 +5,7 @@ import { useUpdateMorphRelationOneToManyFieldInput } from '@/object-record/recor import { RecordFieldComponentInstanceContext } from '@/object-record/record-field/ui/states/contexts/RecordFieldComponentInstanceContext'; import { recordFieldInputLayoutDirectionComponentState } from '@/object-record/record-field/ui/states/recordFieldInputLayoutDirectionComponentState'; import { MultipleRecordPicker } from '@/object-record/record-picker/multiple-record-picker/components/MultipleRecordPicker'; +import { GenericDropdownContentWidth } from '@/ui/layout/dropdown/constants/GenericDropdownContentWidth'; import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow'; import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue'; @@ -40,6 +41,7 @@ export const MorphRelationOneToManyFieldInput = () => { ? 'search-bar-on-top' : 'search-bar-on-bottom' } + dropdownWidth={GenericDropdownContentWidth.ExtraLarge} /> ); }; diff --git a/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/input/components/RelationOneToManyFieldInput.tsx b/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/input/components/RelationOneToManyFieldInput.tsx index 225ab7a2d6c..6d30151ebaf 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/input/components/RelationOneToManyFieldInput.tsx +++ b/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/input/components/RelationOneToManyFieldInput.tsx @@ -10,6 +10,7 @@ import { FieldContext } from '@/object-record/record-field/ui/contexts/FieldCont import { FieldInputEventContext } from '@/object-record/record-field/ui/contexts/FieldInputEventContext'; import { useRelationField } from '@/object-record/record-field/ui/meta-types/hooks/useRelationField'; import { useAddNewRecordAndOpenRightDrawer } from '@/object-record/record-field/ui/meta-types/input/hooks/useAddNewRecordAndOpenRightDrawer'; +import { useUpdateRelationOneToManyFieldInput } from '@/object-record/record-field/ui/meta-types/input/hooks/useUpdateRelationOneToManyFieldInput'; import { RecordFieldComponentInstanceContext } from '@/object-record/record-field/ui/states/contexts/RecordFieldComponentInstanceContext'; import { recordFieldInputLayoutDirectionComponentState } from '@/object-record/record-field/ui/states/recordFieldInputLayoutDirectionComponentState'; import { type FieldDefinition } from '@/object-record/record-field/ui/types/FieldDefinition'; @@ -22,7 +23,6 @@ import { useRecoilComponentCallbackState } from '@/ui/utilities/state/component- import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue'; import { useRecoilCallback } from 'recoil'; import { isDefined } from 'twenty-shared/utils'; -import { useUpdateRelationOneToManyFieldInput } from '@/object-record/record-field/ui/meta-types/input/hooks/useUpdateRelationOneToManyFieldInput'; export const RelationOneToManyFieldInput = () => { const { fieldDefinition, recordId } = useContext(FieldContext); 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 803d6cd3097..6de3e1779eb 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 @@ -29,6 +29,7 @@ type MultipleRecordPickerProps = { onClickOutside: () => void; focusId: string; objectMetadataItemIdForCreate?: string; + dropdownWidth?: number; }; export const MultipleRecordPicker = ({ @@ -40,6 +41,7 @@ export const MultipleRecordPicker = ({ componentInstanceId, focusId, objectMetadataItemIdForCreate, + dropdownWidth, }: MultipleRecordPickerProps) => { const selectableListComponentInstanceId = getMultipleRecordPickerSelectableListId(componentInstanceId); @@ -129,7 +131,7 @@ export const MultipleRecordPicker = ({ containerRef={containerRef} onClickOutside={handleClickOutside} /> - + {layoutDirection === 'search-bar-on-bottom' && ( <> {createNewButtonSection} diff --git a/packages/twenty-front/src/modules/object-record/record-picker/multiple-record-picker/components/MultipleRecordPickerMenuItemContent.tsx b/packages/twenty-front/src/modules/object-record/record-picker/multiple-record-picker/components/MultipleRecordPickerMenuItemContent.tsx index 832a548d42c..0d34a5dba30 100644 --- a/packages/twenty-front/src/modules/object-record/record-picker/multiple-record-picker/components/MultipleRecordPickerMenuItemContent.tsx +++ b/packages/twenty-front/src/modules/object-record/record-picker/multiple-record-picker/components/MultipleRecordPickerMenuItemContent.tsx @@ -10,8 +10,12 @@ import { SelectableListItem } from '@/ui/layout/selectable-list/components/Selec import { isSelectedItemIdComponentFamilySelector } from '@/ui/layout/selectable-list/states/selectors/isSelectedItemIdComponentFamilySelector'; import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow'; import { useRecoilComponentFamilyValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentFamilyValue'; +import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue'; +import { capitalize } from 'twenty-shared/utils'; import { Avatar } from 'twenty-ui/display'; import { MenuItemMultiSelectAvatar } from 'twenty-ui/navigation'; + +import { multipleRecordPickerSearchableObjectMetadataItemsComponentState } from '@/object-record/record-picker/multiple-record-picker/states/multipleRecordPickerSearchableObjectMetadataItemsComponentState'; import { type SearchRecord } from '~/generated-metadata/graphql'; export const StyledSelectableItem = styled(SelectableListItem)` @@ -62,6 +66,13 @@ export const MultipleRecordPickerMenuItemContent = ({ searchRecord.label?.trim() || `Untitled ${objectMetadataItem.labelSingular}`; + const searchableObjectMetadataItems = useRecoilComponentValue( + multipleRecordPickerSearchableObjectMetadataItemsComponentState, + componentInstanceId, + ); + + const showObjectName = searchableObjectMetadataItems.length > 1; + return ( } text={displayText} + contextualText={ + showObjectName + ? capitalize(searchRecord.objectNameSingular) + : undefined + } /> ); diff --git a/packages/twenty-front/src/modules/object-record/record-picker/single-record-picker/components/SingleRecordPickerMenuItem.tsx b/packages/twenty-front/src/modules/object-record/record-picker/single-record-picker/components/SingleRecordPickerMenuItem.tsx index a87b5c0defb..216a2df517e 100644 --- a/packages/twenty-front/src/modules/object-record/record-picker/single-record-picker/components/SingleRecordPickerMenuItem.tsx +++ b/packages/twenty-front/src/modules/object-record/record-picker/single-record-picker/components/SingleRecordPickerMenuItem.tsx @@ -3,14 +3,16 @@ import styled from '@emotion/styled'; import { getAvatarType } from '@/object-metadata/utils/getAvatarType'; import { searchRecordStoreFamilyState } from '@/object-record/record-picker/multiple-record-picker/states/searchRecordStoreComponentFamilyState'; import { SingleRecordPickerComponentInstanceContext } from '@/object-record/record-picker/single-record-picker/states/contexts/SingleRecordPickerComponentInstanceContext'; +import { singleRecordPickerSearchableObjectMetadataItemsComponentState } from '@/object-record/record-picker/single-record-picker/states/singleRecordPickerSearchableObjectMetadataItemsComponentState'; import { getSingleRecordPickerSelectableListId } from '@/object-record/record-picker/single-record-picker/utils/getSingleRecordPickerSelectableListId'; import { type RecordPickerPickableMorphItem } from '@/object-record/record-picker/types/RecordPickerPickableMorphItem'; import { SelectableListItem } from '@/ui/layout/selectable-list/components/SelectableListItem'; import { isSelectedItemIdComponentFamilySelector } from '@/ui/layout/selectable-list/states/selectors/isSelectedItemIdComponentFamilySelector'; import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow'; import { useRecoilComponentFamilyValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentFamilyValue'; +import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue'; import { useRecoilValue } from 'recoil'; -import { isDefined } from 'twenty-shared/utils'; +import { capitalize, isDefined } from 'twenty-shared/utils'; import { Avatar } from 'twenty-ui/display'; import { MenuItemSelectAvatar } from 'twenty-ui/navigation'; @@ -47,10 +49,17 @@ export const SingleRecordPickerMenuItem = ({ searchRecordStoreFamilyState(morphItem.recordId), ); + const searchableObjectMetadataItems = useRecoilComponentValue( + singleRecordPickerSearchableObjectMetadataItemsComponentState, + recordPickerComponentInstanceId, + ); + if (!isDefined(searchRecord)) { return null; } + const showObjectName = searchableObjectMetadataItems.length > 1; + return ( } + contextualText={ + showObjectName + ? capitalize(searchRecord.objectNameSingular) + : undefined + } /> ); diff --git a/packages/twenty-front/src/modules/object-record/record-picker/single-record-picker/hooks/useSingleRecordPickerPerformSearch.ts b/packages/twenty-front/src/modules/object-record/record-picker/single-record-picker/hooks/useSingleRecordPickerPerformSearch.ts index f7f311a50d4..d7402e154e1 100644 --- a/packages/twenty-front/src/modules/object-record/record-picker/single-record-picker/hooks/useSingleRecordPickerPerformSearch.ts +++ b/packages/twenty-front/src/modules/object-record/record-picker/single-record-picker/hooks/useSingleRecordPickerPerformSearch.ts @@ -2,7 +2,10 @@ import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadat import { DEFAULT_SEARCH_REQUEST_LIMIT } from '@/object-record/constants/DefaultSearchRequestLimit'; import { useObjectRecordSearchRecords } from '@/object-record/hooks/useObjectRecordSearchRecords'; import { searchRecordStoreFamilyState } from '@/object-record/record-picker/multiple-record-picker/states/searchRecordStoreComponentFamilyState'; +import { SingleRecordPickerComponentInstanceContext } from '@/object-record/record-picker/single-record-picker/states/contexts/SingleRecordPickerComponentInstanceContext'; +import { singleRecordPickerSearchableObjectMetadataItemsComponentState } from '@/object-record/record-picker/single-record-picker/states/singleRecordPickerSearchableObjectMetadataItemsComponentState'; import { type RecordPickerPickableMorphItem } from '@/object-record/record-picker/types/RecordPickerPickableMorphItem'; +import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow'; import { useRecoilCallback } from 'recoil'; import { CustomError, isDefined } from 'twenty-shared/utils'; import { type SearchQuery } from '~/generated/graphql'; @@ -23,6 +26,12 @@ export const useSingleRecordPickerPerformSearch = ({ pickableMorphItems: RecordPickerPickableMorphItem[]; loading: boolean; } => { + const singleRecordPickerInstanceId = useAvailableComponentInstanceIdOrThrow( + SingleRecordPickerComponentInstanceContext, + ); + + const { objectMetadataItems } = useObjectMetadataItems(); + const onSearchRecordsCompleted = useRecoilCallback( ({ set }) => (data: SearchQuery) => { @@ -34,11 +43,17 @@ export const useSingleRecordPickerPerformSearch = ({ record: undefined, }); }); + + set( + singleRecordPickerSearchableObjectMetadataItemsComponentState.atomFamily( + { instanceId: singleRecordPickerInstanceId }, + ), + objectMetadataItems, + ); }, - [], + [objectMetadataItems, singleRecordPickerInstanceId], ); - const { objectMetadataItems } = useObjectMetadataItems(); const selectedIdsFilter = { id: { in: selectedIds } }; const { loading: selectedRecordsLoading, searchRecords: selectedRecords } = diff --git a/packages/twenty-front/src/modules/object-record/record-picker/single-record-picker/states/singleRecordPickerSearchableObjectMetadataItemsComponentState.ts b/packages/twenty-front/src/modules/object-record/record-picker/single-record-picker/states/singleRecordPickerSearchableObjectMetadataItemsComponentState.ts new file mode 100644 index 00000000000..485bb6f85d0 --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/record-picker/single-record-picker/states/singleRecordPickerSearchableObjectMetadataItemsComponentState.ts @@ -0,0 +1,10 @@ +import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; +import { SingleRecordPickerComponentInstanceContext } from '@/object-record/record-picker/single-record-picker/states/contexts/SingleRecordPickerComponentInstanceContext'; +import { createComponentState } from '@/ui/utilities/state/component-state/utils/createComponentState'; + +export const singleRecordPickerSearchableObjectMetadataItemsComponentState = + createComponentState({ + key: 'singleRecordPickerSearchableObjectMetadataItemsComponentState', + defaultValue: [], + componentInstanceContext: SingleRecordPickerComponentInstanceContext, + }); diff --git a/packages/twenty-ui/src/navigation/menu/menu-item/components/MenuItemMultiSelectAvatar.tsx b/packages/twenty-ui/src/navigation/menu/menu-item/components/MenuItemMultiSelectAvatar.tsx index 27ca8cff317..c6fc82d0407 100644 --- a/packages/twenty-ui/src/navigation/menu/menu-item/components/MenuItemMultiSelectAvatar.tsx +++ b/packages/twenty-ui/src/navigation/menu/menu-item/components/MenuItemMultiSelectAvatar.tsx @@ -23,6 +23,9 @@ const StyledTextContainer = styled.div` align-items: center; flex: 1 0 0; gap: ${({ theme }) => theme.spacing(1)}; + max-width: 100%; + text-overflow: ellipsis; + overflow: hidden; `; type MenuItemMultiSelectAvatarProps = { diff --git a/packages/twenty-ui/src/navigation/menu/menu-item/components/MenuItemSelectAvatar.tsx b/packages/twenty-ui/src/navigation/menu/menu-item/components/MenuItemSelectAvatar.tsx index 875e3819b4e..36411bb69c9 100644 --- a/packages/twenty-ui/src/navigation/menu/menu-item/components/MenuItemSelectAvatar.tsx +++ b/packages/twenty-ui/src/navigation/menu/menu-item/components/MenuItemSelectAvatar.tsx @@ -29,6 +29,9 @@ const StyledTextContainer = styled.div` align-items: center; flex: 1 0 0; gap: ${({ theme }) => theme.spacing(1)}; + max-width: 100%; + text-overflow: ellipsis; + overflow: hidden; `; export const MenuItemSelectAvatar = ({