Settings Option Card component (#8456)

fixes - #8195

---------

Co-authored-by: Charles Bochet <charles@twenty.com>
This commit is contained in:
nitin
2024-11-18 14:52:33 +05:30
committed by GitHub
parent ade1c57ff4
commit 2f5dc26545
56 changed files with 931 additions and 920 deletions

View File

@ -1,23 +1,20 @@
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { objectMetadataItemSchema } from '@/object-metadata/validation-schemas/objectMetadataItemSchema';
import { AdvancedSettingsWrapper } from '@/settings/components/AdvancedSettingsWrapper';
import { SettingsOptionCardContentToggle } from '@/settings/components/SettingsOptions/SettingsOptionCardContentToggle';
import { OBJECT_NAME_MAXIMUM_LENGTH } from '@/settings/data-model/constants/ObjectNameMaximumLength';
import { SyncObjectLabelAndNameToggle } from '@/settings/data-model/objects/forms/components/SyncObjectLabelAndNameToggle';
import { useExpandedHeightAnimation } from '@/settings/hooks/useExpandedHeightAnimation';
import { IconPicker } from '@/ui/input/components/IconPicker';
import { TextArea } from '@/ui/input/components/TextArea';
import { TextInput } from '@/ui/input/components/TextInput';
import { isAdvancedModeEnabledState } from '@/ui/navigation/navigation-drawer/states/isAdvancedModeEnabledState';
import { useTheme } from '@emotion/react';
import styled from '@emotion/styled';
import { AnimatePresence, motion } from 'framer-motion';
import { plural } from 'pluralize';
import { Controller, useFormContext } from 'react-hook-form';
import { useRecoilValue } from 'recoil';
import {
AppTooltip,
Card,
IconInfoCircle,
IconTool,
MAIN_COLORS,
IconRefresh,
TooltipDelay,
} from 'twenty-ui';
import { z } from 'zod';
@ -83,18 +80,6 @@ const StyledAdvancedSettingsContainer = styled.div`
width: 100%;
`;
const StyledIconToolContainer = styled.div`
border-right: 1px solid ${MAIN_COLORS.yellow};
display: flex;
left: ${({ theme }) => theme.spacing(-6)};
position: absolute;
height: 100%;
`;
const StyledIconTool = styled(IconTool)`
margin-right: ${({ theme }) => theme.spacing(0.5)};
`;
const StyledLabel = styled.span`
color: ${({ theme }) => theme.font.color.light};
font-size: ${({ theme }) => theme.font.size.xs};
@ -115,10 +100,6 @@ export const SettingsDataModelObjectAboutForm = ({
const { control, watch, setValue } =
useFormContext<SettingsDataModelObjectAboutFormValues>();
const theme = useTheme();
const isAdvancedModeEnabled = useRecoilValue(isAdvancedModeEnabledState);
const { contentRef, motionAnimationVariants } = useExpandedHeightAnimation(
isAdvancedModeEnabled,
);
const isLabelSyncedWithName = watch(IS_LABEL_SYNCED_WITH_NAME_LABEL);
const labelSingular = watch('labelSingular');
@ -235,122 +216,111 @@ export const SettingsDataModelObjectAboutForm = ({
/>
)}
/>
<AnimatePresence>
{isAdvancedModeEnabled && (
<motion.div
ref={contentRef}
initial="initial"
animate="animate"
exit="exit"
variants={motionAnimationVariants}
>
<StyledAdvancedSettingsOuterContainer>
<StyledAdvancedSettingsContainer>
<StyledIconToolContainer>
<StyledIconTool size={12} color={MAIN_COLORS.yellow} />
</StyledIconToolContainer>
<StyledAdvancedSettingsSectionInputWrapper>
{[
{
label: 'API Name (Singular)',
fieldName: 'nameSingular' as const,
placeholder: 'listing',
defaultValue: objectMetadataItem?.nameSingular,
disabled:
disabled || disableNameEdit || isLabelSyncedWithName,
tooltip: apiNameTooltipText,
},
{
label: 'API Name (Plural)',
fieldName: 'namePlural' as const,
placeholder: 'listings',
defaultValue: objectMetadataItem?.namePlural,
disabled:
disabled || disableNameEdit || isLabelSyncedWithName,
tooltip: apiNameTooltipText,
},
].map(
({
defaultValue,
fieldName,
label,
placeholder,
disabled,
tooltip,
}) => (
<StyledInputContainer
key={`object-${fieldName}-text-input`}
>
<Controller
name={fieldName}
control={control}
defaultValue={defaultValue}
render={({ field: { onChange, value } }) => (
<>
<TextInput
label={label}
placeholder={placeholder}
value={value}
onChange={onChange}
disabled={disabled}
fullWidth
maxLength={OBJECT_NAME_MAXIMUM_LENGTH}
onBlur={onBlur}
RightIcon={() =>
tooltip && (
<>
<IconInfoCircle
id={infoCircleElementId + fieldName}
size={theme.icon.size.md}
color={theme.font.color.tertiary}
style={{ outline: 'none' }}
/>
<AppTooltip
anchorSelect={`#${infoCircleElementId}${fieldName}`}
content={tooltip}
offset={5}
noArrow
place="bottom"
positionStrategy="fixed"
delay={TooltipDelay.shortDelay}
/>
</>
)
}
/>
</>
)}
/>
</StyledInputContainer>
),
)}
<Controller
name={IS_LABEL_SYNCED_WITH_NAME_LABEL}
control={control}
defaultValue={
objectMetadataItem?.isLabelSyncedWithName ?? true
}
render={({ field: { onChange, value } }) => (
<SyncObjectLabelAndNameToggle
value={value ?? true}
disabled={!objectMetadataItem?.isCustom}
onChange={(value) => {
onChange(value);
if (value === true) {
fillNamePluralFromLabelPlural(labelPlural);
fillNameSingularFromLabelSingular(labelSingular);
}
onBlur?.();
}}
/>
)}
/>
</StyledAdvancedSettingsSectionInputWrapper>
</StyledAdvancedSettingsContainer>
</StyledAdvancedSettingsOuterContainer>
</motion.div>
)}
</AnimatePresence>
<StyledAdvancedSettingsOuterContainer>
<AdvancedSettingsWrapper>
<StyledAdvancedSettingsContainer>
<StyledAdvancedSettingsSectionInputWrapper>
{[
{
label: 'API Name (Singular)',
fieldName: 'nameSingular' as const,
placeholder: 'listing',
defaultValue: objectMetadataItem?.nameSingular,
disabled:
disabled || disableNameEdit || isLabelSyncedWithName,
tooltip: apiNameTooltipText,
},
{
label: 'API Name (Plural)',
fieldName: 'namePlural' as const,
placeholder: 'listings',
defaultValue: objectMetadataItem?.namePlural,
disabled:
disabled || disableNameEdit || isLabelSyncedWithName,
tooltip: apiNameTooltipText,
},
].map(
({
defaultValue,
fieldName,
label,
placeholder,
disabled,
tooltip,
}) => (
<StyledInputContainer key={`object-${fieldName}-text-input`}>
<Controller
name={fieldName}
control={control}
defaultValue={defaultValue}
render={({ field: { onChange, value } }) => (
<>
<TextInput
label={label}
placeholder={placeholder}
value={value}
onChange={onChange}
disabled={disabled}
fullWidth
maxLength={OBJECT_NAME_MAXIMUM_LENGTH}
onBlur={onBlur}
RightIcon={() =>
tooltip && (
<>
<IconInfoCircle
id={infoCircleElementId + fieldName}
size={theme.icon.size.md}
color={theme.font.color.tertiary}
style={{ outline: 'none' }}
/>
<AppTooltip
anchorSelect={`#${infoCircleElementId}${fieldName}`}
content={tooltip}
offset={5}
noArrow
place="bottom"
positionStrategy="fixed"
delay={TooltipDelay.shortDelay}
/>
</>
)
}
/>
</>
)}
/>
</StyledInputContainer>
),
)}
<Controller
name={IS_LABEL_SYNCED_WITH_NAME_LABEL}
control={control}
defaultValue={objectMetadataItem?.isLabelSyncedWithName ?? true}
render={({ field: { onChange, value } }) => (
<Card rounded>
<SettingsOptionCardContentToggle
Icon={IconRefresh}
title="Synchronize Objects Labels and API Names"
description="Should changing an object's label also change the API?"
checked={value ?? true}
disabled={!objectMetadataItem?.isCustom}
advancedMode
onChange={(value) => {
onChange(value);
if (value === true) {
fillNamePluralFromLabelPlural(labelPlural);
fillNameSingularFromLabelSingular(labelSingular);
}
onBlur?.();
}}
/>
</Card>
)}
/>
</StyledAdvancedSettingsSectionInputWrapper>
</StyledAdvancedSettingsContainer>
</AdvancedSettingsWrapper>
</StyledAdvancedSettingsOuterContainer>
</>
);
};

