Complete linaria migration (#18361)

## Summary

Completes the migration of the frontend styling system from **Emotion**
(`@emotion/styled`, `@emotion/react`) to **Linaria** (`@linaria/react`,
`@linaria/core`), a zero-runtime CSS-in-JS library where styles are
extracted at build time.

This is the final step of the migration — all ~494 files across
`twenty-front`, `twenty-ui`, `twenty-website`, and `twenty-sdk` are now
fully converted.

## Changes

### Styling Migration (across ~480 component files)
- Replaced all `@emotion/styled` imports with `@linaria/react`
- Converted runtime theme access patterns (`({ theme }) => theme.x.y`)
to build-time `themeCssVariables` CSS custom properties
- Replaced `useTheme()` hook (from Emotion) with
`useContext(ThemeContext)` where runtime theme values are still needed
(e.g., passing colors to non-CSS props like icon components)
- Removed `@emotion/react` `css` helper usages in favor of Linaria
template literals

### Dependency & Configuration Changes
- **Removed**: `@emotion/react`, `@emotion/styled` from root
`package.json`
- **Added**: `@wyw-in-js/babel-preset`, `next-with-linaria` (for
twenty-website SSR support)
- Updated Nx generator defaults from `@emotion/styled` to
`@linaria/react` in `nx.json`
- Simplified `vite.config.ts` (removed Emotion-specific configuration)
- Updated `twenty-website/next.config.js` to use `next-with-linaria` for
SSR Linaria support

### Storybook & Testing
- Removed `ThemeProvider` from Emotion in Storybook previews
(`twenty-front`, `twenty-sdk`)
- Now relies solely on `ThemeContextProvider` for theme injection

### Documentation
- Removed the temporary `docs/emotion-to-linaria-migration-plan.md`
(migration complete)
- Updated `CLAUDE.md` and `README.md` to reflect Linaria as the styling
stack
- Updated frontend style guide docs across all locales

## How it works

Linaria extracts styles at build time via the `@wyw-in-js/vite` plugin.
All expressions in `styled` template literals must be **statically
evaluable** — no runtime theme objects or closures over component state.

- **Static styles** use `themeCssVariables` which map to CSS custom
properties (`var(--theme-color-x)`)
- **Runtime theme access** (for non-CSS use cases like icon `color`
props) uses `useContext(ThemeContext)` instead of Emotion's `useTheme()`
This commit is contained in:
Charles Bochet
2026-03-04 00:50:06 +01:00
committed by GitHub
parent 8a3b96d911
commit 7a2e397ad1
540 changed files with 3654 additions and 4156 deletions
@@ -1,6 +1,8 @@
import { useTheme } from '@emotion/react';
import styled from '@emotion/styled';
import { styled } from '@linaria/react';
import { useContext } from 'react';
import { IconPoint } from 'twenty-ui/display';
import { ThemeContext } from 'twenty-ui/theme';
import { themeCssVariables } from 'twenty-ui/theme-constants';
const StyledWrapper = styled.div`
position: relative;
@@ -18,18 +20,11 @@ const StyledDotContainer = styled.div<{ dotPosition: DotPosition }>`
display: flex;
position: absolute;
height: 100%;
left: ${({ theme }) => theme.spacing(-5)};
left: calc(-1 * ${themeCssVariables.spacing[5]});
${({ dotPosition }) => {
if (dotPosition === 'top') {
return `
top: 0;
`;
}
return `
align-items: center;
`;
}}
top: ${({ dotPosition }) => (dotPosition === 'top' ? '0' : 'auto')};
align-items: ${({ dotPosition }) =>
dotPosition === 'top' ? 'stretch' : 'center'};
`;
const StyledIconPoint = styled(IconPoint)`
@@ -41,7 +36,7 @@ export const AdvancedSettingsContentWrapperWithDot = ({
hideDot = false,
dotPosition = 'centered',
}: AdvancedSettingsContentWrapperWithDotProps) => {
const theme = useTheme();
const { theme } = useContext(ThemeContext);
return (
<StyledWrapper>
{!hideDot && (
@@ -2,7 +2,7 @@ import { AdvancedSettingsContentWrapperWithDot } from '@/settings/components/Adv
import { ADVANCED_SETTINGS_ANIMATION_DURATION } from '@/settings/constants/AdvancedSettingsAnimationDurations';
import { isAdvancedModeEnabledState } from '@/ui/navigation/navigation-drawer/states/isAdvancedModeEnabledState';
import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue';
import styled from '@emotion/styled';
import { styled } from '@linaria/react';
import { AnimatedExpandableContainer } from 'twenty-ui/layout';
const StyledContent = styled.div`
@@ -1,5 +1,6 @@
import styled from '@emotion/styled';
import { styled } from '@linaria/react';
import type { IconComponent } from 'twenty-ui/display';
import { themeCssVariables } from 'twenty-ui/theme-constants';
import { CancelButton } from './CancelButton';
import { SaveButton } from './SaveButton';
@@ -7,7 +8,7 @@ import { SaveButton } from './SaveButton';
const StyledContainer = styled.div`
align-items: center;
display: flex;
gap: ${({ theme }) => theme.spacing(1)};
gap: ${themeCssVariables.spacing[1]};
`;
type SaveAndCancelButtonsProps = {
@@ -1,11 +1,12 @@
import styled from '@emotion/styled';
import { styled } from '@linaria/react';
import { themeCssVariables } from 'twenty-ui/theme-constants';
const StyledHr = styled.hr`
border: none;
border-top: 1px solid ${({ theme }) => theme.border.color.light};
border-top: 1px solid ${themeCssVariables.border.color.light};
margin: 0px;
margin-left: ${({ theme }) => theme.spacing(4)};
margin-right: ${({ theme }) => theme.spacing(4)};
margin-left: ${themeCssVariables.spacing[4]};
margin-right: ${themeCssVariables.spacing[4]};
`;
export const Separator = () => {
@@ -1,11 +1,12 @@
import { useTheme } from '@emotion/react';
import styled from '@emotion/styled';
import { styled } from '@linaria/react';
import { type ReactNode } from 'react';
import { type ReactNode, useContext } from 'react';
import { t } from '@lingui/core/macro';
import { Card, CardContent } from 'twenty-ui/layout';
import { IconChevronRight } from 'twenty-ui/display';
import { Pill } from 'twenty-ui/components';
import { ThemeContext } from 'twenty-ui/theme';
import { themeCssVariables } from 'twenty-ui/theme-constants';
type SettingsCardProps = {
description?: string;
@@ -22,21 +23,23 @@ const StyledCard = styled(Card)<{
disabled?: boolean;
onClick?: () => void;
}>`
color: ${({ disabled, theme }) =>
disabled ? theme.font.color.extraLight : theme.font.color.tertiary};
color: ${({ disabled }) =>
disabled
? themeCssVariables.font.color.extraLight
: themeCssVariables.font.color.tertiary};
cursor: ${({ disabled, onClick }) =>
disabled ? 'not-allowed' : onClick ? 'pointer' : 'default'};
width: 100%;
`;
const StyledCardContent = styled(CardContent)<object>`
const StyledCardContent = styled(CardContent)`
display: flex;
flex-direction: column;
gap: ${({ theme }) => theme.spacing(2)};
padding: ${({ theme }) => theme.spacing(2, 2)};
gap: ${themeCssVariables.spacing[2]};
padding: ${themeCssVariables.spacing[2]} ${themeCssVariables.spacing[2]};
&:hover {
background-color: ${({ theme }) => theme.background.quaternary};
background-color: ${themeCssVariables.background.quaternary};
cursor: pointer;
}
`;
@@ -44,26 +47,28 @@ const StyledCardContent = styled(CardContent)<object>`
const StyledHeader = styled.div`
align-items: center;
display: flex;
gap: ${({ theme }) => theme.spacing(2)};
gap: ${themeCssVariables.spacing[2]};
`;
const StyledTitle = styled.div<{ disabled?: boolean }>`
color: ${({ disabled, theme }) =>
disabled ? 'inherit' : theme.font.color.secondary};
color: ${({ disabled }) =>
disabled
? themeCssVariables.font.color.extraLight
: themeCssVariables.font.color.secondary};
display: flex;
flex: 1 0 auto;
font-weight: ${({ theme }) => theme.font.weight.medium};
gap: ${({ theme }) => theme.spacing(2)};
font-weight: ${themeCssVariables.font.weight.medium};
gap: ${themeCssVariables.spacing[2]};
justify-content: flex-start;
`;
const StyledIconChevronRight = styled(IconChevronRight)`
color: ${({ theme }) => theme.font.color.light};
color: ${themeCssVariables.font.color.light};
`;
const StyledDescription = styled.div`
padding-bottom: ${({ theme }) => theme.spacing(2)};
padding-left: ${({ theme }) => theme.spacing(7)};
padding-bottom: ${themeCssVariables.spacing[2]};
padding-left: ${themeCssVariables.spacing[7]};
`;
const StyledIconContainer = styled.div`
@@ -84,7 +89,7 @@ export const SettingsCard = ({
className,
Status,
}: SettingsCardProps) => {
const theme = useTheme();
const { theme } = useContext(ThemeContext);
return (
<StyledCard
@@ -3,19 +3,21 @@ import { SettingsAccountsConnectedAccountsRowRightContainer } from '@/settings/a
import { SettingsConnectedAccountIcon } from '@/settings/accounts/components/SettingsConnectedAccountIcon';
import { TableCell } from '@/ui/layout/table/components/TableCell';
import { TableRow } from '@/ui/layout/table/components/TableRow';
import { useTheme } from '@emotion/react';
import styled from '@emotion/styled';
import { styled } from '@linaria/react';
import { useContext } from 'react';
import { ThemeContext } from 'twenty-ui/theme';
import { themeCssVariables } from 'twenty-ui/theme-constants';
const StyledNameCell = styled.div`
align-items: center;
color: ${({ theme }) => theme.font.color.primary};
color: ${themeCssVariables.font.color.primary};
display: flex;
gap: ${({ theme }) => theme.spacing(1)};
gap: ${themeCssVariables.spacing[1]};
`;
const StyledTableRow = styled(TableRow)`
&:hover {
background: ${({ theme }) => theme.background.transparent.light};
background: ${themeCssVariables.background.transparent.light};
cursor: pointer;
}
`;
@@ -27,7 +29,7 @@ type SettingsConnectedAccountsTableRowProps = {
export const SettingsConnectedAccountsTableRow = ({
account,
}: SettingsConnectedAccountsTableRowProps) => {
const theme = useTheme();
const { theme } = useContext(ThemeContext);
const IconComponent = SettingsConnectedAccountIcon({ account });
@@ -1,7 +1,8 @@
import { SettingsTextInput } from '@/ui/input/components/SettingsTextInput';
import styled from '@emotion/styled';
import { styled } from '@linaria/react';
import { IconMinus, IconPlus } from 'twenty-ui/display';
import { IconButton } from 'twenty-ui/input';
import { themeCssVariables } from 'twenty-ui/theme-constants';
import { castAsNumberOrNull } from '~/utils/cast-as-number-or-null';
type SettingsCounterProps = {
@@ -16,19 +17,21 @@ type SettingsCounterProps = {
const StyledCounterContainer = styled.div<{ showButtons: boolean }>`
align-items: center;
display: flex;
gap: ${({ theme }) => theme.spacing(1)};
gap: ${themeCssVariables.spacing[1]};
margin-left: auto;
width: ${({ theme, showButtons }) =>
showButtons ? theme.spacing(30) : theme.spacing(16)};
width: ${({ showButtons }) =>
showButtons
? themeCssVariables.spacing[30]
: themeCssVariables.spacing[16]};
`;
const StyledTextInput = styled(SettingsTextInput)`
width: ${({ theme }) => theme.spacing(16)};
width: ${themeCssVariables.spacing[16]};
input {
width: ${({ theme }) => theme.spacing(16)};
height: ${({ theme }) => theme.spacing(6)};
width: ${themeCssVariables.spacing[16]};
height: ${themeCssVariables.spacing[6]};
text-align: center;
font-weight: ${({ theme }) => theme.font.weight.medium};
font-weight: ${themeCssVariables.font.weight.medium};
}
`;
@@ -1,11 +1,12 @@
import { WebhookEntitySelect } from '@/settings/developers/components/WebhookEntitySelect';
import { Select } from '@/ui/input/components/Select';
import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile';
import styled from '@emotion/styled';
import { styled } from '@linaria/react';
import { t } from '@lingui/core/macro';
import { isDefined } from 'twenty-shared/utils';
import { IconBox, IconNorthStar, IconPlus, IconTrash } from 'twenty-ui/display';
import { IconButton, type SelectOption } from 'twenty-ui/input';
import { themeCssVariables } from 'twenty-ui/theme-constants';
const OBJECT_DROPDOWN_WIDTH = 240;
const ACTION_DROPDOWN_WIDTH = 240;
@@ -18,14 +19,14 @@ const StyledFilterRow = styled.div<{ isMobile: boolean }>`
isMobile
? `${OBJECT_MOBILE_WIDTH}px ${ACTION_MOBILE_WIDTH}px auto`
: `${OBJECT_DROPDOWN_WIDTH}px ${ACTION_DROPDOWN_WIDTH}px auto`};
gap: ${({ theme }) => theme.spacing(2)};
margin-bottom: ${({ theme }) => theme.spacing(2)};
gap: ${themeCssVariables.spacing[2]};
margin-bottom: ${themeCssVariables.spacing[2]};
align-items: center;
`;
const StyledPlaceholder = styled.div`
height: ${({ theme }) => theme.spacing(8)};
width: ${({ theme }) => theme.spacing(8)};
height: ${themeCssVariables.spacing[8]};
width: ${themeCssVariables.spacing[8]};
`;
export const SettingsDatabaseEventsForm = ({
@@ -2,7 +2,7 @@ import { Table } from '@/ui/layout/table/components/Table';
import { TableCell } from '@/ui/layout/table/components/TableCell';
import { TableHeader } from '@/ui/layout/table/components/TableHeader';
import { TableRow } from '@/ui/layout/table/components/TableRow';
import styled from '@emotion/styled';
import { styled } from '@linaria/react';
import { t } from '@lingui/core/macro';
import { capitalize, isDefined } from 'twenty-shared/utils';
import { OverflowingTextWithTooltip, Status } from 'twenty-ui/display';
@@ -1,4 +1,4 @@
import styled from '@emotion/styled';
import { styled } from '@linaria/react';
const StyledSettingsHeaderContainer = styled.div`
align-items: center;
@@ -1,9 +1,11 @@
import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState';
import { getItemTagInfo } from '@/settings/data-model/utils/getItemTagInfo';
import { useTheme } from '@emotion/react';
import styled from '@emotion/styled';
import { styled } from '@linaria/react';
import { useContext } from 'react';
import { Avatar } from 'twenty-ui/display';
import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue';
import { ThemeContext } from 'twenty-ui/theme';
import { themeCssVariables } from 'twenty-ui/theme-constants';
type SettingsItemTypeTagProps = {
item: {
@@ -17,16 +19,16 @@ type SettingsItemTypeTagProps = {
const StyledContainer = styled.div`
align-items: center;
display: flex;
font-size: ${({ theme }) => theme.font.size.sm};
gap: ${({ theme }) => theme.spacing(1)};
color: ${({ theme }) => theme.font.color.secondary};
font-size: ${themeCssVariables.font.size.sm};
gap: ${themeCssVariables.spacing[1]};
color: ${themeCssVariables.font.color.secondary};
`;
export const SettingsItemTypeTag = ({
className,
item: { isCustom, isRemote, applicationId },
}: SettingsItemTypeTagProps) => {
const theme = useTheme();
const { theme } = useContext(ThemeContext);
const currentWorkspace = useAtomStateValue(currentWorkspaceState);
const itemTagInfo = getItemTagInfo({
item: { isCustom, isRemote, applicationId },
@@ -1,36 +1,37 @@
import { useTheme } from '@emotion/react';
import styled from '@emotion/styled';
import { type ComponentType } from 'react';
import { styled } from '@linaria/react';
import { type ComponentType, useContext } from 'react';
import { SettingsListSkeletonCard } from '@/settings/components/SettingsListSkeletonCard';
import { type IconComponent, IconPlus } from 'twenty-ui/display';
import { Card, CardFooter } from 'twenty-ui/layout';
import { ThemeContext } from 'twenty-ui/theme';
import { themeCssVariables } from 'twenty-ui/theme-constants';
import { SettingsListItemCardContent } from './SettingsListItemCardContent';
const StyledFooter = styled(CardFooter)`
align-items: center;
display: flex;
padding: ${({ theme }) => theme.spacing(1)};
padding: ${themeCssVariables.spacing[1]};
`;
const StyledButton = styled.button`
align-items: center;
background: ${({ theme }) => theme.background.primary};
background: ${themeCssVariables.background.primary};
border: none;
border-radius: ${({ theme }) => theme.border.radius.sm};
color: ${({ theme }) => theme.font.color.secondary};
gap: ${({ theme }) => theme.spacing(2)};
padding: 0 ${({ theme }) => theme.spacing(1)};
padding-left: ${({ theme }) => theme.spacing(2)};
border-radius: ${themeCssVariables.border.radius.sm};
color: ${themeCssVariables.font.color.secondary};
gap: ${themeCssVariables.spacing[2]};
padding: 0 ${themeCssVariables.spacing[1]};
padding-left: ${themeCssVariables.spacing[2]};
cursor: pointer;
display: flex;
flex: 1 0 0;
height: ${({ theme }) => theme.spacing(8)};
height: ${themeCssVariables.spacing[8]};
width: 100%;
&:hover {
background: ${({ theme }) => theme.background.transparent.light};
background: ${themeCssVariables.background.transparent.light};
}
`;
@@ -71,7 +72,7 @@ export const SettingsListCard = <
to,
rounded,
}: SettingsListCardProps<ListItem>) => {
const theme = useTheme();
const { theme } = useContext(ThemeContext);
if (isLoading === true) return <SettingsListSkeletonCard />;
@@ -1,41 +1,33 @@
import isPropValid from '@emotion/is-prop-valid';
import { useTheme } from '@emotion/react';
import styled from '@emotion/styled';
import { type ReactNode } from 'react';
import { styled } from '@linaria/react';
import { type ReactNode, useContext } from 'react';
import { Link } from 'react-router-dom';
import { isDefined } from 'twenty-shared/utils';
import { IconChevronRight, type IconComponent } from 'twenty-ui/display';
import { CardContent } from 'twenty-ui/layout';
import { ThemeContext } from 'twenty-ui/theme';
import { themeCssVariables } from 'twenty-ui/theme-constants';
const StyledRow = styled(CardContent, {
shouldForwardProp: (prop) => prop !== 'to' && isPropValid(prop),
})<{ to?: boolean }>`
const StyledRow = styled(CardContent)`
align-items: center;
cursor: ${({ onClick, to }) => (onClick || to ? 'pointer' : 'default')};
display: flex;
font-size: ${({ theme }) => theme.font.size.sm};
font-weight: ${({ theme }) => theme.font.weight.medium};
gap: ${({ theme }) => theme.spacing(2)};
padding: ${({ theme }) => theme.spacing(2)};
padding-left: ${({ theme }) => theme.spacing(3)};
min-height: ${({ theme }) => theme.spacing(6)};
&:hover {
${({ to, theme }) =>
to && `background: ${theme.background.transparent.light};`}
}
font-size: ${themeCssVariables.font.size.sm};
font-weight: ${themeCssVariables.font.weight.medium};
gap: ${themeCssVariables.spacing[2]};
padding: ${themeCssVariables.spacing[2]};
padding-left: ${themeCssVariables.spacing[3]};
min-height: ${themeCssVariables.spacing[6]};
`;
const StyledRightContainer = styled.div`
align-items: center;
display: flex;
gap: ${({ theme }) => theme.spacing(1)};
gap: ${themeCssVariables.spacing[1]};
`;
const StyledContent = styled.div`
flex: 1 1 0;
display: flex;
gap: ${({ theme }) => theme.spacing(1)};
gap: ${themeCssVariables.spacing[1]};
min-width: 0;
overflow: hidden;
`;
@@ -47,13 +39,13 @@ const StyledLabel = styled.span`
`;
const StyledDescription = styled.span`
color: ${({ theme }) => theme.font.color.light};
font-weight: ${({ theme }) => theme.font.weight.regular};
padding-left: ${({ theme }) => theme.spacing(1)};
color: ${themeCssVariables.font.color.light};
font-weight: ${themeCssVariables.font.weight.regular};
padding-left: ${themeCssVariables.spacing[1]};
`;
const StyledLink = styled(Link)`
color: ${({ theme }) => theme.font.color.secondary};
color: ${themeCssVariables.font.color.secondary};
text-decoration: none;
`;
@@ -78,10 +70,15 @@ export const SettingsListItemCardContent = ({
rightComponent,
to,
}: SettingsListItemCardContentProps) => {
const theme = useTheme();
const { theme } = useContext(ThemeContext);
const content = (
<StyledRow onClick={onClick} divider={divider} to={!!to}>
<StyledRow
onClick={onClick}
divider={divider}
isClickable={!!onClick || !!to}
hasHoverHighlight={!!to}
>
{!!LeftIcon && (
<LeftIcon
size={theme.icon.size.md}
@@ -1,8 +1,9 @@
import styled from '@emotion/styled';
import { styled } from '@linaria/react';
import { Card } from 'twenty-ui/layout';
import { themeCssVariables } from 'twenty-ui/theme-constants';
const StyledCard = styled(Card)`
background-color: ${({ theme }) => theme.background.secondary};
background-color: ${themeCssVariables.background.secondary};
height: 40px;
`;
@@ -1,4 +1,4 @@
import styled from '@emotion/styled';
import { styled } from '@linaria/react';
import { plural } from '@lingui/core/macro';
import { useMemo, useRef, useState, type MouseEvent } from 'react';
@@ -23,6 +23,7 @@ import { isNonEmptyString } from '@sniptt/guards';
import { isDefined } from 'twenty-shared/utils';
import { IconBox, useIcons, type IconComponent } from 'twenty-ui/display';
import { MenuItem, MenuItemMultiSelect } from 'twenty-ui/navigation';
import { themeCssVariables } from 'twenty-ui/theme-constants';
export type SelectSizeVariant = 'small' | 'default';
@@ -58,23 +59,23 @@ const StyledContainer = styled.div<{ fullWidth?: boolean }>`
`;
const StyledLabel = styled.span`
color: ${({ theme }) => theme.font.color.light};
color: ${themeCssVariables.font.color.light};
display: block;
font-size: ${({ theme }) => theme.font.size.xs};
font-weight: ${({ theme }) => theme.font.weight.semiBold};
margin-bottom: ${({ theme }) => theme.spacing(1)};
font-size: ${themeCssVariables.font.size.xs};
font-weight: ${themeCssVariables.font.weight.semiBold};
margin-bottom: ${themeCssVariables.spacing[1]};
`;
const StyledDescription = styled.span`
color: ${({ theme }) => theme.font.color.light};
font-size: ${({ theme }) => theme.font.size.sm};
color: ${themeCssVariables.font.color.light};
font-size: ${themeCssVariables.font.size.sm};
`;
const StyledError = styled.span`
color: ${({ theme }) => theme.color.red};
color: ${themeCssVariables.color.red};
display: block;
font-size: ${({ theme }) => theme.font.size.xs};
margin-top: ${({ theme }) => theme.spacing(1)};
font-size: ${themeCssVariables.font.size.xs};
margin-top: ${themeCssVariables.spacing[1]};
`;
export const SettingsMorphRelationMultiSelect = ({
@@ -1,4 +1,5 @@
import styled from '@emotion/styled';
import { styled } from '@linaria/react';
import { themeCssVariables } from 'twenty-ui/theme-constants';
type StyledCardContentProps = {
disabled?: boolean;
@@ -9,27 +10,27 @@ type StyledCardContentProps = {
export const StyledSettingsCardContent = styled.div<StyledCardContentProps>`
align-items: ${({ alignItems }) => alignItems ?? 'center'};
display: flex;
gap: ${({ theme }) => theme.spacing(3)};
background-color: ${({ theme }) => theme.background.secondary};
gap: ${themeCssVariables.spacing[3]};
background-color: ${themeCssVariables.background.secondary};
height: ${({ fullHeight }) => (fullHeight ? '100%' : 'auto')};
padding: ${({ theme }) => theme.spacing(4)};
padding: ${themeCssVariables.spacing[4]};
`;
export const StyledSettingsCardIcon = styled.div`
align-items: center;
border: 2px solid ${({ theme }) => theme.border.color.light};
border-radius: ${({ theme }) => theme.border.radius.sm};
background-color: ${({ theme }) => theme.background.primary};
border: 2px solid ${themeCssVariables.border.color.light};
border-radius: ${themeCssVariables.border.radius.sm};
background-color: ${themeCssVariables.background.primary};
display: flex;
height: ${({ theme }) => theme.spacing(7)};
height: ${themeCssVariables.spacing[7]};
justify-content: center;
width: ${({ theme }) => theme.spacing(7)};
min-width: ${({ theme }) => theme.icon.size.md};
width: ${themeCssVariables.spacing[7]};
min-width: ${themeCssVariables.icon.size.md};
`;
export const StyledSettingsCardTitle = styled.div`
color: ${({ theme }) => theme.font.color.primary};
font-weight: ${({ theme }) => theme.font.weight.medium};
margin-bottom: ${({ theme }) => theme.spacing(1)};
color: ${themeCssVariables.font.color.primary};
font-weight: ${themeCssVariables.font.weight.medium};
margin-bottom: ${themeCssVariables.spacing[1]};
`;
export const StyledSettingsCardTextContainer = styled.div`
@@ -38,8 +39,8 @@ export const StyledSettingsCardTextContainer = styled.div`
`;
export const StyledSettingsCardDescription = styled.div`
color: ${({ theme }) => theme.font.color.secondary};
font-size: ${({ theme }) => theme.font.size.sm};
color: ${themeCssVariables.font.color.secondary};
font-size: ${themeCssVariables.font.size.sm};
overflow: hidden;
line-height: 1.5;
@@ -51,7 +52,7 @@ export const StyledSettingsCardDescription = styled.div`
`;
export const StyledSettingsCardThirdLine = styled.div`
color: ${({ theme }) => theme.font.color.light};
font-size: ${({ theme }) => theme.font.size.xs};
margin-top: ${({ theme }) => theme.spacing(1)};
color: ${themeCssVariables.font.color.light};
font-size: ${themeCssVariables.font.size.xs};
margin-top: ${themeCssVariables.spacing[1]};
`;
@@ -6,7 +6,7 @@ import {
StyledSettingsCardTitle,
} from '@/settings/components/SettingsOptions/SettingsCardContentBase';
import { SettingsOptionIconCustomizer } from '@/settings/components/SettingsOptions/SettingsOptionIconCustomizer';
import styled from '@emotion/styled';
import { styled } from '@linaria/react';
import {
type IconComponent,
OverflowingTextWithTooltip,
@@ -6,7 +6,7 @@ import {
StyledSettingsCardTitle,
} from '@/settings/components/SettingsOptions/SettingsCardContentBase';
import { SettingsOptionIconCustomizer } from '@/settings/components/SettingsOptions/SettingsOptionIconCustomizer';
import styled from '@emotion/styled';
import { styled } from '@linaria/react';
import {
type IconComponent,
OverflowingTextWithTooltip,
@@ -7,14 +7,15 @@ import {
StyledSettingsCardTitle,
} from '@/settings/components/SettingsOptions/SettingsCardContentBase';
import { SettingsOptionIconCustomizer } from '@/settings/components/SettingsOptions/SettingsOptionIconCustomizer';
import { useTheme } from '@emotion/react';
import styled from '@emotion/styled';
import { useId } from 'react';
import { styled } from '@linaria/react';
import { useContext, useId } from 'react';
import {
type IconComponent,
OverflowingTextWithTooltip,
} from 'twenty-ui/display';
import { Toggle } from 'twenty-ui/input';
import { ThemeContext } from 'twenty-ui/theme';
import { themeCssVariables } from 'twenty-ui/theme-constants';
const StyledSettingsCardToggleContent = styled(StyledSettingsCardContent)`
cursor: ${({ disabled }) => (disabled ? 'default' : 'pointer')};
@@ -22,15 +23,11 @@ const StyledSettingsCardToggleContent = styled(StyledSettingsCardContent)`
pointer-events: ${({ disabled }) => (disabled ? 'none' : 'auto')};
&:hover {
background: ${({ theme }) => theme.background.transparent.lighter};
background: ${themeCssVariables.background.transparent.lighter};
}
`;
const StyledSettingsCardToggleButton = styled(Toggle)<{
toggleCentered?: boolean;
}>`
align-self: ${({ toggleCentered }) =>
toggleCentered ? 'center' : 'flex-start'};
const StyledSettingsCardToggleButton = styled(Toggle)`
flex-shrink: 0;
margin-left: auto;
`;
@@ -64,7 +61,7 @@ export const SettingsOptionCardContentToggle = ({
checked,
onChange,
}: SettingsOptionCardContentToggleProps) => {
const theme = useTheme();
const { theme } = useContext(ThemeContext);
const toggleId = useId();
return (
@@ -95,7 +92,7 @@ export const SettingsOptionCardContentToggle = ({
disabled={disabled}
toggleSize="small"
color={advancedMode ? theme.color.yellow : theme.color.blue}
toggleCentered={toggleCentered}
centered={toggleCentered}
/>
</StyledSettingsCardToggleContent>
{divider && <Separator />}
@@ -1,6 +1,7 @@
import { useTheme } from '@emotion/react';
import styled from '@emotion/styled';
import { styled } from '@linaria/react';
import { useContext } from 'react';
import { type IconComponent } from 'twenty-ui/display';
import { ThemeContext } from 'twenty-ui/theme';
type SettingsOptionIconCustomizerProps = {
Icon: IconComponent;
@@ -20,7 +21,7 @@ export const SettingsOptionIconCustomizer = ({
zoom = 1,
rotate = -4,
}: SettingsOptionIconCustomizerProps) => {
const theme = useTheme();
const { theme } = useContext(ThemeContext);
return (
<StyledIconCustomizer zoom={zoom} rotate={rotate}>
<Icon
@@ -1,5 +1,5 @@
import { SettingsOptionCardContentCounter } from '@/settings/components/SettingsOptions/SettingsOptionCardContentCounter';
import styled from '@emotion/styled';
import { styled } from '@linaria/react';
import { type Meta, type StoryObj } from '@storybook/react-vite';
import { useState } from 'react';
import { IconUsers } from 'twenty-ui/display';
@@ -1,7 +1,7 @@
import { SettingsOptionCardContentSelect } from '@/settings/components/SettingsOptions/SettingsOptionCardContentSelect';
import { Select } from '@/ui/input/components/Select';
import { type SelectValue } from '@/ui/input/components/internal/select/types';
import styled from '@emotion/styled';
import { styled } from '@linaria/react';
import { type Meta, type StoryObj } from '@storybook/react-vite';
import { useState } from 'react';
import {
@@ -1,5 +1,5 @@
import { SettingsOptionCardContentToggle } from '@/settings/components/SettingsOptions/SettingsOptionCardContentToggle';
import styled from '@emotion/styled';
import { styled } from '@linaria/react';
import { type Meta, type StoryObj } from '@storybook/react-vite';
import { useState } from 'react';
import { IconBell, IconLock, IconRobot, IconUsers } from 'twenty-ui/display';
@@ -2,20 +2,22 @@ import { OBJECT_SETTINGS_WIDTH } from '@/settings/data-model/constants/ObjectSet
import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile';
import { ScrollWrapper } from '@/ui/utilities/scroll/components/ScrollWrapper';
import { useScrollRestoration } from '@/ui/utilities/scroll/hooks/useScrollRestoration';
import styled from '@emotion/styled';
import { styled } from '@linaria/react';
import { type ReactNode, useMemo } from 'react';
import { matchPath, useLocation } from 'react-router-dom';
import { SettingsPath } from 'twenty-shared/types';
import { getSettingsPath, isDefined } from 'twenty-shared/utils';
import { themeCssVariables } from 'twenty-ui/theme-constants';
const StyledSettingsPageContainer = styled.div<{
width?: number;
}>`
display: flex;
flex-direction: column;
gap: ${({ theme }) => theme.spacing(8)};
gap: ${themeCssVariables.spacing[8]};
overflow: auto;
padding: ${({ theme }) => theme.spacing(6, 8, 8)};
padding: ${themeCssVariables.spacing[6]} ${themeCssVariables.spacing[8]}
${themeCssVariables.spacing[8]};
width: ${({ width }) => {
if (isDefined(width)) {
return width + 'px';
@@ -25,7 +27,7 @@ const StyledSettingsPageContainer = styled.div<{
}
return OBJECT_SETTINGS_WIDTH + 'px';
}};
padding-bottom: ${({ theme }) => theme.spacing(20)};
padding-bottom: ${themeCssVariables.spacing[20]};
`;
export const SettingsPageContainer = ({
@@ -1,37 +1,39 @@
import { useTheme } from '@emotion/react';
import styled from '@emotion/styled';
import { styled } from '@linaria/react';
import { useContext } from 'react';
import { CardContent } from 'twenty-ui/layout';
import { type IconComponent } from 'twenty-ui/display';
import { Radio } from 'twenty-ui/input';
import { ThemeContext } from 'twenty-ui/theme';
import { themeCssVariables } from 'twenty-ui/theme-constants';
const StyledRadioCardContent = styled(CardContent)`
display: flex;
align-items: center;
padding: ${({ theme }) => theme.spacing(2)};
border: 1px solid ${({ theme }) => theme.border.color.medium};
border-radius: ${({ theme }) => theme.border.radius.sm};
padding: ${themeCssVariables.spacing[2]};
border: 1px solid ${themeCssVariables.border.color.medium};
border-radius: ${themeCssVariables.border.radius.sm};
flex-grow: 1;
gap: ${({ theme }) => theme.spacing(2)};
gap: ${themeCssVariables.spacing[2]};
cursor: pointer;
&:hover {
background: ${({ theme }) => theme.background.transparent.lighter};
background: ${themeCssVariables.background.transparent.lighter};
}
`;
const StyledRadio = styled(Radio)`
margin-left: auto;
padding: ${({ theme }) => theme.spacing(1)};
padding: ${themeCssVariables.spacing[1]};
`;
const StyledTitle = styled.div`
color: ${({ theme }) => theme.font.color.secondary};
font-weight: ${({ theme }) => theme.font.weight.medium};
color: ${themeCssVariables.font.color.secondary};
font-weight: ${themeCssVariables.font.weight.medium};
`;
const StyledDescription = styled.div`
color: ${({ theme }) => theme.font.color.tertiary};
font-size: ${({ theme }) => theme.font.size.sm};
color: ${themeCssVariables.font.color.tertiary};
font-size: ${themeCssVariables.font.size.sm};
`;
type SettingsRadioCardProps = {
@@ -53,7 +55,7 @@ export const SettingsRadioCard = ({
isSelected,
Icon,
}: SettingsRadioCardProps) => {
const theme = useTheme();
const { theme } = useContext(ThemeContext);
const onClick = () => handleSelect(value);
@@ -1,11 +1,12 @@
import styled from '@emotion/styled';
import { styled } from '@linaria/react';
import { SettingsRadioCard } from '@/settings/components/SettingsRadioCard';
import { type IconComponent } from 'twenty-ui/display';
import { themeCssVariables } from 'twenty-ui/theme-constants';
const StyledRadioCardContainer = styled.div`
display: flex;
flex-wrap: wrap;
gap: ${({ theme }) => theme.spacing(4)};
gap: ${themeCssVariables.spacing[4]};
`;
type SettingsRadioCardContainerProps = {
@@ -1,9 +1,11 @@
import { SKELETON_LOADER_HEIGHT_SIZES } from '@/activities/components/SkeletonLoader';
import { PageBody } from '@/ui/layout/page/components/PageBody';
import { PageHeader } from '@/ui/layout/page/components/PageHeader';
import { useTheme } from '@emotion/react';
import styled from '@emotion/styled';
import { styled } from '@linaria/react';
import { useContext } from 'react';
import Skeleton, { SkeletonTheme } from 'react-loading-skeleton';
import { ThemeContext } from 'twenty-ui/theme';
import { themeCssVariables } from 'twenty-ui/theme-constants';
const StyledContainer = styled.div`
display: flex;
@@ -12,11 +14,12 @@ const StyledContainer = styled.div`
`;
const StyledTitleLoaderContainer = styled.div`
margin: ${({ theme }) => theme.spacing(8, 8, 2)};
margin: ${themeCssVariables.spacing[8]} ${themeCssVariables.spacing[8]}
${themeCssVariables.spacing[2]};
`;
export const SettingsSkeletonLoader = () => {
const theme = useTheme();
const { theme } = useContext(ThemeContext);
return (
<StyledContainer>
<PageHeader
@@ -1,6 +1,7 @@
import styled from '@emotion/styled';
import { styled } from '@linaria/react';
import { type ReactNode } from 'react';
import { Card, CardContent } from 'twenty-ui/layout';
import { themeCssVariables } from 'twenty-ui/theme-constants';
type SettingsSummaryCardProps = {
title: ReactNode;
@@ -10,16 +11,16 @@ type SettingsSummaryCardProps = {
const StyledCardContent = styled(CardContent)`
align-items: center;
display: flex;
gap: ${({ theme }) => theme.spacing(2)};
padding: ${({ theme }) => theme.spacing(2)};
min-height: ${({ theme }) => theme.spacing(6)};
gap: ${themeCssVariables.spacing[2]};
padding: ${themeCssVariables.spacing[2]};
min-height: ${themeCssVariables.spacing[6]};
`;
const StyledTitle = styled.div`
color: ${({ theme }) => theme.font.color.primary};
color: ${themeCssVariables.font.color.primary};
display: flex;
font-weight: ${({ theme }) => theme.font.weight.medium};
gap: ${({ theme }) => theme.spacing(2)};
font-weight: ${themeCssVariables.font.weight.medium};
gap: ${themeCssVariables.spacing[2]};
margin-right: auto;
`;