Migrate twenty ui to linaria (#18307)
## Migrate twenty-ui from Emotion to Linaria
Completes the migration of all `twenty-ui` components from Emotion
(runtime CSS-in-JS) to Linaria (zero-runtime, CSS extracted at build
time).
- Replaced `@emotion/styled` with `@linaria/react` across ~170 files
- Removed all Emotion dependencies from `twenty-ui`
- Introduced a CSS custom properties-based theme system:
`themeCssVariables` where every leaf is a `var(--t-xxx)` reference,
injected onto `document.documentElement` by
`ThemeCssVariableInjectorEffect`
- No more `theme` prop threading — styled components reference
`themeCssVariables.x.y` directly at build time
- Updated `twenty-front` consumers to remove `theme={theme}` prop
passing
**Before / After:**
```tsx
// Emotion
color: ${({ theme }) => theme.font.color.primary};
padding: ${({ theme }) => theme.spacing(4)};
// Linaria
color: ${themeCssVariables.font.color.primary};
padding: ${themeCssVariables.spacing[4]};
```
### Theme architecture
Two build-time utilities produce the theme system:
- **`buildThemeReferencingRootCssVariables`** — walks the theme object
and builds a nested mirror where every leaf is a `var(--t-xxx)` string
(evaluated at build time by wyw-in-js)
- **`prepareThemeForRootCssVariableInjection`** — walks the runtime
theme and collects flat `[--css-variable-name, value]` pairs, injected
onto `document.documentElement` by `ThemeCssVariableInjectorEffect`
Both share naming conventions (`camelToKebab`, `SPACING_VALUES`,
`formatSpacingKey`) and are unit tested.
### Spacing cleanup
Spacing scale now uses integers 0–32 (generated via loop), with `0.5`
and `1.5` as the only fractional exceptions. All other fractional
spacing usages (`0.25`, `0.75`, `1.25`, `2.5`, `3.5`) were replaced with
literal pixel values across ~20 twenty-front files.
### Framer Motion integration
Linaria doesn't support `styled(motion.div)` — wrapping a motion element
with `styled()` causes the component body to be stripped at build time.
Instead, we define the styled component first, then wrap it with
`motion.create()`:
```tsx
const StyledBarBase = styled.div`
background-color: ${themeCssVariables.font.color.primary};
height: 100%;
`;
const StyledBar = motion.create(StyledBarBase);
```
### Block interpolations
Linaria doesn't support interpolations that return multiple CSS
declarations (Linaria wraps the entire block in a single `var()`,
producing invalid CSS). These were split into individual property
interpolations:
```tsx
// Emotion — single interpolation returning multiple declarations
border-left: ${({ divider, theme }) => {
const border = `1px solid ${theme.border.color.light}`;
return divider ? `border-${divider}: ${border}` : '';
}}
// Linaria — one interpolation per property
border-left: ${({ divider }) =>
divider === 'left' ? `1px solid ${themeCssVariables.border.color.light}` : 'none'};
border-right: ${({ divider }) =>
divider === 'right' ? `1px solid ${themeCssVariables.border.color.light}` : 'none'};
```
### Dynamic styles via CSS variables
When a component needs to compute styles from multiple props with
complex branching logic (e.g. `Button` combining `variant`, `accent`,
`inverted`, `disabled`, `focus`, `position`), Linaria's prop
interpolations become unwieldy. In those cases we use a
`computeDynamicStyles` function that returns a `CSSProperties` object
injected via `style={}`, referenced from the static CSS with `var()`:
```tsx
const StyledButton = styled.button`
background: var(--btn-bg);
border-color: var(--btn-border-color);
&:hover { background: var(--btn-hover-bg); }
`;
const dynamicStyles = useMemo(() => {
const s = computeButtonDynamicStyles(variant, accent, ...);
return { '--btn-bg': s.background, '--btn-hover-bg': s.hoverBackground } as CSSProperties;
}, [variant, accent, ...]);
return <StyledButton style={dynamicStyles} />;
```
### CSS var + unit concatenation
CSS custom properties can't be concatenated with unit suffixes directly
(`var(--x)px` is invalid). Values that need units use `calc()`:
```tsx
// Broken
transition: background ${themeCssVariables.animation.duration.instant}s ease;
// Fixed
transition: background calc(${themeCssVariables.animation.duration.instant} * 1s) ease;
```
This commit is contained in:
@@ -24,7 +24,7 @@ const StyledTaskBody = styled.div`
|
||||
max-width: calc(80% - ${({ theme }) => theme.spacing(2)});
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
padding-bottom: ${({ theme }) => theme.spacing(0.25)};
|
||||
padding-bottom: 1px;
|
||||
`;
|
||||
|
||||
const StyledTaskTitle = styled.div<{
|
||||
@@ -33,7 +33,7 @@ const StyledTaskTitle = styled.div<{
|
||||
color: ${({ theme }) => theme.font.color.primary};
|
||||
font-weight: ${({ theme }) => theme.font.weight.medium};
|
||||
padding: 0 ${({ theme }) => theme.spacing(2)};
|
||||
padding-bottom: ${({ theme }) => theme.spacing(0.25)};
|
||||
padding-bottom: 1px;
|
||||
text-decoration: ${({ completed }) => (completed ? 'line-through' : 'none')};
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
|
||||
@@ -49,7 +49,7 @@ const StyledMessageText = styled.div<{ isUser?: boolean }>`
|
||||
word-wrap: break-word;
|
||||
max-width: 100%;
|
||||
line-height: 1.4;
|
||||
padding: ${({ theme }) => `${theme.spacing(0.25)} ${theme.spacing(0.75)}`};
|
||||
padding: 1px 3px;
|
||||
border-radius: ${({ theme }) => theme.border.radius.sm};
|
||||
background: ${({ theme }) => theme.background.tertiary};
|
||||
}
|
||||
|
||||
@@ -31,10 +31,10 @@ const StyledThreadItem = styled.div<{ isSelected?: boolean }>`
|
||||
margin-bottom: ${({ theme }) => theme.spacing(1)};
|
||||
border-left: 3px solid transparent;
|
||||
cursor: pointer;
|
||||
padding: ${({ theme }) => theme.spacing(1, 0.25)};
|
||||
right: ${({ theme }) => theme.spacing(0.75)};
|
||||
padding: ${({ theme }) => theme.spacing(1)} 1px;
|
||||
right: 3px;
|
||||
position: relative;
|
||||
width: calc(100% + ${({ theme }) => theme.spacing(0.25)});
|
||||
width: calc(100% + 1px);
|
||||
|
||||
&:hover {
|
||||
background: ${({ theme }) => theme.background.transparent.light};
|
||||
|
||||
+4
-7
@@ -1,9 +1,6 @@
|
||||
import { ThemeProvider as EmotionThemeProvider } from '@emotion/react';
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import {
|
||||
THEME_LIGHT,
|
||||
ThemeContextProvider,
|
||||
ThemeProvider,
|
||||
} from 'twenty-ui/theme';
|
||||
import { THEME_LIGHT, ThemeContextProvider } from 'twenty-ui/theme';
|
||||
import { type ExtendedUIMessagePart } from 'twenty-shared/ai';
|
||||
|
||||
import { AIChatAssistantMessageRenderer } from '@/ai/components/AIChatAssistantMessageRenderer';
|
||||
@@ -46,14 +43,14 @@ jest.mock('@/ai/components/CodeExecutionDisplay', () => ({
|
||||
|
||||
const renderAssistantRenderer = (messageParts: ExtendedUIMessagePart[]) => {
|
||||
return render(
|
||||
<ThemeProvider theme={THEME_LIGHT}>
|
||||
<EmotionThemeProvider theme={THEME_LIGHT}>
|
||||
<ThemeContextProvider theme={THEME_LIGHT}>
|
||||
<AIChatAssistantMessageRenderer
|
||||
messageParts={messageParts}
|
||||
isLastMessageStreaming={false}
|
||||
/>
|
||||
</ThemeContextProvider>
|
||||
</ThemeProvider>,
|
||||
</EmotionThemeProvider>,
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
+4
-7
@@ -1,10 +1,7 @@
|
||||
import { ThemeProvider as EmotionThemeProvider } from '@emotion/react';
|
||||
import { render, screen, waitFor } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import {
|
||||
THEME_LIGHT,
|
||||
ThemeContextProvider,
|
||||
ThemeProvider,
|
||||
} from 'twenty-ui/theme';
|
||||
import { THEME_LIGHT, ThemeContextProvider } from 'twenty-ui/theme';
|
||||
|
||||
import { ThinkingStepsDisplay } from '@/ai/components/ThinkingStepsDisplay';
|
||||
import { type ThinkingStepPart } from '@/ai/utils/thinkingStepPart';
|
||||
@@ -84,7 +81,7 @@ const renderThinkingStepsDisplay = ({
|
||||
hasAssistantTextResponseStarted?: boolean;
|
||||
}) => {
|
||||
return render(
|
||||
<ThemeProvider theme={THEME_LIGHT}>
|
||||
<EmotionThemeProvider theme={THEME_LIGHT}>
|
||||
<ThemeContextProvider theme={THEME_LIGHT}>
|
||||
<ThinkingStepsDisplay
|
||||
parts={parts}
|
||||
@@ -92,7 +89,7 @@ const renderThinkingStepsDisplay = ({
|
||||
hasAssistantTextResponseStarted={hasAssistantTextResponseStarted}
|
||||
/>
|
||||
</ThemeContextProvider>
|
||||
</ThemeProvider>,
|
||||
</EmotionThemeProvider>,
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -18,8 +18,7 @@ const StyledIconContainer = styled.div<{
|
||||
flex-shrink: 0;
|
||||
height: ${({ size }) => (size === 'small' ? '14px' : 'auto')};
|
||||
justify-content: center;
|
||||
padding: ${({ theme, size }) =>
|
||||
size === 'small' ? '0' : theme.spacing(1.25)};
|
||||
padding: ${({ size }) => (size === 'small' ? '0' : '5px')};
|
||||
width: ${({ size }) => (size === 'small' ? '14px' : 'auto')};
|
||||
`;
|
||||
|
||||
|
||||
+4
-4
@@ -38,11 +38,11 @@ const StyledViewOverlay = styled.div<{ $backgroundColor: string }>`
|
||||
border-radius: 4px;
|
||||
bottom: -7px;
|
||||
display: flex;
|
||||
height: ${({ theme }) => theme.spacing(3.5)};
|
||||
height: 14px;
|
||||
justify-content: center;
|
||||
position: absolute;
|
||||
right: -7px;
|
||||
width: ${({ theme }) => theme.spacing(3.5)};
|
||||
width: 14px;
|
||||
`;
|
||||
|
||||
export type ObjectIconWithViewOverlayProps = {
|
||||
@@ -69,14 +69,14 @@ export const ObjectIconWithViewOverlay = ({
|
||||
$borderColor={objectStyle.borderColor}
|
||||
>
|
||||
<ObjectIcon
|
||||
size={theme.spacing(3.5)}
|
||||
size="14px"
|
||||
stroke={theme.icon.stroke.md}
|
||||
color={objectStyle.iconColor}
|
||||
/>
|
||||
</StyledObjectIconWrapper>
|
||||
<StyledViewOverlay $backgroundColor={theme.grayScale.gray4}>
|
||||
<ViewIcon
|
||||
size={theme.spacing(2.5)}
|
||||
size="10px"
|
||||
stroke={theme.icon.stroke.lg}
|
||||
color={theme.grayScale.gray10}
|
||||
/>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { themeColorSchema, type ThemeColor } from 'twenty-ui/theme';
|
||||
import { type ThemeColor } from 'twenty-ui/theme';
|
||||
import { themeColorSchema } from 'twenty-ui/utilities';
|
||||
|
||||
import { DEFAULT_NAV_ITEM_ICON_COLOR } from '@/navigation-menu-item/constants/NavigationMenuItemDefaultIconColor.constant';
|
||||
|
||||
|
||||
+2
-2
@@ -37,7 +37,7 @@ const StyledTabsPill = styled.div`
|
||||
border: 1px solid ${({ theme }) => theme.border.color.medium};
|
||||
background: ${({ theme }) => theme.background.secondary};
|
||||
border-radius: ${({ theme }) => theme.border.radius.pill};
|
||||
padding: ${({ theme }) => theme.spacing(0.75)};
|
||||
padding: 3px;
|
||||
height: ${({ theme }) => theme.spacing(7)};
|
||||
display: flex;
|
||||
width: ${({ theme }) => theme.spacing(18)};
|
||||
@@ -85,7 +85,7 @@ const StyledNewChatButtonWrapper = styled.div<{ isExpanded: boolean }>`
|
||||
isExpanded ? theme.spacing(7) : theme.spacing(6)};
|
||||
justify-content: center;
|
||||
padding: ${({ theme, isExpanded }) =>
|
||||
isExpanded ? theme.spacing(0.75) : theme.spacing(0.5)};
|
||||
isExpanded ? '3px' : theme.spacing(0.5)};
|
||||
width: ${({ theme, isExpanded }) =>
|
||||
isExpanded ? theme.spacing(25.75) : theme.spacing(6)};
|
||||
transition:
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
import { metadataLabelSchema } from '@/object-metadata/validation-schemas/metadataLabelSchema';
|
||||
import { themeColorSchema } from 'twenty-ui/theme';
|
||||
import { themeColorSchema } from 'twenty-ui/utilities';
|
||||
import { FieldMetadataType, RelationType } from '~/generated-metadata/graphql';
|
||||
import { camelCaseStringSchema } from '~/utils/validation-schemas/camelCaseStringSchema';
|
||||
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
import { themeColorSchema } from 'twenty-ui/theme';
|
||||
import { themeColorSchema } from 'twenty-ui/utilities';
|
||||
import { computeOptionValueFromLabel } from '~/pages/settings/data-model/utils/computeOptionValueFromLabel';
|
||||
|
||||
const selectOptionSchema = z
|
||||
|
||||
+1
-1
@@ -5,7 +5,7 @@ const StyledCardBodyContainer = styled.div`
|
||||
flex-direction: column;
|
||||
gap: ${({ theme }) => theme.spacing(0.5)};
|
||||
padding-bottom: ${({ theme }) => theme.spacing(2)};
|
||||
padding-left: ${({ theme }) => theme.spacing(2.5)};
|
||||
padding-left: 10px;
|
||||
padding-right: ${({ theme }) => theme.spacing(2)};
|
||||
span {
|
||||
align-items: center;
|
||||
|
||||
+1
-2
@@ -4,7 +4,6 @@ import { useContext, type ReactNode } from 'react';
|
||||
import { FieldContext } from '@/object-record/record-field/ui/contexts/FieldContext';
|
||||
import { isFieldIdentifierDisplay } from '@/object-record/record-field/ui/meta-types/display/utils/isFieldIdentifierDisplay';
|
||||
import { RECORD_CHIP_CLICK_OUTSIDE_ID } from '@/object-record/record-table/constants/RecordChipClickOutsideId';
|
||||
import { RECORD_TABLE_ROW_HEIGHT } from '@/object-record/record-table/constants/RecordTableRowHeight';
|
||||
import { RecordTableCellContext } from '@/object-record/record-table/contexts/RecordTableCellContext';
|
||||
import { useOpenRecordTableCellFromCell } from '@/object-record/record-table/record-table-cell/hooks/useOpenRecordTableCellFromCell';
|
||||
import { ThemeContext } from 'twenty-ui/theme';
|
||||
@@ -21,7 +20,7 @@ const StyledBaseContainer = styled.div<{
|
||||
box-sizing: border-box;
|
||||
cursor: ${({ isReadOnly }) => (isReadOnly ? 'default' : 'pointer')};
|
||||
display: flex;
|
||||
height: ${RECORD_TABLE_ROW_HEIGHT}px;
|
||||
height: 32px;
|
||||
user-select: none;
|
||||
|
||||
position: relative;
|
||||
|
||||
+1
-1
@@ -14,7 +14,7 @@ import { isDefined } from 'twenty-shared/utils';
|
||||
const StyledVirtualizedRowContainer = styled.div<{
|
||||
pixelsFromTop: number;
|
||||
}>`
|
||||
height: ${RECORD_TABLE_ROW_HEIGHT + 1};
|
||||
height: 33px;
|
||||
position: absolute;
|
||||
top: ${({ pixelsFromTop }) => pixelsFromTop}px;
|
||||
`;
|
||||
|
||||
+1
-1
@@ -16,7 +16,7 @@ import { isDefined } from 'twenty-shared/utils';
|
||||
const StyledDebugRow = styled.div`
|
||||
position: absolute;
|
||||
left: 250px;
|
||||
top: ${({ theme }) => theme.spacing(1.25)};
|
||||
top: 5px;
|
||||
z-index: 20;
|
||||
color: ${({ theme }) => theme.font.color.primary};
|
||||
background-color: ${({ theme }) => theme.color.gray3};
|
||||
|
||||
+1
-1
@@ -16,7 +16,7 @@ const StyledGraphContainer = styled.div`
|
||||
height: 240px;
|
||||
border: 1px solid ${({ theme }) => theme.border.color.medium};
|
||||
margin-bottom: ${({ theme }) => theme.spacing(4)};
|
||||
padding-top: ${({ theme }) => theme.spacing(2.5)};
|
||||
padding-top: 10px;
|
||||
width: 100%;
|
||||
`;
|
||||
|
||||
|
||||
+1
-1
@@ -68,7 +68,7 @@ type SettingsDataModelFieldSelectFormProps = {
|
||||
};
|
||||
|
||||
const StyledContainer = styled(CardContent)`
|
||||
padding-bottom: ${({ theme }) => theme.spacing(3.5)};
|
||||
padding-bottom: 14px;
|
||||
`;
|
||||
|
||||
const StyledOptionsLabel = styled.div<{
|
||||
|
||||
+3
-3
@@ -79,8 +79,8 @@ const StyledColorSample = styled(ColorSample)`
|
||||
margin-top: ${({ theme }) => theme.spacing(1)};
|
||||
margin-bottom: ${({ theme }) => theme.spacing(1)};
|
||||
|
||||
margin-right: ${({ theme }) => theme.spacing(3.5)};
|
||||
margin-left: ${({ theme }) => theme.spacing(3.5)};
|
||||
margin-right: 14px;
|
||||
margin-left: 14px;
|
||||
`;
|
||||
|
||||
const StyledOptionInput = styled(SettingsTextInput)`Chip
|
||||
@@ -92,7 +92,7 @@ const StyledOptionInput = styled(SettingsTextInput)`Chip
|
||||
`;
|
||||
|
||||
const StyledIconGripVertical = styled(IconGripVertical)`
|
||||
margin-right: ${({ theme }) => theme.spacing(0.75)};
|
||||
margin-right: 3px;
|
||||
`;
|
||||
|
||||
const StyledLightIconButton = styled(LightIconButton)`
|
||||
|
||||
+1
-1
@@ -9,7 +9,7 @@ import { isUndefinedOrNull } from '~/utils/isUndefinedOrNull';
|
||||
const StyledFooter = styled(Modal.Footer)`
|
||||
border-top: 1px solid ${({ theme }) => theme.border.color.medium};
|
||||
box-shadow: ${({ theme }) => theme.boxShadow.strong};
|
||||
gap: ${({ theme }) => theme.spacing(2.5)};
|
||||
gap: 10px;
|
||||
justify-content: space-between;
|
||||
padding: ${({ theme }) => theme.spacing(4)};
|
||||
height: auto;
|
||||
|
||||
+1
-1
@@ -10,7 +10,7 @@ const StyledBanner = styled(Banner, {
|
||||
background: ${({ allMatched, theme }) =>
|
||||
allMatched ? theme.accent.secondary : theme.background.transparent.light};
|
||||
border-radius: ${({ theme }) => theme.spacing(2)};
|
||||
padding: ${({ theme }) => theme.spacing(2) + ' ' + theme.spacing(2.5)};
|
||||
padding: ${({ theme }) => theme.spacing(2) + ' 10px'};
|
||||
`;
|
||||
|
||||
const StyledText = styled('div', {
|
||||
|
||||
@@ -3,11 +3,10 @@ import { t } from '@lingui/core/macro';
|
||||
import { IconCheck, IconX } from 'twenty-ui/display';
|
||||
import { THEME_COMMON } from 'twenty-ui/theme';
|
||||
|
||||
const spacing = THEME_COMMON.spacingMultiplicator * 1;
|
||||
const iconSizeSm = THEME_COMMON.icon.size.sm;
|
||||
|
||||
const StyledBooleanFieldValue = styled.div`
|
||||
margin-left: ${spacing}px;
|
||||
margin-left: 4px;
|
||||
`;
|
||||
|
||||
type BooleanDisplayProps = {
|
||||
|
||||
@@ -5,7 +5,6 @@ import { ExpandableList } from '@/ui/layout/expandable-list/components/Expandabl
|
||||
import { styled } from '@linaria/react';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { RoundedLink } from 'twenty-ui/navigation';
|
||||
import { THEME_COMMON } from 'twenty-ui/theme';
|
||||
|
||||
type EmailsDisplayProps = {
|
||||
value?: FieldEmailsValue;
|
||||
@@ -13,12 +12,10 @@ type EmailsDisplayProps = {
|
||||
onEmailClick?: (email: string, event: React.MouseEvent<HTMLElement>) => void;
|
||||
};
|
||||
|
||||
const themeSpacing = THEME_COMMON.spacingMultiplicator;
|
||||
|
||||
const StyledContainer = styled.div`
|
||||
align-items: center;
|
||||
display: flex;
|
||||
gap: ${themeSpacing * 1}px;
|
||||
gap: 4px;
|
||||
justify-content: flex-start;
|
||||
|
||||
max-width: 100%;
|
||||
|
||||
+1
-4
@@ -2,14 +2,11 @@ import { type FieldMultiSelectValue } from '@/object-record/record-field/ui/type
|
||||
import { styled } from '@linaria/react';
|
||||
import { Tag } from 'twenty-ui/components';
|
||||
import { type SelectOption } from 'twenty-ui/input';
|
||||
import { THEME_COMMON } from 'twenty-ui/theme';
|
||||
|
||||
const spacing1 = THEME_COMMON.spacing(1);
|
||||
|
||||
const StyledContainer = styled.div`
|
||||
align-items: center;
|
||||
display: flex;
|
||||
gap: ${spacing1};
|
||||
gap: 4px;
|
||||
justify-content: flex-start;
|
||||
|
||||
max-width: 100%;
|
||||
|
||||
@@ -8,7 +8,6 @@ import { styled } from '@linaria/react';
|
||||
import { parsePhoneNumber } from 'libphonenumber-js';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { RoundedLink } from 'twenty-ui/navigation';
|
||||
import { THEME_COMMON } from 'twenty-ui/theme';
|
||||
import { logError } from '~/utils/logError';
|
||||
|
||||
type PhonesDisplayProps = {
|
||||
@@ -20,12 +19,10 @@ type PhonesDisplayProps = {
|
||||
) => void;
|
||||
};
|
||||
|
||||
const themeSpacing = THEME_COMMON.spacingMultiplicator;
|
||||
|
||||
const StyledContainer = styled.div`
|
||||
align-items: center;
|
||||
display: flex;
|
||||
gap: ${themeSpacing * 1}px;
|
||||
gap: 4px;
|
||||
justify-content: flex-start;
|
||||
|
||||
max-width: 100%;
|
||||
|
||||
@@ -5,7 +5,7 @@ const StyledInputErrorHelper = styled.div`
|
||||
color: ${({ theme }) => theme.color.red};
|
||||
font-size: ${({ theme }) => theme.font.size.xs};
|
||||
position: absolute;
|
||||
margin-top: ${({ theme }) => theme.spacing(0.25)};
|
||||
margin-top: 1px;
|
||||
`;
|
||||
|
||||
export const InputErrorHelper = ({
|
||||
|
||||
@@ -409,7 +409,7 @@ const StyledAutogrowWrapper = styled(AutogrowWrapper)<{
|
||||
: sizeVariant === 'md'
|
||||
? '28px'
|
||||
: '32px'};
|
||||
padding: 0 ${({ theme }) => theme.spacing(1.25)};
|
||||
padding: 0 5px;
|
||||
`;
|
||||
|
||||
const TextInputWithAutoGrowWrapper = forwardRef<
|
||||
|
||||
+1
-1
@@ -40,7 +40,7 @@ const StyledLabel = styled.span`
|
||||
const StyledDescription = styled.span`
|
||||
color: ${({ theme }) => theme.font.color.light};
|
||||
font-size: ${({ theme }) => theme.font.size.sm};
|
||||
margin-top: ${({ theme }) => theme.spacing(0.25)};
|
||||
margin-top: 1px;
|
||||
`;
|
||||
|
||||
const StyledIconPickerContainer = styled.div`
|
||||
|
||||
Reference in New Issue
Block a user