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
@@ -19,8 +19,8 @@ import {
} from 'twenty-ui/display';
import { ProgressBar, useProgressAnimation } from 'twenty-ui/feedback';
import { LightButton, LightIconButton } from 'twenty-ui/input';
import { MOBILE_VIEWPORT, ThemeContext } from 'twenty-ui/theme';
import { themeCssVariables } from 'twenty-ui/theme-constants';
import { ThemeContext } from 'twenty-ui/theme';
import { MOBILE_VIEWPORT, themeCssVariables } from 'twenty-ui/theme-constants';
export enum SnackBarVariant {
Default = 'default',
@@ -7,9 +7,8 @@ import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile';
import { snackBarInternalComponentState } from '@/ui/feedback/snack-bar-manager/states/snackBarInternalComponentState';
import { RootStackingContextZIndices } from '@/ui/layout/constants/RootStackingContextZIndices';
import { useAtomComponentStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateValue';
import { MOBILE_VIEWPORT } from 'twenty-ui/theme';
import { MOBILE_VIEWPORT, themeCssVariables } from 'twenty-ui/theme-constants';
import { SnackBar } from './SnackBar';
import { themeCssVariables } from 'twenty-ui/theme-constants';
const StyledSnackBarContainer = styled.div`
bottom: ${themeCssVariables.spacing[3]};
@@ -15,7 +15,7 @@ import { activeDropdownFocusIdState } from '@/ui/layout/dropdown/states/activeDr
import { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside';
import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue';
import { isDefined } from 'twenty-shared/utils';
import { MOBILE_VIEWPORT } from 'twenty-ui/theme';
import { MOBILE_VIEWPORT } from 'twenty-ui/theme-constants';
import { v4 } from 'uuid';
import { t } from '@lingui/core/macro';
@@ -7,14 +7,28 @@ import { CurrencyPickerDropdownButton } from '@/ui/input/components/internal/cur
import { type Currency } from '@/ui/input/components/internal/types/Currency';
import { IMaskInput } from 'react-imask';
import { type IconComponent } from 'twenty-ui/display';
import { TEXT_INPUT_STYLE, ThemeContext } from 'twenty-ui/theme';
import { ThemeContext } from 'twenty-ui/theme';
import { themeCssVariables } from 'twenty-ui/theme-constants';
export const StyledIMaskInput = styled(IMaskInput)`
margin: 0;
${TEXT_INPUT_STYLE}
width: 100%;
background-color: transparent;
border: none;
color: ${themeCssVariables.font.color.primary};
font-family: ${themeCssVariables.font.family};
font-size: inherit;
font-weight: inherit;
outline: none;
padding: ${themeCssVariables.spacing[0]} ${themeCssVariables.spacing[1.5]};
&::placeholder,
&::-webkit-input-placeholder {
color: ${themeCssVariables.font.color.light};
font-family: ${themeCssVariables.font.family};
font-weight: ${themeCssVariables.font.weight.medium};
}
width: 100%;
`;
const StyledContainer = styled.div`
@@ -5,7 +5,6 @@ import TextareaAutosize from 'react-textarea-autosize';
import { LightCopyIconButton } from '@/object-record/record-field/ui/components/LightCopyIconButton';
import { useRegisterInputEvents } from '@/object-record/record-field/ui/meta-types/input/hooks/useRegisterInputEvents';
import { isDefined } from 'twenty-shared/utils';
import { TEXT_INPUT_STYLE } from 'twenty-ui/theme';
import { turnIntoEmptyStringIfWhitespacesOnly } from '~/utils/string/turnIntoEmptyStringIfWhitespacesOnly';
import { themeCssVariables } from 'twenty-ui/theme-constants';
@@ -27,7 +26,22 @@ export type TextAreaInputProps = {
};
const StyledTextArea = styled(TextareaAutosize)`
${TEXT_INPUT_STYLE}
background-color: transparent;
border: none;
color: ${themeCssVariables.font.color.primary};
font-family: ${themeCssVariables.font.family};
font-size: inherit;
font-weight: inherit;
outline: none;
padding: ${themeCssVariables.spacing[0]} ${themeCssVariables.spacing[2]};
&::placeholder,
&::-webkit-input-placeholder {
color: ${themeCssVariables.font.color.light};
font-family: ${themeCssVariables.font.family};
font-weight: ${themeCssVariables.font.weight.medium};
}
align-items: center;
display: flex;
justify-content: center;
@@ -3,12 +3,26 @@ import { useEffect, useRef, useState, type ChangeEvent } from 'react';
import { LightCopyIconButton } from '@/object-record/record-field/ui/components/LightCopyIconButton';
import { useRegisterInputEvents } from '@/object-record/record-field/ui/meta-types/input/hooks/useRegisterInputEvents';
import { TEXT_INPUT_STYLE } from 'twenty-ui/theme';
import { themeCssVariables } from 'twenty-ui/theme-constants';
export const StyledTextInput = styled.input`
margin: 0;
${TEXT_INPUT_STYLE}
background-color: transparent;
border: none;
color: ${themeCssVariables.font.color.primary};
font-family: ${themeCssVariables.font.family};
font-size: inherit;
font-weight: inherit;
outline: none;
padding: ${themeCssVariables.spacing[0]} ${themeCssVariables.spacing[2]};
&::placeholder,
&::-webkit-input-placeholder {
color: ${themeCssVariables.font.color.light};
font-family: ${themeCssVariables.font.family};
font-weight: ${themeCssVariables.font.weight.medium};
}
width: 100%;
&:disabled {
@@ -16,7 +30,7 @@ export const StyledTextInput = styled.input`
}
`;
type TextInputProps = {
export type TextInputProps = {
instanceId: string;
placeholder?: string;
autoFocus?: boolean;
@@ -30,6 +44,7 @@ type TextInputProps = {
copyButton?: boolean;
shouldTrim?: boolean;
disabled?: boolean;
className?: string;
};
const getValue = (value: string, shouldTrim: boolean) => {
@@ -54,6 +69,7 @@ export const TextInput = ({
copyButton = true,
shouldTrim = true,
disabled,
className,
}: TextInputProps) => {
const [internalText, setInternalText] = useState(value);
@@ -91,6 +107,7 @@ export const TextInput = ({
autoFocus={autoFocus}
value={internalText}
disabled={disabled}
className={className}
/>
{copyButton && (
<div ref={copyRef}>
@@ -44,17 +44,12 @@ const StyledPicture = styled.button<{ withPicture: boolean }>`
color: ${themeCssVariables.font.color.tertiary};
}
${({ withPicture, disabled }) => {
if ((withPicture || disabled) === true) {
return '';
}
return `
&:hover {
background: ${themeCssVariables.background.transparent.medium};
}
`;
}};
&:hover {
background: ${({ withPicture, disabled }) =>
withPicture || disabled
? 'transparent'
: themeCssVariables.background.transparent.medium};
}
`;
const StyledContent = styled.div`
@@ -8,7 +8,6 @@ import {
import 'react-phone-number-input/style.css';
import { useRegisterInputEvents } from '@/object-record/record-field/ui/meta-types/input/hooks/useRegisterInputEvents';
import { TEXT_INPUT_STYLE } from 'twenty-ui/theme';
import { useCombinedRefs } from '~/hooks/useCombinedRefs';
import { themeCssVariables } from 'twenty-ui/theme-constants';
@@ -16,7 +15,21 @@ const StyledInput = styled.input<{
withRightComponent?: boolean;
hasError?: boolean;
}>`
${TEXT_INPUT_STYLE}
background-color: transparent;
border: none;
color: ${themeCssVariables.font.color.primary};
font-family: ${themeCssVariables.font.family};
font-size: inherit;
font-weight: inherit;
outline: none;
padding: ${themeCssVariables.spacing[0]} ${themeCssVariables.spacing[2]};
&::placeholder,
&::-webkit-input-placeholder {
color: ${themeCssVariables.font.color.light};
font-family: ${themeCssVariables.font.family};
font-weight: ${themeCssVariables.font.weight.medium};
}
box-sizing: border-box;
font-weight: ${themeCssVariables.font.weight.medium};
@@ -3,7 +3,6 @@ import { useLingui } from '@lingui/react/macro';
import { useInputFocusWithoutScrollOnMount } from '@/ui/input/hooks/useInputFocusWithoutScrollOnMount';
import { styled } from '@linaria/react';
import { forwardRef, type InputHTMLAttributes } from 'react';
import { TEXT_INPUT_STYLE } from 'twenty-ui/theme';
import { themeCssVariables } from 'twenty-ui/theme-constants';
const StyledDropdownMenuSearchInputContainer = styled.div`
@@ -18,7 +17,21 @@ const StyledDropdownMenuSearchInputContainer = styled.div`
`;
const StyledInput = styled.input`
${TEXT_INPUT_STYLE}
background-color: transparent;
border: none;
color: ${themeCssVariables.font.color.primary};
font-family: ${themeCssVariables.font.family};
font-size: inherit;
font-weight: inherit;
outline: none;
padding: ${themeCssVariables.spacing[0]} ${themeCssVariables.spacing[2]};
&::placeholder,
&::-webkit-input-placeholder {
color: ${themeCssVariables.font.color.light};
font-family: ${themeCssVariables.font.family};
font-weight: ${themeCssVariables.font.weight.medium};
}
font-size: ${themeCssVariables.font.size.sm};
background-color: transparent;
@@ -0,0 +1,71 @@
import { RootStackingContextZIndices } from '@/ui/layout/constants/RootStackingContextZIndices';
import { PageHeader } from '@/ui/layout/page/components/PageHeader';
import {
Breadcrumb,
type BreadcrumbProps,
} from '@/ui/navigation/bread-crumb/components/Breadcrumb';
import { styled } from '@linaria/react';
import { forwardRef } from 'react';
import { themeCssVariables } from 'twenty-ui/theme-constants';
const StyledFullScreenOverlay = styled.div`
background: ${themeCssVariables.background.noisy};
bottom: 0;
display: flex;
flex-direction: column;
height: 100vh;
left: 0;
position: fixed;
right: 0;
top: 0;
width: 100%;
z-index: ${RootStackingContextZIndices.RootModal};
`;
const StyledFullScreenHeader = styled(PageHeader)`
padding-left: ${themeCssVariables.spacing[3]};
`;
const StyledFullScreenContent = styled.div`
display: flex;
flex: 1;
flex-direction: column;
gap: ${themeCssVariables.spacing[3]};
min-height: 0;
padding: 0 ${themeCssVariables.spacing[3]} ${themeCssVariables.spacing[3]}
${themeCssVariables.spacing[3]};
> * {
display: flex;
flex: 1;
flex-direction: column;
min-height: 0;
row-gap: ${themeCssVariables.spacing[5]};
}
`;
type FullScreenModalProps = {
children: React.ReactNode;
links: BreadcrumbProps['links'];
onClose: () => void;
hasClosePageButton?: boolean;
};
export const FullScreenModal = forwardRef<HTMLDivElement, FullScreenModalProps>(
({ children, links, onClose, hasClosePageButton = true }, ref) => {
return (
<StyledFullScreenOverlay
ref={ref}
data-globally-prevent-click-outside="true"
tabIndex={-1}
>
<StyledFullScreenHeader
title={<Breadcrumb links={links} />}
hasClosePageButton={hasClosePageButton}
onClosePage={onClose}
/>
<StyledFullScreenContent>{children}</StyledFullScreenContent>
</StyledFullScreenOverlay>
);
},
);
@@ -1,51 +1,7 @@
import { RootStackingContextZIndices } from '@/ui/layout/constants/RootStackingContextZIndices';
import { PageHeader } from '@/ui/layout/page/components/PageHeader';
import {
Breadcrumb,
type BreadcrumbProps,
} from '@/ui/navigation/bread-crumb/components/Breadcrumb';
import { styled } from '@linaria/react';
import { FullScreenModal } from '@/ui/layout/fullscreen/components/FullScreenModal';
import { type BreadcrumbProps } from '@/ui/navigation/bread-crumb/components/Breadcrumb';
import { useRef } from 'react';
import { createPortal } from 'react-dom';
import { themeCssVariables } from 'twenty-ui/theme-constants';
const StyledFullScreenOverlay = styled.div`
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: ${themeCssVariables.background.noisy};
display: flex;
flex-direction: column;
width: 100%;
height: 100vh;
z-index: ${RootStackingContextZIndices.RootModal};
`;
const StyledFullScreenHeader = styled(PageHeader)`
padding-left: ${themeCssVariables.spacing[3]};
`;
const StyledFullScreenContent = styled.div`
display: flex;
flex-direction: column;
gap: ${themeCssVariables.spacing[3]};
flex: 1;
min-height: 0;
padding: 0 ${themeCssVariables.spacing[3]} ${themeCssVariables.spacing[3]}
${themeCssVariables.spacing[3]};
// Make the immediate child a flex column that grows, so nested components
// with height="100%" (e.g., editors) can size correctly.
> * {
display: flex;
flex-direction: column;
flex: 1;
min-height: 0;
row-gap: ${themeCssVariables.spacing[5]};
}
`;
type UseFullScreenModalProps = {
links: BreadcrumbProps['links'];
@@ -67,18 +23,14 @@ export const useFullScreenModal = ({
if (!isOpen) return null;
return createPortal(
<StyledFullScreenOverlay
<FullScreenModal
ref={overlayRef}
data-globally-prevent-click-outside="true"
tabIndex={-1}
links={links}
onClose={onClose}
hasClosePageButton={hasClosePageButton}
>
<StyledFullScreenHeader
title={<Breadcrumb links={links} />}
hasClosePageButton={hasClosePageButton}
onClosePage={onClose}
/>
<StyledFullScreenContent>{children}</StyledFullScreenContent>
</StyledFullScreenOverlay>,
{children}
</FullScreenModal>,
document.body,
);
};
@@ -15,8 +15,8 @@ import {
OverflowingTextWithTooltip,
} from 'twenty-ui/display';
import { LightIconButton } from 'twenty-ui/input';
import { MOBILE_VIEWPORT, ThemeContext } from 'twenty-ui/theme';
import { themeCssVariables } from 'twenty-ui/theme-constants';
import { ThemeContext } from 'twenty-ui/theme';
import { MOBILE_VIEWPORT, themeCssVariables } from 'twenty-ui/theme-constants';
const StyledTopBarContainer = styled.div<{ isMobile: boolean }>`
align-items: center;
@@ -3,7 +3,7 @@ import { type ReactNode, useEffect, useRef } from 'react';
import { SelectableListItemHotkeyEffect } from '@/ui/layout/selectable-list/components/SelectableListItemHotkeyEffect';
import { isSelectedItemIdComponentFamilyState } from '@/ui/layout/selectable-list/states/isSelectedItemIdComponentFamilyState';
import { useAtomComponentFamilyStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentFamilyStateValue';
import styled from '@emotion/styled';
import { styled } from '@linaria/react';
import { isDefined } from 'twenty-shared/utils';
const StyledListItemContainer = styled.div`
@@ -18,12 +18,14 @@ export type SelectableListItemProps = {
itemId: string;
children: ReactNode;
onEnter?: () => void;
className?: string;
};
export const SelectableListItem = ({
itemId,
children,
onEnter,
className,
}: SelectableListItemProps) => {
const isSelectedItemId = useAtomComponentFamilyStateValue(
isSelectedItemIdComponentFamilyState,
@@ -56,7 +58,7 @@ export const SelectableListItem = ({
{isSelectedItemId && isDefined(onEnter) && (
<SelectableListItemHotkeyEffect itemId={itemId} onEnter={onEnter} />
)}
<StyledListItemContainer ref={listItemRef}>
<StyledListItemContainer ref={listItemRef} className={className}>
{children}
</StyledListItemContainer>
</>
@@ -3,12 +3,13 @@ import { type CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectN
import { recordStoreFamilySelector } from '@/object-record/record-store/states/selectors/recordStoreFamilySelector';
import { useTargetRecord } from '@/ui/layout/contexts/useTargetRecord';
import { ScrollWrapper } from '@/ui/utilities/scroll/components/ScrollWrapper';
import { useTheme } from '@emotion/react';
import styled from '@emotion/styled';
import { lazy, Suspense } from 'react';
import { styled } from '@linaria/react';
import { lazy, Suspense, useContext } from 'react';
import Skeleton, { SkeletonTheme } from 'react-loading-skeleton';
import { useAtomFamilySelectorValue } from '@/ui/utilities/state/jotai/hooks/useAtomFamilySelectorValue';
import { isDefined } from 'twenty-shared/utils';
import { ThemeContext } from 'twenty-ui/theme';
import { themeCssVariables } from 'twenty-ui/theme-constants';
const ActivityRichTextEditor = lazy(() =>
import('@/activities/components/ActivityRichTextEditor').then((module) => ({
@@ -17,7 +18,7 @@ const ActivityRichTextEditor = lazy(() =>
);
const StyledShowPageActivityContainer = styled.div`
margin-top: ${({ theme }) => theme.spacing(6)};
margin-top: ${themeCssVariables.spacing[6]};
width: 100%;
padding-inline: 44px;
box-sizing: border-box;
@@ -27,11 +28,11 @@ const StyledSkeletonContainer = styled.div`
display: flex;
flex-direction: column;
justify-content: center;
padding: ${({ theme }) => theme.spacing(0, 4)};
padding: 0 ${themeCssVariables.spacing[4]};
`;
const LoadingSkeleton = () => {
const theme = useTheme();
const { theme } = useContext(ThemeContext);
return (
<StyledSkeletonContainer>
@@ -1,8 +1,7 @@
import { SKELETON_LOADER_HEIGHT_SIZES } from '@/activities/components/SkeletonLoader';
import { useTheme } from '@emotion/react';
import styled from '@emotion/styled';
import { styled } from '@linaria/react';
import { Trans } from '@lingui/react/macro';
import { type ChangeEvent, type ReactNode, useRef } from 'react';
import { type ChangeEvent, type ReactNode, useContext, useRef } from 'react';
import Skeleton, { SkeletonTheme } from 'react-loading-skeleton';
import { isDefined } from 'twenty-shared/utils';
import {
@@ -12,6 +11,8 @@ import {
type IconComponent,
} from 'twenty-ui/display';
import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue';
import { ThemeContext } from 'twenty-ui/theme';
import { themeCssVariables } from 'twenty-ui/theme-constants';
import { v4 as uuidV4 } from 'uuid';
import { dateLocaleState } from '~/localization/states/dateLocaleState';
import {
@@ -39,11 +40,11 @@ export const StyledShowPageSummaryCard = styled.div<{
align-items: center;
display: flex;
flex-direction: ${({ isMobile }) => (isMobile ? 'row' : 'column')};
gap: ${({ theme, isMobile }) =>
isMobile ? theme.spacing(2) : theme.spacing(3)};
gap: ${({ isMobile }) =>
isMobile ? themeCssVariables.spacing[2] : themeCssVariables.spacing[3]};
justify-content: ${({ isMobile }) => (isMobile ? 'flex-start' : 'center')};
padding: ${({ theme }) => theme.spacing(4)};
border-bottom: 1px solid ${({ theme }) => theme.border.color.light};
padding: ${themeCssVariables.spacing[4]};
border-bottom: 1px solid ${themeCssVariables.border.color.light};
height: ${({ isMobile }) => (isMobile ? '77px' : '127px')};
box-sizing: border-box;
`;
@@ -52,21 +53,21 @@ const StyledInfoContainer = styled.div<{ isMobile: boolean }>`
align-items: ${({ isMobile }) => (isMobile ? 'flex-start' : 'center')};
display: flex;
flex-direction: column;
gap: ${({ theme }) => theme.spacing(1)};
gap: ${themeCssVariables.spacing[1]};
width: 100%;
`;
const StyledDate = styled.div<{ isMobile: boolean }>`
color: ${({ theme }) => theme.font.color.tertiary};
color: ${themeCssVariables.font.color.tertiary};
cursor: pointer;
padding-left: ${({ theme }) => theme.spacing(1)};
padding-left: ${themeCssVariables.spacing[1]};
`;
const StyledTitle = styled.div<{ isMobile: boolean }>`
color: ${({ theme }) => theme.font.color.primary};
color: ${themeCssVariables.font.color.primary};
display: flex;
font-size: ${({ theme }) => theme.font.size.xl};
font-weight: ${({ theme }) => theme.font.weight.semiBold};
font-size: ${themeCssVariables.font.size.xl};
font-weight: ${themeCssVariables.font.weight.semiBold};
justify-content: ${({ isMobile }) => (isMobile ? 'flex-start' : 'center')};
width: 90%;
`;
@@ -75,9 +76,9 @@ const StyledAvatarWrapper = styled.div<{
isAvatarEditable: boolean;
hasIcon: boolean;
}>`
background-color: ${({ theme, hasIcon }) =>
hasIcon ? theme.background.transparent.light : 'unset'};
border-radius: ${({ theme }) => theme.border.radius.sm};
background-color: ${({ hasIcon }) =>
hasIcon ? themeCssVariables.background.transparent.light : 'unset'};
border-radius: ${themeCssVariables.border.radius.sm};
cursor: ${({ isAvatarEditable }) =>
isAvatarEditable ? 'pointer' : 'default'};
`;
@@ -95,7 +96,7 @@ const StyledSubSkeleton = styled.div`
`;
const StyledShowPageSummaryCardSkeletonLoader = () => {
const theme = useTheme();
const { theme } = useContext(ThemeContext);
return (
<SkeletonTheme
baseColor={theme.background.tertiary}
@@ -1,10 +1,11 @@
import { type SingleTabProps } from '@/ui/layout/tab-list/types/SingleTabProps';
import { useTheme } from '@emotion/react';
import { useContext } from 'react';
import { isDefined } from 'twenty-shared/utils';
import { Avatar } from 'twenty-ui/display';
import { ThemeContext } from 'twenty-ui/theme';
export const TabAvatar = ({ tab }: { tab: SingleTabProps }) => {
const theme = useTheme();
const { theme } = useContext(ThemeContext);
if (isDefined(tab.logo)) {
return <Avatar avatarUrl={tab.logo} size="md" placeholder={tab.title} />;
@@ -7,23 +7,24 @@ import { NodeDimension } from '@/ui/utilities/dimensions/components/NodeDimensio
import { TabListHiddenMeasurements } from '@/ui/layout/tab-list/components/TabListHiddenMeasurements';
import { useTabListMeasurements } from '@/ui/layout/tab-list/hooks/useTabListMeasurements';
import { useAtomComponentState } from '@/ui/utilities/state/jotai/hooks/useAtomComponentState';
import styled from '@emotion/styled';
import { styled } from '@linaria/react';
import { useCallback, useEffect, useMemo } from 'react';
import { useNavigate } from 'react-router-dom';
import { TabButton } from 'twenty-ui/input';
import { themeCssVariables } from 'twenty-ui/theme-constants';
import { TabListDropdown } from './TabListDropdown';
import { TabListFromUrlOptionalEffect } from './TabListFromUrlOptionalEffect';
const StyledContainer = styled.div`
box-sizing: border-box;
display: flex;
height: ${({ theme }) => theme.spacing(10)};
height: ${themeCssVariables.spacing[10]};
position: relative;
user-select: none;
width: 100%;
&::after {
background-color: ${({ theme }) => theme.border.color.light};
background-color: ${themeCssVariables.border.color.light};
bottom: 0;
content: '';
height: 1px;
@@ -3,7 +3,7 @@ import type { ReactNode } from 'react';
import { TAB_LIST_GAP } from '@/ui/layout/tab-list/constants/TabListGap';
import { type SingleTabProps } from '@/ui/layout/tab-list/types/SingleTabProps';
import { NodeDimension } from '@/ui/utilities/dimensions/components/NodeDimension';
import styled from '@emotion/styled';
import { styled } from '@linaria/react';
import { IconPlus } from 'twenty-ui/display';
import { TabButton } from 'twenty-ui/input';
@@ -1,10 +1,11 @@
import styled from '@emotion/styled';
import { styled } from '@linaria/react';
import { t } from '@lingui/core/macro';
import { IconChevronDown } from 'twenty-ui/display';
import { TabButton } from 'twenty-ui/input';
import { themeCssVariables } from 'twenty-ui/theme-constants';
const StyledTabMoreButton = styled(TabButton)`
height: ${({ theme }) => theme.spacing(10)};
height: ${themeCssVariables.spacing[10]};
`;
export const TabMoreButton = ({
@@ -1,5 +1,5 @@
import { TabList } from '@/ui/layout/tab-list/components/TabList';
import styled from '@emotion/styled';
import { styled } from '@linaria/react';
import { type Meta, type StoryObj } from '@storybook/react-vite';
import {
IconCalendar,
@@ -35,12 +35,14 @@ const tabs = [
{ id: 'reports', title: 'Reports', Icon: IconCheckbox },
];
import { themeCssVariables } from 'twenty-ui/theme-constants';
const StyledInteractiveContainer = styled.div`
border: 1px solid ${({ theme }) => theme.border.color.strong};
border: 1px solid ${themeCssVariables.border.color.strong};
max-width: 100%;
min-width: 300px;
overflow: auto;
padding: ${({ theme }) => theme.spacing(5)};
padding: ${themeCssVariables.spacing[5]};
resize: horizontal;
width: 600px;
`;
@@ -1,4 +1,4 @@
import styled from '@emotion/styled';
import { styled } from '@linaria/react';
const StyledTable = styled.div``;
@@ -1,10 +1,11 @@
import styled from '@emotion/styled';
import { styled } from '@linaria/react';
import { themeCssVariables } from 'twenty-ui/theme-constants';
const StyledTableBody = styled.div`
display: flex;
flex-direction: column;
gap: 2px;
padding: ${({ theme }) => theme.spacing(2)} 0;
padding: ${themeCssVariables.spacing[2]} 0;
`;
export { StyledTableBody as TableBody };
@@ -1,4 +1,5 @@
import styled from '@emotion/styled';
import { styled } from '@linaria/react';
import { themeCssVariables } from 'twenty-ui/theme-constants';
type TableCellProps = {
align?: 'left' | 'center' | 'right';
@@ -7,16 +8,16 @@ type TableCellProps = {
const StyledTableCell = styled.div<TableCellProps>`
align-items: center;
color: ${({ color, theme }) => color || theme.font.color.secondary};
color: ${({ color }) => color || themeCssVariables.font.color.secondary};
display: flex;
height: ${({ theme }) => theme.spacing(8)};
height: ${themeCssVariables.spacing[8]};
justify-content: ${({ align }) =>
align === 'right'
? 'flex-end'
: align === 'center'
? 'center'
: 'flex-start'};
padding: 0 ${({ theme }) => theme.spacing(2)};
padding: 0 ${themeCssVariables.spacing[2]};
text-align: ${({ align }) => align ?? 'left'};
`;
@@ -1,23 +1,24 @@
import styled from '@emotion/styled';
import { styled } from '@linaria/react';
import { themeCssVariables } from 'twenty-ui/theme-constants';
const StyledTableHeader = styled.div<{
align?: 'left' | 'center' | 'right';
onClick?: () => void;
}>`
gap: ${({ theme }) => theme.spacing(1)};
gap: ${themeCssVariables.spacing[1]};
align-items: center;
border-bottom: 1px solid ${({ theme }) => theme.border.color.light};
color: ${({ theme }) => theme.font.color.tertiary};
border-bottom: 1px solid ${themeCssVariables.border.color.light};
color: ${themeCssVariables.font.color.tertiary};
display: flex;
font-weight: ${({ theme }) => theme.font.weight.medium};
height: ${({ theme }) => theme.spacing(8)};
font-weight: ${themeCssVariables.font.weight.medium};
height: ${themeCssVariables.spacing[8]};
justify-content: ${({ align }) =>
align === 'right'
? 'flex-end'
: align === 'center'
? 'center'
: 'flex-start'};
padding: 0 ${({ theme }) => theme.spacing(2)};
padding: 0 ${themeCssVariables.spacing[2]};
text-align: ${({ align }) => align ?? 'left'};
cursor: ${({ onClick }) => (onClick ? 'pointer' : 'default')};
`;
@@ -1,7 +1,8 @@
import styled from '@emotion/styled';
import { styled } from '@linaria/react';
import { themeCssVariables } from 'twenty-ui/theme-constants';
const StyledTableHeaderText = styled.div`
padding-bottom: ${({ theme }) => theme.spacing(0.5)};
padding-bottom: ${themeCssVariables.spacing['0.5']};
`;
export { StyledTableHeaderText as TableHeaderText };
@@ -1,12 +1,8 @@
import isPropValid from '@emotion/is-prop-valid';
import styled from '@emotion/styled';
import { styled } from '@linaria/react';
import { Link } from 'react-router-dom';
import { MOBILE_VIEWPORT } from 'twenty-ui/theme';
import { MOBILE_VIEWPORT, themeCssVariables } from 'twenty-ui/theme-constants';
const StyledTableRow = styled('div', {
shouldForwardProp: (prop) =>
!['isSelected'].includes(prop) && isPropValid(prop),
})<{
const StyledTableRow = styled.div<{
isSelected?: boolean;
onClick?: () => void;
to?: string;
@@ -14,15 +10,13 @@ const StyledTableRow = styled('div', {
gridTemplateColumns?: string;
mobileGridAutoColumns?: string;
}>`
background-color: ${({ isSelected, theme }) =>
isSelected ? theme.accent.quaternary : 'transparent'};
border-radius: ${({ theme }) => theme.border.radius.sm};
background-color: ${({ isSelected }) =>
isSelected ? themeCssVariables.accent.quaternary : 'transparent'};
border-radius: ${themeCssVariables.border.radius.sm};
display: grid;
grid-auto-columns: ${({ gridAutoColumns }) => gridAutoColumns ?? '1fr'};
${({ gridTemplateColumns }) =>
gridTemplateColumns
? `grid-template-columns: ${gridTemplateColumns};`
: ''};
grid-template-columns: ${({ gridTemplateColumns }) =>
gridTemplateColumns ?? 'none'};
@media (max-width: ${MOBILE_VIEWPORT}px) {
grid-auto-columns: ${({ mobileGridAutoColumns, gridAutoColumns }) =>
@@ -31,13 +25,15 @@ const StyledTableRow = styled('div', {
grid-auto-flow: column;
transition: background-color
${({ theme }) => theme.animation.duration.normal}s;
calc(${themeCssVariables.animation.duration.normal} * 1s);
width: 100%;
text-decoration: none;
&:hover {
background-color: ${({ onClick, to, theme }) =>
onClick || to ? theme.background.transparent.light : 'transparent'};
background-color: ${({ onClick, to }) =>
onClick || to
? themeCssVariables.background.transparent.light
: 'transparent'};
cursor: ${({ onClick, to }) => (onClick || to ? 'pointer' : 'default')};
}
@@ -1,8 +1,9 @@
import { useTheme } from '@emotion/react';
import styled from '@emotion/styled';
import { type ReactNode, useState } from 'react';
import { styled } from '@linaria/react';
import { type ReactNode, useContext, useState } from 'react';
import { TableBody } from './TableBody';
import { IconChevronDown, IconChevronUp, Label } from 'twenty-ui/display';
import { ThemeContext } from 'twenty-ui/theme';
import { themeCssVariables } from 'twenty-ui/theme-constants';
type TableSectionProps = {
children: ReactNode;
@@ -12,27 +13,27 @@ type TableSectionProps = {
const StyledSectionHeader = styled.div<{ isExpanded: boolean }>`
align-items: center;
background-color: ${({ theme }) => theme.background.transparent.lighter};
border-bottom: 1px solid ${({ theme }) => theme.border.color.light};
background-color: ${themeCssVariables.background.transparent.lighter};
border-bottom: 1px solid ${themeCssVariables.border.color.light};
cursor: pointer;
display: flex;
height: ${({ theme }) => theme.spacing(6)};
height: ${themeCssVariables.spacing[6]};
justify-content: space-between;
padding: 0 ${({ theme }) => theme.spacing(2)};
padding: 0 ${themeCssVariables.spacing[2]};
text-align: left;
`;
const StyledSection = styled.div<{ isExpanded: boolean }>`
max-height: ${({ isExpanded }) => (isExpanded ? 'fit-content' : 0)};
max-height: ${({ isExpanded }) => (isExpanded ? 'fit-content' : '0')};
opacity: ${({ isExpanded }) => (isExpanded ? 1 : 0)};
overflow: hidden;
transition:
max-height ${({ theme }) => theme.animation.duration.normal}s,
opacity ${({ theme }) => theme.animation.duration.normal}s;
max-height calc(${themeCssVariables.animation.duration.normal} * 1s),
opacity calc(${themeCssVariables.animation.duration.normal} * 1s);
`;
const StyledSectionContent = styled(TableBody)`
border-bottom: 1px solid ${({ theme }) => theme.border.color.light};
border-bottom: 1px solid ${themeCssVariables.border.color.light};
`;
export const TableSection = ({
@@ -41,7 +42,7 @@ export const TableSection = ({
title,
}: TableSectionProps) => {
const [isExpanded, setIsExpanded] = useState(isInitiallyExpanded);
const theme = useTheme();
const { theme } = useContext(ThemeContext);
const handleToggleSection = () =>
setIsExpanded((previousIsExpanded) => !previousIsExpanded);
@@ -1,9 +1,10 @@
import styled from '@emotion/styled';
import { styled } from '@linaria/react';
import { themeCssVariables } from 'twenty-ui/theme-constants';
import { TableRow } from '@/ui/layout/table/components/TableRow';
const StyledTableSubRow = styled(TableRow)`
padding-left: ${({ theme }) => theme.spacing(4)};
padding-left: ${themeCssVariables.spacing[4]};
`;
export const TableSubRow = StyledTableSubRow;
@@ -13,7 +13,6 @@ const meta: Meta<typeof Table> = {
decorators: [ComponentDecorator],
argTypes: {
as: { table: { disable: true } },
theme: { table: { disable: true } },
},
};
@@ -1,5 +1,6 @@
import styled from '@emotion/styled';
import { styled } from '@linaria/react';
import { type ReactNode } from 'react';
import { themeCssVariables } from 'twenty-ui/theme-constants';
type TopBarProps = {
className?: string;
@@ -10,9 +11,9 @@ type TopBarProps = {
};
const StyledContainer = styled.div`
border-bottom: ${({ theme }) => `1px solid ${theme.border.color.light}`};
border-bottom: 1px solid ${themeCssVariables.border.color.light};
display: flex;
margin-left: ${({ theme }) => theme.spacing(3)};
margin-left: ${themeCssVariables.spacing[3]};
flex-direction: column;
`;
@@ -21,13 +22,13 @@ const StyledTopBar = styled.div`
align-items: center;
box-sizing: border-box;
color: ${({ theme }) => theme.font.color.secondary};
color: ${themeCssVariables.font.color.secondary};
display: flex;
flex-direction: row;
font-weight: ${({ theme }) => theme.font.weight.medium};
font-weight: ${themeCssVariables.font.weight.medium};
height: 39px;
justify-content: space-between;
padding-right: ${({ theme }) => theme.spacing(2)};
padding-right: ${themeCssVariables.spacing[2]};
z-index: 7;
`;
@@ -38,8 +39,8 @@ const StyledLeftSection = styled.div`
const StyledRightSection = styled.div`
display: flex;
font-weight: ${({ theme }) => theme.font.weight.regular};
gap: ${({ theme }) => theme.betweenSiblingsGap};
font-weight: ${themeCssVariables.font.weight.regular};
gap: ${themeCssVariables.betweenSiblingsGap};
`;
export const TopBar = ({
@@ -1,8 +1,9 @@
import { MobileBreadcrumb } from '@/ui/navigation/bread-crumb/components/MobileBreadcrumb';
import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile';
import styled from '@emotion/styled';
import { styled } from '@linaria/react';
import { Fragment, type ReactNode } from 'react';
import { Link } from 'react-router-dom';
import { themeCssVariables } from 'twenty-ui/theme-constants';
export type BreadcrumbProps = {
className?: string;
@@ -11,14 +12,14 @@ export type BreadcrumbProps = {
const StyledWrapper = styled.nav`
align-items: center;
color: ${({ theme }) => theme.font.color.tertiary};
color: ${themeCssVariables.font.color.tertiary};
display: grid;
font-size: ${({ theme }) => theme.font.size.md};
font-size: ${themeCssVariables.font.size.md};
grid-auto-flow: column;
grid-column-gap: ${({ theme }) => theme.spacing(1)};
grid-column-gap: ${themeCssVariables.spacing[1]};
max-width: 100%;
min-width: 0;
height: ${({ theme }) => theme.spacing(8)};
height: ${themeCssVariables.spacing[8]};
`;
const StyledLink = styled(Link)`
@@ -30,14 +31,14 @@ const StyledLink = styled(Link)`
`;
const StyledText = styled.span`
color: ${({ theme }) => theme.font.color.primary};
color: ${themeCssVariables.font.color.primary};
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
`;
const StyledDivider = styled.span`
width: ${({ theme }) => theme.spacing(2)};
width: ${themeCssVariables.spacing[2]};
`;
export const Breadcrumb = ({ className, links }: BreadcrumbProps) => {
@@ -1,11 +1,12 @@
import { t } from '@lingui/core/macro';
import { useOpenSettingsMenu } from '@/navigation/hooks/useOpenSettings';
import { useTheme } from '@emotion/react';
import styled from '@emotion/styled';
import { styled } from '@linaria/react';
import { isNonEmptyString } from '@sniptt/guards';
import { type ReactNode } from 'react';
import { type ReactNode, useContext } from 'react';
import { Link } from 'react-router-dom';
import { IconChevronLeft } from 'twenty-ui/display';
import { ThemeContext } from 'twenty-ui/theme';
import { themeCssVariables } from 'twenty-ui/theme-constants';
export type MobileBreadcrumbProps = {
className?: string;
@@ -14,14 +15,14 @@ export type MobileBreadcrumbProps = {
const StyledWrapper = styled.nav`
align-items: center;
color: ${({ theme }) => theme.font.color.tertiary};
color: ${themeCssVariables.font.color.tertiary};
display: grid;
font-size: ${({ theme }) => theme.font.size.md};
font-size: ${themeCssVariables.font.size.md};
grid-auto-flow: column;
grid-column-gap: ${({ theme }) => theme.spacing(1)};
grid-column-gap: ${themeCssVariables.spacing[1]};
max-width: 100%;
min-width: 0;
height: ${({ theme }) => theme.spacing(8)};
height: ${themeCssVariables.spacing[8]};
`;
const StyledLink = styled(Link)`
@@ -43,7 +44,7 @@ export const MobileBreadcrumb = ({
className,
links,
}: MobileBreadcrumbProps) => {
const theme = useTheme();
const { theme } = useContext(ThemeContext);
const { openSettingsMenu } = useOpenSettingsMenu();
@@ -1,12 +1,13 @@
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
import { useTheme } from '@emotion/react';
import { type Placement } from '@floating-ui/react';
import {
type FunctionComponent,
type MouseEvent,
type ReactElement,
type ReactNode,
useContext,
} from 'react';
import { ThemeContext } from 'twenty-ui/theme';
import {
IconChevronRight,
type IconComponent,
@@ -62,7 +63,7 @@ export const MenuItemWithOptionDropdown = ({
hasSubMenu = false,
dropdownPlacement = 'bottom-end',
}: MenuItemWithOptionDropdownProps) => {
const theme = useTheme();
const { theme } = useContext(ThemeContext);
const handleMenuItemClick = (event: MouseEvent<HTMLDivElement>) => {
if (!onClick) return;
@@ -7,13 +7,14 @@ import {
} from '@/ui/navigation/navigation-drawer/components/MultiWorkspaceDropdown/internal/MultiWorkspacesDropdownStyles';
import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState';
import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue';
import { useTheme } from '@emotion/react';
import { useContext } from 'react';
import { isNavigationDrawerExpandedState } from '@/ui/navigation/states/isNavigationDrawerExpanded';
import { Avatar } from 'twenty-ui/display';
import { ThemeContext } from 'twenty-ui/theme';
export const MultiWorkspaceDropdownClickableComponent = () => {
const currentWorkspace = useAtomStateValue(currentWorkspaceState);
const theme = useTheme();
const { theme } = useContext(ThemeContext);
const isNavigationDrawerExpanded = useAtomStateValue(
isNavigationDrawerExpandedState,
@@ -25,8 +25,9 @@ import { useAtomState } from '@/ui/utilities/state/jotai/hooks/useAtomState';
import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue';
import { useSetAtomState } from '@/ui/utilities/state/jotai/hooks/useSetAtomState';
import { type ApolloError } from '@apollo/client';
import styled from '@emotion/styled';
import { styled } from '@linaria/react';
import { useLingui } from '@lingui/react/macro';
import { themeCssVariables } from 'twenty-ui/theme-constants';
import { isNonEmptyString } from '@sniptt/guards';
import { useLocation } from 'react-router-dom';
import { AppPath, SettingsPath } from 'twenty-shared/types';
@@ -54,8 +55,8 @@ import {
import { getWorkspaceUrl } from '~/utils/getWorkspaceUrl';
const StyledDescription = styled.div`
color: ${({ theme }) => theme.font.color.light};
padding-left: ${({ theme }) => theme.spacing(1)};
color: ${themeCssVariables.font.color.light};
padding-left: ${themeCssVariables.spacing[1]};
`;
export const MultiWorkspaceDropdownDefaultComponents = () => {
@@ -1,39 +1,42 @@
import styled from '@emotion/styled';
import { styled } from '@linaria/react';
import { IconChevronDown } from 'twenty-ui/display';
import { themeCssVariables } from 'twenty-ui/theme-constants';
export const StyledContainer = styled.div<{
isNavigationDrawerExpanded: boolean;
}>`
align-items: center;
cursor: pointer;
color: ${({ theme }) => theme.font.color.primary};
border-radius: ${({ theme }) => theme.border.radius.sm};
color: ${themeCssVariables.font.color.primary};
border-radius: ${themeCssVariables.border.radius.sm};
border: 1px solid transparent;
display: flex;
justify-content: space-between;
height: ${({ theme }) => theme.spacing(5)};
padding: calc(${({ theme }) => theme.spacing(1)} - 1px);
height: ${themeCssVariables.spacing[5]};
padding: calc(${themeCssVariables.spacing[1]} - 1px);
width: ${({ isNavigationDrawerExpanded }) =>
isNavigationDrawerExpanded ? '100%' : 'auto'};
gap: ${({ theme, isNavigationDrawerExpanded }) =>
isNavigationDrawerExpanded ? theme.spacing(1) : '0'};
gap: ${({ isNavigationDrawerExpanded }) =>
isNavigationDrawerExpanded ? themeCssVariables.spacing[1] : '0'};
&:hover {
background-color: ${({ theme }) => theme.background.transparent.lighter};
border: 1px solid ${({ theme }) => theme.border.color.medium};
background-color: ${themeCssVariables.background.transparent.lighter};
border: 1px solid ${themeCssVariables.border.color.medium};
}
`;
export const StyledLabel = styled.div`
align-items: center;
display: flex;
font-weight: ${({ theme }) => theme.font.weight.medium};
font-weight: ${themeCssVariables.font.weight.medium};
`;
export const StyledIconChevronDown = styled(IconChevronDown)<{
disabled?: boolean;
}>`
align-items: center;
color: ${({ disabled, theme }) =>
disabled ? theme.font.color.extraLight : theme.font.color.tertiary};
color: ${({ disabled }) =>
disabled
? themeCssVariables.font.color.extraLight
: themeCssVariables.font.color.tertiary};
display: flex;
`;
@@ -1,4 +1,4 @@
import styled from '@emotion/styled';
import { styled } from '@linaria/react';
import { type ReactNode, useState } from 'react';
import { useIsSettingsDrawer } from '@/navigation/hooks/useIsSettingsDrawer';
@@ -18,7 +18,7 @@ import {
import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile';
import { useAtomState } from '@/ui/utilities/state/jotai/hooks/useAtomState';
import { useSetAtomState } from '@/ui/utilities/state/jotai/hooks/useSetAtomState';
import { MOBILE_VIEWPORT } from 'twenty-ui/theme';
import { MOBILE_VIEWPORT, themeCssVariables } from 'twenty-ui/theme-constants';
import { NavigationDrawerBackButton } from './NavigationDrawerBackButton';
import { NavigationDrawerHeader } from './NavigationDrawerHeader';
@@ -39,8 +39,10 @@ const StyledAnimatedContainer = styled.div<{
isExpanded
? `var(${NAVIGATION_DRAWER_WIDTH_VAR})`
: `${NAVIGATION_DRAWER_COLLAPSED_WIDTH}px`};
transition: ${({ isResizing, theme }) =>
isResizing ? 'none' : `width ${theme.animation.duration.normal}s`};
transition: ${({ isResizing }) =>
isResizing
? 'none'
: `width calc(${themeCssVariables.animation.duration.normal} * 1s)`};
@media (max-width: ${MOBILE_VIEWPORT}px) {
width: ${({ isExpanded }) => (isExpanded ? '100%' : '0')};
@@ -57,18 +59,18 @@ const StyledContainer = styled.div<{
flex-direction: column;
width: ${({ isExpanded }) =>
isExpanded ? `var(${NAVIGATION_DRAWER_WIDTH_VAR})` : '100%'};
gap: ${({ theme }) => theme.spacing(3)};
gap: ${themeCssVariables.spacing[3]};
height: 100%;
padding: ${({ theme, isSettings, isMobile }) =>
padding: ${({ isSettings, isMobile }) =>
isSettings
? isMobile
? theme.spacing(3, 0, 0, 8)
: theme.spacing(3, 0, 4, 0)
: theme.spacing(3, 0, 4, 2)};
? `${themeCssVariables.spacing[3]} 0 0 ${themeCssVariables.spacing[8]}`
: `${themeCssVariables.spacing[3]} 0 ${themeCssVariables.spacing[4]} 0`
: `${themeCssVariables.spacing[3]} 0 ${themeCssVariables.spacing[4]} ${themeCssVariables.spacing[2]}`};
@media (max-width: ${MOBILE_VIEWPORT}px) {
width: 100%;
padding-left: ${({ theme }) => theme.spacing(5)};
padding-right: ${({ theme }) => theme.spacing(5)};
padding-left: ${themeCssVariables.spacing[5]};
padding-right: ${themeCssVariables.spacing[5]};
}
`;
@@ -1,24 +1,27 @@
import { useIsSettingsPage } from '@/navigation/hooks/useIsSettingsPage';
import { isNavigationDrawerExpandedState } from '@/ui/navigation/states/isNavigationDrawerExpanded';
import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue';
import { useTheme } from '@emotion/react';
import styled from '@emotion/styled';
import { styled } from '@linaria/react';
import { useContext } from 'react';
import {
type AnimationControls,
motion,
type TargetAndTransition,
} from 'framer-motion';
import { ThemeContext } from 'twenty-ui/theme';
const StyledAnimatedContainer = styled(motion.span)`
const StyledAnimatedContainerBase = styled.span`
display: block;
`;
const StyledAnimatedContainer = motion.create(StyledAnimatedContainerBase);
export const NavigationDrawerAnimatedCollapseWrapper = ({
children,
}: {
children: React.ReactNode;
}) => {
const theme = useTheme();
const { theme } = useContext(ThemeContext);
const isSettingsPage = useIsSettingsPage();
const isNavigationDrawerExpanded = useAtomStateValue(
isNavigationDrawerExpandedState,
@@ -1,5 +1,5 @@
import { useTheme } from '@emotion/react';
import styled from '@emotion/styled';
import { styled } from '@linaria/react';
import { useContext } from 'react';
import { isNavigationDrawerExpandedState } from '@/ui/navigation/states/isNavigationDrawerExpanded';
import { navigationDrawerExpandedMemorizedState } from '@/ui/navigation/states/navigationDrawerExpandedMemorizedState';
@@ -10,6 +10,8 @@ import { useIsWorkspaceActivationStatusEqualsTo } from '@/workspace/hooks/useIsW
import { WorkspaceActivationStatus } from 'twenty-shared/workspace';
import { IconX } from 'twenty-ui/display';
import { UndecoratedLink } from 'twenty-ui/navigation';
import { ThemeContext } from 'twenty-ui/theme';
import { themeCssVariables } from 'twenty-ui/theme-constants';
type NavigationDrawerBackButtonProps = {
title: string;
@@ -19,18 +21,18 @@ const StyledIconAndButtonContainer = styled.button`
align-items: center;
background: inherit;
border: none;
color: ${({ theme }) => theme.font.color.secondary};
color: ${themeCssVariables.font.color.secondary};
cursor: pointer;
display: flex;
flex-direction: row;
font-weight: ${({ theme }) => theme.font.weight.medium};
gap: ${({ theme }) => theme.spacing(2)};
padding: ${({ theme }) => theme.spacing(1.5, 1)};
font-weight: ${themeCssVariables.font.weight.medium};
gap: ${themeCssVariables.spacing[2]};
padding: ${themeCssVariables.spacing['1.5']} ${themeCssVariables.spacing[1]};
width: 100%;
font-family: ${({ theme }) => theme.font.family};
font-family: ${themeCssVariables.font.family};
&:hover {
background: ${({ theme }) => theme.background.transparent.light};
border-radius: ${({ theme }) => theme.border.radius.sm};
background: ${themeCssVariables.background.transparent.light};
border-radius: ${themeCssVariables.border.radius.sm};
}
`;
@@ -38,15 +40,15 @@ const StyledContainer = styled.div`
align-items: center;
display: flex;
flex-direction: row;
height: ${({ theme }) => theme.spacing(8)};
height: ${themeCssVariables.spacing[8]};
justify-content: space-between;
padding-left: ${({ theme }) => theme.spacing(5)};
padding-left: ${themeCssVariables.spacing[5]};
`;
export const NavigationDrawerBackButton = ({
title,
}: NavigationDrawerBackButtonProps) => {
const theme = useTheme();
const { theme } = useContext(ThemeContext);
const navigationMemorizedUrl = useAtomStateValue(navigationMemorizedUrlState);
const setIsNavigationDrawerExpanded = useSetAtomState(
@@ -3,17 +3,18 @@ import { navigationDrawerActiveTabState } from '@/ui/navigation/states/navigatio
import { NAVIGATION_DRAWER_TABS } from '@/ui/navigation/states/navigationDrawerTabs';
import { useAtomState } from '@/ui/utilities/state/jotai/hooks/useAtomState';
import { useSetAtomState } from '@/ui/utilities/state/jotai/hooks/useSetAtomState';
import styled from '@emotion/styled';
import { styled } from '@linaria/react';
import {
IconLayoutSidebarLeftCollapse,
IconLayoutSidebarRightCollapse,
} from 'twenty-ui/display';
import { LightIconButton } from 'twenty-ui/input';
import { themeCssVariables } from 'twenty-ui/theme-constants';
const StyledCollapseButton = styled.div`
align-items: center;
border-radius: ${({ theme }) => theme.border.radius.md};
color: ${({ theme }) => theme.font.color.light};
border-radius: ${themeCssVariables.border.radius.md};
color: ${themeCssVariables.font.color.light};
cursor: pointer;
display: flex;
justify-content: center;
@@ -2,20 +2,22 @@ import { type ReactNode } from 'react';
import { useIsSettingsDrawer } from '@/navigation/hooks/useIsSettingsDrawer';
import { NavigationDrawerSection } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerSection';
import styled from '@emotion/styled';
import { styled } from '@linaria/react';
import { useIsMobile } from 'twenty-ui/utilities';
import { themeCssVariables } from 'twenty-ui/theme-constants';
const StyledFixedContainer = styled.div<{
isSettings?: boolean;
isMobile?: boolean;
}>`
${({ isSettings, theme, isMobile }) =>
padding-left: ${({ isSettings }) =>
isSettings ? themeCssVariables.spacing[5] : '0'};
padding-right: ${({ isSettings, isMobile }) =>
isSettings
? `
padding-left: ${theme.spacing(5)};
padding-right: ${isMobile ? theme.spacing(5) : theme.spacing(8)};
`
: ''}
? isMobile
? themeCssVariables.spacing[5]
: themeCssVariables.spacing[8]
: '0'};
`;
export const NavigationDrawerFixedContent = ({
children,
@@ -1,7 +1,8 @@
import styled from '@emotion/styled';
import { styled } from '@linaria/react';
import { t } from '@lingui/core/macro';
import { IconSearch } from 'twenty-ui/display';
import { LightIconButton } from 'twenty-ui/input';
import { themeCssVariables } from 'twenty-ui/theme-constants';
import { useOpenRecordsSearchPageInCommandMenu } from '@/command-menu/hooks/useOpenRecordsSearchPageInCommandMenu';
import { PAGE_BAR_MIN_HEIGHT } from '@/ui/layout/page/constants/PageBarMinHeight';
@@ -15,28 +16,28 @@ const StyledContainer = styled.div<{ isExpanded: boolean }>`
align-items: center;
display: flex;
flex-direction: ${({ isExpanded }) => (isExpanded ? 'row' : 'column')};
gap: ${({ theme, isExpanded }) => (isExpanded ? 0 : theme.spacing(4))};
gap: ${({ isExpanded }) => (isExpanded ? '0' : themeCssVariables.spacing[4])};
user-select: none;
padding-right: ${({ theme }) => theme.spacing(2)};
padding-right: ${themeCssVariables.spacing[2]};
min-height: ${PAGE_BAR_MIN_HEIGHT}px;
transition: gap ${({ theme }) => theme.animation.duration.normal}s ease;
transition: gap calc(${themeCssVariables.animation.duration.normal} * 1s) ease;
`;
const StyledRightActions = styled.div<{ isExpanded: boolean }>`
align-items: center;
display: flex;
flex-direction: ${({ isExpanded }) => (isExpanded ? 'row' : 'column')};
gap: ${({ theme, isExpanded }) => (isExpanded ? 0 : theme.spacing(1))};
margin-left: ${({ isExpanded }) => (isExpanded ? 'auto' : 0)};
transition: gap ${({ theme }) => theme.animation.duration.normal}s ease;
gap: ${({ isExpanded }) => (isExpanded ? '0' : themeCssVariables.spacing[1])};
margin-left: ${({ isExpanded }) => (isExpanded ? 'auto' : '0')};
transition: gap calc(${themeCssVariables.animation.duration.normal} * 1s) ease;
`;
const StyledNavigationDrawerCollapseButton = styled(
NavigationDrawerCollapseButton,
)`
height: ${({ theme }) => theme.spacing(6)};
padding-right: ${({ theme }) => theme.spacing(1)};
width: ${({ theme }) => theme.spacing(6)};
height: ${themeCssVariables.spacing[6]};
padding-right: ${themeCssVariables.spacing[1]};
width: ${themeCssVariables.spacing[6]};
`;
type NavigationDrawerHeaderProps = {
@@ -9,12 +9,10 @@ import { isNavigationDrawerExpandedState } from '@/ui/navigation/states/isNaviga
import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile';
import { useAtomState } from '@/ui/utilities/state/jotai/hooks/useAtomState';
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
import isPropValid from '@emotion/is-prop-valid';
import { css, useTheme } from '@emotion/react';
import styled from '@emotion/styled';
import { styled } from '@linaria/react';
import { t } from '@lingui/core/macro';
import { isNonEmptyString } from '@sniptt/guards';
import { type ReactNode } from 'react';
import { type ReactNode, useContext } from 'react';
import { Link } from 'react-router-dom';
import { isDefined } from 'twenty-shared/utils';
import { Pill } from 'twenty-ui/components';
@@ -27,7 +25,8 @@ import {
TooltipDelay,
TooltipPosition,
} from 'twenty-ui/display';
import { MOBILE_VIEWPORT } from 'twenty-ui/theme';
import { ThemeContext } from 'twenty-ui/theme';
import { MOBILE_VIEWPORT, themeCssVariables } from 'twenty-ui/theme-constants';
import {
type TriggerEventType,
useMouseDownNavigation,
@@ -81,80 +80,70 @@ type StyledItemProps = Pick<
rel?: string;
};
const StyledItem = styled('button', {
shouldForwardProp: (prop) =>
![
'active',
'danger',
'soon',
'isDragging',
'isSelectedInEditMode',
].includes(prop) && isPropValid(prop),
})<StyledItemProps>`
const StyledItem = styled.button<StyledItemProps>`
box-sizing: border-box;
align-items: center;
background: ${(props) =>
props.active ? props.theme.background.transparent.light : 'inherit'};
height: ${({ theme }) => theme.spacing(7)};
border: ${({ theme, isSelectedInEditMode }) =>
background: ${({ active }) =>
active ? themeCssVariables.background.transparent.light : 'transparent'};
height: ${themeCssVariables.spacing[7]};
border: ${({ isSelectedInEditMode }) =>
isSelectedInEditMode
? `1px solid ${theme.color.blue}`
? `1px solid ${themeCssVariables.color.blue}`
: '1px solid transparent'};
border-radius: ${({ theme }) => theme.border.radius.sm};
border-radius: ${themeCssVariables.border.radius.sm};
text-decoration: none;
color: ${(props) => {
if (props.active === true) {
return props.theme.font.color.primary;
color: ${({ active, danger, soon }) => {
if (active === true) {
return themeCssVariables.font.color.primary;
}
if (props.danger === true) {
return props.theme.color.red;
if (danger === true) {
return themeCssVariables.color.red;
}
if (props.soon === true) {
return props.theme.font.color.light;
if (soon === true) {
return themeCssVariables.font.color.light;
}
return props.theme.font.color.secondary;
return themeCssVariables.font.color.secondary;
}};
cursor: ${(props) => (props.soon ? 'default' : 'pointer')};
cursor: ${({ soon, isDragging }) =>
isDragging ? 'grabbing' : soon ? 'default' : 'pointer'};
display: flex;
font-family: ${({ theme }) => theme.font.family};
font-size: ${({ theme }) => theme.font.size.md};
font-family: ${themeCssVariables.font.family};
font-size: ${themeCssVariables.font.size.md};
padding-bottom: ${({ theme }) => theme.spacing(1)};
padding-left: ${({ theme }) => theme.spacing(1)};
padding-right: ${({ theme, hasRightOptions }) =>
hasRightOptions ? theme.spacing(0.5) : theme.spacing(1)};
padding-top: ${({ theme }) => theme.spacing(1)};
padding-bottom: ${themeCssVariables.spacing[1]};
padding-left: ${themeCssVariables.spacing[1]};
padding-right: ${({ hasRightOptions }) =>
hasRightOptions
? themeCssVariables.spacing['0.5']
: themeCssVariables.spacing[1]};
padding-top: ${themeCssVariables.spacing[1]};
margin-top: ${({ indentationLevel }) =>
indentationLevel === 2 ? '2px' : '0'};
pointer-events: ${(props) => (props.soon ? 'none' : 'auto')};
pointer-events: ${({ soon }) => (soon ? 'none' : 'auto')};
width: ${(props) =>
!props.isNavigationDrawerExpanded
? `calc(${NAVIGATION_DRAWER_COLLAPSED_WIDTH}px - ${props.theme.spacing(6)} + ${props.theme.spacing(1)} + ${props.hasRightOptions ? props.theme.spacing(0.5) : props.theme.spacing(1)})`
: `calc(100% - ${props.theme.spacing(1.5)} + ${props.theme.spacing(1)} + ${props.hasRightOptions ? props.theme.spacing(0.5) : props.theme.spacing(1)})`};
width: ${({ isNavigationDrawerExpanded, hasRightOptions }) =>
!isNavigationDrawerExpanded
? `calc(${NAVIGATION_DRAWER_COLLAPSED_WIDTH}px - ${themeCssVariables.spacing[6]} + ${themeCssVariables.spacing[1]} + ${hasRightOptions ? themeCssVariables.spacing['0.5'] : themeCssVariables.spacing[1]})`
: `calc(100% - ${themeCssVariables.spacing['1.5']} + ${themeCssVariables.spacing[1]} + ${hasRightOptions ? themeCssVariables.spacing['0.5'] : themeCssVariables.spacing[1]})`};
${({ isDragging }) =>
isDragging &&
`
cursor: grabbing;
`}
:hover {
background: ${({ theme }) => theme.background.transparent.light};
color: ${(props) =>
props.danger ? props.theme.color.red : props.theme.font.color.primary};
&:hover {
background: ${themeCssVariables.background.transparent.light};
color: ${({ danger }) =>
danger
? themeCssVariables.color.red
: themeCssVariables.font.color.primary};
}
:hover .keyboard-shortcuts {
&:hover .keyboard-shortcuts {
visibility: visible;
}
user-select: none;
@media (max-width: ${MOBILE_VIEWPORT}px) {
font-size: ${({ theme }) => theme.font.size.lg};
font-size: ${themeCssVariables.font.size.lg};
}
`;
@@ -175,22 +164,22 @@ const StyledLabelParent = styled.div`
`;
const StyledItemLabel = styled.span`
font-weight: ${({ theme }) => theme.font.weight.medium};
font-weight: ${themeCssVariables.font.weight.medium};
`;
const StyledItemSecondaryLabel = styled.span`
color: ${({ theme }) => theme.font.color.light};
font-weight: ${({ theme }) => theme.font.weight.regular};
color: ${themeCssVariables.font.color.light};
font-weight: ${themeCssVariables.font.weight.regular};
`;
const StyledItemCount = styled.span`
align-items: center;
background-color: ${({ theme }) => theme.color.blue};
border-radius: ${({ theme }) => theme.border.radius.rounded};
color: ${({ theme }) => theme.grayScale.gray1};
background-color: ${themeCssVariables.color.blue};
border-radius: ${themeCssVariables.border.radius.rounded};
color: ${themeCssVariables.grayScale.gray1};
display: flex;
font-size: ${({ theme }) => theme.font.size.xs};
font-weight: ${({ theme }) => theme.font.weight.semiBold};
font-size: ${themeCssVariables.font.size.xs};
font-weight: ${themeCssVariables.font.weight.semiBold};
height: 16px;
justify-content: center;
margin-left: auto;
@@ -201,15 +190,15 @@ const StyledKeyBoardShortcut = styled.span`
align-items: center;
display: flex;
flex-direction: column;
gap: ${({ theme }) => theme.spacing(2)};
height: ${({ theme }) => theme.spacing(4)};
gap: ${themeCssVariables.spacing[2]};
height: ${themeCssVariables.spacing[4]};
justify-content: center;
width: ${({ theme }) => theme.spacing(4)};
width: ${themeCssVariables.spacing[4]};
box-sizing: border-box;
border-radius: ${({ theme }) => theme.border.radius.sm};
border: 1px solid ${({ theme }) => theme.border.color.strong};
background: ${({ theme }) => theme.background.transparent.lighter};
border-radius: ${themeCssVariables.border.radius.sm};
border: 1px solid ${themeCssVariables.border.color.strong};
background: ${themeCssVariables.background.transparent.lighter};
`;
const StyledNavigationDrawerItemContainer = styled.div`
@@ -230,18 +219,18 @@ const StyledIcon = styled.div<{
flex-grow: 0;
flex-shrink: 0;
justify-content: center;
margin-right: ${({ theme }) => theme.spacing(2)};
${({ theme, $backgroundColor, $borderColor }) =>
$backgroundColor &&
css`
background-color: ${$backgroundColor};
border-radius: 4px;
box-sizing: border-box;
height: ${theme.spacing(4)};
width: ${theme.spacing(4)};
${$borderColor ? `border: 1px solid ${$borderColor};` : ''}
`}
margin-right: ${themeCssVariables.spacing[2]};
background-color: ${({ $backgroundColor }) =>
$backgroundColor || 'transparent'};
border-radius: ${({ $backgroundColor }) => ($backgroundColor ? '4px' : '0')};
box-sizing: ${({ $backgroundColor }) =>
$backgroundColor ? 'border-box' : 'content-box'};
height: ${({ $backgroundColor }) =>
$backgroundColor ? themeCssVariables.spacing[4] : 'auto'};
width: ${({ $backgroundColor }) =>
$backgroundColor ? themeCssVariables.spacing[4] : 'auto'};
border: ${({ $backgroundColor, $borderColor }) =>
$backgroundColor && $borderColor ? `1px solid ${$borderColor}` : 'none'};
`;
const StyledRightOptionsContainer = styled.div`
@@ -250,42 +239,31 @@ const StyledRightOptionsContainer = styled.div`
justify-content: center;
flex-shrink: 0;
flex-grow: 0;
height: ${({ theme }) => theme.spacing(6)};
border-radius: ${({ theme }) => theme.border.radius.sm};
height: ${themeCssVariables.spacing[6]};
border-radius: ${themeCssVariables.border.radius.sm};
`;
const visibleStateStyles = css`
clip-path: unset;
display: flex;
height: unset;
opacity: 1;
overflow: unset;
position: unset;
width: unset;
`;
const StyledRightOptionsVisbility = styled.div<{
isMobile: boolean;
isRightOptionsDropdownOpen?: boolean;
alwaysVisible?: boolean;
}>`
const StyledRightOptionsVisbility = styled.div`
display: block;
opacity: 0;
transition: opacity 150ms;
position: absolute;
padding-left: ${({ theme }) => theme.spacing(2)};
padding-left: ${themeCssVariables.spacing[2]};
overflow: hidden;
clip-path: inset(1px);
white-space: nowrap;
height: 1px;
width: 1px;
${({ isMobile, isRightOptionsDropdownOpen, alwaysVisible }) =>
(isMobile || isRightOptionsDropdownOpen || alwaysVisible) &&
visibleStateStyles}
&[data-visible='true'],
.navigation-drawer-item:hover & {
${visibleStateStyles}
clip-path: unset;
display: flex;
height: unset;
opacity: 1;
overflow: unset;
position: unset;
width: unset;
}
`;
@@ -314,7 +292,7 @@ export const NavigationDrawerItem = ({
preventCollapseOnMobile = false,
isSelectedInEditMode = false,
}: NavigationDrawerItemProps) => {
const theme = useTheme();
const { theme } = useContext(ThemeContext);
const isMobile = useIsMobile();
const isSettingsPage = useIsSettingsPage();
const isNavigationMenuItemEditingEnabled = useIsFeatureEnabled(
@@ -472,10 +450,12 @@ export const NavigationDrawerItem = ({
}}
>
<StyledRightOptionsVisbility
alwaysVisible={alwaysShowRightOptions}
isMobile={isMobile}
isRightOptionsDropdownOpen={
isRightOptionsDropdownOpen || false
data-visible={
isMobile ||
isRightOptionsDropdownOpen ||
alwaysShowRightOptions
? 'true'
: undefined
}
>
{rightOptions}
@@ -1,5 +1,6 @@
import { type NavigationDrawerSubItemState } from '@/ui/navigation/navigation-drawer/types/NavigationDrawerSubItemState';
import styled from '@emotion/styled';
import { styled } from '@linaria/react';
import { themeCssVariables } from 'twenty-ui/theme-constants';
export type NavigationDrawerItemBreadcrumbProps = {
state?: NavigationDrawerSubItemState;
@@ -9,13 +10,15 @@ const StyledNavigationDrawerItemBreadcrumbContainer = styled.div`
height: 28px;
margin-left: 7.5px;
margin-right: ${({ theme }) => theme.spacing(2)};
margin-right: ${themeCssVariables.spacing[2]};
width: 9px;
`;
const StyledGapVerticalLine = styled.div<{ darker: boolean }>`
background: ${({ theme, darker }) =>
darker ? theme.font.color.tertiary : theme.border.color.strong};
background: ${({ darker }) =>
darker
? themeCssVariables.font.color.tertiary
: themeCssVariables.border.color.strong};
position: relative;
top: -2px;
@@ -25,8 +28,10 @@ const StyledGapVerticalLine = styled.div<{ darker: boolean }>`
`;
const StyledSecondaryFullVerticalBar = styled.div<{ darker: boolean }>`
background: ${({ theme, darker }) =>
darker ? theme.font.color.tertiary : theme.border.color.strong};
background: ${({ darker }) =>
darker
? themeCssVariables.font.color.tertiary
: themeCssVariables.border.color.strong};
position: relative;
top: -17px;
@@ -35,21 +40,23 @@ const StyledSecondaryFullVerticalBar = styled.div<{ darker: boolean }>`
`;
const StyledRoundedProtrusion = styled.div<{ darker: boolean }>`
position: relative;
top: -2px;
border: 1px solid
${({ darker }) =>
darker
? themeCssVariables.font.color.tertiary
: themeCssVariables.border.color.strong};
border-bottom-left-radius: 4px;
border: 1px solid
${({ theme, darker }) =>
darker ? theme.font.color.tertiary : theme.border.color.strong};
${({ darker }) => (darker ? 'z-index: 1;' : '')}
border-right: none;
border-top: none;
border-right: none;
height: 14px;
position: relative;
top: -2px;
width: 8px;
z-index: ${({ darker }) => (darker ? '1' : 'auto')};
`;
export const NavigationDrawerItemBreadcrumb = ({
@@ -1,4 +1,4 @@
import styled from '@emotion/styled';
import { styled } from '@linaria/react';
const StyledGroup = styled.div`
display: flex;
@@ -1,16 +1,20 @@
import { useIsSettingsPage } from '@/navigation/hooks/useIsSettingsPage';
import { isNavigationDrawerExpandedState } from '@/ui/navigation/states/isNavigationDrawerExpanded';
import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue';
import { useTheme } from '@emotion/react';
import styled from '@emotion/styled';
import { styled } from '@linaria/react';
import { useContext, type ReactNode } from 'react';
import {
type AnimationControls,
motion,
type TargetAndTransition,
} from 'framer-motion';
import { type ReactNode } from 'react';
import { ThemeContext } from 'twenty-ui/theme';
const StyledAnimationGroupContainer = styled(motion.div)``;
const StyledAnimationGroupContainerBase = styled.div``;
const StyledAnimationGroupContainer = motion.create(
StyledAnimationGroupContainerBase,
);
type NavigationDrawerItemsCollapsableContainerProps = {
isGroup?: boolean;
@@ -21,7 +25,7 @@ export const NavigationDrawerItemsCollapsableContainer = ({
isGroup = false,
children,
}: NavigationDrawerItemsCollapsableContainerProps) => {
const theme = useTheme();
const { theme } = useContext(ThemeContext);
const isSettingsPage = useIsSettingsPage();
const isNavigationDrawerExpanded = useAtomStateValue(
isNavigationDrawerExpandedState,
@@ -1,8 +1,9 @@
import { useIsSettingsDrawer } from '@/navigation/hooks/useIsSettingsDrawer';
import { ScrollWrapper } from '@/ui/utilities/scroll/components/ScrollWrapper';
import styled from '@emotion/styled';
import { styled } from '@linaria/react';
import { type ReactNode } from 'react';
import { useIsMobile } from 'twenty-ui/utilities';
import { themeCssVariables } from 'twenty-ui/theme-constants';
const StyledItemsContainer = styled.div`
display: flex;
@@ -14,9 +15,9 @@ const StyledItemsContainer = styled.div`
const StyledScrollableInnerContainer = styled.div<{ isMobile?: boolean }>`
height: 100%;
padding-left: ${({ theme }) => theme.spacing(5)};
padding-right: ${({ theme, isMobile }) =>
isMobile ? theme.spacing(5) : theme.spacing(8)};
padding-left: ${themeCssVariables.spacing[5]};
padding-right: ${({ isMobile }) =>
isMobile ? themeCssVariables.spacing[5] : themeCssVariables.spacing[8]};
`;
export const NavigationDrawerScrollableContent = ({
@@ -1,10 +1,11 @@
import { useIsSettingsDrawer } from '@/navigation/hooks/useIsSettingsDrawer';
import styled from '@emotion/styled';
import { styled } from '@linaria/react';
import { useIsMobile } from 'twenty-ui/utilities';
import { themeCssVariables } from 'twenty-ui/theme-constants';
const StyledSection = styled.div<{ isSettingsDrawer?: boolean }>`
margin-bottom: ${({ theme, isSettingsDrawer }) =>
isSettingsDrawer ? theme.spacing(3) : 0};
margin-bottom: ${({ isSettingsDrawer }) =>
isSettingsDrawer ? themeCssVariables.spacing[3] : '0'};
width: 100%;
`;
@@ -14,9 +15,11 @@ const StyledSectionInnerContainerMinusScrollPadding = styled.div<{
}>`
display: flex;
flex-direction: column;
gap: ${({ theme }) => theme.betweenSiblingsGap};
width: ${({ isMobile, theme, isSettingsDrawer }) =>
`calc(100% - ${isMobile || isSettingsDrawer ? 0 : theme.spacing(2)})`};
gap: ${themeCssVariables.betweenSiblingsGap};
width: ${({ isMobile, isSettingsDrawer }) =>
isMobile || isSettingsDrawer
? '100%'
: `calc(100% - ${themeCssVariables.spacing[2]})`};
`;
export const NavigationDrawerSection = ({
@@ -5,25 +5,26 @@ import { NavigationDrawerSectionTitleSkeletonLoader } from '@/ui/navigation/navi
import { isNavigationDrawerExpandedState } from '@/ui/navigation/states/isNavigationDrawerExpanded';
import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile';
import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue';
import styled from '@emotion/styled';
import { styled } from '@linaria/react';
import React from 'react';
import { isDefined } from 'twenty-shared/utils';
import { Label } from 'twenty-ui/display';
import { themeCssVariables } from 'twenty-ui/theme-constants';
const StyledTitle = styled.div`
align-items: center;
border-radius: ${({ theme }) => theme.border.radius.sm};
border-radius: ${themeCssVariables.border.radius.sm};
display: flex;
height: ${({ theme }) => theme.spacing(5)};
padding-left: ${({ theme }) => theme.spacing(1)};
padding-right: ${({ theme }) => theme.spacing(0.5)};
padding-top: ${({ theme }) => theme.spacing(1)};
padding-bottom: ${({ theme }) => theme.spacing(1)};
height: ${themeCssVariables.spacing[5]};
padding-left: ${themeCssVariables.spacing[1]};
padding-right: ${themeCssVariables.spacing['0.5']};
padding-top: ${themeCssVariables.spacing[1]};
padding-bottom: ${themeCssVariables.spacing[1]};
justify-content: space-between;
&:hover {
cursor: pointer;
background-color: ${({ theme }) => theme.background.transparent.light};
background-color: ${themeCssVariables.background.transparent.light};
}
`;
@@ -1,15 +1,17 @@
import { SKELETON_LOADER_HEIGHT_SIZES } from '@/activities/components/SkeletonLoader';
import { useTheme } from '@emotion/react';
import styled from '@emotion/styled';
import { styled } from '@linaria/react';
import { useContext } from 'react';
import Skeleton, { SkeletonTheme } from 'react-loading-skeleton';
import { ThemeContext } from 'twenty-ui/theme';
import { themeCssVariables } from 'twenty-ui/theme-constants';
const StyledSkeletonTitle = styled.div`
margin-bottom: ${(props) => props.theme.spacing(2)};
padding-left: ${({ theme }) => theme.spacing(1)};
margin-bottom: ${themeCssVariables.spacing[2]};
padding-left: ${themeCssVariables.spacing[1]};
`;
export const NavigationDrawerSectionTitleSkeletonLoader = () => {
const theme = useTheme();
const { theme } = useContext(ThemeContext);
return (
<SkeletonTheme
baseColor={theme.background.tertiary}
@@ -1,4 +1,4 @@
import styled from '@emotion/styled';
import { styled } from '@linaria/react';
import { type Meta, type StoryObj } from '@storybook/react-vite';
import { NavigationDrawerItem } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerItem';
@@ -1,5 +1,5 @@
import { createAtomState } from '@/ui/utilities/state/jotai/utils/createAtomState';
import { MOBILE_VIEWPORT } from 'twenty-ui/theme';
import { MOBILE_VIEWPORT } from 'twenty-ui/theme-constants';
const isMobile = window.innerWidth <= MOBILE_VIEWPORT;
@@ -1,4 +1,4 @@
import { MOBILE_VIEWPORT } from 'twenty-ui/theme';
import { MOBILE_VIEWPORT } from 'twenty-ui/theme-constants';
import { createAtomState } from '@/ui/utilities/state/jotai/utils/createAtomState';
@@ -1,10 +1,11 @@
import { useTheme } from '@emotion/react';
import styled from '@emotion/styled';
import { styled } from '@linaria/react';
import { useContext } from 'react';
import { motion } from 'framer-motion';
import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile';
import { AnimatedCheckmark } from 'twenty-ui/display';
import { MOBILE_VIEWPORT } from 'twenty-ui/theme';
import { ThemeContext } from 'twenty-ui/theme';
import { MOBILE_VIEWPORT, themeCssVariables } from 'twenty-ui/theme-constants';
const StyledContainer = styled.div<{ isLast: boolean }>`
align-items: center;
@@ -15,15 +16,15 @@ const StyledContainer = styled.div<{ isLast: boolean }>`
}
`;
const StyledStepCircle = styled(motion.div)<{ isInNextSteps: boolean }>`
const StyledStepCircleBase = styled.div<{ isInNextSteps: boolean }>`
align-items: center;
border-radius: 50%;
border-style: solid;
border-width: 1px;
border-color: ${({ theme, isInNextSteps }) =>
border-color: ${({ isInNextSteps }) =>
isInNextSteps
? theme.border.color.medium
: theme.border.color.inverted} !important;
? themeCssVariables.border.color.medium
: themeCssVariables.border.color.inverted} !important;
display: flex;
flex-basis: auto;
flex-shrink: 0;
@@ -34,30 +35,38 @@ const StyledStepCircle = styled(motion.div)<{ isInNextSteps: boolean }>`
width: 20px;
`;
const StyledStepCircle = motion.create(StyledStepCircleBase);
const StyledStepIndex = styled.span<{ isCurrentStep: boolean }>`
color: ${({ theme, isCurrentStep }) =>
isCurrentStep ? theme.font.color.inverted : theme.font.color.tertiary};
font-size: ${({ theme }) => theme.font.size.md};
font-weight: ${({ theme }) => theme.font.weight.medium};
color: ${({ isCurrentStep }) =>
isCurrentStep
? themeCssVariables.font.color.inverted
: themeCssVariables.font.color.tertiary};
font-size: ${themeCssVariables.font.size.md};
font-weight: ${themeCssVariables.font.weight.medium};
`;
const StyledStepLabel = styled.span<{ isInNextSteps: boolean }>`
color: ${({ theme, isInNextSteps }) =>
isInNextSteps ? theme.font.color.tertiary : theme.font.color.primary};
font-size: ${({ theme }) => theme.font.size.md};
font-weight: ${({ theme }) => theme.font.weight.semiBold};
margin-left: ${({ theme }) => theme.spacing(2)};
color: ${({ isInNextSteps }) =>
isInNextSteps
? themeCssVariables.font.color.tertiary
: themeCssVariables.font.color.primary};
font-size: ${themeCssVariables.font.size.md};
font-weight: ${themeCssVariables.font.weight.semiBold};
margin-left: ${themeCssVariables.spacing[2]};
white-space: nowrap;
`;
const StyledStepLine = styled(motion.div)`
const StyledStepLineBase = styled.div`
height: 2px;
margin-left: ${({ theme }) => theme.spacing(2)};
margin-right: ${({ theme }) => theme.spacing(2)};
margin-left: ${themeCssVariables.spacing[2]};
margin-right: ${themeCssVariables.spacing[2]};
overflow: hidden;
width: 100%;
`;
const StyledStepLine = motion.create(StyledStepLineBase);
export type StepProps = React.PropsWithChildren &
React.ComponentProps<'div'> & {
isLast?: boolean;
@@ -73,7 +82,7 @@ export const Step = ({
children,
activeStep = 0,
}: StepProps) => {
const theme = useTheme();
const { theme } = useContext(ThemeContext);
const isMobile = useIsMobile();
const variantsLine = {
@@ -1,9 +1,9 @@
import styled from '@emotion/styled';
import { styled } from '@linaria/react';
import React from 'react';
import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile';
import { MOBILE_VIEWPORT } from 'twenty-ui/theme';
import { MOBILE_VIEWPORT } from 'twenty-ui/theme-constants';
import { Step, type StepProps } from './Step';
const StyledContainer = styled.div`
@@ -1,4 +1,3 @@
import { ThemeProvider } from '@emotion/react';
import { createContext } from 'react';
import { useSystemColorScheme } from '@/ui/theme/hooks/useSystemColorScheme';
@@ -32,9 +31,7 @@ export const BaseThemeProvider = ({ children }: BaseThemeProviderProps) => {
return (
<ThemeSchemeContext.Provider value={setPersistedColorScheme}>
<ThemeProvider theme={theme}>
<ThemeContextProvider theme={theme}>{children}</ThemeContextProvider>
</ThemeProvider>
<ThemeContextProvider theme={theme}>{children}</ThemeContextProvider>
</ThemeSchemeContext.Provider>
);
};
@@ -1,4 +1,4 @@
import styled from '@emotion/styled';
import { styled } from '@linaria/react';
import { type RefObject, useCallback, useState } from 'react';
import { useDragSelect } from '@/ui/utilities/drag-select/hooks/useDragSelect';
@@ -23,12 +23,14 @@ type Position = {
y: number;
};
import { themeCssVariables } from 'twenty-ui/theme-constants';
const StyledDragSelection = styled.div<SelectionBox>`
position: absolute;
z-index: 99;
opacity: 0.2;
border: 1px solid ${({ theme }) => theme.color.blue3};
background: ${({ theme }) => theme.color.blue7};
border: 1px solid ${themeCssVariables.color.blue3};
background: ${themeCssVariables.color.blue7};
top: ${({ top }) => top}px;
left: ${({ left }) => left}px;
width: ${({ width }) => width}px;
@@ -1,4 +1,4 @@
import styled from '@emotion/styled';
import { styled } from '@linaria/react';
import { type Meta, type StoryObj } from '@storybook/react-vite';
import { useRef, useState } from 'react';
import { userEvent, within } from 'storybook/test';
@@ -7,13 +7,14 @@ import { DragSelect } from '@/ui/utilities/drag-select/components/DragSelect';
import { ScrollWrapper } from '@/ui/utilities/scroll/components/ScrollWrapper';
import { isDefined } from 'twenty-shared/utils';
import { ComponentDecorator } from 'twenty-ui/testing';
import { themeCssVariables } from 'twenty-ui/theme-constants';
const StyledContainer = styled.div`
border: 1px solid ${({ theme }) => theme.border.color.light};
border-radius: ${({ theme }) => theme.border.radius.md};
border: 1px solid ${themeCssVariables.border.color.light};
border-radius: ${themeCssVariables.border.radius.md};
height: 400px;
overflow: hidden;
padding: ${({ theme }) => theme.spacing(4)};
padding: ${themeCssVariables.spacing[4]};
position: relative;
width: 600px;
`;
@@ -21,27 +22,31 @@ const StyledContainer = styled.div`
const StyledSelectableItem = styled.div<{ selected?: boolean }>`
width: 100px;
height: 80px;
border: 1px solid ${({ theme }) => theme.border.color.medium};
border-radius: ${({ theme }) => theme.border.radius.sm};
background: ${({ theme, selected }) =>
selected ? theme.color.blue3 : theme.background.secondary};
border: 1px solid ${themeCssVariables.border.color.medium};
border-radius: ${themeCssVariables.border.radius.sm};
background: ${({ selected }) =>
selected
? themeCssVariables.color.blue3
: themeCssVariables.background.secondary};
display: flex;
align-items: center;
justify-content: center;
margin: ${({ theme }) => theme.spacing(1)};
margin: ${themeCssVariables.spacing[1]};
user-select: none;
cursor: pointer;
&:hover {
background: ${({ theme, selected }) =>
selected ? theme.color.blue5 : theme.background.tertiary};
background: ${({ selected }) =>
selected
? themeCssVariables.color.blue5
: themeCssVariables.background.tertiary};
}
`;
const StyledGrid = styled.div`
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: ${({ theme }) => theme.spacing(2)};
gap: ${themeCssVariables.spacing[2]};
width: 100%;
height: 100%;
`;
@@ -49,15 +54,15 @@ const StyledGrid = styled.div`
const StyledLargeGrid = styled.div`
display: grid;
grid-template-columns: repeat(6, 1fr);
gap: ${({ theme }) => theme.spacing(2)};
padding: ${({ theme }) => theme.spacing(4)};
gap: ${themeCssVariables.spacing[2]};
padding: ${themeCssVariables.spacing[4]};
width: 900px;
height: 600px;
`;
const StyledScrollableWrapper = styled(ScrollWrapper)`
border: 1px solid ${({ theme }) => theme.border.color.light};
border-radius: ${({ theme }) => theme.border.radius.md};
border: 1px solid ${themeCssVariables.border.color.light};
border-radius: ${themeCssVariables.border.radius.md};
height: 300px;
width: 600px;
`;
@@ -1,5 +1,5 @@
import { useMediaQuery } from 'react-responsive';
import { MOBILE_VIEWPORT } from 'twenty-ui/theme';
import { MOBILE_VIEWPORT } from 'twenty-ui/theme-constants';
export const useIsMobile = () =>
useMediaQuery({ query: `(max-width: ${MOBILE_VIEWPORT}px)` });
@@ -1,4 +1,4 @@
import styled from '@emotion/styled';
import { styled } from '@linaria/react';
import { ScrollWrapperInitEffect } from '@/ui/utilities/scroll/components/internal/ScrollWrapperInitEffect';
import { ScrollWrapperComponentInstanceContext } from '@/ui/utilities/scroll/states/contexts/ScrollWrapperComponentInstanceContext';