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:
+24
-24
@@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
+8
-3
@@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
-21
@@ -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]};
|
||||
`;
|
||||
+29
@@ -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}
|
||||
/>
|
||||
);
|
||||
+51
-42
@@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
+16
-11
@@ -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}
|
||||
|
||||
+19
-15
@@ -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}
|
||||
|
||||
Reference in New Issue
Block a user