Remove all styled(Component) patterns in favor of parent wrappers and props (#18430)

## Summary

Eliminates all ~350 `styled(Component)` usages across `twenty-front` and
`twenty-ui` (212 files changed). Each was replaced following these
rules:

- **Margin/layout CSS** (margin, padding, flex, align-self, width) →
wrapped in a `styled.div`/`styled.span` parent container
- **Third-party components** (Link, TextareaAutosize,
ReactPhoneNumberInput, Handle, etc.) → parent container with child CSS
selectors (`> a`, `> textarea`, `> input`, etc.)
- **Intrinsic behavior via existing props** (TableRow
`gridTemplateColumns`, TableCell `color`/`align`) → replaced
`styled(TableRow)` / `styled(TableCell)` with direct prop usage
- **Other visual overrides on twenty-ui components** (Card, Section,
TabList, Button, MenuItem, ScrollWrapper, etc.) → parent wrappers with
`> div` / `> *` child selectors
- **Extending styled.div/span** → merged all CSS into a single
`styled.div`/`styled.span`

Also adds `overflow: hidden` to parent containers wrapping
`ScrollWrapper` so scroll activates correctly with the new wrapper
structure.

### Migration patterns

| Before | After |
|--------|-------|
| `styled(Avatar)` with `margin-right` | `<StyledAvatarContainer><Avatar
/></StyledAvatarContainer>` |
| `styled(Link)` with `text-decoration: none` |
`<StyledLinkContainer><Link /></StyledLinkContainer>` with `> a { ... }`
|
| `styled(TableRow)` with `grid-template-columns` | `<TableRow
gridTemplateColumns="..." />` |
| `styled(TableCell)` with `color` / `align` | `<TableCell color={...}
align="right" />` |
| `styled(Card)` with `margin-top` | `<StyledCardContainer><Card
/></StyledCardContainer>` |
| `styled(TabList)` with `background` |
`<StyledTabListContainer><TabList /></StyledTabListContainer>` with `>
div { ... }` |
| `styled(StyledBase)` extending a `styled.div` | Single merged
`styled.div` with all styles inlined |
This commit is contained in:
Charles Bochet
2026-03-05 18:16:25 +01:00
committed by GitHub
parent e5e3132ddd
commit c53a13417e
224 changed files with 4765 additions and 3837 deletions
@@ -36,14 +36,8 @@ type SettingsObjectFieldItemTableRowProps = {
mode: 'view' | 'new-field';
};
export const StyledObjectFieldTableRow = styled(TableRow)`
grid-template-columns: minmax(0, 1fr) 148px 148px 36px;
`;
const StyledNameTableCell = styled(TableCell)`
color: ${themeCssVariables.font.color.primary};
gap: ${themeCssVariables.spacing[2]};
`;
export const OBJECT_FIELD_TABLE_ROW_GRID_TEMPLATE_COLUMNS =
'minmax(0, 1fr) 148px 148px 36px';
const StyledNameContainer = styled.div`
display: flex;
@@ -74,13 +68,10 @@ const StyledInactiveLabel = styled.span`
}
`;
const StyledIconTableCell = styled(TableCell)`
justify-content: center;
padding-right: ${themeCssVariables.spacing[1]};
`;
const StyledIconChevronRight = styled(IconChevronRight)`
const StyledIconChevronRightContainer = styled.span`
align-items: center;
color: ${themeCssVariables.font.color.tertiary};
display: flex;
`;
export const SettingsObjectFieldItemTableRow = ({
@@ -177,11 +168,15 @@ export const SettingsObjectFieldItemTableRow = ({
: relationObjectMetadataItem?.labelPlural;
return (
<StyledObjectFieldTableRow
<TableRow
gridTemplateColumns={OBJECT_FIELD_TABLE_ROW_GRID_TEMPLATE_COLUMNS}
onClick={mode === 'view' ? navigateToFieldEdit : undefined}
>
<UndecoratedLink to={linkToNavigate}>
<StyledNameTableCell>
<TableCell
color={themeCssVariables.font.color.primary}
gap={themeCssVariables.spacing[2]}
>
{!!Icon && (
<Icon
style={{
@@ -199,7 +194,7 @@ export const SettingsObjectFieldItemTableRow = ({
<StyledInactiveLabel>{t`Deactivated`}</StyledInactiveLabel>
)}
</StyledNameContainer>
</StyledNameTableCell>
</TableCell>
</UndecoratedLink>
<TableCell>
@@ -232,14 +227,19 @@ export const SettingsObjectFieldItemTableRow = ({
}}
/>
</TableCell>
<StyledIconTableCell>
<TableCell
align="center"
padding={`0 ${themeCssVariables.spacing[1]} 0 ${themeCssVariables.spacing[2]}`}
>
{status === 'active' ? (
mode === 'view' ? (
<UndecoratedLink to={linkToNavigate}>
<StyledIconChevronRight
size={theme.icon.size.md}
stroke={theme.icon.stroke.sm}
/>
<StyledIconChevronRightContainer>
<IconChevronRight
size={theme.icon.size.md}
stroke={theme.icon.stroke.sm}
/>
</StyledIconChevronRightContainer>
</UndecoratedLink>
) : (
canToggleField && (
@@ -273,7 +273,7 @@ export const SettingsObjectFieldItemTableRow = ({
onClick={handleToggleField}
/>
)}
</StyledIconTableCell>
</StyledObjectFieldTableRow>
</TableCell>
</TableRow>
);
};
@@ -5,10 +5,11 @@ import { useLingui } from '@lingui/react/macro';
import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { isHiddenSystemField } from '@/object-metadata/utils/isHiddenSystemField';
import { SettingsItemTypeTag } from '@/settings/components/SettingsItemTypeTag';
import { TableRow } from '@/ui/layout/table/components/TableRow';
import {
SETTINGS_OBJECT_TABLE_ROW_GRID_TEMPLATE_COLUMNS,
StyledActionTableCell,
StyledNameTableCell,
StyledObjectTableRow,
} from '@/settings/data-model/object-details/components/SettingsObjectItemTableRowStyledComponents';
import { TableCell } from '@/ui/layout/table/components/TableCell';
import { useIcons } from 'twenty-ui/display';
@@ -63,7 +64,11 @@ export const SettingsObjectMetadataItemTableRow = ({
const Icon = getIcon(objectMetadataItem.icon);
return (
<StyledObjectTableRow key={objectMetadataItem.namePlural} to={link}>
<TableRow
gridTemplateColumns={SETTINGS_OBJECT_TABLE_ROW_GRID_TEMPLATE_COLUMNS}
key={objectMetadataItem.namePlural}
to={link}
>
<StyledNameTableCell>
{!!Icon && (
<Icon
@@ -95,6 +100,6 @@ export const SettingsObjectMetadataItemTableRow = ({
</TableCell>
<TableCell align="right">{totalObjectCount}</TableCell>
<StyledActionTableCell>{action}</StyledActionTableCell>
</StyledObjectTableRow>
</TableRow>
);
};
@@ -1,21 +0,0 @@
import { styled } from '@linaria/react';
import { TableCell } from '@/ui/layout/table/components/TableCell';
import { TableRow } from '@/ui/layout/table/components/TableRow';
import { themeCssVariables } from 'twenty-ui/theme-constants';
export const SETTINGS_OBJECT_TABLE_COLUMN_WIDTH = '98.7px';
export const StyledObjectTableRow = styled(TableRow)`
grid-template-columns: 180px ${SETTINGS_OBJECT_TABLE_COLUMN_WIDTH} ${SETTINGS_OBJECT_TABLE_COLUMN_WIDTH} ${SETTINGS_OBJECT_TABLE_COLUMN_WIDTH} 36px;
`;
export const StyledNameTableCell = styled(TableCell)`
color: ${themeCssVariables.font.color.primary};
gap: ${themeCssVariables.spacing[2]};
`;
export const StyledActionTableCell = styled(TableCell)`
justify-content: center;
padding-right: ${themeCssVariables.spacing[1]};
`;
@@ -0,0 +1,29 @@
import { TableCell } from '@/ui/layout/table/components/TableCell';
import { themeCssVariables } from 'twenty-ui/theme-constants';
import React from 'react';
export const SETTINGS_OBJECT_TABLE_COLUMN_WIDTH = '98.7px';
export const SETTINGS_OBJECT_TABLE_ROW_GRID_TEMPLATE_COLUMNS = `180px ${SETTINGS_OBJECT_TABLE_COLUMN_WIDTH} ${SETTINGS_OBJECT_TABLE_COLUMN_WIDTH} ${SETTINGS_OBJECT_TABLE_COLUMN_WIDTH} 36px`;
export const StyledNameTableCell = (
props: React.ComponentProps<typeof TableCell>,
) => (
<TableCell
color={themeCssVariables.font.color.primary}
gap={themeCssVariables.spacing[2]}
// eslint-disable-next-line react/jsx-props-no-spreading
{...props}
/>
);
export const StyledActionTableCell = (
props: React.ComponentProps<typeof TableCell>,
) => (
<TableCell
align="center"
padding={`0 ${themeCssVariables.spacing[1]} 0 ${themeCssVariables.spacing[2]}`}
// eslint-disable-next-line react/jsx-props-no-spreading
{...props}
/>
);
@@ -25,14 +25,8 @@ type SettingsObjectRelationItemTableRowProps = {
objectMetadataItem: ObjectMetadataItem;
};
export const StyledObjectRelationTableRow = styled(TableRow)`
grid-template-columns: minmax(0, 1fr) 148px 148px 36px;
`;
const StyledNameTableCell = styled(TableCell)`
color: ${themeCssVariables.font.color.primary};
gap: ${themeCssVariables.spacing[2]};
`;
export const OBJECT_RELATION_TABLE_ROW_GRID_TEMPLATE_COLUMNS =
'minmax(0, 1fr) 148px 148px 36px';
const StyledNameContainer = styled.div`
display: flex;
@@ -63,13 +57,10 @@ const StyledInactiveLabel = styled.span`
}
`;
const StyledIconTableCell = styled(TableCell)`
justify-content: center;
padding-right: ${themeCssVariables.spacing[1]};
`;
const StyledIconChevronRight = styled(IconChevronRight)`
const StyledIconChevronRightContainer = styled.span`
align-items: center;
color: ${themeCssVariables.font.color.tertiary};
display: flex;
`;
const StyledRelationType = styled.div`
@@ -79,18 +70,24 @@ const StyledRelationType = styled.div`
gap: ${themeCssVariables.spacing[1]};
`;
const StyledLink = styled(Link)`
color: ${themeCssVariables.font.color.primary};
text-decoration: underline;
text-decoration-color: ${themeCssVariables.border.color.strong};
text-underline-offset: 2px;
const StyledLinkContainer = styled.div`
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
&:hover {
color: ${themeCssVariables.color.blue};
text-decoration-color: ${themeCssVariables.color.blue};
> a {
color: ${themeCssVariables.font.color.primary};
text-decoration: underline;
text-decoration-color: ${themeCssVariables.border.color.strong};
text-underline-offset: 2px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
&:hover {
color: ${themeCssVariables.color.blue};
text-decoration-color: ${themeCssVariables.color.blue};
}
}
`;
@@ -157,9 +154,14 @@ export const SettingsObjectRelationItemTableRow = ({
: fieldMetadataItem.label;
return (
// eslint-disable-next-line twenty/no-navigate-prefer-link
<StyledObjectRelationTableRow onClick={navigateToFieldEdit}>
<StyledNameTableCell>
<TableRow
gridTemplateColumns={OBJECT_RELATION_TABLE_ROW_GRID_TEMPLATE_COLUMNS}
to={linkToNavigate}
>
<TableCell
color={themeCssVariables.font.color.primary}
gap={themeCssVariables.spacing[2]}
>
{!!Icon && (
<Icon
style={{
@@ -171,15 +173,17 @@ export const SettingsObjectRelationItemTableRow = ({
)}
<StyledNameContainer>
{isRelatedObjectLinkable ? (
<StyledLink
to={getSettingsPath(SettingsPath.ObjectDetail, {
objectNamePlural: relationObjectMetadataItem.namePlural,
})}
onClick={(event) => event.stopPropagation()}
title={targetObjectLabel}
>
{targetObjectLabel}
</StyledLink>
<StyledLinkContainer>
<Link
to={getSettingsPath(SettingsPath.ObjectDetail, {
objectNamePlural: relationObjectMetadataItem.namePlural,
})}
onClick={(event) => event.stopPropagation()}
title={targetObjectLabel}
>
{targetObjectLabel}
</Link>
</StyledLinkContainer>
) : (
<StyledNameLabel title={targetObjectLabel}>
{targetObjectLabel}
@@ -189,7 +193,7 @@ export const SettingsObjectRelationItemTableRow = ({
<StyledInactiveLabel>{t`Deactivated`}</StyledInactiveLabel>
)}
</StyledNameContainer>
</StyledNameTableCell>
</TableCell>
<TableCell>
<SettingsItemTypeTag
@@ -213,13 +217,18 @@ export const SettingsObjectRelationItemTableRow = ({
</StyledRelationType>
</TableCell>
<StyledIconTableCell>
<TableCell
align="center"
padding={`0 ${themeCssVariables.spacing[1]} 0 ${themeCssVariables.spacing[2]}`}
>
{fieldMetadataItem.isActive ? (
<UndecoratedLink to={linkToNavigate}>
<StyledIconChevronRight
size={theme.icon.size.md}
stroke={theme.icon.stroke.sm}
/>
<StyledIconChevronRightContainer>
<IconChevronRight
size={theme.icon.size.md}
stroke={theme.icon.stroke.sm}
/>
</StyledIconChevronRightContainer>
</UndecoratedLink>
) : (
<SettingsObjectFieldInactiveActionDropdown
@@ -238,7 +247,7 @@ export const SettingsObjectRelationItemTableRow = ({
}
/>
)}
</StyledIconTableCell>
</StyledObjectRelationTableRow>
</TableCell>
</TableRow>
);
};
@@ -27,9 +27,10 @@ import { Button } from 'twenty-ui/input';
import { MenuItemToggle } from 'twenty-ui/navigation';
import { themeCssVariables } from 'twenty-ui/theme-constants';
import { normalizeSearchText } from '~/utils/normalizeSearchText';
import { TableRow } from '@/ui/layout/table/components/TableRow';
import {
OBJECT_RELATION_TABLE_ROW_GRID_TEMPLATE_COLUMNS,
SettingsObjectRelationItemTableRow,
StyledObjectRelationTableRow,
} from './SettingsObjectRelationItemTableRow';
const StyledSearchAndFilterContainer = styled.div`
@@ -39,7 +40,7 @@ const StyledSearchAndFilterContainer = styled.div`
width: 100%;
`;
const StyledSearchInput = styled(SettingsTextInput)`
const StyledSearchInputContainer = styled.div`
flex: 1;
`;
@@ -118,13 +119,15 @@ export const SettingsObjectRelationsTable = ({
return (
<>
<StyledSearchAndFilterContainer>
<StyledSearchInput
instanceId="object-relation-table-search"
LeftIcon={IconSearch}
placeholder={t`Search a field...`}
value={searchTerm}
onChange={setSearchTerm}
/>
<StyledSearchInputContainer>
<SettingsTextInput
instanceId="object-relation-table-search"
LeftIcon={IconSearch}
placeholder={t`Search a field...`}
value={searchTerm}
onChange={setSearchTerm}
/>
</StyledSearchInputContainer>
<Dropdown
dropdownId="settings-relations-filter-dropdown"
dropdownPlacement="bottom-end"
@@ -165,7 +168,9 @@ export const SettingsObjectRelationsTable = ({
/>
</StyledSearchAndFilterContainer>
<Table>
<StyledObjectRelationTableRow>
<TableRow
gridTemplateColumns={OBJECT_RELATION_TABLE_ROW_GRID_TEMPLATE_COLUMNS}
>
{tableMetadata.fields.map((item) => (
<SortableTableHeader
key={item.fieldName}
@@ -176,7 +181,7 @@ export const SettingsObjectRelationsTable = ({
/>
))}
<TableHeader></TableHeader>
</StyledObjectRelationTableRow>
</TableRow>
{filteredRelationFields.map((fieldMetadataItem) => (
<SettingsObjectRelationItemTableRow
key={fieldMetadataItem.id}
@@ -29,8 +29,10 @@ const StyledContentContainer = styled.div`
gap: ${themeCssVariables.spacing[8]};
`;
const StyledFormSection = styled(Section)`
padding-left: 0 !important;
const StyledFormSectionContainer = styled.div`
> * {
padding-left: 0 !important;
}
`;
const StyledDangerButtonsContainer = styled.div`
@@ -90,16 +92,18 @@ export const ObjectSettings = ({
return (
<StyledContentContainer>
<StyledFormSection>
<H2Title
title={t`About`}
description={t`Name in both singular (e.g., 'Invoice') and plural (e.g., 'Invoices') forms.`}
/>
<SettingsUpdateDataModelObjectAboutForm
objectMetadataItem={objectMetadataItem}
/>
</StyledFormSection>
<StyledFormSection>
<StyledFormSectionContainer>
<Section>
<H2Title
title={t`About`}
description={t`Name in both singular (e.g., 'Invoice') and plural (e.g., 'Invoices') forms.`}
/>
<SettingsUpdateDataModelObjectAboutForm
objectMetadataItem={objectMetadataItem}
/>
</Section>
</StyledFormSectionContainer>
<StyledFormSectionContainer>
<Section>
<H2Title
title={t`Options`}
@@ -109,9 +113,9 @@ export const ObjectSettings = ({
objectMetadataItem={objectMetadataItem}
/>
</Section>
</StyledFormSection>
</StyledFormSectionContainer>
{!isReadOnly && (
<StyledFormSection>
<StyledFormSectionContainer>
<Section>
<H2Title
title={t`Danger zone`}
@@ -136,7 +140,7 @@ export const ObjectSettings = ({
)}
</StyledDangerButtonsContainer>
</Section>
</StyledFormSection>
</StyledFormSectionContainer>
)}
<ConfirmationModal
modalInstanceId={DELETE_OBJECT_MODAL_ID}