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,11 +1,12 @@
import styled from '@emotion/styled';
import { styled } from '@linaria/react';
import { themeCssVariables } from 'twenty-ui/theme-constants';
const StyledTitle = styled.h3`
color: ${({ theme }) => theme.font.color.extraLight};
font-size: ${({ theme }) => theme.font.size.sm};
font-weight: ${({ theme }) => theme.font.weight.medium};
color: ${themeCssVariables.font.color.extraLight};
font-size: ${themeCssVariables.font.size.sm};
font-weight: ${themeCssVariables.font.weight.medium};
margin: 0;
margin-bottom: ${({ theme }) => theme.spacing(4)};
margin-bottom: ${themeCssVariables.spacing[4]};
`;
export { StyledTitle as SettingsDataModelCardTitle };
@@ -3,23 +3,25 @@ import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent';
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
import { useCloseDropdown } from '@/ui/layout/dropdown/hooks/useCloseDropdown';
import { useTheme } from '@emotion/react';
import styled from '@emotion/styled';
import { styled } from '@linaria/react';
import { t } from '@lingui/core/macro';
import { useContext } from 'react';
import { useLocation, useParams, useSearchParams } from 'react-router-dom';
import { SettingsPath } from 'twenty-shared/types';
import { isDefined } from 'twenty-shared/utils';
import { IconChevronDown } from 'twenty-ui/display';
import { Button } from 'twenty-ui/input';
import { MenuItem } from 'twenty-ui/navigation';
import { ThemeContext } from 'twenty-ui/theme';
import { themeCssVariables } from 'twenty-ui/theme-constants';
import { useNavigateSettings } from '~/hooks/useNavigateSettings';
const StyledContainer = styled.div`
align-items: center;
color: ${({ theme }) => theme.font.color.tertiary};
color: ${themeCssVariables.font.color.tertiary};
cursor: default;
display: flex;
font-size: ${({ theme }) => theme.font.size.md};
font-size: ${themeCssVariables.font.size.md};
`;
const StyledButtonContainer = styled.div`
@@ -28,9 +30,9 @@ const StyledButtonContainer = styled.div`
`;
const StyledDownChevron = styled(IconChevronDown)`
color: ${({ theme }) => theme.font.color.primary};
color: ${themeCssVariables.font.color.primary};
position: absolute;
right: ${({ theme }) => theme.spacing(1.5)};
right: ${themeCssVariables.spacing['1.5']};
top: 50%;
transform: translateY(-50%);
`;
@@ -40,28 +42,13 @@ const StyledMenuItemWrapper = styled.div<{ disabled?: boolean }>`
width: 100%;
`;
const StyledMenuItem = styled(MenuItem)<{
selected?: boolean;
disabled?: boolean;
}>`
background: ${({ theme, selected }) =>
selected ? theme.background.quaternary : 'transparent'};
opacity: ${({ disabled }) => (disabled ? 0.5 : 1)};
pointer-events: ${({ disabled }) => (disabled ? 'none' : 'auto')};
&:hover {
background: ${({ theme, disabled }) =>
disabled ? 'transparent' : theme.background.tertiary};
}
`;
const StyledSpan = styled.span`
margin-left: ${({ theme }) => theme.spacing(2)};
margin-left: ${themeCssVariables.spacing[2]};
`;
const StyledButton = styled(Button)`
color: ${({ theme }) => theme.font.color.primary};
padding-right: ${({ theme }) => theme.spacing(6)};
color: ${themeCssVariables.font.color.primary};
padding-right: ${themeCssVariables.spacing[6]};
`;
export const SettingsDataModelNewFieldBreadcrumbDropDown = () => {
@@ -71,7 +58,7 @@ export const SettingsDataModelNewFieldBreadcrumbDropDown = () => {
const location = useLocation();
const { objectNamePlural = '' } = useParams();
const [searchParams] = useSearchParams();
const theme = useTheme();
const { theme } = useContext(ThemeContext);
const fieldType = searchParams.get('fieldType') as SettingsFieldType;
const isConfigureStep = location.pathname.includes('/configure');
@@ -113,14 +100,14 @@ export const SettingsDataModelNewFieldBreadcrumbDropDown = () => {
<DropdownContent>
<DropdownMenuItemsContainer>
<StyledMenuItemWrapper>
<StyledMenuItem
<MenuItem
text={t`1. Type`}
onClick={() => handleClick('select')}
selected={!isConfigureStep}
/>
</StyledMenuItemWrapper>
<StyledMenuItemWrapper disabled={!isDefined(fieldType)}>
<StyledMenuItem
<MenuItem
text={t`2. Configure`}
onClick={() => handleClick('configure')}
selected={isConfigureStep}
@@ -1,9 +1,10 @@
import styled from '@emotion/styled';
import { styled } from '@linaria/react';
import { type ReactNode } from 'react';
import { StyledFormCardTitle } from '@/settings/data-model/fields/components/StyledFormCardTitle';
import { Trans } from '@lingui/react/macro';
import { Card, CardContent } from 'twenty-ui/layout';
import { themeCssVariables } from 'twenty-ui/theme-constants';
type SettingsDataModelPreviewFormCardProps = {
className?: string;
@@ -13,7 +14,7 @@ type SettingsDataModelPreviewFormCardProps = {
};
const StyledPreviewContainer = styled(CardContent)`
background-color: ${({ theme }) => theme.background.transparent.lighter};
background-color: ${themeCssVariables.background.transparent.lighter};
`;
const StyledFormContainer = styled(CardContent)`
@@ -1,9 +1,10 @@
import styled from '@emotion/styled';
import { styled } from '@linaria/react';
import { themeCssVariables } from 'twenty-ui/theme-constants';
export const StyledFormCardTitle = styled.h3`
color: ${({ theme }) => theme.font.color.extraLight};
font-size: ${({ theme }) => theme.font.size.sm};
font-weight: ${({ theme }) => theme.font.weight.medium};
color: ${themeCssVariables.font.color.extraLight};
font-size: ${themeCssVariables.font.size.sm};
font-weight: ${themeCssVariables.font.weight.medium};
margin: 0;
margin-bottom: ${({ theme }) => theme.spacing(4)};
margin-bottom: ${themeCssVariables.spacing[4]};
`;
@@ -1,4 +1,4 @@
import styled from '@emotion/styled';
import { styled } from '@linaria/react';
import { Controller, useFormContext } from 'react-hook-form';
import { type z } from 'zod';
@@ -11,8 +11,8 @@ import { DATABASE_IDENTIFIER_MAXIMUM_LENGTH } from '@/settings/data-model/consta
import { getErrorMessageFromError } from '@/settings/data-model/fields/forms/utils/errorMessages';
import { IconPicker } from '@/ui/input/components/IconPicker';
import { SettingsTextInput } from '@/ui/input/components/SettingsTextInput';
import { useTheme } from '@emotion/react';
import { useLingui } from '@lingui/react/macro';
import { useContext } from 'react';
import { FieldMetadataType } from 'twenty-shared/types';
import { isDefined } from 'twenty-shared/utils';
import {
@@ -22,6 +22,8 @@ import {
TooltipDelay,
} from 'twenty-ui/display';
import { Card } from 'twenty-ui/layout';
import { ThemeContext } from 'twenty-ui/theme';
import { themeCssVariables } from 'twenty-ui/theme-constants';
import { computeMetadataNameFromLabel } from '~/pages/settings/data-model/utils/computeMetadataNameFromLabel';
export const settingsDataModelFieldIconLabelFormSchema = (
@@ -48,26 +50,26 @@ type SettingsDataModelFieldIconLabelFormValues = z.infer<
const StyledInputsContainer = styled.div`
display: flex;
gap: ${({ theme }) => theme.spacing(2)};
margin-bottom: ${({ theme }) => theme.spacing(1)};
gap: ${themeCssVariables.spacing[2]};
margin-bottom: ${themeCssVariables.spacing[1]};
width: 100%;
`;
const StyledAdvancedSettingsSectionInputWrapper = styled.div`
display: flex;
flex-direction: column;
gap: ${({ theme }) => theme.spacing(4)};
gap: ${themeCssVariables.spacing[4]};
width: 100%;
flex: 1;
`;
const StyledAdvancedSettingsOuterContainer = styled.div`
padding-top: ${({ theme }) => theme.spacing(4)};
padding-top: ${themeCssVariables.spacing[4]};
`;
const StyledAdvancedSettingsContainer = styled.div`
display: flex;
gap: ${({ theme }) => theme.spacing(2)};
gap: ${themeCssVariables.spacing[2]};
position: relative;
width: 100%;
`;
@@ -93,7 +95,7 @@ export const SettingsDataModelFieldIconLabelForm = ({
trigger,
} = useFormContext<SettingsDataModelFieldIconLabelFormValues>();
const theme = useTheme();
const { theme } = useContext(ThemeContext);
const label = watch('label');
@@ -126,15 +128,12 @@ export const SettingsDataModelFieldIconLabelForm = ({
const isCustomButNotRelationField =
fieldMetadataItem?.isCustom === true && !isRelation;
// TODO: remove the custom RELATION edge case, this will result in canToggleSyncLabelWithName = isCustom
const canToggleSyncLabelWithName =
!isCreationMode && isCustomButNotRelationField;
// TODO: remove custom RELATION edge case, this will result in isNameEditEnabled = isCustom
const isNameEditEnabled =
isLabelSyncedWithName === false && isCustomButNotRelationField;
// TODO: remove custom RELATION edge case, this will result in isLabelEditEnabled = true
const isLabelEditEnabled =
isCreationMode ||
(!isCreationMode &&
@@ -10,16 +10,17 @@ import { useSelectSettingsFormInitialValues } from '@/settings/data-model/fields
import { type FieldType } from '@/settings/data-model/types/FieldType';
import { type SettingsFieldType } from '@/settings/data-model/types/SettingsFieldType';
import { SettingsTextInput } from '@/ui/input/components/SettingsTextInput';
import { useTheme } from '@emotion/react';
import styled from '@emotion/styled';
import { styled } from '@linaria/react';
import { t } from '@lingui/core/macro';
import { Section } from '@react-email/components';
import { useState } from 'react';
import { useContext, useState } from 'react';
import { Controller, useFormContext } from 'react-hook-form';
import { SettingsPath } from 'twenty-shared/types';
import { getSettingsPath } from 'twenty-shared/utils';
import { H2Title, IconSearch } from 'twenty-ui/display';
import { UndecoratedLink } from 'twenty-ui/navigation';
import { ThemeContext } from 'twenty-ui/theme';
import { themeCssVariables } from 'twenty-ui/theme-constants';
import { FieldMetadataType } from '~/generated-metadata/graphql';
import { type SettingsDataModelFieldTypeFormValues } from '~/pages/settings/data-model/new-field/SettingsObjectNewFieldSelect';
@@ -43,7 +44,7 @@ const StyledTypeSelectContainer = styled.div`
const StyledContainer = styled.div`
display: flex;
gap: ${({ theme }) => theme.spacing(2)};
gap: ${themeCssVariables.spacing[2]};
justify-content: flex-start;
flex-wrap: wrap;
width: 100%;
@@ -53,7 +54,7 @@ const StyledCardContainer = styled.div`
display: flex;
position: relative;
width: calc(50% - ${({ theme }) => theme.spacing(1)});
width: calc(50% - ${themeCssVariables.spacing[1]});
`;
const StyledSearchInput = styled(SettingsTextInput)`
@@ -64,7 +65,7 @@ export const SettingsObjectNewFieldSelector = ({
excludedFieldTypes = [],
objectNamePlural,
}: SettingsObjectNewFieldSelectorProps) => {
const theme = useTheme();
const { theme } = useContext(ThemeContext);
const { control, setValue } =
useFormContext<SettingsDataModelFieldTypeFormValues>();
const [searchQuery, setSearchQuery] = useState('');
@@ -132,8 +133,6 @@ export const SettingsObjectNewFieldSelector = ({
<StyledContainer>
{fieldTypeConfigs
.filter(([, config]) => config.category === category)
// by default, we hide the relation type and create only the morph relation type
// on submit the new field, we choose the relation type based on the amount of target object
.filter(([key]) => key !== FieldMetadataType.RELATION)
.map(
([key, config]) =>
@@ -1,4 +1,4 @@
import styled from '@emotion/styled';
import { styled } from '@linaria/react';
import { type Meta, type StoryObj } from '@storybook/react-vite';
import { FormProviderDecorator } from '~/testing/decorators/FormProviderDecorator';
@@ -10,10 +10,11 @@ import { useDateSettingsFormInitialValues } from '@/settings/data-model/fields/f
import { getDisplayFormatLabel } from '@/settings/data-model/fields/forms/date/utils/getDisplayFormatLabel';
import { Select } from '@/ui/input/components/Select';
import { SettingsTextInput } from '@/ui/input/components/SettingsTextInput';
import styled from '@emotion/styled';
import { styled } from '@linaria/react';
import { useLingui } from '@lingui/react/macro';
import { IconSlash } from 'twenty-ui/display';
import { AnimatedExpandableContainer } from 'twenty-ui/layout';
import { themeCssVariables } from 'twenty-ui/theme-constants';
const fieldDateSettings = z.discriminatedUnion('displayFormat', [
z.object({
@@ -33,7 +34,7 @@ export const settingsDataModelFieldDateFormSchema = z.object({
});
const StyledTextInput = styled(SettingsTextInput)`
padding: ${({ theme }) => theme.spacing(4)};
padding: ${themeCssVariables.spacing[4]};
padding-top: 0;
`;
@@ -15,28 +15,29 @@ import { IconPicker } from '@/ui/input/components/IconPicker';
import { Select } from '@/ui/input/components/Select';
import { SettingsTextInput } from '@/ui/input/components/SettingsTextInput';
import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile';
import styled from '@emotion/styled';
import { styled } from '@linaria/react';
import { useLingui } from '@lingui/react/macro';
import { isDefined } from 'twenty-shared/utils';
import { themeCssVariables } from 'twenty-ui/theme-constants';
import { RelationType } from '~/generated-metadata/graphql';
const StyledSelectsContainer = styled.div<{ isMobile: boolean }>`
display: grid;
gap: ${({ theme }) => theme.spacing(4)};
gap: ${themeCssVariables.spacing[4]};
grid-template-columns: ${({ isMobile }) => (isMobile ? '1fr' : '1fr 1fr')};
margin-bottom: ${({ theme }) => theme.spacing(4)};
margin-bottom: ${themeCssVariables.spacing[4]};
`;
const StyledInputsLabel = 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 StyledInputsContainer = styled.div`
display: flex;
gap: ${({ theme }) => theme.spacing(2)};
gap: ${themeCssVariables.spacing[2]};
width: 100%;
`;
@@ -1,4 +1,4 @@
import styled from '@emotion/styled';
import { styled } from '@linaria/react';
import { type ReactNode } from 'react';
const StyledPreviewContent = styled.div<{ isMobile: boolean }>`
@@ -1,4 +1,4 @@
import styled from '@emotion/styled';
import { styled } from '@linaria/react';
const StyledRelationImage = styled.img<{
flip?: boolean;
@@ -1,4 +1,4 @@
import styled from '@emotion/styled';
import { styled } from '@linaria/react';
import { type DropResult } from '@hello-pangea/dnd';
import { Controller, useFormContext } from 'react-hook-form';
import { z } from 'zod';
@@ -29,9 +29,8 @@ import { GenericDropdownContentWidth } from '@/ui/layout/dropdown/constants/Gene
import { useCloseDropdown } from '@/ui/layout/dropdown/hooks/useCloseDropdown';
import { isAdvancedModeEnabledState } from '@/ui/navigation/navigation-drawer/states/isAdvancedModeEnabledState';
import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue';
import { useTheme } from '@emotion/react';
import { t } from '@lingui/core/macro';
import { useEffect, useState } from 'react';
import { useContext, useEffect, useState } from 'react';
import { useSearchParams } from 'react-router-dom';
import { isDefined } from 'twenty-shared/utils';
import {
@@ -44,6 +43,8 @@ import {
import { LightButton, LightIconButton } from 'twenty-ui/input';
import { CardContent, CardFooter } from 'twenty-ui/layout';
import { MenuItem } from 'twenty-ui/navigation';
import { ThemeContext } from 'twenty-ui/theme';
import { themeCssVariables } from 'twenty-ui/theme-constants';
import { SettingsDataModelFieldSelectFormOptionRow } from './SettingsDataModelFieldSelectFormOptionRow';
export const settingsDataModelFieldSelectFormSchema = z.object({
@@ -75,29 +76,30 @@ const StyledOptionsLabel = styled.div<{
isAdvancedModeEnabled: boolean;
isBulkInputMode: boolean;
}>`
color: ${({ theme }) => theme.font.color.light};
font-size: ${({ theme }) => theme.font.size.xs};
font-weight: ${({ theme }) => theme.font.weight.semiBold};
margin-bottom: ${({ theme }) => theme.spacing(1.5)};
margin-top: ${({ theme }) => theme.spacing(1)};
color: ${themeCssVariables.font.color.light};
font-size: ${themeCssVariables.font.size.xs};
font-weight: ${themeCssVariables.font.weight.semiBold};
margin-bottom: ${themeCssVariables.spacing['1.5']};
margin-top: ${themeCssVariables.spacing[1]};
width: 100%;
margin-left: ${({ theme, isAdvancedModeEnabled, isBulkInputMode }) =>
theme.spacing(isAdvancedModeEnabled && !isBulkInputMode ? 10 : 0)};
};
margin-left: ${({ isAdvancedModeEnabled, isBulkInputMode }) =>
isAdvancedModeEnabled && !isBulkInputMode
? themeCssVariables.spacing[10]
: themeCssVariables.spacing[0]};
`;
const StyledApiKeyContainer = styled.div`
display: flex;
gap: ${({ theme }) => theme.spacing(2)};
gap: ${themeCssVariables.spacing[2]};
width: 100%;
`;
const StyledApiKey = styled.span`
color: ${({ theme }) => theme.font.color.light};
font-size: ${({ theme }) => theme.font.size.xs};
font-weight: ${({ theme }) => theme.font.weight.semiBold};
margin-bottom: ${({ theme }) => theme.spacing(1.5)};
margin-top: ${({ theme }) => theme.spacing(1)};
color: ${themeCssVariables.font.color.light};
font-size: ${themeCssVariables.font.size.xs};
font-weight: ${themeCssVariables.font.weight.semiBold};
margin-bottom: ${themeCssVariables.spacing['1.5']};
margin-top: ${themeCssVariables.spacing[1]};
width: 100%;
white-space: nowrap;
@@ -111,20 +113,20 @@ const StyledLabelContainer = styled.div`
const StyledIconContainer = styled.div`
align-items: center;
border-right: 1px solid ${({ theme }) => theme.color.yellow};
border-right: 1px solid ${themeCssVariables.color.yellow};
display: flex;
margin-bottom: ${({ theme }) => theme.spacing(1.5)};
margin-top: ${({ theme }) => theme.spacing(1)};
margin-bottom: ${themeCssVariables.spacing['1.5']};
margin-top: ${themeCssVariables.spacing[1]};
`;
const StyledIconPoint = styled(IconPoint)`
margin-right: ${({ theme }) => theme.spacing(0.5)};
margin-right: ${themeCssVariables.spacing['0.5']};
`;
const StyledFooter = styled(CardFooter)`
background-color: ${({ theme }) => theme.background.secondary};
padding: ${({ theme }) => theme.spacing(1)};
background-color: ${themeCssVariables.background.secondary};
padding: ${themeCssVariables.spacing[1]};
`;
const StyledButton = styled(LightButton)`
@@ -136,20 +138,20 @@ const StyledOptionsHeaderContainer = styled.div`
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: ${({ theme }) => theme.spacing(1.5)};
margin-top: ${({ theme }) => theme.spacing(1)};
margin-bottom: ${themeCssVariables.spacing['1.5']};
margin-top: ${themeCssVariables.spacing[1]};
width: 100%;
`;
const StyledTextAreaContainer = styled.div`
margin-bottom: ${({ theme }) => theme.spacing(2)};
margin-bottom: ${themeCssVariables.spacing[2]};
`;
const StyledHelpText = styled.div`
color: ${({ theme }) => theme.font.color.light};
font-size: ${({ theme }) => theme.font.size.xs};
margin-top: ${({ theme }) => theme.spacing(1)};
margin-bottom: ${({ theme }) => theme.spacing(2)};
color: ${themeCssVariables.font.color.light};
font-size: ${themeCssVariables.font.size.xs};
margin-top: ${themeCssVariables.spacing[1]};
margin-bottom: ${themeCssVariables.spacing[2]};
`;
export const SettingsDataModelFieldSelectForm = ({
@@ -300,7 +302,7 @@ export const SettingsDataModelFieldSelectForm = ({
setFormValue('options', newOptions, { shouldDirty: true });
};
const theme = useTheme();
const { theme } = useContext(ThemeContext);
return (
<>
@@ -6,8 +6,8 @@ import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent';
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
import { useCloseDropdown } from '@/ui/layout/dropdown/hooks/useCloseDropdown';
import { useTheme } from '@emotion/react';
import styled from '@emotion/styled';
import { useContext } from 'react';
import { styled } from '@linaria/react';
import { t } from '@lingui/core/macro';
import {
ColorSample,
@@ -23,7 +23,8 @@ import {
MenuItem,
MenuItemSelectColor,
} from 'twenty-ui/navigation';
import { MAIN_COLOR_NAMES } from 'twenty-ui/theme';
import { MAIN_COLOR_NAMES, ThemeContext } from 'twenty-ui/theme';
import { themeCssVariables } from 'twenty-ui/theme-constants';
import { computeOptionValueFromLabel } from '~/pages/settings/data-model/utils/computeOptionValueFromLabel';
const useColorLabels = (): ColorLabels => ({
@@ -70,24 +71,25 @@ type SettingsDataModelFieldSelectFormOptionRowProps = {
const StyledRow = styled.div`
align-items: center;
display: flex;
height: ${({ theme }) => theme.spacing(6)};
padding: ${({ theme }) => theme.spacing(1.5)} 0;
height: ${themeCssVariables.spacing[6]};
padding: ${themeCssVariables.spacing['1.5']} 0;
`;
const StyledColorSample = styled(ColorSample)`
cursor: pointer;
margin-top: ${({ theme }) => theme.spacing(1)};
margin-bottom: ${({ theme }) => theme.spacing(1)};
margin-top: ${themeCssVariables.spacing[1]};
margin-bottom: ${themeCssVariables.spacing[1]};
margin-right: 14px;
margin-left: 14px;
`;
const StyledOptionInput = styled(SettingsTextInput)`Chip
const StyledOptionInputContainer = styled.div`
flex-grow: 1;
width: 100%;
& input {
height: ${({ theme }) => theme.spacing(6)};
height: ${themeCssVariables.spacing[6]};
}
`;
@@ -96,7 +98,7 @@ const StyledIconGripVertical = styled(IconGripVertical)`
`;
const StyledLightIconButton = styled(LightIconButton)`
margin-left: ${({ theme }) => theme.spacing(2)};
margin-left: ${themeCssVariables.spacing[2]};
`;
export const SettingsDataModelFieldSelectFormOptionRow = ({
@@ -111,7 +113,7 @@ export const SettingsDataModelFieldSelectFormOptionRow = ({
isNewRow,
fieldIsNullable,
}: SettingsDataModelFieldSelectFormOptionRowProps) => {
const theme = useTheme();
const { theme } = useContext(ThemeContext);
const colorLabels = useColorLabels();
const SELECT_COLOR_DROPDOWN_ID = `select-color-dropdown-${option.id}`;
const SELECT_ACTIONS_DROPDOWN_ID = `select-actions-dropdown-${option.id}`;
@@ -134,18 +136,20 @@ export const SettingsDataModelFieldSelectFormOptionRow = ({
color={theme.font.color.extraLight}
/>
<AdvancedSettingsWrapper animationDimension="width" hideDot>
<StyledOptionInput
instanceId={`select-option-value-${option.id}`}
value={option.value}
onChange={(input) =>
onChange({
...option,
value: computeOptionValueFromLabel(input),
})
}
RightIcon={isDefault ? IconCheck : undefined}
maxLength={OPTION_VALUE_MAXIMUM_LENGTH}
/>
<StyledOptionInputContainer>
<SettingsTextInput
instanceId={`select-option-value-${option.id}`}
value={option.value}
onChange={(input) =>
onChange({
...option,
value: computeOptionValueFromLabel(input),
})
}
RightIcon={isDefault ? IconCheck : undefined}
maxLength={OPTION_VALUE_MAXIMUM_LENGTH}
/>
</StyledOptionInputContainer>
</AdvancedSettingsWrapper>
<Dropdown
dropdownId={SELECT_COLOR_DROPDOWN_ID}
@@ -170,27 +174,29 @@ export const SettingsDataModelFieldSelectFormOptionRow = ({
</DropdownContent>
}
/>
<StyledOptionInput
instanceId={`select-option-label-${option.id}`}
value={option.label}
onChange={(label) => {
const optionNameHasBeenEdited = !(
option.value === computeOptionValueFromLabel(option.label)
);
onChange({
...option,
label,
value: optionNameHasBeenEdited
? option.value
: computeOptionValueFromLabel(label),
});
}}
RightIcon={isDefault ? IconCheck : undefined}
maxLength={OPTION_VALUE_MAXIMUM_LENGTH}
onInputEnter={handleInputEnter}
autoFocusOnMount={isNewRow}
autoSelectOnMount={isNewRow}
/>
<StyledOptionInputContainer>
<SettingsTextInput
instanceId={`select-option-label-${option.id}`}
value={option.label}
onChange={(label) => {
const optionNameHasBeenEdited = !(
option.value === computeOptionValueFromLabel(option.label)
);
onChange({
...option,
label,
value: optionNameHasBeenEdited
? option.value
: computeOptionValueFromLabel(label),
});
}}
RightIcon={isDefault ? IconCheck : undefined}
maxLength={OPTION_VALUE_MAXIMUM_LENGTH}
onInputEnter={handleInputEnter}
autoFocusOnMount={isNewRow}
autoSelectOnMount={isNewRow}
/>
</StyledOptionInputContainer>
<Dropdown
dropdownId={SELECT_ACTIONS_DROPDOWN_ID}
dropdownPlacement="right-start"
@@ -1,5 +1,5 @@
import { useTheme } from '@emotion/react';
import styled from '@emotion/styled';
import { useContext } from 'react';
import { styled } from '@linaria/react';
import { useLabelIdentifierFieldMetadataItem } from '@/object-metadata/hooks/useLabelIdentifierFieldMetadataItem';
import { type FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
@@ -13,6 +13,8 @@ import { SettingsDataModelSetFieldValueEffect } from '@/settings/data-model/fiel
import { SettingsDataModelSetLabelIdentifierRecordEffect } from '@/settings/data-model/fields/preview/components/SettingsDataModelSetLabelIdentifierRecordEffect';
import { useFieldPreviewValue } from '@/settings/data-model/fields/preview/hooks/useFieldPreviewValue';
import { useIcons } from 'twenty-ui/display';
import { ThemeContext } from 'twenty-ui/theme';
import { themeCssVariables } from 'twenty-ui/theme-constants';
import { FieldMetadataType } from '~/generated-metadata/graphql';
type SettingsDataModelFieldPreviewProps = {
@@ -27,27 +29,28 @@ type SettingsDataModelFieldPreviewProps = {
const StyledFieldPreview = styled.div<{ shrink?: boolean }>`
align-items: center;
background-color: ${({ theme }) => theme.background.primary};
border: 1px solid ${({ theme }) => theme.border.color.medium};
border-radius: ${({ theme }) => theme.border.radius.sm};
background-color: ${themeCssVariables.background.primary};
border: 1px solid ${themeCssVariables.border.color.medium};
border-radius: ${themeCssVariables.border.radius.sm};
display: flex;
gap: ${({ theme }) => theme.spacing(2)};
gap: ${themeCssVariables.spacing[2]};
height: fit-content;
line-height: 24px;
overflow: hidden;
padding: 0
${({ shrink, theme }) => (shrink ? theme.spacing(1) : theme.spacing(2))};
${({ shrink }) =>
shrink ? themeCssVariables.spacing[1] : themeCssVariables.spacing[2]};
white-space: nowrap;
margin-top: ${({ theme }) => theme.spacing(2)};
padding-top: ${({ theme }) => theme.spacing(2)};
padding-bottom: ${({ theme }) => theme.spacing(2)};
margin-top: ${themeCssVariables.spacing[2]};
padding-top: ${themeCssVariables.spacing[2]};
padding-bottom: ${themeCssVariables.spacing[2]};
`;
const StyledFieldLabel = styled.div`
align-items: center;
color: ${({ theme }) => theme.font.color.tertiary};
color: ${themeCssVariables.font.color.tertiary};
display: flex;
gap: ${({ theme }) => theme.spacing(1)};
gap: ${themeCssVariables.spacing[1]};
`;
export const SettingsDataModelFieldPreview = ({
@@ -56,7 +59,7 @@ export const SettingsDataModelFieldPreview = ({
shrink,
withFieldLabel = true,
}: SettingsDataModelFieldPreviewProps) => {
const theme = useTheme();
const { theme } = useContext(ThemeContext);
const { labelIdentifierFieldMetadataItem } =
useLabelIdentifierFieldMetadataItem({
objectNameSingular: objectNameSingular,
@@ -1,10 +1,11 @@
import styled from '@emotion/styled';
import { styled } from '@linaria/react';
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
import { type FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
import { SettingsDataModelFieldPreview } from '@/settings/data-model/fields/preview/components/SettingsDataModelFieldPreview';
import { SettingsDataModelObjectPreview } from '@/settings/data-model/objects/components/SettingsDataModelObjectSummary';
import { Card, CardContent } from 'twenty-ui/layout';
import { themeCssVariables } from 'twenty-ui/theme-constants';
import { computeMetadataNameFromLabel } from '~/pages/settings/data-model/utils/computeMetadataNameFromLabel';
type SettingsDataModelFieldPreviewWidgetProps = {
@@ -21,12 +22,12 @@ type SettingsDataModelFieldPreviewWidgetProps = {
};
const StyledCard = styled(Card)`
border-radius: ${({ theme }) => theme.border.radius.md};
color: ${({ theme }) => theme.font.color.primary};
border-radius: ${themeCssVariables.border.radius.md};
color: ${themeCssVariables.font.color.primary};
`;
const StyledCardContent = styled(CardContent)`
padding: ${({ theme }) => theme.spacing(2)};
padding: ${themeCssVariables.spacing[2]};
`;
export const SettingsDataModelFieldPreviewWidget = ({
@@ -1,5 +1,5 @@
import { useTheme } from '@emotion/react';
import styled from '@emotion/styled';
import { useContext } from 'react';
import { styled } from '@linaria/react';
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
@@ -11,6 +11,8 @@ import { RecordFieldComponentInstanceContext } from '@/object-record/record-fiel
import { SettingsDataModelSetFieldValueEffect } from '@/settings/data-model/fields/preview/components/SettingsDataModelSetFieldValueEffect';
import { useFieldPreviewValue } from '@/settings/data-model/fields/preview/hooks/useFieldPreviewValue';
import { useIcons } from 'twenty-ui/display';
import { ThemeContext } from 'twenty-ui/theme';
import { themeCssVariables } from 'twenty-ui/theme-constants';
import { v4 } from 'uuid';
import { FieldMetadataType } from '~/generated-metadata/graphql';
@@ -26,27 +28,28 @@ type SettingsDataModelRelationFieldPreviewProps = {
const StyledFieldPreview = styled.div<{ shrink?: boolean }>`
align-items: center;
background-color: ${({ theme }) => theme.background.primary};
border: 1px solid ${({ theme }) => theme.border.color.medium};
border-radius: ${({ theme }) => theme.border.radius.sm};
background-color: ${themeCssVariables.background.primary};
border: 1px solid ${themeCssVariables.border.color.medium};
border-radius: ${themeCssVariables.border.radius.sm};
display: flex;
gap: ${({ theme }) => theme.spacing(2)};
gap: ${themeCssVariables.spacing[2]};
height: fit-content;
line-height: 24px;
overflow: hidden;
padding: 0
${({ shrink, theme }) => (shrink ? theme.spacing(1) : theme.spacing(2))};
${({ shrink }) =>
shrink ? themeCssVariables.spacing[1] : themeCssVariables.spacing[2]};
white-space: nowrap;
margin-top: ${({ theme }) => theme.spacing(2)};
padding-top: ${({ theme }) => theme.spacing(2)};
padding-bottom: ${({ theme }) => theme.spacing(2)};
margin-top: ${themeCssVariables.spacing[2]};
padding-top: ${themeCssVariables.spacing[2]};
padding-bottom: ${themeCssVariables.spacing[2]};
`;
const StyledFieldLabel = styled.div`
align-items: center;
color: ${({ theme }) => theme.font.color.tertiary};
color: ${themeCssVariables.font.color.tertiary};
display: flex;
gap: ${({ theme }) => theme.spacing(1)};
gap: ${themeCssVariables.spacing[1]};
`;
export const SettingsDataModelRelationFieldPreview = ({
@@ -60,7 +63,7 @@ export const SettingsDataModelRelationFieldPreview = ({
objectNameSingular: relationTargetObjectNameSingular,
});
const theme = useTheme();
const { theme } = useContext(ThemeContext);
const { getIcon } = useIcons();
const FieldIcon = getIcon(fieldMetadataItem.icon);
@@ -1,4 +1,4 @@
import styled from '@emotion/styled';
import { styled } from '@linaria/react';
import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadataItems';
import { type FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
@@ -6,6 +6,7 @@ import { SettingsDataModelRelationFieldPreview } from '@/settings/data-model/fie
import { SettingsDataModelObjectPreview } from '@/settings/data-model/objects/components/SettingsDataModelObjectSummary';
import { isDefined } from 'twenty-shared/utils';
import { Card, CardContent } from 'twenty-ui/layout';
import { themeCssVariables } from 'twenty-ui/theme-constants';
export type SettingsDataModelRelationFieldPreviewSubWidgetProps = {
fieldMetadataItem: Pick<
@@ -21,13 +22,13 @@ export type SettingsDataModelRelationFieldPreviewSubWidgetProps = {
};
const StyledCard = styled(Card)`
border-radius: ${({ theme }) => theme.border.radius.md};
color: ${({ theme }) => theme.font.color.primary};
border-radius: ${themeCssVariables.border.radius.md};
color: ${themeCssVariables.font.color.primary};
margin: auto;
`;
const StyledCardContent = styled(CardContent)`
padding: ${({ theme }) => theme.spacing(2)};
padding: ${themeCssVariables.spacing[2]};
`;
export const SettingsDataModelRelationFieldPreviewSubWidget = ({
@@ -2,7 +2,7 @@ import { SettingsDataModelOverviewEffect } from '@/settings/data-model/graph-ove
import { SettingsDataModelOverviewObject } from '@/settings/data-model/graph-overview/components/SettingsDataModelOverviewObject';
import { SettingsDataModelOverviewRelationMarkers } from '@/settings/data-model/graph-overview/components/SettingsDataModelOverviewRelationMarkers';
import { calculateHandlePosition } from '@/settings/data-model/graph-overview/utils/calculateHandlePosition';
import styled from '@emotion/styled';
import { styled } from '@linaria/react';
import {
Background,
type Edge,
@@ -31,6 +31,7 @@ import {
IconX,
} from 'twenty-ui/display';
import { Button, IconButtonGroup } from 'twenty-ui/input';
import { themeCssVariables } from 'twenty-ui/theme-constants';
const nodeTypes: NodeTypes = {
object: SettingsDataModelOverviewObject,
@@ -63,8 +64,8 @@ const StyledContainer = styled.div`
const StyledCloseButton = styled.div`
position: absolute;
top: ${({ theme }) => theme.spacing(3)};
left: ${({ theme }) => theme.spacing(3)};
top: ${themeCssVariables.spacing[3]};
left: ${themeCssVariables.spacing[3]};
z-index: 5;
`;
@@ -1,9 +1,9 @@
import { useTheme } from '@emotion/react';
import { type Edge, type Node } from '@xyflow/react';
import { useEffect } from 'react';
import { useContext, useEffect } from 'react';
import { useFilteredObjectMetadataItems } from '@/object-metadata/hooks/useFilteredObjectMetadataItems';
import { isDefined } from 'twenty-shared/utils';
import { ThemeContext } from 'twenty-ui/theme';
import { isUndefinedOrNull } from '~/utils/isUndefinedOrNull';
type SettingsDataModelOverviewEffectProps = {
@@ -15,7 +15,7 @@ export const SettingsDataModelOverviewEffect = ({
setEdges,
setNodes,
}: SettingsDataModelOverviewEffectProps) => {
const theme = useTheme();
const { theme } = useContext(ThemeContext);
const { activeNonSystemObjectMetadataItems: items } =
useFilteredObjectMetadataItems();
@@ -1,10 +1,12 @@
import { useTheme } from '@emotion/react';
import styled from '@emotion/styled';
import { styled } from '@linaria/react';
import { Handle, Position } from '@xyflow/react';
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue';
import { type FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
import { useContext } from 'react';
import { useIcons } from 'twenty-ui/display';
import { ThemeContext } from 'twenty-ui/theme';
import { themeCssVariables } from 'twenty-ui/theme-constants';
import { RelationType } from '~/generated-metadata/graphql';
type ObjectFieldRowProps = {
@@ -14,20 +16,20 @@ type ObjectFieldRowProps = {
const StyledRow = styled.div`
align-items: center;
display: flex;
gap: ${({ theme }) => theme.spacing(2)};
gap: ${themeCssVariables.spacing[2]};
position: relative;
width: 100%;
padding: 0 ${({ theme }) => theme.spacing(2)};
padding: 0 ${themeCssVariables.spacing[2]};
`;
const StyledFieldName = styled.div`
color: ${({ theme }) => theme.font.color.primary};
color: ${themeCssVariables.font.color.primary};
`;
export const ObjectFieldRow = ({ field }: ObjectFieldRowProps) => {
const objectMetadataItems = useAtomStateValue(objectMetadataItemsState);
const { getIcon } = useIcons();
const theme = useTheme();
const { theme } = useContext(ThemeContext);
const relatedObjectId = field.relation?.targetObjectMetadata.id;
@@ -1,8 +1,10 @@
import { useTheme } from '@emotion/react';
import styled from '@emotion/styled';
import { useContext } from 'react';
import { styled } from '@linaria/react';
import { type FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
import { useIcons } from 'twenty-ui/display';
import { ThemeContext } from 'twenty-ui/theme';
import { themeCssVariables } from 'twenty-ui/theme-constants';
type ObjectFieldRowWithoutRelationProps = {
field: FieldMetadataItem;
@@ -11,21 +13,21 @@ type ObjectFieldRowWithoutRelationProps = {
const StyledRow = styled.div`
align-items: center;
display: flex;
gap: ${({ theme }) => theme.spacing(2)};
gap: ${themeCssVariables.spacing[2]};
position: relative;
width: 100%;
padding: 0 ${({ theme }) => theme.spacing(2)};
padding: 0 ${themeCssVariables.spacing[2]};
`;
const StyledFieldName = styled.div`
color: ${({ theme }) => theme.font.color.primary};
color: ${themeCssVariables.font.color.primary};
`;
export const ObjectFieldRowWithoutRelation = ({
field,
}: ObjectFieldRowWithoutRelationProps) => {
const { getIcon } = useIcons();
const theme = useTheme();
const { theme } = useContext(ThemeContext);
const Icon = getIcon(field?.icon);
@@ -1,5 +1,5 @@
import { useTheme } from '@emotion/react';
import styled from '@emotion/styled';
import { useContext, useState } from 'react';
import { styled } from '@linaria/react';
import { type Node, type NodeProps } from '@xyflow/react';
import { Link } from 'react-router-dom';
@@ -11,26 +11,27 @@ import { FieldMetadataType } from '~/generated-metadata/graphql';
import { ObjectFieldRowWithoutRelation } from '@/settings/data-model/graph-overview/components/SettingsDataModelOverviewFieldWithoutRelation';
import '@xyflow/react/dist/style.css';
import { useState } from 'react';
import { SettingsPath } from 'twenty-shared/types';
import { getSettingsPath } from 'twenty-shared/utils';
import { IconChevronDown, IconChevronUp, useIcons } from 'twenty-ui/display';
import { SettingsItemTypeTag } from '@/settings/components/SettingsItemTypeTag';
import { ThemeContext } from 'twenty-ui/theme';
import { themeCssVariables } from 'twenty-ui/theme-constants';
type SettingsDataModelOverviewObjectNode = Node<ObjectMetadataItem, 'object'>;
type SettingsDataModelOverviewObjectProps =
NodeProps<SettingsDataModelOverviewObjectNode>;
const StyledNode = styled.div`
background-color: ${({ theme }) => theme.background.secondary};
border-radius: ${({ theme }) => theme.border.radius.md};
background-color: ${themeCssVariables.background.secondary};
border-radius: ${themeCssVariables.border.radius.md};
display: flex;
flex-direction: column;
width: 220px;
padding: ${({ theme }) => theme.spacing(2)};
gap: ${({ theme }) => theme.spacing(2)};
border: 1px solid ${({ theme }) => theme.border.color.medium};
box-shadow: ${({ theme }) => theme.boxShadow.light};
padding: ${themeCssVariables.spacing[2]};
gap: ${themeCssVariables.spacing[2]};
border: 1px solid ${themeCssVariables.border.color.medium};
box-shadow: ${themeCssVariables.boxShadow.light};
`;
const StyledHeader = styled.div`
@@ -43,29 +44,28 @@ const StyledObjectName = styled.div`
border: 0;
border-radius: 4px 4px 0 0;
display: flex;
font-weight: ${({ theme }) => theme.font.weight.medium};
gap: ${({ theme }) => theme.spacing(1)};
font-weight: ${themeCssVariables.font.weight.medium};
gap: ${themeCssVariables.spacing[1]};
position: relative;
text-align: center;
`;
const StyledInnerCard = styled.div`
border: 1px solid ${({ theme }) => theme.border.color.light};
background-color: ${({ theme }) => theme.background.primary};
border-radius: ${({ theme }) => theme.border.radius.sm};
padding: ${({ theme }) => theme.spacing(2)} 0
${({ theme }) => theme.spacing(2)} 0;
border: 1px solid ${themeCssVariables.border.color.light};
background-color: ${themeCssVariables.background.primary};
border-radius: ${themeCssVariables.border.radius.sm};
padding: ${themeCssVariables.spacing[2]} 0 ${themeCssVariables.spacing[2]} 0;
display: flex;
flex-flow: column nowrap;
gap: ${({ theme }) => theme.spacing(0.5)};
color: ${({ theme }) => theme.font.color.tertiary};
gap: ${themeCssVariables.spacing['0.5']};
color: ${themeCssVariables.font.color.tertiary};
`;
const StyledCardRow = styled.div`
align-items: center;
display: flex;
height: 24px;
gap: ${({ theme }) => theme.spacing(1)};
gap: ${themeCssVariables.spacing[1]};
`;
const StyledCardRowOther = styled.div`
@@ -73,36 +73,36 @@ const StyledCardRowOther = styled.div`
cursor: pointer;
display: flex;
height: 24px;
padding: 0 ${({ theme }) => theme.spacing(2)};
gap: ${({ theme }) => theme.spacing(2)};
padding: 0 ${themeCssVariables.spacing[2]};
gap: ${themeCssVariables.spacing[2]};
&:hover {
background-color: ${({ theme }) => theme.background.tertiary};
background-color: ${themeCssVariables.background.tertiary};
}
`;
const StyledCardRowText = styled.div``;
const StyledObjectInstanceCount = styled.div`
color: ${({ theme }) => theme.font.color.tertiary};
color: ${themeCssVariables.font.color.tertiary};
`;
const StyledObjectLink = styled(Link)`
align-items: center;
display: flex;
gap: ${({ theme }) => theme.spacing(1)};
gap: ${themeCssVariables.spacing[1]};
text-decoration: none;
color: ${({ theme }) => theme.font.color.primary};
color: ${themeCssVariables.font.color.primary};
&:hover {
color: ${({ theme }) => theme.font.color.secondary};
color: ${themeCssVariables.font.color.secondary};
}
`;
export const SettingsDataModelOverviewObject = ({
data: objectMetadataItem,
}: SettingsDataModelOverviewObjectProps) => {
const theme = useTheme();
const { theme } = useContext(ThemeContext);
const { getIcon } = useIcons();
const [otherFieldsExpanded, setOtherFieldsExpanded] = useState(false);
@@ -1,7 +1,8 @@
import { useTheme } from '@emotion/react';
import { useContext } from 'react';
import { ThemeContext } from 'twenty-ui/theme';
export const SettingsDataModelOverviewRelationMarkers = () => {
const theme = useTheme();
const { theme } = useContext(ThemeContext);
return (
<svg style={{ position: 'absolute', top: 0, left: 0 }}>
<defs>
@@ -1,11 +1,13 @@
import { useTheme } from '@emotion/react';
import styled from '@emotion/styled';
import { useContext } from 'react';
import { styled } from '@linaria/react';
import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { TableCell } from '@/ui/layout/table/components/TableCell';
import { TableRow } from '@/ui/layout/table/components/TableRow';
import { Checkbox } from 'twenty-ui/input';
import { useIcons } from 'twenty-ui/display';
import { ThemeContext } from 'twenty-ui/theme';
import { themeCssVariables } from 'twenty-ui/theme-constants';
type SettingsAvailableStandardObjectItemTableRowProps = {
isSelected?: boolean;
@@ -20,12 +22,12 @@ export const StyledAvailableStandardObjectTableRow = styled(TableRow)`
const StyledCheckboxTableCell = styled(TableCell)`
justify-content: center;
padding: 0;
padding-left: ${({ theme }) => theme.spacing(1)};
padding-left: ${themeCssVariables.spacing[1]};
`;
const StyledNameTableCell = styled(TableCell)`
color: ${({ theme }) => theme.font.color.primary};
gap: ${({ theme }) => theme.spacing(2)};
color: ${themeCssVariables.font.color.primary};
gap: ${themeCssVariables.spacing[2]};
`;
const StyledDescription = styled.div`
@@ -39,7 +41,7 @@ export const SettingsAvailableStandardObjectItemTableRow = ({
objectItem,
onClick,
}: SettingsAvailableStandardObjectItemTableRowProps) => {
const theme = useTheme();
const { theme } = useContext(ThemeContext);
const { getIcon } = useIcons();
const Icon = getIcon(objectItem.icon);
@@ -1,10 +1,12 @@
import { css, useTheme } from '@emotion/react';
import styled from '@emotion/styled';
import { useContext } from 'react';
import { styled } from '@linaria/react';
import { Link } from 'react-router-dom';
import { type SettingsFieldType } from '@/settings/data-model/types/SettingsFieldType';
import { getSettingsFieldTypeConfig } from '@/settings/data-model/utils/getSettingsFieldTypeConfig';
import { type IconComponent, IconTwentyStar } from 'twenty-ui/display';
import { ThemeContext } from 'twenty-ui/theme';
import { themeCssVariables } from 'twenty-ui/theme-constants';
type SettingsObjectFieldDataTypeProps = {
to?: string;
@@ -21,28 +23,26 @@ const StyledDataType = styled.div<{
}>`
align-items: center;
border: 1px solid transparent;
border-radius: ${({ theme }) => theme.border.radius.sm};
border-radius: ${themeCssVariables.border.radius.sm};
color: ${({ to }) =>
to
? themeCssVariables.font.color.secondary
: themeCssVariables.font.color.primary};
cursor: ${({ to }) => (to ? 'pointer' : 'default')};
display: flex;
font-size: ${({ theme }) => theme.font.size.sm};
gap: ${({ theme }) => theme.spacing(2)};
font-size: ${themeCssVariables.font.size.sm};
gap: ${themeCssVariables.spacing[2]};
height: 20px;
overflow: hidden;
text-decoration: none;
text-decoration: ${({ to }) => (to ? 'underline' : 'none')};
user-select: none;
${({ to, theme }) =>
to
? css`
cursor: pointer;
color: ${theme.font.color.secondary};
text-decoration: underline;
&:hover {
color: ${theme.font.color.primary};
}
`
: ''}
&:hover {
color: ${({ to }) =>
to
? themeCssVariables.font.color.primary
: themeCssVariables.font.color.primary};
}
`;
const StyledLabelContainer = styled.div`
@@ -51,8 +51,13 @@ const StyledLabelContainer = styled.div`
white-space: nowrap;
`;
const StyledIconWrapper = styled.div`
display: flex;
flex: 1 0 auto;
`;
const StyledSpan = styled.span`
color: ${({ theme }) => theme.font.color.extraLight};
color: ${themeCssVariables.font.color.extraLight};
`;
export const SettingsObjectFieldDataType = ({
to,
@@ -62,17 +67,13 @@ export const SettingsObjectFieldDataType = ({
labelDetail,
onClick,
}: SettingsObjectFieldDataTypeProps) => {
const theme = useTheme();
const { theme } = useContext(ThemeContext);
const fieldTypeConfig = getSettingsFieldTypeConfig(value);
const Icon: IconComponent =
IconFromProps ?? fieldTypeConfig?.Icon ?? IconTwentyStar;
const label = labelFromProps ?? fieldTypeConfig?.label;
const StyledIcon = styled(Icon)`
flex: 1 0 auto;
`;
return (
<StyledDataType
as={to ? Link : 'div'}
@@ -80,7 +81,9 @@ export const SettingsObjectFieldDataType = ({
value={value}
onClick={onClick}
>
<StyledIcon size={theme.icon.size.sm} />
<StyledIconWrapper>
<Icon size={theme.icon.size.sm} />
</StyledIconWrapper>
<StyledLabelContainer>
{label} <StyledSpan>{labelDetail && `· ${labelDetail}`}</StyledSpan>
</StyledLabelContainer>
@@ -10,11 +10,10 @@ import { settingsObjectFieldsFamilyState } from '@/settings/data-model/object-de
import { isFieldTypeSupportedInSettings } from '@/settings/data-model/utils/isFieldTypeSupportedInSettings';
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 { useContext, useMemo } from 'react';
import { styled } from '@linaria/react';
import { useLingui } from '@lingui/react/macro';
import { useSetAtomFamilyState } from '@/ui/utilities/state/jotai/hooks/useSetAtomFamilyState';
import { useMemo } from 'react';
import { FieldMetadataType, SettingsPath } from 'twenty-shared/types';
import { getSettingsPath, isDefined } from 'twenty-shared/utils';
import {
@@ -25,6 +24,8 @@ import {
} from 'twenty-ui/display';
import { LightIconButton } from 'twenty-ui/input';
import { UndecoratedLink } from 'twenty-ui/navigation';
import { ThemeContext } from 'twenty-ui/theme';
import { themeCssVariables } from 'twenty-ui/theme-constants';
import { RelationType } from '~/generated-metadata/graphql';
import { useNavigateSettings } from '~/hooks/useNavigateSettings';
import { type SettingsObjectDetailTableItem } from '~/pages/settings/data-model/types/SettingsObjectDetailTableItem';
@@ -41,8 +42,8 @@ export const StyledObjectFieldTableRow = styled(TableRow)`
`;
const StyledNameTableCell = styled(TableCell)`
color: ${({ theme }) => theme.font.color.primary};
gap: ${({ theme }) => theme.spacing(2)};
color: ${themeCssVariables.font.color.primary};
gap: ${themeCssVariables.spacing[2]};
`;
const StyledNameContainer = styled.div`
@@ -50,7 +51,7 @@ const StyledNameContainer = styled.div`
align-items: center;
flex: 1;
min-width: 0;
gap: ${({ theme }) => theme.spacing(1)};
gap: ${themeCssVariables.spacing[1]};
`;
const StyledNameLabel = styled.div`
@@ -60,8 +61,8 @@ const StyledNameLabel = styled.div`
`;
const StyledInactiveLabel = styled.span`
color: ${({ theme }) => theme.font.color.extraLight};
font-size: ${({ theme }) => theme.font.size.sm};
color: ${themeCssVariables.font.color.extraLight};
font-size: ${themeCssVariables.font.size.sm};
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
@@ -70,17 +71,17 @@ const StyledInactiveLabel = styled.span`
&::before {
content: '·';
margin-right: ${({ theme }) => theme.spacing(1)};
margin-right: ${themeCssVariables.spacing[1]};
}
`;
const StyledIconTableCell = styled(TableCell)`
justify-content: center;
padding-right: ${({ theme }) => theme.spacing(1)};
padding-right: ${themeCssVariables.spacing[1]};
`;
const StyledIconChevronRight = styled(IconChevronRight)`
color: ${({ theme }) => theme.font.color.tertiary};
color: ${themeCssVariables.font.color.tertiary};
`;
export const SettingsObjectFieldItemTableRow = ({
@@ -98,7 +99,7 @@ export const SettingsObjectFieldItemTableRow = ({
const navigate = useNavigateSettings();
const theme = useTheme();
const { theme } = useContext(ThemeContext);
const { getIcon } = useIcons();
const Icon = getIcon(fieldMetadataItem.icon);
@@ -1,7 +1,6 @@
import { useTheme } from '@emotion/react';
import styled from '@emotion/styled';
import { useContext, type ReactNode } from 'react';
import { styled } from '@linaria/react';
import { useLingui } from '@lingui/react/macro';
import { type ReactNode } from 'react';
import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { isHiddenSystemField } from '@/object-metadata/utils/isHiddenSystemField';
@@ -13,6 +12,8 @@ import {
} from '@/settings/data-model/object-details/components/SettingsObjectItemTableRowStyledComponents';
import { TableCell } from '@/ui/layout/table/components/TableCell';
import { useIcons } from 'twenty-ui/display';
import { ThemeContext } from 'twenty-ui/theme';
import { themeCssVariables } from 'twenty-ui/theme-constants';
export type SettingsObjectMetadataItemTableRowProps = {
action: ReactNode;
@@ -26,7 +27,7 @@ const StyledNameContainer = styled.div`
align-items: center;
flex: 1;
min-width: 0;
gap: ${({ theme }) => theme.spacing(1)};
gap: ${themeCssVariables.spacing[1]};
`;
const StyledNameLabel = styled.div`
@@ -36,8 +37,8 @@ const StyledNameLabel = styled.div`
`;
const StyledInactiveLabel = styled.span`
color: ${({ theme }) => theme.font.color.extraLight};
font-size: ${({ theme }) => theme.font.size.sm};
color: ${themeCssVariables.font.color.extraLight};
font-size: ${themeCssVariables.font.size.sm};
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
@@ -46,7 +47,7 @@ const StyledInactiveLabel = styled.span`
&::before {
content: '·';
margin-right: ${({ theme }) => theme.spacing(1)};
margin-right: ${themeCssVariables.spacing[1]};
}
`;
@@ -57,7 +58,7 @@ export const SettingsObjectMetadataItemTableRow = ({
totalObjectCount,
}: SettingsObjectMetadataItemTableRowProps) => {
const { t } = useLingui();
const theme = useTheme();
const { theme } = useContext(ThemeContext);
const { getIcon } = useIcons();
const Icon = getIcon(objectMetadataItem.icon);
@@ -1,7 +1,8 @@
import styled from '@emotion/styled';
import { styled } from '@linaria/react';
import { TableCell } from '@/ui/layout/table/components/TableCell';
import { TableRow } from '@/ui/layout/table/components/TableRow';
import { themeCssVariables } from 'twenty-ui/theme-constants';
export const SETTINGS_OBJECT_TABLE_COLUMN_WIDTH = '98.7px';
@@ -10,11 +11,11 @@ export const StyledObjectTableRow = styled(TableRow)`
`;
export const StyledNameTableCell = styled(TableCell)`
color: ${({ theme }) => theme.font.color.primary};
gap: ${({ theme }) => theme.spacing(2)};
color: ${themeCssVariables.font.color.primary};
gap: ${themeCssVariables.spacing[2]};
`;
export const StyledActionTableCell = styled(TableCell)`
justify-content: center;
padding-right: ${({ theme }) => theme.spacing(1)};
padding-right: ${themeCssVariables.spacing[1]};
`;
@@ -9,15 +9,16 @@ import { RELATION_TYPES } from '@/settings/data-model/constants/RelationTypes';
import { SettingsObjectFieldInactiveActionDropdown } from '@/settings/data-model/object-details/components/SettingsObjectFieldDisabledActionDropdown';
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 { useLingui } from '@lingui/react/macro';
import { useMemo } from 'react';
import { useContext, useMemo } from 'react';
import { Link } from 'react-router-dom';
import { FieldMetadataType, SettingsPath } from 'twenty-shared/types';
import { getSettingsPath, isDefined } from 'twenty-shared/utils';
import { IconChevronRight, useIcons } from 'twenty-ui/display';
import { UndecoratedLink } from 'twenty-ui/navigation';
import { ThemeContext } from 'twenty-ui/theme';
import { themeCssVariables } from 'twenty-ui/theme-constants';
import { useNavigateSettings } from '~/hooks/useNavigateSettings';
type SettingsObjectRelationItemTableRowProps = {
@@ -30,8 +31,8 @@ export const StyledObjectRelationTableRow = styled(TableRow)`
`;
const StyledNameTableCell = styled(TableCell)`
color: ${({ theme }) => theme.font.color.primary};
gap: ${({ theme }) => theme.spacing(2)};
color: ${themeCssVariables.font.color.primary};
gap: ${themeCssVariables.spacing[2]};
`;
const StyledNameContainer = styled.div`
@@ -39,7 +40,7 @@ const StyledNameContainer = styled.div`
align-items: center;
flex: 1;
min-width: 0;
gap: ${({ theme }) => theme.spacing(1)};
gap: ${themeCssVariables.spacing[1]};
`;
const StyledNameLabel = styled.div`
@@ -49,8 +50,8 @@ const StyledNameLabel = styled.div`
`;
const StyledInactiveLabel = styled.span`
color: ${({ theme }) => theme.font.color.extraLight};
font-size: ${({ theme }) => theme.font.size.sm};
color: ${themeCssVariables.font.color.extraLight};
font-size: ${themeCssVariables.font.size.sm};
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
@@ -59,38 +60,38 @@ const StyledInactiveLabel = styled.span`
&::before {
content: '·';
margin-right: ${({ theme }) => theme.spacing(1)};
margin-right: ${themeCssVariables.spacing[1]};
}
`;
const StyledIconTableCell = styled(TableCell)`
justify-content: center;
padding-right: ${({ theme }) => theme.spacing(1)};
padding-right: ${themeCssVariables.spacing[1]};
`;
const StyledIconChevronRight = styled(IconChevronRight)`
color: ${({ theme }) => theme.font.color.tertiary};
color: ${themeCssVariables.font.color.tertiary};
`;
const StyledRelationType = styled.div`
align-items: center;
display: flex;
font-size: ${({ theme }) => theme.font.size.sm};
gap: ${({ theme }) => theme.spacing(1)};
font-size: ${themeCssVariables.font.size.sm};
gap: ${themeCssVariables.spacing[1]};
`;
const StyledLink = styled(Link)`
color: ${({ theme }) => theme.font.color.primary};
color: ${themeCssVariables.font.color.primary};
text-decoration: underline;
text-decoration-color: ${({ theme }) => theme.border.color.strong};
text-decoration-color: ${themeCssVariables.border.color.strong};
text-underline-offset: 2px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
&:hover {
color: ${({ theme }) => theme.color.blue};
text-decoration-color: ${({ theme }) => theme.color.blue};
color: ${themeCssVariables.color.blue};
text-decoration-color: ${themeCssVariables.color.blue};
}
`;
@@ -100,7 +101,7 @@ export const SettingsObjectRelationItemTableRow = ({
}: SettingsObjectRelationItemTableRowProps) => {
const { t } = useLingui();
const navigate = useNavigateSettings();
const theme = useTheme();
const { theme } = useContext(ThemeContext);
const { getIcon } = useIcons();
const Icon = getIcon(fieldMetadataItem.icon);
@@ -12,7 +12,7 @@ import { useSortedArray } from '@/ui/layout/table/hooks/useSortedArray';
import { type TableMetadata } from '@/ui/layout/table/types/TableMetadata';
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 { msg } from '@lingui/core/macro';
import { useLingui } from '@lingui/react/macro';
import { useMemo, useState } from 'react';
@@ -25,6 +25,7 @@ import {
} from 'twenty-ui/display';
import { Button } from 'twenty-ui/input';
import { MenuItemToggle } from 'twenty-ui/navigation';
import { themeCssVariables } from 'twenty-ui/theme-constants';
import { normalizeSearchText } from '~/utils/normalizeSearchText';
import {
SettingsObjectRelationItemTableRow,
@@ -33,8 +34,8 @@ import {
const StyledSearchAndFilterContainer = styled.div`
display: flex;
gap: ${({ theme }) => theme.spacing(2)};
padding-bottom: ${({ theme }) => theme.spacing(2)};
gap: ${themeCssVariables.spacing[2]};
padding-bottom: ${themeCssVariables.spacing[2]};
width: 100%;
`;
@@ -2,7 +2,7 @@ import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataI
import { isHiddenSystemField } from '@/object-metadata/utils/isHiddenSystemField';
import { isObjectMetadataReadOnly } from '@/object-record/read-only/utils/isObjectMetadataReadOnly';
import { SettingsObjectRelationsTable } from '@/settings/data-model/object-details/components/SettingsObjectRelationsTable';
import styled from '@emotion/styled';
import { styled } from '@linaria/react';
import { useLingui } from '@lingui/react/macro';
import { FieldMetadataType, SettingsPath } from 'twenty-shared/types';
import { getSettingsPath } from 'twenty-shared/utils';
@@ -10,12 +10,13 @@ import { H2Title, IconPlus } from 'twenty-ui/display';
import { Button } from 'twenty-ui/input';
import { Section } from 'twenty-ui/layout';
import { UndecoratedLink } from 'twenty-ui/navigation';
import { themeCssVariables } from 'twenty-ui/theme-constants';
import { SettingsObjectFieldTable } from '~/pages/settings/data-model/SettingsObjectFieldTable';
const StyledButtonContainer = styled.div`
display: flex;
justify-content: flex-end;
padding-top: ${({ theme }) => theme.spacing(2)};
padding-top: ${themeCssVariables.spacing[2]};
`;
type ObjectFieldsProps = {
@@ -8,12 +8,13 @@ import { SettingsDataModelObjectSettingsFormCard } from '@/settings/data-model/o
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
import { ConfirmationModal } from '@/ui/layout/modal/components/ConfirmationModal';
import { useModal } from '@/ui/layout/modal/hooks/useModal';
import styled from '@emotion/styled';
import { styled } from '@linaria/react';
import { useLingui } from '@lingui/react/macro';
import { SettingsPath } from 'twenty-shared/types';
import { H2Title, IconArchive, IconTrash } from 'twenty-ui/display';
import { Button } from 'twenty-ui/input';
import { Section } from 'twenty-ui/layout';
import { themeCssVariables } from 'twenty-ui/theme-constants';
import { useNavigateSettings } from '~/hooks/useNavigateSettings';
type ObjectSettingsProps = {
@@ -25,7 +26,7 @@ type ObjectSettingsProps = {
const StyledContentContainer = styled.div`
display: flex;
flex-direction: column;
gap: ${({ theme }) => theme.spacing(8)};
gap: ${themeCssVariables.spacing[8]};
`;
const StyledFormSection = styled(Section)`
@@ -34,7 +35,7 @@ const StyledFormSection = styled(Section)`
const StyledDangerButtonsContainer = styled.div`
display: flex;
gap: ${({ theme }) => theme.spacing(2)};
gap: ${themeCssVariables.spacing[2]};
`;
const DELETE_OBJECT_MODAL_ID = 'delete-object-confirmation-modal';
@@ -1,6 +1,6 @@
import { t } from '@lingui/core/macro';
import { useTheme } from '@emotion/react';
import styled from '@emotion/styled';
import { useContext } from 'react';
import { styled } from '@linaria/react';
import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import {
@@ -9,6 +9,8 @@ import {
useIcons,
} from 'twenty-ui/display';
import { SettingsItemTypeTag } from '@/settings/components/SettingsItemTypeTag';
import { ThemeContext } from 'twenty-ui/theme';
import { themeCssVariables } from 'twenty-ui/theme-constants';
export type SettingsDataModelObjectPreviewProps = {
className?: string;
@@ -27,17 +29,17 @@ const StyledObjectPreview = styled.div`
const StyledObjectName = styled.div`
display: flex;
gap: ${({ theme }) => theme.spacing(2)};
gap: ${themeCssVariables.spacing[2]};
max-width: 60%;
`;
const StyledOverflowingTextWithTooltip = styled.div`
color: ${({ theme }) => theme.font.color.tertiary};
color: ${themeCssVariables.font.color.tertiary};
`;
const StyledNumber = styled.div`
color: ${({ theme }) => theme.font.color.tertiary};
padding-right: ${({ theme }) => theme.spacing(2)};
color: ${themeCssVariables.font.color.tertiary};
padding-right: ${themeCssVariables.spacing[2]};
`;
const StyledIconContainer = styled.div`
@@ -46,10 +48,10 @@ const StyledIconContainer = styled.div`
const StyledSeparator = styled.div`
align-self: stretch;
background: ${({ theme }) => theme.background.quaternary};
background: ${themeCssVariables.background.quaternary};
height: 1px;
margin-bottom: ${({ theme }) => theme.spacing(2)};
margin-top: ${({ theme }) => theme.spacing(2)};
margin-bottom: ${themeCssVariables.spacing[2]};
margin-top: ${themeCssVariables.spacing[2]};
`;
type SettingsDataModelObjectPreviewItemProps = {
@@ -66,7 +68,7 @@ const SettingsDataModelObjectPreviewItem = ({
pluralizeLabel = true,
index,
}: SettingsDataModelObjectPreviewItemProps) => {
const theme = useTheme();
const { theme } = useContext(ThemeContext);
const { getIcon } = useIcons();
const ObjectIcon = getIcon(objectMetadataItem.icon);
@@ -100,7 +102,7 @@ const SettingsDataModelObjectPreviewOtherObjects = ({
}: {
selected: number;
}) => {
const theme = useTheme();
const { theme } = useContext(ThemeContext);
return (
<>
<StyledSeparator />
@@ -1,6 +1,7 @@
import styled from '@emotion/styled';
import { styled } from '@linaria/react';
import { useLingui } from '@lingui/react/macro';
import { useContext } from 'react';
import { SettingsPath } from 'twenty-shared/types';
import { getSettingsPath } from 'twenty-shared/utils';
import { IconEye } from 'twenty-ui/display';
@@ -9,30 +10,36 @@ import { Card } from 'twenty-ui/layout';
import DarkCoverImage from '@/settings/data-model/assets/cover-dark.png';
import LightCoverImage from '@/settings/data-model/assets/cover-light.png';
import { ThemeContext } from 'twenty-ui/theme';
import { themeCssVariables } from 'twenty-ui/theme-constants';
const StyledCoverImageContainer = styled(Card)`
align-items: center;
background-image: ${({ theme }) =>
theme.name === 'light'
? `url('${LightCoverImage.toString()}')`
: `url('${DarkCoverImage.toString()}')`};
background-size: cover;
border-radius: ${({ theme }) => theme.border.radius.md};
border-radius: ${themeCssVariables.border.radius.md};
box-sizing: border-box;
display: flex;
min-height: 153px;
justify-content: center;
position: relative;
margin-bottom: ${({ theme }) => theme.spacing(8)};
margin-bottom: ${themeCssVariables.spacing[8]};
`;
const StyledButtonContainer = styled.div`
padding-top: ${({ theme }) => theme.spacing(5)};
padding-top: ${themeCssVariables.spacing[5]};
`;
export const SettingsObjectCoverImage = () => {
const { t } = useLingui();
const { theme } = useContext(ThemeContext);
return (
<StyledCoverImageContainer>
<StyledCoverImageContainer
style={{
backgroundImage:
theme.name === 'light'
? `url('${LightCoverImage.toString()}')`
: `url('${DarkCoverImage.toString()}')`,
}}
>
<StyledButtonContainer>
<FloatingButton
Icon={IconEye}
@@ -6,8 +6,8 @@ import { type SettingsDataModelObjectAboutFormValues } from '@/settings/data-mod
import { IconPicker } from '@/ui/input/components/IconPicker';
import { SettingsTextInput } from '@/ui/input/components/SettingsTextInput';
import { TextArea } from '@/ui/input/components/TextArea';
import { useTheme } from '@emotion/react';
import styled from '@emotion/styled';
import { useContext } from 'react';
import { styled } from '@linaria/react';
import { useLingui } from '@lingui/react/macro';
import { plural } from 'pluralize';
import { Controller, useFormContext } from 'react-hook-form';
@@ -22,6 +22,8 @@ import {
} from 'twenty-ui/display';
import { Button } from 'twenty-ui/input';
import { Card } from 'twenty-ui/layout';
import { ThemeContext } from 'twenty-ui/theme';
import { themeCssVariables } from 'twenty-ui/theme-constants';
import { type StringKeyOf } from 'type-fest';
import { useNavigateSettings } from '~/hooks/useNavigateSettings';
import { computeMetadataNameFromLabel } from '~/pages/settings/data-model/utils/computeMetadataNameFromLabel';
@@ -35,8 +37,8 @@ type SettingsDataModelObjectAboutFormProps = {
const StyledInputsContainer = styled.div`
display: flex;
gap: ${({ theme }) => theme.spacing(2)};
margin-bottom: ${({ theme }) => theme.spacing(2)};
gap: ${themeCssVariables.spacing[2]};
margin-bottom: ${themeCssVariables.spacing[2]};
width: 100%;
`;
@@ -48,60 +50,60 @@ const StyledInputContainer = styled.div`
const StyledAdvancedSettingsSectionInputWrapper = styled.div`
display: flex;
flex-direction: column;
gap: ${({ theme }) => theme.spacing(4)};
gap: ${themeCssVariables.spacing[4]};
width: 100%;
flex: 1;
`;
const StyledAdvancedSettingsOuterContainer = styled.div`
padding-top: ${({ theme }) => theme.spacing(4)};
padding-top: ${themeCssVariables.spacing[4]};
`;
const StyledAdvancedSettingsContainer = styled.div`
display: flex;
gap: ${({ theme }) => theme.spacing(2)};
gap: ${themeCssVariables.spacing[2]};
position: relative;
width: 100%;
`;
const StyledLabel = styled.span`
color: ${({ theme }) => theme.font.color.light};
font-size: ${({ theme }) => theme.font.size.xs};
font-weight: ${({ theme }) => theme.font.weight.semiBold};
margin-bottom: ${({ theme }) => theme.spacing(1)};
color: ${themeCssVariables.font.color.light};
font-size: ${themeCssVariables.font.size.xs};
font-weight: ${themeCssVariables.font.weight.semiBold};
margin-bottom: ${themeCssVariables.spacing[1]};
`;
const StyledConflictBanner = styled.div`
align-items: center;
background-color: ${({ theme }) => theme.accent.secondary};
border-radius: ${({ theme }) => theme.border.radius.md};
background-color: ${themeCssVariables.accent.secondary};
border-radius: ${themeCssVariables.border.radius.md};
box-sizing: border-box;
display: flex;
gap: ${({ theme }) => theme.spacing(2)};
margin-bottom: ${({ theme }) => theme.spacing(2)};
padding: ${({ theme }) => theme.spacing(2)};
gap: ${themeCssVariables.spacing[2]};
margin-bottom: ${themeCssVariables.spacing[2]};
padding: ${themeCssVariables.spacing[2]};
`;
const StyledBannerContent = styled.div`
align-items: center;
display: flex;
gap: ${({ theme }) => theme.spacing(2)};
gap: ${themeCssVariables.spacing[2]};
flex: 1;
`;
const StyledBannerText = styled.span`
color: ${({ theme }) => theme.color.blue};
color: ${themeCssVariables.color.blue};
flex: 1;
`;
const StyledConflictButton = styled(Button)`
border-color: ${({ theme }) => theme.color.blue};
color: ${({ theme }) => theme.color.blue};
border-color: ${themeCssVariables.color.blue};
color: ${themeCssVariables.color.blue};
&:hover {
background: ${({ theme }) => theme.accent.secondary};
background: ${themeCssVariables.accent.secondary};
}
&:focus-visible {
box-shadow: 0 0 0 3px ${({ theme }) => theme.accent.tertiary};
box-shadow: 0 0 0 3px ${themeCssVariables.accent.tertiary};
}
`;
@@ -116,7 +118,7 @@ export const SettingsDataModelObjectAboutForm = ({
const { control, watch, setValue } =
useFormContext<SettingsDataModelObjectAboutFormValues>();
const { t } = useLingui();
const theme = useTheme();
const { theme } = useContext(ThemeContext);
const navigateSettings = useNavigateSettings();
const isLabelSyncedWithName = watch('isLabelSyncedWithName');
@@ -4,7 +4,7 @@ import { getActiveFieldMetadataItems } from '@/object-metadata/utils/getActiveFi
import { objectMetadataItemSchema } from '@/object-metadata/validation-schemas/objectMetadataItemSchema';
import { isObjectMetadataReadOnly } from '@/object-record/read-only/utils/isObjectMetadataReadOnly';
import { Select } from '@/ui/input/components/Select';
import styled from '@emotion/styled';
import { styled } from '@linaria/react';
import { zodResolver } from '@hookform/resolvers/zod';
import { t } from '@lingui/core/macro';
import { useMemo } from 'react';
@@ -13,6 +13,7 @@ import { useNavigate } from 'react-router-dom';
import { isLabelIdentifierFieldMetadataTypes } from 'twenty-shared/utils';
import { IconCircleOff, IconPlus, useIcons } from 'twenty-ui/display';
import { type SelectOption } from 'twenty-ui/input';
import { themeCssVariables } from 'twenty-ui/theme-constants';
import { type z } from 'zod';
export const settingsDataModelObjectIdentifiersFormSchema =
@@ -36,7 +37,7 @@ const IMAGE_IDENTIFIER_FIELD_METADATA_ID: SettingsDataModelObjectIdentifiers =
const StyledContainer = styled.div`
display: flex;
gap: ${({ theme }) => theme.spacing(4)};
gap: ${themeCssVariables.spacing[4]};
`;
export const SettingsDataModelObjectIdentifiersForm = ({
@@ -1,4 +1,4 @@
import styled from '@emotion/styled';
import { styled } from '@linaria/react';
import { useMemo } from 'react';
import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
@@ -9,23 +9,24 @@ import { SettingsDataModelObjectPreview } from '@/settings/data-model/objects/co
import { SettingsDataModelObjectIdentifiersForm } from '@/settings/data-model/objects/forms/components/SettingsDataModelObjectIdentifiersForm';
import { Trans } from '@lingui/react/macro';
import { Card, CardContent } from 'twenty-ui/layout';
import { themeCssVariables } from 'twenty-ui/theme-constants';
type SettingsDataModelObjectSettingsFormCardProps = {
objectMetadataItem: ObjectMetadataItem;
};
const StyledTopCardContent = styled(CardContent)`
background-color: ${({ theme }) => theme.background.transparent.lighter};
background-color: ${themeCssVariables.background.transparent.lighter};
`;
const StyledObjectSummaryCard = styled(Card)`
border-radius: ${({ theme }) => theme.border.radius.md};
color: ${({ theme }) => theme.font.color.primary};
border-radius: ${themeCssVariables.border.radius.md};
color: ${themeCssVariables.font.color.primary};
max-width: 480px;
`;
const StyledObjectSummaryCardContent = styled(CardContent)`
padding: ${({ theme }) => theme.spacing(2)};
padding: ${themeCssVariables.spacing[2]};
`;
export const SettingsDataModelObjectSettingsFormCard = ({
@@ -1,4 +1,4 @@
import styled from '@emotion/styled';
import { styled } from '@linaria/react';
import { type Meta, type StoryObj } from '@storybook/react-vite';
import { FormProviderDecorator } from '~/testing/decorators/FormProviderDecorator';
import { IconsProviderDecorator } from '~/testing/decorators/IconsProviderDecorator';