View File

@ -1,80 +0,0 @@
import { useTheme } from '@emotion/react';
import styled from '@emotion/styled';
import { IconRefresh, MAIN_COLORS, Toggle } from 'twenty-ui';
const StyledToggleContainer = styled.div`
align-items: center;
border: 1px solid ${({ theme }) => theme.border.color.medium};
border-radius: ${({ theme }) => theme.border.radius.md};
display: flex;
justify-content: space-between;
padding: ${({ theme }) => theme.spacing(4)};
background: ${({ theme }) => theme.background.secondary};
`;
const StyledIconRefreshContainer = styled.div`
border: 2px solid ${({ theme }) => theme.border.color.medium};
border-radius: 3px;
margin-right: ${({ theme }) => theme.spacing(3)};
width: ${({ theme }) => theme.spacing(8)};
height: ${({ theme }) => theme.spacing(8)};
display: flex;
align-items: center;
justify-content: center;
`;
const StyledTitleContainer = styled.div`
align-items: center;
display: flex;
justify-content: space-between;
`;
const StyledTitle = styled.h2`
color: ${({ theme }) => theme.font.color.primary};
font-size: ${({ theme }) => theme.font.size.md};
font-weight: ${({ theme }) => theme.font.weight.semiBold};
margin: 0;
`;
const StyledDescription = styled.h3`
color: ${({ theme }) => theme.font.color.tertiary};
font-size: ${({ theme }) => theme.font.size.md};
font-weight: ${({ theme }) => theme.font.weight.regular};
margin: 0;
margin-top: ${({ theme }) => theme.spacing(1)};
`;
type SyncObjectLabelAndNameToggleProps = {
value: boolean;
onChange: (value: boolean) => void;
disabled?: boolean;
};
export const SyncObjectLabelAndNameToggle = ({
value,
onChange,
disabled,
}: SyncObjectLabelAndNameToggleProps) => {
const theme = useTheme();
return (
<StyledToggleContainer>
<StyledTitleContainer>
<StyledIconRefreshContainer>
<IconRefresh size={22.5} color={theme.font.color.tertiary} />
</StyledIconRefreshContainer>
<div>
<StyledTitle>Synchronize Objects Labels and API Names</StyledTitle>
<StyledDescription>
Should changing an object's label also change the API?
</StyledDescription>
</div>
</StyledTitleContainer>
<Toggle
onChange={onChange}
color={MAIN_COLORS.yellow}
value={value}
disabled={disabled}
/>
</StyledToggleContainer>
);
};