chore(twenty-front): migrate command-menu, workflow, page-layout and UI modules from Emotion to Linaria (PR 4-6/10) (#18342)
## Summary
Continues the Emotion → Linaria migration (PR 4-6 from the [migration
plan](docs/emotion-to-linaria-migration-plan.md)). Migrates **311
files** across four module groups:
| Module | Files |
|---|---|
| command-menu | 53 |
| workflow | 84 |
| page-layout | 84 |
| UI (partial - first ~80 files) | ~80 |
| twenty-ui (TEXT_INPUT_STYLE) | 1 |
| misc (hooks, keyboard-shortcut-menu, file-upload) | ~9 |
### Migration patterns applied
- `import styled from '@emotion/styled'` → `import { styled } from
'@linaria/react'`
- `import { useTheme } from '@emotion/react'` → `import { useContext }
from 'react'` + `import { ThemeContext } from 'twenty-ui/theme'`
- `${({ theme }) => theme.X.Y.Z}` → `${themeCssVariables.X.Y.Z}` (static
CSS variables)
- `theme.spacing(N)` → `themeCssVariables.spacing[N]`
- `styled(motion.div)` → `motion.create(StyledBase)` (11 components)
- `styled(Component)<TypeParams>` → wrapper div approach for non-HTML
elements
- Multi-declaration interpolations split into one CSS property per
interpolation
- Interpolation return types fixed (`&&` → ternary `? : ''`)
- `TEXT_INPUT_STYLE` converted from function to static string constant
(backward compatible)
- Emotion `<Global>` replaced with `useEffect` style injection
- Complex runtime-dependent styles use CSS custom properties via
`style={}` prop
### After this PR
- **Remaining files**: ~400 (object-record: ~160, settings: ~200, UI:
~44)
- **No breaking changes**: CSS variables resolve identically to the
previous Emotion theme values
This commit is contained in:
+7
-3
@@ -4,7 +4,7 @@ import { differenceInSeconds, endOfDay, format } from 'date-fns';
|
||||
|
||||
import { CalendarEventRow } from '@/activities/calendar/components/CalendarEventRow';
|
||||
import { getCalendarEventStartDate } from '@/activities/calendar/utils/getCalendarEventStartDate';
|
||||
import { CardContent } from 'twenty-ui/layout';
|
||||
import { motion } from 'framer-motion';
|
||||
import { ThemeContext } from 'twenty-ui/theme';
|
||||
import { themeCssVariables } from 'twenty-ui/theme-constants';
|
||||
import { type TimelineCalendarEvent } from '~/generated/graphql';
|
||||
@@ -14,15 +14,19 @@ type CalendarDayCardContentProps = {
|
||||
divider?: boolean;
|
||||
};
|
||||
|
||||
const StyledCardContent = styled(CardContent)`
|
||||
const StyledCardContentBase = styled.div<{ divider?: boolean }>`
|
||||
align-items: flex-start;
|
||||
border-color: ${themeCssVariables.border.color.light};
|
||||
background-color: ${themeCssVariables.background.secondary};
|
||||
border-bottom: ${({ divider }) =>
|
||||
divider ? `1px solid ${themeCssVariables.border.color.light}` : 'none'};
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: ${themeCssVariables.spacing[3]};
|
||||
padding: ${themeCssVariables.spacing[2]} ${themeCssVariables.spacing[3]};
|
||||
`;
|
||||
|
||||
const StyledCardContent = motion.create(StyledCardContentBase);
|
||||
|
||||
const StyledDayContainer = styled.div`
|
||||
text-align: center;
|
||||
width: ${themeCssVariables.spacing[6]};
|
||||
|
||||
+23
-16
@@ -19,7 +19,6 @@ import {
|
||||
} from '@/object-record/record-field/ui/contexts/FieldContext';
|
||||
import { RecordFieldComponentInstanceContext } from '@/object-record/record-field/ui/states/contexts/RecordFieldComponentInstanceContext';
|
||||
import { RecordInlineCell } from '@/object-record/record-inline-cell/components/RecordInlineCell';
|
||||
import { PropertyBox } from '@/object-record/record-inline-cell/property-box/components/PropertyBox';
|
||||
import { useIsRecordReadOnly } from '@/object-record/read-only/hooks/useIsRecordReadOnly';
|
||||
import { isRecordFieldReadOnly } from '@/object-record/read-only/utils/isRecordFieldReadOnly';
|
||||
import { getRecordFieldInputInstanceId } from '@/object-record/utils/getRecordFieldInputId';
|
||||
@@ -53,10 +52,14 @@ const StyledContainer = styled.div`
|
||||
box-sizing: border-box;
|
||||
`;
|
||||
|
||||
const StyledEventChip = styled(Chip)`
|
||||
gap: ${themeCssVariables.spacing[2]};
|
||||
padding-left: ${themeCssVariables.spacing[2]};
|
||||
padding-right: ${themeCssVariables.spacing[2]};
|
||||
const StyledEventChipWrapper = styled.span`
|
||||
display: inline-flex;
|
||||
|
||||
& > [data-testid='chip'] {
|
||||
gap: ${themeCssVariables.spacing[2]};
|
||||
padding-left: ${themeCssVariables.spacing[2]};
|
||||
padding-right: ${themeCssVariables.spacing[2]};
|
||||
}
|
||||
`;
|
||||
|
||||
const StyledHeader = styled.header``;
|
||||
@@ -67,7 +70,7 @@ const StyledTitle = styled.h2<{ canceled?: boolean }>`
|
||||
margin: ${themeCssVariables.spacing[0]} ${themeCssVariables.spacing[0]}
|
||||
${themeCssVariables.spacing[2]};
|
||||
|
||||
${({ canceled }) => (canceled ? 'text-decoration: line-through' : '')}
|
||||
text-decoration: ${({ canceled }) => (canceled ? 'line-through' : 'none')};
|
||||
`;
|
||||
|
||||
const StyledCreatedAt = styled.div`
|
||||
@@ -81,9 +84,11 @@ const StyledFields = styled.div`
|
||||
width: 100%;
|
||||
`;
|
||||
|
||||
const StyledPropertyBox = styled(PropertyBox)`
|
||||
const StyledPropertyBox = styled.div`
|
||||
align-self: stretch;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: ${themeCssVariables.spacing[6]};
|
||||
padding: 0;
|
||||
width: 100%;
|
||||
`;
|
||||
|
||||
@@ -199,14 +204,16 @@ export const CalendarEventDetails = ({
|
||||
value={{ scopeInstanceId: INPUT_ID_PREFIX }}
|
||||
>
|
||||
<StyledContainer>
|
||||
<StyledEventChip
|
||||
accent={ChipAccent.TextSecondary}
|
||||
size={ChipSize.Large}
|
||||
variant={ChipVariant.Highlighted}
|
||||
clickable={false}
|
||||
leftComponent={<AvatarOrIcon Icon={IconCalendarEvent} />}
|
||||
label={t`Event`}
|
||||
/>
|
||||
<StyledEventChipWrapper>
|
||||
<Chip
|
||||
accent={ChipAccent.TextSecondary}
|
||||
size={ChipSize.Large}
|
||||
variant={ChipVariant.Highlighted}
|
||||
clickable={false}
|
||||
leftComponent={<AvatarOrIcon Icon={IconCalendarEvent} />}
|
||||
label={t`Event`}
|
||||
/>
|
||||
</StyledEventChipWrapper>
|
||||
<StyledHeader>
|
||||
<StyledTitle canceled={calendarEvent.isCanceled}>
|
||||
{calendarEvent.title}
|
||||
|
||||
+9
-7
@@ -2,27 +2,29 @@ import { useContext } from 'react';
|
||||
import { styled } from '@linaria/react';
|
||||
import { Trans } from '@lingui/react/macro';
|
||||
import { IconLock } from 'twenty-ui/display';
|
||||
import { Card, CardContent } from 'twenty-ui/layout';
|
||||
import { ThemeContext } from 'twenty-ui/theme';
|
||||
import { themeCssVariables } from 'twenty-ui/theme-constants';
|
||||
|
||||
const StyledVisibilityCard = styled(Card)`
|
||||
border-color: ${themeCssVariables.border.color.light};
|
||||
const StyledVisibilityCard = styled.div`
|
||||
border: 1px solid ${themeCssVariables.border.color.light};
|
||||
border-radius: ${themeCssVariables.border.radius.sm};
|
||||
color: ${themeCssVariables.font.color.light};
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
transition: color ${themeCssVariables.animation.duration.normal} ease;
|
||||
width: 100%;
|
||||
`;
|
||||
|
||||
const StyledVisibilityCardContent = styled(CardContent)`
|
||||
const StyledVisibilityCardContent = styled.div`
|
||||
align-items: center;
|
||||
background-color: ${themeCssVariables.background.transparent.lighter};
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
font-size: ${themeCssVariables.font.size.sm};
|
||||
font-weight: ${themeCssVariables.font.weight.medium};
|
||||
display: flex;
|
||||
gap: ${themeCssVariables.spacing[1]};
|
||||
padding: ${themeCssVariables.spacing[0]} ${themeCssVariables.spacing[1]};
|
||||
height: ${themeCssVariables.spacing[6]};
|
||||
background-color: ${themeCssVariables.background.transparent.lighter};
|
||||
padding: ${themeCssVariables.spacing[0]} ${themeCssVariables.spacing[1]};
|
||||
`;
|
||||
|
||||
export const CalendarEventNotSharedContent = () => {
|
||||
|
||||
+4
-3
@@ -3,7 +3,6 @@ import { styled } from '@linaria/react';
|
||||
|
||||
import { type CalendarEventParticipant } from '@/activities/calendar/types/CalendarEventParticipant';
|
||||
import { ParticipantChip } from '@/activities/components/ParticipantChip';
|
||||
import { PropertyBox } from '@/object-record/record-inline-cell/property-box/components/PropertyBox';
|
||||
import { EllipsisDisplay } from '@/ui/field/display/components/EllipsisDisplay';
|
||||
import { ExpandableList } from '@/ui/layout/expandable-list/components/ExpandableList';
|
||||
import { IconCheck, IconQuestionMark, IconX } from 'twenty-ui/display';
|
||||
@@ -22,9 +21,11 @@ const StyledInlineCellBaseContainer = styled.div`
|
||||
user-select: none;
|
||||
`;
|
||||
|
||||
const StyledPropertyBox = styled(PropertyBox)`
|
||||
const StyledPropertyBox = styled.div`
|
||||
align-self: stretch;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: ${themeCssVariables.spacing[6]};
|
||||
padding: 0;
|
||||
width: 100%;
|
||||
`;
|
||||
|
||||
|
||||
+9
-10
@@ -34,13 +34,13 @@ const StyledContainer = styled.div<{ showTitle?: boolean }>`
|
||||
`;
|
||||
|
||||
const StyledAttendanceIndicator = styled.div<{ active?: boolean }>`
|
||||
background-color: ${themeCssVariables.tag.background.gray};
|
||||
background-color: ${({ active }) =>
|
||||
active
|
||||
? themeCssVariables.tag.background.red
|
||||
: themeCssVariables.tag.background.gray};
|
||||
height: 100%;
|
||||
width: ${themeCssVariables.spacing[1]};
|
||||
border-radius: ${themeCssVariables.border.radius.xs};
|
||||
|
||||
${({ active }) =>
|
||||
active ? `background-color: ${themeCssVariables.tag.background.red}` : ''}
|
||||
`;
|
||||
|
||||
const StyledLabels = styled.div`
|
||||
@@ -60,17 +60,16 @@ const StyledTime = styled.div`
|
||||
`;
|
||||
|
||||
const StyledTitle = styled.div<{ active: boolean; canceled: boolean }>`
|
||||
color: ${({ active }) =>
|
||||
active ? themeCssVariables.font.color.primary : 'inherit'};
|
||||
flex: 1 0 auto;
|
||||
font-weight: ${({ active }) =>
|
||||
active ? themeCssVariables.font.weight.medium : 'inherit'};
|
||||
overflow: hidden;
|
||||
text-decoration: ${({ canceled }) => (canceled ? 'line-through' : 'none')};
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
width: ${themeCssVariables.spacing[10]};
|
||||
${({ active }) =>
|
||||
active
|
||||
? `color: ${themeCssVariables.font.color.primary}; font-weight: ${themeCssVariables.font.weight.medium}`
|
||||
: ''}
|
||||
|
||||
${({ canceled }) => (canceled ? 'text-decoration: line-through' : '')}
|
||||
`;
|
||||
|
||||
export const CalendarEventRow = ({
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import { styled } from '@linaria/react';
|
||||
import React from 'react';
|
||||
import { CardContent } from 'twenty-ui/layout';
|
||||
import { themeCssVariables } from 'twenty-ui/theme-constants';
|
||||
|
||||
const StyledRowContent = styled(CardContent)`
|
||||
const StyledRowContent = styled.div<{ isClickable: boolean }>`
|
||||
align-items: center;
|
||||
background-color: ${themeCssVariables.background.secondary};
|
||||
cursor: ${({ isClickable }) => (isClickable ? 'pointer' : 'default')};
|
||||
display: flex;
|
||||
gap: ${themeCssVariables.spacing[2]};
|
||||
height: ${themeCssVariables.spacing[12]};
|
||||
|
||||
+23
-17
@@ -25,7 +25,7 @@ const StyledParticipantsContainer = styled.div`
|
||||
display: flex;
|
||||
`;
|
||||
|
||||
const StyledAvatar = styled(Avatar)`
|
||||
const StyledAvatarWrapper = styled.div`
|
||||
margin-left: calc(-1 * ${themeCssVariables.spacing[1]});
|
||||
`;
|
||||
|
||||
@@ -123,24 +123,30 @@ export const EmailThreadPreview = ({ thread }: EmailThreadPreviewProps) => {
|
||||
type="rounded"
|
||||
/>
|
||||
{thread?.lastTwoParticipants?.[0] && (
|
||||
<StyledAvatar
|
||||
avatarUrl={thread.lastTwoParticipants[0].avatarUrl}
|
||||
placeholder={thread.lastTwoParticipants[0].displayName}
|
||||
placeholderColorSeed={
|
||||
thread.lastTwoParticipants[0].workspaceMemberId ||
|
||||
thread.lastTwoParticipants[0].personId
|
||||
}
|
||||
type="rounded"
|
||||
/>
|
||||
<StyledAvatarWrapper>
|
||||
<Avatar
|
||||
avatarUrl={thread.lastTwoParticipants[0].avatarUrl}
|
||||
placeholder={thread.lastTwoParticipants[0].displayName}
|
||||
placeholderColorSeed={
|
||||
thread.lastTwoParticipants[0].workspaceMemberId ||
|
||||
thread.lastTwoParticipants[0].personId
|
||||
}
|
||||
type="rounded"
|
||||
/>
|
||||
</StyledAvatarWrapper>
|
||||
)}
|
||||
{finalDisplayedName && (
|
||||
<StyledAvatar
|
||||
avatarUrl={finalAvatarUrl}
|
||||
placeholder={finalDisplayedName}
|
||||
type="rounded"
|
||||
color={isCountIcon ? theme.grayScale.gray11 : undefined}
|
||||
backgroundColor={isCountIcon ? theme.grayScale.gray2 : undefined}
|
||||
/>
|
||||
<StyledAvatarWrapper>
|
||||
<Avatar
|
||||
avatarUrl={finalAvatarUrl}
|
||||
placeholder={finalDisplayedName}
|
||||
type="rounded"
|
||||
color={isCountIcon ? theme.grayScale.gray11 : undefined}
|
||||
backgroundColor={
|
||||
isCountIcon ? theme.grayScale.gray2 : undefined
|
||||
}
|
||||
/>
|
||||
</StyledAvatarWrapper>
|
||||
)}
|
||||
</StyledParticipantsContainer>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user