chore(twenty-front): migrate small modules from Emotion to Linaria (PR 1/10)#18314
chore(twenty-front): migrate small modules from Emotion to Linaria (PR 1/10)#18314charlesBochet merged 12 commits intomainfrom
Conversation
… move divider to Chip, fix Storybook - Fix wyw-in-js Storybook crash by setting enforce: 'pre' on the Vite plugin - Rename AvatarChip to AvatarOrIcon to better reflect its purpose - Move rightComponentDivider from AvatarOrIcon to Chip/LinkChip where it belongs - Remove unused MultipleAvatarChip component (zero consumers) - Migrate CalendarEventDetails and FileIcon to use AvatarOrIcon - Enhance Chip and LinkChip stories with full CatalogDecorator coverage Made-with: Cursor
Breaks ~998 files into 10 PRs of ~100 files each, ordered from lowest-risk (small standalone modules) to highest-complexity (settings, object-record). Includes migration patterns, risk assessment, and validation checklist. Made-with: Cursor
|
Too many files changed for review. ( |
There was a problem hiding this comment.
Pull request overview
This PR is the first of a 10-part series migrating twenty-front from Emotion (runtime CSS-in-JS) to Linaria (zero-runtime, build-time extraction). It covers ~100 files across 10 small standalone modules, replaces @emotion/styled with @linaria/react's styled, converts theme prop-functions to themeCssVariables references, and replaces useTheme() with useContext(ThemeContext). It also renames AvatarChip to AvatarOrIcon, removes MultipleAvatarChip, and adds a rightComponentDivider prop to Chip/LinkChip.
Changes:
- Migrate ~100 files from Emotion to Linaria across 10 modules (billing, views, spreadsheet-import, navigation-menu-item, blocknote-editor, advanced-text-editor, favorites, navigation, information-banner, sign-in-background-mock)
- Rename
AvatarChip→AvatarOrIconand deleteMultipleAvatarChip; addrightComponentDividerprop toChipandLinkChip - Add
docs/emotion-to-linaria-migration-plan.mdcovering all 10 planned PRs and patterns
Reviewed changes
Copilot reviewed 104 out of 104 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
packages/twenty-ui/vite.config.ts / vite.config.individual.ts |
Add enforce: 'pre' to wyw plugin to ensure correct processing order |
packages/twenty-ui/src/components/index.ts |
Replace AvatarChip/MultipleAvatarChip exports with AvatarOrIcon |
packages/twenty-ui/src/components/avatar-or-icon/AvatarOrIcon.tsx |
Rename component, remove divider prop, complete Linaria migration |
packages/twenty-ui/src/components/chip/Chip.tsx / LinkChip.tsx |
Add rightComponentDivider prop with divider UI element |
packages/twenty-ui/src/components/chip/__stories__/Chip.stories.tsx / LinkChip.stories.tsx |
Expand story coverage with new variants and catalog stories |
packages/twenty-ui/src/components/avatar-chip/MultipleAvatarChip.tsx |
Deleted entirely |
packages/twenty-front/src/modules/views/** |
Linaria migration for views module (14 files) |
packages/twenty-front/src/modules/spreadsheet-import/** |
Linaria migration for spreadsheet-import module (28 files) |
packages/twenty-front/src/modules/billing/** |
Linaria migration for billing module (10 files) |
packages/twenty-front/src/modules/navigation-menu-item/** |
Linaria migration for navigation-menu-item module (17 files) |
packages/twenty-front/src/modules/favorites/** |
Linaria migration for favorites module (7 files) |
packages/twenty-front/src/modules/blocknote-editor/** |
Linaria migration for blocknote-editor module (7 files) |
packages/twenty-front/src/modules/advanced-text-editor/** |
Linaria migration for advanced-text-editor module (7 files) |
packages/twenty-front/src/modules/navigation/** |
Linaria migration for navigation module (4 files) |
packages/twenty-front/src/modules/information-banner/** |
Linaria migration for information-banner module (3 files) |
packages/twenty-front/src/modules/sign-in-background-mock/** |
Linaria migration for sign-in-background-mock module (3 files) |
packages/twenty-front/src/modules/ai/components/internal/AgentChatFilePreview.tsx |
Partial migration: AvatarChip→AvatarOrIcon but useTheme not replaced |
packages/twenty-front/src/modules/file/components/FileIcon.tsx |
Still uses Emotion, not migrated |
packages/twenty-front/src/modules/activities/calendar/components/CalendarEventDetails.tsx |
AvatarOrIcon introduced but still uses useTheme |
packages/twenty-front/src/modules/object-record/record-field/ui/form-types/constants/FormFieldPlaceholderStyles.ts |
Converted from Emotion css function to static string |
docs/emotion-to-linaria-migration-plan.md |
New migration reference document |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| gap: ${({ isExpanded }) => (isExpanded ? themeCssVariables.spacing[2] : 0)}; | ||
| width: 100%; | ||
| transition: gap ${({ theme }) => theme.animation.duration.normal}s ease; | ||
| transition: gap ${themeCssVariables.animation.duration.normal}s ease; |
There was a problem hiding this comment.
The transition CSS property in StyledRow uses ${themeCssVariables.animation.duration.normal}s ease. However, themeCssVariables.animation.duration.normal is a CSS var() reference (e.g. var(--t-animation-duration-normal)). Appending s directly to a CSS custom property (var(--x)s) is invalid CSS. Per the migration plan (section 8), this should instead use calc(${themeCssVariables.animation.duration.normal} * 1s) ease.
| height ${themeCssVariables.animation.duration.normal}s ease, | ||
| padding ${themeCssVariables.animation.duration.normal}s ease; | ||
| `; | ||
|
|
||
| const StyledNewChatButton = styled.div` | ||
| align-items: center; | ||
| justify-content: center; | ||
| display: flex; | ||
| cursor: pointer; | ||
| font-size: ${({ theme }) => theme.font.size.sm}; | ||
| font-weight: ${({ theme }) => theme.font.weight.medium}; | ||
| gap: ${({ theme }) => theme.spacing(1)}; | ||
| font-size: ${themeCssVariables.font.size.sm}; | ||
| font-weight: ${themeCssVariables.font.weight.medium}; | ||
| gap: ${themeCssVariables.spacing[1]}; | ||
| height: 100%; | ||
| width: 100%; | ||
| border-radius: inherit; | ||
| color: ${({ theme }) => theme.font.color.secondary}; | ||
| color: ${themeCssVariables.font.color.secondary}; | ||
| transition: | ||
| background ${({ theme }) => theme.animation.duration.fast}s ease, | ||
| color ${({ theme }) => theme.animation.duration.fast}s ease; | ||
| background ${themeCssVariables.animation.duration.fast}s ease, | ||
| color ${themeCssVariables.animation.duration.fast}s ease; |
There was a problem hiding this comment.
The transition properties in StyledNewChatButtonWrapper and StyledNewChatButton also use ${themeCssVariables.animation.duration.normal}s ease and ${themeCssVariables.animation.duration.fast}s ease patterns. CSS custom properties (CSS var()) cannot be concatenated directly with a unit suffix like s. These will produce invalid CSS such as var(--t-animation-duration-normal)s ease. They should use calc(${themeCssVariables.animation.duration.normal} * 1s) instead, as documented in the migration plan section 8.
| const IMAGE_MAX_WIDTH = 600; | ||
|
|
||
| const StyledNodeViewWrapper = styled(NodeViewWrapper)` | ||
| const StyledNodeViewWrapperContainer = styled.div<{ |
There was a problem hiding this comment.
In ResizableImageView.tsx, the original StyledNodeViewWrapper = styled(NodeViewWrapper) was replaced with StyledNodeViewWrapperContainer = styled.div (a plain div), and a separate <NodeViewWrapper> is now the outer element. This is a behavioral change: TipTap's NodeViewWrapper sets a data-node-view-wrapper attribute and manages its own rendering context. Wrapping it with an extra div may affect TipTap's node view layout, positioning, and selection behavior. The styled div should ideally still be styled(NodeViewWrapper) if that API supports it with Linaria, or the nesting structure should be validated.
| const StyledNodeViewWrapperContainer = styled.div<{ | |
| const StyledNodeViewWrapperContainer = styled(NodeViewWrapper)<{ |
| const StyledBar = styled.div` | ||
| align-items: center; | ||
| align-items: center; | ||
| border-top: 1px solid ${({ theme }) => theme.border.color.light}; | ||
| border-top: 1px solid ${({ theme }) => theme.border.color.light}; | ||
| border-top: 1px solid ${themeCssVariables.border.color.light}; | ||
| border-top: 1px solid ${themeCssVariables.border.color.light}; |
There was a problem hiding this comment.
The StyledBar styled component in ViewBarDetails.tsx has duplicated CSS declarations that were carried over from the original Emotion code. Both align-items: center; and border-top: 1px solid ${themeCssVariables.border.color.light}; appear twice. The duplicates should be removed.
There was a problem hiding this comment.
2 issues found across 124 files
Note: This PR contains a large number of files. cubic only reviews up to 75 files per PR, so some files may not have been reviewed.
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="packages/twenty-front/src/modules/navigation/components/MainNavigationDrawerTabsRow.tsx">
<violation number="1" location="packages/twenty-front/src/modules/navigation/components/MainNavigationDrawerTabsRow.tsx:33">
P1: Duration CSS variables are concatenated with `s` directly; use `calc(var * 1s)` so transition values are valid.</violation>
<violation number="2" location="packages/twenty-front/src/modules/navigation/components/MainNavigationDrawerTabsRow.tsx:94">
P2: Unsupported fractional spacing key (`25.75`) can produce an invalid width; replace it with a literal pixel value.</violation>
</file>
Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.
packages/twenty-front/src/modules/navigation/components/MainNavigationDrawerTabsRow.tsx
Outdated
Show resolved
Hide resolved
packages/twenty-front/src/modules/navigation/components/MainNavigationDrawerTabsRow.tsx
Outdated
Show resolved
Hide resolved
…R 1/10) Migrate 100 files across 10 small standalone modules from @emotion/styled + useTheme to @linaria/react + themeCssVariables: - spreadsheet-import (28 files) - navigation-menu-item (17 files) - views (14 files) - billing (10 files) - blocknote-editor (7 files) - advanced-text-editor (7 files) - favorites (7 files) - navigation (4 files) - information-banner (3 files) - sign-in-background-mock (3 files) Also adds className support to NavigationDrawerSection and DropdownMenuItemsContainer for Linaria styled() compatibility, and converts FormFieldPlaceholderStyles to use themeCssVariables. Made-with: Cursor
fa5a0ec to
0f7882b
Compare
|
🚀 Preview Environment Ready! Your preview environment is available at: http://bore.pub:6543 This environment will automatically shut down after 5 hours. |
There was a problem hiding this comment.
1 issue found across 168 files (changes from recent commits).
Note: This PR contains a large number of files. cubic only reviews up to 75 files per PR, so some files may not have been reviewed.
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="packages/twenty-front/src/modules/spreadsheet-import/steps/components/SpreadsheetImportStepperContainer.tsx">
<violation number="1" location="packages/twenty-front/src/modules/spreadsheet-import/steps/components/SpreadsheetImportStepperContainer.tsx:13">
P2: Combine duplicate imports from `twenty-ui/theme-constants` into one statement to satisfy enforced no-duplicate-import rules.</violation>
</file>
Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.
...-front/src/modules/spreadsheet-import/steps/components/SpreadsheetImportStepperContainer.tsx
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
1 issue found across 12 files (changes from recent commits).
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="packages/twenty-ui/project.json">
<violation number="1" location="packages/twenty-ui/project.json:46">
P2: `generateThemeConstants` should declare a build dependency; otherwise the target can fail when `dist` artifacts are missing.</violation>
</file>
Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.
packages/twenty-front/src/modules/navigation/components/MainNavigationDrawerTabsRow.tsx
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
1 issue found across 40 files (changes from recent commits).
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="packages/twenty-ui/project.json">
<violation number="1" location="packages/twenty-ui/project.json:49">
P1: This cache input set is incomplete for `generateThemeConstants`: the script consumes `dist/theme.cjs` and `dist/theme-constants.cjs`, so excluding them can cause stale cached generated theme constants.</violation>
</file>
Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.
| "executor": "nx:run-commands", | ||
| "cache": true, | ||
| "inputs": [ | ||
| "{projectRoot}/scripts/generateThemeConstants.ts" |
There was a problem hiding this comment.
P1: This cache input set is incomplete for generateThemeConstants: the script consumes dist/theme.cjs and dist/theme-constants.cjs, so excluding them can cause stale cached generated theme constants.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/twenty-ui/project.json, line 49:
<comment>This cache input set is incomplete for `generateThemeConstants`: the script consumes `dist/theme.cjs` and `dist/theme-constants.cjs`, so excluding them can cause stale cached generated theme constants.</comment>
<file context>
@@ -46,9 +46,7 @@
- "{projectRoot}/scripts/generateThemeConstants.ts",
- "{projectRoot}/dist/theme.cjs",
- "{projectRoot}/dist/theme-constants.cjs"
+ "{projectRoot}/scripts/generateThemeConstants.ts"
],
"outputs": [
</file context>
| "{projectRoot}/scripts/generateThemeConstants.ts" | |
| "{projectRoot}/scripts/generateThemeConstants.ts", | |
| "{projectRoot}/dist/theme.cjs", | |
| "{projectRoot}/dist/theme-constants.cjs" |
Made-with: Cursor # Conflicts: # packages/twenty-front/src/locales/generated/af-ZA.ts # packages/twenty-front/src/locales/generated/ar-SA.ts # packages/twenty-front/src/locales/generated/ca-ES.ts # packages/twenty-front/src/locales/generated/cs-CZ.ts # packages/twenty-front/src/locales/generated/da-DK.ts # packages/twenty-front/src/locales/generated/de-DE.ts # packages/twenty-front/src/locales/generated/el-GR.ts # packages/twenty-front/src/locales/generated/en.ts # packages/twenty-front/src/locales/generated/es-ES.ts # packages/twenty-front/src/locales/generated/fi-FI.ts # packages/twenty-front/src/locales/generated/fr-FR.ts # packages/twenty-front/src/locales/generated/he-IL.ts # packages/twenty-front/src/locales/generated/hu-HU.ts # packages/twenty-front/src/locales/generated/it-IT.ts # packages/twenty-front/src/locales/generated/ja-JP.ts # packages/twenty-front/src/locales/generated/ko-KR.ts # packages/twenty-front/src/locales/generated/nl-NL.ts # packages/twenty-front/src/locales/generated/no-NO.ts # packages/twenty-front/src/locales/generated/pl-PL.ts # packages/twenty-front/src/locales/generated/pseudo-en.ts # packages/twenty-front/src/locales/generated/pt-BR.ts # packages/twenty-front/src/locales/generated/pt-PT.ts # packages/twenty-front/src/locales/generated/ro-RO.ts # packages/twenty-front/src/locales/generated/ru-RU.ts # packages/twenty-front/src/locales/generated/sr-Cyrl.ts # packages/twenty-front/src/locales/generated/sv-SE.ts # packages/twenty-front/src/locales/generated/tr-TR.ts # packages/twenty-front/src/locales/generated/uk-UA.ts # packages/twenty-front/src/locales/generated/vi-VN.ts # packages/twenty-front/src/locales/generated/zh-CN.ts # packages/twenty-front/src/locales/generated/zh-TW.ts
There was a problem hiding this comment.
1 issue found across 7 files (changes from recent commits).
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="packages/twenty-front/src/modules/file/hooks/useFileIconColors.ts">
<violation number="1" location="packages/twenty-front/src/modules/file/hooks/useFileIconColors.ts:14">
P2: `IMAGE` color mapping is inconsistent with the existing file-category color map, which can cause mismatched icon colors across the UI.</violation>
</file>
Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.
| return { | ||
| ARCHIVE: theme.color.gray, | ||
| AUDIO: theme.color.pink, | ||
| IMAGE: theme.color.amber, |
There was a problem hiding this comment.
P2: IMAGE color mapping is inconsistent with the existing file-category color map, which can cause mismatched icon colors across the UI.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/twenty-front/src/modules/file/hooks/useFileIconColors.ts, line 14:
<comment>`IMAGE` color mapping is inconsistent with the existing file-category color map, which can cause mismatched icon colors across the UI.</comment>
<file context>
@@ -0,0 +1,21 @@
+ return {
+ ARCHIVE: theme.color.gray,
+ AUDIO: theme.color.pink,
+ IMAGE: theme.color.amber,
+ PRESENTATION: theme.color.orange,
+ SPREADSHEET: theme.color.turquoise,
</file context>
|
Hey @charlesBochet! After you've done the QA of your Pull Request, you can mark it as done here. Thank you! |
…R 1/10) (twentyhq#18314) ## Emotion → Linaria migration — PR 1 of 10 First batch of the `twenty-front` migration from Emotion (runtime CSS-in-JS) to Linaria (zero-runtime, build-time extraction via wyw-in-js). Covers **100 files** across 10 small standalone modules — chosen as the lowest-risk starting point. ### Modules migrated spreadsheet-import (28) · navigation-menu-item (17) · views (14) · billing (10) · blocknote-editor (7) · advanced-text-editor (7) · favorites (7) · navigation (4) · information-banner (3) · sign-in-background-mock (3) ### Migration pattern Every file follows the same mechanical transformation: | Emotion | Linaria | |---|---| | `import styled from '@emotion/styled'` | `import { styled } from '@linaria/react'` | | `${({ theme }) => theme.font.color.primary}` | `${themeCssVariables.font.color.primary}` | | `${({ theme }) => theme.spacing(4)}` | `${themeCssVariables.spacing[4]}` | | `const theme = useTheme()` | `const { theme } = useContext(ThemeContext)` | | `import { type Theme } from '@emotion/react'` | `import { type ThemeType } from 'twenty-ui/theme'` | `themeCssVariables` is a build-time object where every leaf is a `var(--t-xxx)` CSS custom property reference, evaluated statically by wyw-in-js. Runtime theme access (icon sizes, colors passed as props) uses `useContext(ThemeContext)`. ### Gotchas encountered & fixed - **Interpolation return types** — wyw-in-js requires `string | number`, never `false`/`undefined`. Replaced `condition && 'css'` with `condition ? 'css' : ''`. - **`css` tag inside `styled` templates** — Linaria `css` returns a class name, not CSS text. Replaced with plain template strings. - **`styled(Component)` needs `className`** — added `className` prop to `NavigationDrawerSection`, `DropdownMenuItemsContainer`, and `Heading`. - **`shouldForwardProp` not supported** — Linaria filters invalid DOM props automatically for HTML elements. For custom components, used wrapper divs where needed. - **`FormFieldPlaceholderStyles`** — converted from Emotion `css` function to a static string using `themeCssVariables`. (cherry picked from commit c4140f8)
Emotion → Linaria migration — PR 1 of 10
First batch of the
twenty-frontmigration from Emotion (runtime CSS-in-JS) to Linaria (zero-runtime, build-time extraction via wyw-in-js). Covers 100 files across 10 small standalone modules — chosen as the lowest-risk starting point.Modules migrated
spreadsheet-import (28) · navigation-menu-item (17) · views (14) · billing (10) · blocknote-editor (7) · advanced-text-editor (7) · favorites (7) · navigation (4) · information-banner (3) · sign-in-background-mock (3)
Migration pattern
Every file follows the same mechanical transformation:
import styled from '@emotion/styled'import { styled } from '@linaria/react'${({ theme }) => theme.font.color.primary}${themeCssVariables.font.color.primary}${({ theme }) => theme.spacing(4)}${themeCssVariables.spacing[4]}const theme = useTheme()const { theme } = useContext(ThemeContext)import { type Theme } from '@emotion/react'import { type ThemeType } from 'twenty-ui/theme'themeCssVariablesis a build-time object where every leaf is avar(--t-xxx)CSS custom property reference, evaluated statically by wyw-in-js. Runtime theme access (icon sizes, colors passed as props) usesuseContext(ThemeContext).Gotchas encountered & fixed
string | number, neverfalse/undefined. Replacedcondition && 'css'withcondition ? 'css' : ''.csstag insidestyledtemplates — Linariacssreturns a class name, not CSS text. Replaced with plain template strings.styled(Component)needsclassName— addedclassNameprop toNavigationDrawerSection,DropdownMenuItemsContainer, andHeading.shouldForwardPropnot supported — Linaria filters invalid DOM props automatically for HTML elements. For custom components, used wrapper divs where needed.FormFieldPlaceholderStyles— converted from Emotioncssfunction to a static string usingthemeCssVariables.