feat: improve the design of the fields widget (#17003)
Closes [2005](https://github.com/twentyhq/core-team-issues/issues/2005). This is how it looks like and I have a feeling that it matches the Figma design. I am not sure if we need to remove more padding as mentioned in the issue since removing it takes it away from the Figma design. Please review and let me know. <img width="329" height="807" alt="image" src="https://github.com/user-attachments/assets/1d9051e4-81fc-4c43-9aa8-54857bbd8f8f" /> <br /> <br /> Figma design itself: <img width="912" height="1052" alt="image" src="https://github.com/user-attachments/assets/4174a4a3-22af-4afc-8632-a0838ec5af08" /> In terms of data that we receive, I believe it would be handled by Fields Configuration coming from the Backend, so General and Other are decided at that layer, unless I am missing something.
This commit is contained in:
+47
-13
@@ -25,26 +25,60 @@ export const useTemporaryFieldsConfiguration = (
|
||||
field.type !== FieldMetadataType.RICH_TEXT_V2,
|
||||
);
|
||||
|
||||
const fields = fieldsToDisplay.map((field, index) => ({
|
||||
fieldMetadataId: field.id,
|
||||
position: index,
|
||||
}));
|
||||
if (fieldsToDisplay.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (fields.length === 0) {
|
||||
const generalFields: Array<{ fieldMetadataId: string; position: number }> =
|
||||
[];
|
||||
const otherFields: Array<{ fieldMetadataId: string; position: number }> =
|
||||
[];
|
||||
|
||||
let generalPosition = 0;
|
||||
let otherPosition = 0;
|
||||
|
||||
fieldsToDisplay.forEach((field) => {
|
||||
if (field.type === FieldMetadataType.LINKS) {
|
||||
otherFields.push({
|
||||
fieldMetadataId: field.id,
|
||||
position: otherPosition++,
|
||||
});
|
||||
} else {
|
||||
generalFields.push({
|
||||
fieldMetadataId: field.id,
|
||||
position: generalPosition++,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
const sections = [];
|
||||
|
||||
if (generalFields.length > 0) {
|
||||
sections.push({
|
||||
id: `${objectNameSingular}-section-general`,
|
||||
title: t`General`,
|
||||
position: 0,
|
||||
fields: generalFields,
|
||||
});
|
||||
}
|
||||
|
||||
if (otherFields.length > 0) {
|
||||
sections.push({
|
||||
id: `${objectNameSingular}-section-other`,
|
||||
title: t`Other`,
|
||||
position: 1,
|
||||
fields: otherFields,
|
||||
});
|
||||
}
|
||||
|
||||
if (sections.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
__typename: 'FieldsConfiguration',
|
||||
configurationType: 'FIELDS',
|
||||
sections: [
|
||||
{
|
||||
id: `${objectNameSingular}-section-general`,
|
||||
title: t`General`,
|
||||
position: 0,
|
||||
fields,
|
||||
},
|
||||
],
|
||||
sections,
|
||||
};
|
||||
}, [objectMetadataItem, objectNameSingular, t]);
|
||||
|
||||
|
||||
+38
-4
@@ -1,14 +1,22 @@
|
||||
import { useTheme } from '@emotion/react';
|
||||
import styled from '@emotion/styled';
|
||||
import { Section } from 'twenty-ui/layout';
|
||||
import { useState } from 'react';
|
||||
import { IconChevronDown } from 'twenty-ui/display';
|
||||
import { AnimatedExpandableContainer, Section } from 'twenty-ui/layout';
|
||||
|
||||
const StyledFieldsWidgetSectionContainer = styled(Section)`
|
||||
padding-top: ${({ theme }) => theme.spacing(3)};
|
||||
padding-bottom: ${({ theme }) => theme.spacing(3)};
|
||||
padding-bottom: 0;
|
||||
width: auto;
|
||||
|
||||
&:not(:first-of-type) {
|
||||
padding-top: 0;
|
||||
}
|
||||
`;
|
||||
|
||||
const StyledHeader = styled.header`
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
height: 24px;
|
||||
justify-content: space-between;
|
||||
@@ -17,9 +25,18 @@ const StyledHeader = styled.header`
|
||||
`;
|
||||
|
||||
const StyledTitleLabel = styled.div`
|
||||
color: ${({ theme }) => theme.font.color.tertiary};
|
||||
font-weight: ${({ theme }) => theme.font.weight.medium};
|
||||
`;
|
||||
|
||||
const StyledChevronIcon = styled(IconChevronDown)<{ isExpanded: boolean }>`
|
||||
color: ${({ theme }) => theme.font.color.tertiary};
|
||||
transform: ${({ isExpanded }) =>
|
||||
isExpanded ? 'rotate(180deg)' : 'rotate(0deg)'};
|
||||
transition: ${({ theme }) =>
|
||||
`transform ${theme.animation.duration.normal}s ease`};
|
||||
`;
|
||||
|
||||
type FieldsWidgetSectionContainerProps = {
|
||||
children: React.ReactNode;
|
||||
title: string;
|
||||
@@ -29,12 +46,29 @@ export const FieldsWidgetSectionContainer = ({
|
||||
children,
|
||||
title,
|
||||
}: FieldsWidgetSectionContainerProps) => {
|
||||
const [isExpanded, setIsExpanded] = useState(true);
|
||||
const theme = useTheme();
|
||||
|
||||
const handleToggleSection = () =>
|
||||
setIsExpanded((previousIsExpanded) => !previousIsExpanded);
|
||||
|
||||
return (
|
||||
<StyledFieldsWidgetSectionContainer>
|
||||
<StyledHeader>
|
||||
<StyledHeader onClick={handleToggleSection}>
|
||||
<StyledTitleLabel>{title}</StyledTitleLabel>
|
||||
<StyledChevronIcon
|
||||
isExpanded={isExpanded}
|
||||
size={theme.icon.size.md}
|
||||
stroke={theme.icon.stroke.sm}
|
||||
/>
|
||||
</StyledHeader>
|
||||
{children}
|
||||
<AnimatedExpandableContainer
|
||||
isExpanded={isExpanded}
|
||||
initial={false}
|
||||
mode="fit-content"
|
||||
>
|
||||
{children}
|
||||
</AnimatedExpandableContainer>
|
||||
</StyledFieldsWidgetSectionContainer>
|
||||
);
|
||||
};
|
||||
|
||||
+3
-1
@@ -30,6 +30,7 @@ type AnimatedExpandableContainerProps = {
|
||||
animationDurations?: AnimationDurations;
|
||||
mode?: AnimationMode;
|
||||
containAnimation?: boolean;
|
||||
initial?: boolean;
|
||||
};
|
||||
|
||||
export const AnimatedExpandableContainer = ({
|
||||
@@ -39,6 +40,7 @@ export const AnimatedExpandableContainer = ({
|
||||
animationDurations = 'default',
|
||||
mode = 'scroll-height',
|
||||
containAnimation = true,
|
||||
initial = true,
|
||||
}: AnimatedExpandableContainerProps) => {
|
||||
const theme = useTheme();
|
||||
const contentRef = useRef<HTMLDivElement>(null);
|
||||
@@ -71,7 +73,7 @@ export const AnimatedExpandableContainer = ({
|
||||
);
|
||||
|
||||
return (
|
||||
<AnimatePresence>
|
||||
<AnimatePresence initial={initial}>
|
||||
{isExpanded && (
|
||||
<StyledMotionContainer
|
||||
containAnimation={containAnimation}
|
||||
|
||||
Reference in New Issue
Block a user