## 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`.
145 lines
3.6 KiB
JSON
145 lines
3.6 KiB
JSON
{
|
|
"name": "twenty-ui",
|
|
"$schema": "../../node_modules/nx/schemas/project-schema.json",
|
|
"sourceRoot": "packages/twenty-ui/src",
|
|
"projectType": "library",
|
|
"tags": [
|
|
"scope:shared"
|
|
],
|
|
"targets": {
|
|
"build": {
|
|
"dependsOn": [
|
|
"^build",
|
|
"generateBarrels"
|
|
],
|
|
"outputs": [
|
|
"{projectRoot}/dist",
|
|
"{projectRoot}/accessibility/package.json",
|
|
"{projectRoot}/accessibility/dist",
|
|
"{projectRoot}/assets/package.json",
|
|
"{projectRoot}/assets/dist",
|
|
"{projectRoot}/components/package.json",
|
|
"{projectRoot}/components/dist",
|
|
"{projectRoot}/display/package.json",
|
|
"{projectRoot}/display/dist",
|
|
"{projectRoot}/feedback/package.json",
|
|
"{projectRoot}/feedback/dist",
|
|
"{projectRoot}/input/package.json",
|
|
"{projectRoot}/input/dist",
|
|
"{projectRoot}/json-visualizer/package.json",
|
|
"{projectRoot}/json-visualizer/dist",
|
|
"{projectRoot}/layout/package.json",
|
|
"{projectRoot}/layout/dist",
|
|
"{projectRoot}/navigation/package.json",
|
|
"{projectRoot}/navigation/dist",
|
|
"{projectRoot}/testing/package.json",
|
|
"{projectRoot}/testing/dist",
|
|
"{projectRoot}/theme/package.json",
|
|
"{projectRoot}/theme/dist",
|
|
"{projectRoot}/theme-constants/package.json",
|
|
"{projectRoot}/theme-constants/dist",
|
|
"{projectRoot}/utilities/package.json",
|
|
"{projectRoot}/utilities/dist"
|
|
]
|
|
},
|
|
"generateThemeConstants": {
|
|
"executor": "nx:run-commands",
|
|
"dependsOn": [
|
|
"build"
|
|
],
|
|
"cache": true,
|
|
"inputs": [
|
|
"{projectRoot}/scripts/generateThemeConstants.ts"
|
|
],
|
|
"outputs": [
|
|
"{projectRoot}/src/theme-constants/generated"
|
|
],
|
|
"options": {
|
|
"command": "tsx {projectRoot}/scripts/generateThemeConstants.ts"
|
|
}
|
|
},
|
|
"generateBarrels": {
|
|
"executor": "nx:run-commands",
|
|
"cache": true,
|
|
"inputs": [
|
|
"production",
|
|
"{projectRoot}/scripts/generateBarrels.ts"
|
|
],
|
|
"outputs": [
|
|
"{projectRoot}/src/**/*/index.ts",
|
|
"{projectRoot}/package.json"
|
|
],
|
|
"options": {
|
|
"command": "tsx {projectRoot}/scripts/generateBarrels.ts"
|
|
}
|
|
},
|
|
"build:individual": {
|
|
"executor": "nx:run-commands",
|
|
"cache": true,
|
|
"dependsOn": [
|
|
"build"
|
|
],
|
|
"inputs": [
|
|
"production",
|
|
"^production"
|
|
],
|
|
"outputs": [
|
|
"{projectRoot}/dist/individual"
|
|
],
|
|
"options": {
|
|
"cwd": "{projectRoot}",
|
|
"command": "npx vite build -c vite.config.individual.ts"
|
|
}
|
|
},
|
|
"clean": {
|
|
"executor": "nx:run-commands",
|
|
"options": {
|
|
"command": "rimraf {projectRoot}/dist"
|
|
}
|
|
},
|
|
"lint": {
|
|
"options": {
|
|
"lintFilePatterns": [
|
|
"{projectRoot}/src/**/*.{ts,tsx,json}",
|
|
"{projectRoot}/package.json"
|
|
]
|
|
},
|
|
"configurations": {
|
|
"fix": {}
|
|
}
|
|
},
|
|
"fmt": {
|
|
"options": {
|
|
"files": "src"
|
|
},
|
|
"configurations": {
|
|
"fix": {}
|
|
}
|
|
},
|
|
"test": {},
|
|
"typecheck": {},
|
|
"storybook:build": {
|
|
"configurations": {
|
|
"test": {}
|
|
}
|
|
},
|
|
"storybook:serve:dev": {
|
|
"options": {
|
|
"port": 6007
|
|
}
|
|
},
|
|
"storybook:serve:static": {
|
|
"options": {
|
|
"buildTarget": "twenty-ui:storybook:build",
|
|
"port": 6007
|
|
},
|
|
"configurations": {
|
|
"test": {}
|
|
}
|
|
},
|
|
"storybook:test": {},
|
|
"storybook:test:no-coverage": {},
|
|
"storybook:coverage": {}
|
|
}
|
|
}
|