Fix field metadata creation page (#11285)

in this pr 
1. fixing error helper was no longer showing. unfortunately had to
resort to `formConfig.trigger`...
2. closes https://github.com/twentyhq/twenty/issues/11262
3. closes https://github.com/twentyhq/twenty/issues/11263


https://github.com/user-attachments/assets/11f763bb-3098-4b0e-bc96-8a0de3cb3c19
This commit is contained in:
Marie
2025-04-01 14:09:24 +02:00
committed by GitHub
parent 9cbc2e3df0
commit 366106bb2b
5 changed files with 99 additions and 67 deletions

View File

@ -0,0 +1,59 @@
import { useTheme } from '@emotion/react';
import styled from '@emotion/styled';
import { IconPoint } from 'twenty-ui';
const StyledWrapper = styled.div`
position: relative;
width: 100%;
`;
type DotPosition = 'top' | 'centered';
type AdvancedSettingsContentWrapperWithDotProps = {
children: React.ReactNode;
hideDot?: boolean;
dotPosition?: DotPosition;
};
const StyledDotContainer = styled.div<{ dotPosition: DotPosition }>`
display: flex;
position: absolute;
height: 100%;
left: ${({ theme }) => theme.spacing(-5)};
${({ dotPosition }) => {
if (dotPosition === 'top') {
return `
top: 0;
`;
}
return `
align-items: center;
`;
}}
`;
const StyledIconPoint = styled(IconPoint)`
margin-right: 0;
`;
export const AdvancedSettingsContentWrapperWithDot = ({
children,
hideDot = false,
dotPosition = 'centered',
}: AdvancedSettingsContentWrapperWithDotProps) => {
const theme = useTheme();
return (
<StyledWrapper>
{!hideDot && (
<StyledDotContainer dotPosition={dotPosition}>
<StyledIconPoint
size={12}
color={theme.color.yellow}
fill={theme.color.yellow}
/>
</StyledDotContainer>
)}
{children}
</StyledWrapper>
);
};

View File

@ -1,41 +1,14 @@
import { AdvancedSettingsContentWrapperWithDot } from '@/settings/components/AdvancedSettingsContentWrapperWithDot';
import { ADVANCED_SETTINGS_ANIMATION_DURATION } from '@/settings/constants/AdvancedSettingsAnimationDurations'; import { ADVANCED_SETTINGS_ANIMATION_DURATION } from '@/settings/constants/AdvancedSettingsAnimationDurations';
import { isAdvancedModeEnabledState } from '@/ui/navigation/navigation-drawer/states/isAdvancedModeEnabledState'; import { isAdvancedModeEnabledState } from '@/ui/navigation/navigation-drawer/states/isAdvancedModeEnabledState';
import styled from '@emotion/styled'; import styled from '@emotion/styled';
import { useRecoilValue } from 'recoil'; import { useRecoilValue } from 'recoil';
import { AnimatedExpandableContainer, IconPoint, MAIN_COLORS } from 'twenty-ui'; import { AnimatedExpandableContainer } from 'twenty-ui';
type DotPosition = 'top' | 'centered';
const StyledAdvancedWrapper = styled.div`
position: relative;
width: 100%;
`;
const StyledDotContainer = styled.div<{ dotPosition: DotPosition }>`
display: flex;
position: absolute;
height: 100%;
left: ${({ theme }) => theme.spacing(-5)};
${({ dotPosition }) => {
if (dotPosition === 'top') {
return `
top: 0;
`;
}
return `
align-items: center;
`;
}}
`;
const StyledContent = styled.div` const StyledContent = styled.div`
width: 100%; width: 100%;
`; `;
const StyledIconPoint = styled(IconPoint)` type DotPosition = 'top' | 'centered';
margin-right: 0;
`;
type AdvancedSettingsWrapperProps = { type AdvancedSettingsWrapperProps = {
children: React.ReactNode; children: React.ReactNode;
@ -60,18 +33,12 @@ export const AdvancedSettingsWrapper = ({
mode="scroll-height" mode="scroll-height"
containAnimation={false} containAnimation={false}
> >
<StyledAdvancedWrapper> <AdvancedSettingsContentWrapperWithDot
{!hideDot && ( hideDot={hideDot}
<StyledDotContainer dotPosition={dotPosition}> dotPosition={dotPosition}
<StyledIconPoint >
size={12}
color={MAIN_COLORS.yellow}
fill={MAIN_COLORS.yellow}
/>
</StyledDotContainer>
)}
<StyledContent>{children}</StyledContent> <StyledContent>{children}</StyledContent>
</StyledAdvancedWrapper> </AdvancedSettingsContentWrapperWithDot>
</AnimatedExpandableContainer> </AnimatedExpandableContainer>
); );
}; };

View File

@ -4,6 +4,7 @@ import { z } from 'zod';
import { FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem'; import { FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
import { fieldMetadataItemSchema } from '@/object-metadata/validation-schemas/fieldMetadataItemSchema'; import { fieldMetadataItemSchema } from '@/object-metadata/validation-schemas/fieldMetadataItemSchema';
import { AdvancedSettingsContentWrapperWithDot } from '@/settings/components/AdvancedSettingsContentWrapperWithDot';
import { AdvancedSettingsWrapper } from '@/settings/components/AdvancedSettingsWrapper'; import { AdvancedSettingsWrapper } from '@/settings/components/AdvancedSettingsWrapper';
import { SettingsOptionCardContentToggle } from '@/settings/components/SettingsOptions/SettingsOptionCardContentToggle'; import { SettingsOptionCardContentToggle } from '@/settings/components/SettingsOptions/SettingsOptionCardContentToggle';
import { DATABASE_IDENTIFIER_MAXIMUM_LENGTH } from '@/settings/data-model/constants/DatabaseIdentifierMaximumLength'; import { DATABASE_IDENTIFIER_MAXIMUM_LENGTH } from '@/settings/data-model/constants/DatabaseIdentifierMaximumLength';
@ -47,7 +48,7 @@ type SettingsDataModelFieldIconLabelFormValues = z.infer<
const StyledInputsContainer = styled.div` const StyledInputsContainer = styled.div`
display: flex; display: flex;
gap: ${({ theme }) => theme.spacing(2)}; gap: ${({ theme }) => theme.spacing(2)};
margin-bottom: ${({ theme }) => theme.spacing(2)}; margin-bottom: ${({ theme }) => theme.spacing(1)};
width: 100%; width: 100%;
`; `;
@ -86,6 +87,7 @@ export const SettingsDataModelFieldIconLabelForm = ({
setValue, setValue,
watch, watch,
formState: { errors }, formState: { errors },
trigger,
} = useFormContext<SettingsDataModelFieldIconLabelFormValues>(); } = useFormContext<SettingsDataModelFieldIconLabelFormValues>();
const theme = useTheme(); const theme = useTheme();
@ -135,6 +137,7 @@ export const SettingsDataModelFieldIconLabelForm = ({
value={value} value={value}
onChange={(value) => { onChange={(value) => {
onChange(value); onChange(value);
trigger('label');
if (isLabelSyncedWithName === true) { if (isLabelSyncedWithName === true) {
fillNameFromLabel(value); fillNameFromLabel(value);
} }
@ -152,8 +155,8 @@ export const SettingsDataModelFieldIconLabelForm = ({
/> />
</StyledInputsContainer> </StyledInputsContainer>
{canToggleSyncLabelWithName && ( {canToggleSyncLabelWithName && (
<StyledAdvancedSettingsOuterContainer> <AdvancedSettingsWrapper hideDot>
<AdvancedSettingsWrapper> <StyledAdvancedSettingsOuterContainer>
<StyledAdvancedSettingsContainer> <StyledAdvancedSettingsContainer>
<StyledAdvancedSettingsSectionInputWrapper> <StyledAdvancedSettingsSectionInputWrapper>
<StyledInputsContainer> <StyledInputsContainer>
@ -207,27 +210,32 @@ export const SettingsDataModelFieldIconLabelForm = ({
fieldMetadataItem?.isLabelSyncedWithName ?? true fieldMetadataItem?.isLabelSyncedWithName ?? true
} }
render={({ field: { onChange, value } }) => ( render={({ field: { onChange, value } }) => (
<Card rounded> <AdvancedSettingsContentWrapperWithDot
<SettingsOptionCardContentToggle hideDot={false}
Icon={IconRefresh} dotPosition="centered"
title={t`Synchronize Field Label and API Name`} >
description={t`Should changing a field's label also change the API name?`} <Card rounded>
checked={value ?? true} <SettingsOptionCardContentToggle
advancedMode Icon={IconRefresh}
onChange={(value) => { title={t`Synchronize Field Label and API Name`}
onChange(value); description={t`Should changing a field's label also change the API name?`}
if (value === true) { checked={value ?? true}
fillNameFromLabel(label); advancedMode
} onChange={(value) => {
}} onChange(value);
/> if (value === true) {
</Card> fillNameFromLabel(label);
}
}}
/>
</Card>
</AdvancedSettingsContentWrapperWithDot>
)} )}
/> />
</StyledAdvancedSettingsSectionInputWrapper> </StyledAdvancedSettingsSectionInputWrapper>
</StyledAdvancedSettingsContainer> </StyledAdvancedSettingsContainer>
</AdvancedSettingsWrapper> </StyledAdvancedSettingsOuterContainer>
</StyledAdvancedSettingsOuterContainer> </AdvancedSettingsWrapper>
)} )}
</> </>
); );

View File

@ -5,10 +5,7 @@ const StyledInputErrorHelper = styled.div`
color: ${({ theme }) => theme.color.red}; color: ${({ theme }) => theme.color.red};
font-size: ${({ theme }) => theme.font.size.xs}; font-size: ${({ theme }) => theme.font.size.xs};
position: absolute; position: absolute;
`; top: calc(100% + ${({ theme }) => theme.spacing(0.25)});
const StyledErrorContainer = styled.div`
margin-top: ${({ theme }) => theme.spacing(1)};
`; `;
export const InputErrorHelper = ({ export const InputErrorHelper = ({
@ -16,11 +13,11 @@ export const InputErrorHelper = ({
}: { }: {
children?: React.ReactNode; children?: React.ReactNode;
}) => ( }) => (
<StyledErrorContainer> <div>
{children && ( {children && (
<StyledInputErrorHelper aria-live="polite"> <StyledInputErrorHelper aria-live="polite">
{children} {children}
</StyledInputErrorHelper> </StyledInputErrorHelper>
)} )}
</StyledErrorContainer> </div>
); );

View File

@ -22,6 +22,7 @@ const StyledContainer = styled.div<
display: inline-flex; display: inline-flex;
flex-direction: column; flex-direction: column;
width: ${({ fullWidth }) => (fullWidth ? `100%` : 'auto')}; width: ${({ fullWidth }) => (fullWidth ? `100%` : 'auto')};
position: relative;
`; `;
const StyledInputContainer = styled.div` const StyledInputContainer = styled.div`