Morph-picker-MultiSelect-picker (#14113)

# 🚀 Multi-Select Morph Relations / One to Many

Adds **multi-select picker support** for morph relations on the **One To
Many** side, allowing users to select multiple related records from
different object types.

### 🔑 Main Changes (TL;DR)
- **New Component**: `MorphRelationOneToManyFieldInput` for
multi-selection
- **Enhanced Hooks**: `useAttachMorphRelatedRecordFromRecord` &
`useDetachMorphRelatedRecordFromRecord`
- **Better UX**: Optimistic updates, improved search, loading states
- **24 files changed**: Mostly additions (1,277 lines added, 56 removed)


**Summary**: This adds multi-select capability to morph relations with
proper state management and optimistic updates. The changes are mostly
additive and follow existing patterns.


**TODO** :
- optimistic rendering not working currently
- single select picker (many to one)
This commit is contained in:
Guillim
2025-08-29 10:04:51 +02:00
committed by GitHub
parent ab9cabce04
commit 0e6ce123c9
20 changed files with 1127 additions and 56 deletions
@@ -0,0 +1,17 @@
import { useRecoilValue } from 'recoil';
import { objectMetadataItemsBySingularNameSelector } from '@/object-metadata/states/objectMetadataItemsBySingularNameSelector';
import { isDefined } from 'twenty-shared/utils';
export const useDoObjectMetadataItemsExist = (
objectNameSingulars: string[],
) => {
const objectMetadataItems = useRecoilValue(
objectMetadataItemsBySingularNameSelector(objectNameSingulars),
);
return objectMetadataItems.every((objectMetadataItem) =>
isDefined(objectMetadataItem),
);
};
@@ -0,0 +1,27 @@
import { selectorFamily } from 'recoil';
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { isDefined } from 'twenty-shared/utils';
export const objectMetadataItemsBySingularNameSelector = selectorFamily<
ObjectMetadataItem[],
string[]
>({
key: 'objectMetadataItemsSelector',
get:
(objectNameSingulars: string[]) =>
({ get }) => {
const objectMetadataItems = get(objectMetadataItemsState);
return objectNameSingulars
.map(
(objectNameSingular) =>
objectMetadataItems.find(
(objectMetadataItem) =>
objectMetadataItem.nameSingular === objectNameSingular,
) ?? null,
)
.filter(isDefined);
},
});