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
@@ -7,7 +7,7 @@ import { IconFilter } from 'twenty-ui/display';
import { Button } from 'twenty-ui/input';
import { themeCssVariables } from 'twenty-ui/theme-constants';
const StyledButton = styled(Button)`
const StyledButtonContainer = styled.div`
margin-top: ${themeCssVariables.spacing[2]};
`;
@@ -17,15 +17,17 @@ export const WorkflowStepFilterAddRootStepFilterButton = () => {
const { addRootStepFilter } = useAddRootStepFilter();
return (
<StyledButton
Icon={IconFilter}
size="small"
variant="secondary"
accent="default"
onClick={addRootStepFilter}
ariaLabel={t`Add first filter`}
title={t`Add first filter`}
disabled={readonly}
/>
<StyledButtonContainer>
<Button
Icon={IconFilter}
size="small"
variant="secondary"
accent="default"
onClick={addRootStepFilter}
ariaLabel={t`Add first filter`}
title={t`Add first filter`}
disabled={readonly}
/>
</StyledButtonContainer>
);
};
@@ -1,4 +1,5 @@
import { styled } from '@linaria/react';
import React from 'react';
import { IconChevronRight, Label } from 'twenty-ui/display';
import { themeCssVariables } from 'twenty-ui/theme-constants';
@@ -6,11 +7,17 @@ export const StyledText = styled.div`
color: ${themeCssVariables.font.color.secondary};
`;
export const StyledLabel = styled(Label)`
const StyledLabelWrapper = styled.div`
margin: ${themeCssVariables.spacing[3]} ${themeCssVariables.spacing[3]}
${themeCssVariables.spacing[0]};
`;
export const StyledLabel = ({ children }: { children: React.ReactNode }) => (
<StyledLabelWrapper>
<Label>{children}</Label>
</StyledLabelWrapper>
);
export const StyledRow = styled.div<{ isDisabled?: boolean }>`
align-items: center;
display: flex;
@@ -61,6 +68,18 @@ export const StyledIconContainer = styled.div`
color: ${themeCssVariables.font.color.tertiary};
`;
export const StyledIconChevronRight = styled(IconChevronRight)`
const StyledIconChevronRightWrapper = styled.div`
align-items: center;
color: ${themeCssVariables.font.color.tertiary};
display: inline-flex;
`;
export const StyledIconChevronRight = ({
size,
color,
stroke,
}: React.ComponentProps<typeof IconChevronRight>) => (
<StyledIconChevronRightWrapper>
<IconChevronRight size={size} color={color} stroke={stroke} />
</StyledIconChevronRightWrapper>
);
@@ -26,11 +26,11 @@ import { WorkflowAiAgentPermissionsFlagList } from './WorkflowAiAgentPermissions
import { WorkflowAiAgentPermissionsObjectsList } from './WorkflowAiAgentPermissionsObjectsList';
import { getFilteredPermissions } from './workflowAiAgentPermissions.utils';
import { themeCssVariables } from 'twenty-ui/theme-constants';
const StyledSearchInput = styled(TextInput)`
const StyledSearchInputContainer = styled.div`
width: 100%;
height: 40px;
border-block: 1px solid ${themeCssVariables.border.color.medium};
input {
& input {
height: 40px;
line-height: 40px;
border: none;
@@ -198,16 +198,18 @@ export const WorkflowAiAgentPermissionsTab = ({
</StyledBackButton>
)}
<StyledSearchInput
value={searchQuery}
onChange={(value: string) => setSearchQuery(value)}
placeholder={t`Type anything...`}
onKeyDown={(event) => {
if (isNonTextWritingKey(event.key)) {
event.stopPropagation();
}
}}
/>
<StyledSearchInputContainer>
<TextInput
value={searchQuery}
onChange={(value: string) => setSearchQuery(value)}
placeholder={t`Type anything...`}
onKeyDown={(event) => {
if (isNonTextWritingKey(event.key)) {
event.stopPropagation();
}
}}
/>
</StyledSearchInputContainer>
{shouldShowCrudList && (
<WorkflowAiAgentPermissionsCrudList
@@ -50,7 +50,7 @@ type WorkflowEditActionAiAgentProps = {
};
};
const StyledTabList = styled(TabList)`
const StyledTabListContainer = styled.div`
background-color: ${themeCssVariables.background.secondary};
padding-left: ${themeCssVariables.spacing[2]};
`;
@@ -270,11 +270,13 @@ export const WorkflowEditActionAiAgent = ({
<SidePanelSkeletonLoader />
) : (
<>
<StyledTabList
tabs={tabs}
componentInstanceId={componentInstanceId}
behaveAsLinks={false}
/>
<StyledTabListContainer>
<TabList
tabs={tabs}
componentInstanceId={componentInstanceId}
behaveAsLinks={false}
/>
</StyledTabListContainer>
{currentTabId === WORKFLOW_AI_AGENT_TABS.PERMISSIONS ? (
<WorkflowStepBody paddingBlock="0" paddingInline="0">
<WorkflowAiAgentPermissionsTab
@@ -55,7 +55,7 @@ const StyledCodeEditorContainer = styled.div`
position: relative;
`;
const StyledTabList = styled(TabList)`
const StyledTabListContainer = styled.div`
background-color: ${themeCssVariables.background.secondary};
padding-left: ${themeCssVariables.spacing[2]};
`;
@@ -370,11 +370,13 @@ export const WorkflowEditActionCode = ({
!loading && (
<>
<LogicFunctionTestInputInitEffect logicFunctionId={logicFunctionId} />
<StyledTabList
tabs={tabs}
behaveAsLinks={false}
componentInstanceId={WORKFLOW_LOGIC_FUNCTION_TAB_LIST_COMPONENT_ID}
/>
<StyledTabListContainer>
<TabList
tabs={tabs}
behaveAsLinks={false}
componentInstanceId={WORKFLOW_LOGIC_FUNCTION_TAB_LIST_COMPONENT_ID}
/>
</StyledTabListContainer>
<WorkflowStepBody>
{activeTabId === WorkflowLogicFunctionTabId.CODE && (
<>
@@ -139,8 +139,11 @@ const StyledCalloutContainer = styled.div`
padding-top: ${themeCssVariables.spacing[2]};
`;
const StyledNotClosableCalloutContainer = styled(StyledCalloutContainer)`
const StyledNotClosableCalloutContainer = styled.div`
padding-bottom: ${themeCssVariables.spacing[4]};
padding-left: ${themeCssVariables.spacing[7]};
padding-right: ${themeCssVariables.spacing[7]};
padding-top: ${themeCssVariables.spacing[2]};
`;
export const WorkflowEditActionFormBuilder = ({
@@ -32,7 +32,7 @@ const StyledContainer = styled.div`
gap: ${themeCssVariables.spacing[2]};
`;
const StyledSelectDropdown = styled(Select)`
const StyledSelectDropdownContainer = styled.div`
margin-bottom: ${themeCssVariables.spacing[2]};
`;
const StyledNoBodyMessage = styled.div`
@@ -163,23 +163,25 @@ export const BodyInput = ({
return (
<FormFieldInputContainer>
<InputLabel>{t`Body Input`}</InputLabel>
<StyledSelectDropdown
options={[
{ label: t`Key/Value`, value: BODY_TYPES.KEY_VALUE, Icon: IconKey },
{
label: t`Raw JSON`,
value: BODY_TYPES.RAW_JSON,
Icon: IconFileText,
},
{ label: t`Form Data`, value: BODY_TYPES.FORM_DATA, Icon: IconKey },
{ label: t`Text`, value: BODY_TYPES.TEXT, Icon: IconFileText },
{ label: t`None`, value: BODY_TYPES.NONE, Icon: IconFileText },
]}
dropdownId="body-input-mode"
value={getBodyTypeFromHeaders(headers) || BODY_TYPES.NONE}
onChange={(value) => handleModeChange(value as BodyType)}
disabled={readonly}
/>
<StyledSelectDropdownContainer>
<Select
options={[
{ label: t`Key/Value`, value: BODY_TYPES.KEY_VALUE, Icon: IconKey },
{
label: t`Raw JSON`,
value: BODY_TYPES.RAW_JSON,
Icon: IconFileText,
},
{ label: t`Form Data`, value: BODY_TYPES.FORM_DATA, Icon: IconKey },
{ label: t`Text`, value: BODY_TYPES.TEXT, Icon: IconFileText },
{ label: t`None`, value: BODY_TYPES.NONE, Icon: IconFileText },
]}
dropdownId="body-input-mode"
value={getBodyTypeFromHeaders(headers) || BODY_TYPES.NONE}
onChange={(value) => handleModeChange(value as BodyType)}
disabled={readonly}
/>
</StyledSelectDropdownContainer>
<StyledContainer>
{isBodyTypeRawJson ? (
@@ -39,7 +39,7 @@ type WorkflowEditActionHttpRequestProps = {
};
};
const StyledTabList = styled(TabList)`
const StyledTabListContainer = styled.div`
background-color: ${themeCssVariables.background.secondary};
padding-left: ${themeCssVariables.spacing[2]};
`;
@@ -61,7 +61,7 @@ const StyledConfigurationTabContent = styled.div`
flex: 1;
`;
const StyledFullHeightFormRawJsonFieldInput = styled(FormRawJsonFieldInput)`
const StyledFullHeightFormRawJsonFieldInputContainer = styled.div`
flex: 1;
display: flex;
flex-direction: column;
@@ -128,11 +128,13 @@ export const WorkflowEditActionHttpRequest = ({
return (
<>
<StyledTabList
tabs={tabs}
behaveAsLinks={false}
componentInstanceId={WORKFLOW_HTTP_REQUEST_TAB_LIST_COMPONENT_ID}
/>
<StyledTabListContainer>
<TabList
tabs={tabs}
behaveAsLinks={false}
componentInstanceId={WORKFLOW_HTTP_REQUEST_TAB_LIST_COMPONENT_ID}
/>
</StyledTabListContainer>
<WorkflowStepBody>
{activeTabId === WorkflowHttpRequestTabId.CONFIGURATION && (
<StyledConfigurationTabContent>
@@ -178,14 +180,16 @@ export const WorkflowEditActionHttpRequest = ({
/>
)}
<StyledFullHeightFormRawJsonFieldInput
label={t`Expected Response Body`}
placeholder={JSON_RESPONSE_PLACEHOLDER}
defaultValue={outputSchema}
onChange={handleOutputSchemaChange}
readonly={actionOptions.readonly}
error={error}
/>
<StyledFullHeightFormRawJsonFieldInputContainer>
<FormRawJsonFieldInput
label={t`Expected Response Body`}
placeholder={JSON_RESPONSE_PLACEHOLDER}
defaultValue={outputSchema}
onChange={handleOutputSchemaChange}
readonly={actionOptions.readonly}
error={error}
/>
</StyledFullHeightFormRawJsonFieldInputContainer>
</StyledConfigurationTabContent>
)}
{activeTabId === WorkflowHttpRequestTabId.TEST && (