From 2bb3b41e6277a75f797da7224f64bec6ba723ac1 Mon Sep 17 00:00:00 2001 From: "Abdullah." <125115953+mabdullahabaid@users.noreply.github.com> Date: Fri, 9 Jan 2026 15:27:10 +0500 Subject: [PATCH] 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. image

Figma design itself: image 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. --- .../hooks/useTemporaryFieldsConfiguration.ts | 60 +++++++++++++++---- .../FieldsWidgetSectionContainer.tsx | 42 +++++++++++-- .../AnimatedExpandableContainer.tsx | 4 +- 3 files changed, 88 insertions(+), 18 deletions(-) diff --git a/packages/twenty-front/src/modules/page-layout/hooks/useTemporaryFieldsConfiguration.ts b/packages/twenty-front/src/modules/page-layout/hooks/useTemporaryFieldsConfiguration.ts index 26d794ed09..01a3a488c1 100644 --- a/packages/twenty-front/src/modules/page-layout/hooks/useTemporaryFieldsConfiguration.ts +++ b/packages/twenty-front/src/modules/page-layout/hooks/useTemporaryFieldsConfiguration.ts @@ -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]); diff --git a/packages/twenty-front/src/modules/page-layout/widgets/fields/components/FieldsWidgetSectionContainer.tsx b/packages/twenty-front/src/modules/page-layout/widgets/fields/components/FieldsWidgetSectionContainer.tsx index eacd19d88f..eed1cf46a8 100644 --- a/packages/twenty-front/src/modules/page-layout/widgets/fields/components/FieldsWidgetSectionContainer.tsx +++ b/packages/twenty-front/src/modules/page-layout/widgets/fields/components/FieldsWidgetSectionContainer.tsx @@ -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 ( - + {title} + - {children} + + {children} + ); }; diff --git a/packages/twenty-ui/src/layout/animated-expandable-container/components/AnimatedExpandableContainer.tsx b/packages/twenty-ui/src/layout/animated-expandable-container/components/AnimatedExpandableContainer.tsx index b8eb1e6f60..4f9bc75e39 100644 --- a/packages/twenty-ui/src/layout/animated-expandable-container/components/AnimatedExpandableContainer.tsx +++ b/packages/twenty-ui/src/layout/animated-expandable-container/components/AnimatedExpandableContainer.tsx @@ -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(null); @@ -71,7 +73,7 @@ export const AnimatedExpandableContainer = ({ ); return ( - + {isExpanded && (