Morph-front-settings (#13872)

# Implement Morph Frontend Settings

Possiblity to create a morh relation from the Settings !

## Overview
Morph object system with new frontend settings UI for configuring morph
relations.

## What Changed
- **New Components**: `SettingsDataModelFieldMorphRelationForm`,
`SettingsMorphRelationMultiSelect`
- **Form System**: React Hook Form + Zod validation for morph relation
configuration
- **Preview System**: Live preview of field configurations with
validation
- **State Management**: Enhanced component state handling for complex
form interactions

## Technical Details
- **Architecture**: Restructured field input/display components for
better maintainability
- **Types**: Added comprehensive TypeScript types for morph relations
(`isFieldMorphRelation`, etc.)
- **Validation**: Dynamic validation rules based on field types and
relation patterns
- **Performance**: Optimized rendering with proper state management

---------

Co-authored-by: Charles Bochet <charles@twenty.com>
This commit is contained in:
Guillim
2025-08-19 16:37:26 +02:00
committed by GitHub
parent 9eaede6e1e
commit 90c237a185
31 changed files with 1209 additions and 83 deletions
@@ -4,15 +4,22 @@ import styled from '@emotion/styled';
import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { SettingsDataModelObjectTypeTag } from '@/settings/data-model/objects/components/SettingsDataModelObjectTypeTag';
import { getObjectTypeLabel } from '@/settings/data-model/utils/getObjectTypeLabel';
import { OverflowingTextWithTooltip, useIcons } from 'twenty-ui/display';
import {
IconBox,
OverflowingTextWithTooltip,
useIcons,
} from 'twenty-ui/display';
export type SettingsDataModelObjectSummaryProps = {
export type SettingsDataModelObjectPreviewProps = {
className?: string;
objectMetadataItem: ObjectMetadataItem;
objectMetadataItems: Pick<
ObjectMetadataItem,
'icon' | 'labelSingular' | 'labelPlural' | 'isCustom' | 'isRemote'
>[];
pluralizeLabel?: boolean;
};
const StyledObjectSummary = styled.div`
const StyledObjectPreview = styled.div`
align-items: center;
display: flex;
justify-content: space-between;
@@ -24,36 +31,124 @@ const StyledObjectName = styled.div`
max-width: 60%;
`;
const StyledOverflowingTextWithTooltip = styled.div`
color: ${({ theme }) => theme.font.color.tertiary};
`;
const StyledNumber = styled.div`
color: ${({ theme }) => theme.font.color.tertiary};
padding-right: ${({ theme }) => theme.spacing(2)};
`;
const StyledIconContainer = styled.div`
flex-shrink: 0;
`;
export const SettingsDataModelObjectSummary = ({
className,
const StyledSeparator = styled.div`
align-self: stretch;
background: ${({ theme }) => theme.background.quaternary};
height: 1px;
margin-bottom: ${({ theme }) => theme.spacing(2)};
margin-top: ${({ theme }) => theme.spacing(2)};
`;
type SettingsDataModelObjectPreviewItemProps = {
objectMetadataItem: Pick<
ObjectMetadataItem,
'icon' | 'labelSingular' | 'labelPlural' | 'isCustom' | 'isRemote'
>;
pluralizeLabel: boolean;
index: number;
};
const SettingsDataModelObjectPreviewItem = ({
objectMetadataItem,
pluralizeLabel = true,
}: SettingsDataModelObjectSummaryProps) => {
index,
}: SettingsDataModelObjectPreviewItemProps) => {
const theme = useTheme();
const { getIcon } = useIcons();
const ObjectIcon = getIcon(objectMetadataItem.icon);
const objectTypeLabel = getObjectTypeLabel(objectMetadataItem);
return (
<StyledObjectSummary className={className}>
<StyledObjectName>
<StyledIconContainer>
<ObjectIcon size={theme.icon.size.sm} stroke={theme.icon.stroke.md} />
</StyledIconContainer>
<OverflowingTextWithTooltip
text={
pluralizeLabel
? objectMetadataItem.labelPlural
: objectMetadataItem.labelSingular
}
/>
</StyledObjectName>
<SettingsDataModelObjectTypeTag objectTypeLabel={objectTypeLabel} />
</StyledObjectSummary>
<>
{index > 0 && <StyledSeparator />}
<StyledObjectPreview key={`${objectMetadataItem.labelSingular}-${index}`}>
<StyledObjectName>
<StyledIconContainer>
<ObjectIcon
size={theme.icon.size.sm}
stroke={theme.icon.stroke.md}
/>
</StyledIconContainer>
<OverflowingTextWithTooltip
text={
pluralizeLabel
? objectMetadataItem.labelPlural
: objectMetadataItem.labelSingular
}
/>
</StyledObjectName>
<SettingsDataModelObjectTypeTag objectTypeLabel={objectTypeLabel} />
</StyledObjectPreview>
</>
);
};
const SettingsDataModelObjectPreviewOtherObjects = ({
selected,
}: {
selected: number;
}) => {
const theme = useTheme();
return (
<>
<StyledSeparator />
<StyledObjectPreview key={`other-objects`}>
<StyledObjectName>
<StyledIconContainer>
<IconBox
size={theme.icon.size.sm}
stroke={theme.icon.stroke.md}
color={theme.font.color.tertiary}
/>
</StyledIconContainer>
<StyledOverflowingTextWithTooltip>
<OverflowingTextWithTooltip text={`Other objects`} />
</StyledOverflowingTextWithTooltip>
</StyledObjectName>
<StyledNumber>
<OverflowingTextWithTooltip text={`${selected - 3}`} />
</StyledNumber>
</StyledObjectPreview>
</>
);
};
export const SettingsDataModelObjectPreview = ({
objectMetadataItems,
pluralizeLabel = true,
}: SettingsDataModelObjectPreviewProps) => {
let selected = 0;
return (
<>
{objectMetadataItems.map((objectMetadataItem, index) => {
selected++;
return selected <= 3 ? (
<SettingsDataModelObjectPreviewItem
objectMetadataItem={objectMetadataItem}
pluralizeLabel={pluralizeLabel}
index={index}
/>
) : (
<></>
);
})}
{selected > 3 && (
<SettingsDataModelObjectPreviewOtherObjects selected={selected} />
)}
</>
);
};