Remove CSS modules (#6017)

CSS modules were used as a first test for performance optimization.

We later found out that Linaria was a better tradeoff.

This PR removes what was implemented in CSS modules and also the CSS
theme file that was created that was overlapping with the TS theme
files.
This commit is contained in:
Lucas Bordeau 2024-06-30 21:54:11 +02:00 committed by GitHub
parent afb3f4b7b7
commit be1503c719
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
19 changed files with 164 additions and 278 deletions

View File

@ -1,32 +0,0 @@
.td-in-edit-mode {
z-index: 4 !important;
}
.td-not-in-edit-mode {
z-index: 3;
}
.td-is-selected {
background: var(--twentycrm-accent-quaternary);
}
.td-is-not-selected {
background: var(--twentycrm-background-primary);
}
.cell-base-container {
align-items: center;
box-sizing: border-box;
cursor: pointer;
display: flex;
height: 32px;
position: relative;
user-select: none;
}
.cell-base-container-soft-focus {
background: var(--twentycrm-background-transparent-secondary);
border-radius: var(--twentycrm-border-radius-sm);
border: 1px solid var(--twentycrm-font-color-extra-light);
}

View File

@ -1,6 +1,7 @@
import React, { ReactElement, useContext } from 'react';
import { clsx } from 'clsx';
import { styled } from '@linaria/react';
import { useRecoilValue } from 'recoil';
import { BORDER_COMMON, ThemeContext } from 'twenty-ui';
import { useFieldFocus } from '@/object-record/record-field/hooks/useFieldFocus';
import { RecordTableContext } from '@/object-record/record-table/contexts/RecordTableContext';
@ -22,7 +23,38 @@ import { TableHotkeyScope } from '../../types/TableHotkeyScope';
import { RecordTableCellDisplayMode } from './RecordTableCellDisplayMode';
import { RecordTableCellEditMode } from './RecordTableCellEditMode';
import styles from './RecordTableCellContainer.module.css';
const StyledTd = styled.td<{
isInEditMode: boolean;
backgroundColor: string;
}>`
background: ${({ backgroundColor }) => backgroundColor};
z-index: ${({ isInEditMode }) => (isInEditMode ? '4 !important' : 3)};
`;
const borderRadiusSm = BORDER_COMMON.radius.sm;
const StyledBaseContainer = styled.div<{
hasSoftFocus: boolean;
fontColorExtraLight: string;
backgroundColorTransparentSecondary: string;
}>`
align-items: center;
box-sizing: border-box;
cursor: pointer;
display: flex;
height: 32px;
position: relative;
user-select: none;
background: ${({ hasSoftFocus, backgroundColorTransparentSecondary }) =>
hasSoftFocus ? backgroundColorTransparentSecondary : 'none'};
border-radius: ${({ hasSoftFocus }) =>
hasSoftFocus ? borderRadiusSm : 'none'};
border: ${({ hasSoftFocus, fontColorExtraLight }) =>
hasSoftFocus ? `1px solid ${fontColorExtraLight}` : 'none'};
`;
export type RecordTableCellContainerProps = {
editModeContent: ReactElement;
@ -43,6 +75,8 @@ export const RecordTableCellContainer = ({
nonEditModeContent,
editHotkeyScope,
}: RecordTableCellContainerProps) => {
const { theme } = useContext(ThemeContext);
const { setIsFocused } = useFieldFocus();
const { openTableCell } = useOpenRecordTableCellFromCell();
@ -100,27 +134,28 @@ export const RecordTableCellContainer = ({
}
};
const tdBackgroundColor = isSelected
? theme.accent.quaternary
: theme.background.primary;
return (
<td
className={clsx({
[styles.tdInEditMode]: isInEditMode,
[styles.tdNotInEditMode]: !isInEditMode,
[styles.tdIsSelected]: isSelected,
[styles.tdIsNotSelected]: !isSelected,
})}
<StyledTd
backgroundColor={tdBackgroundColor}
isInEditMode={isInEditMode}
onContextMenu={handleContextMenu}
>
<CellHotkeyScopeContext.Provider
value={editHotkeyScope ?? DEFAULT_CELL_SCOPE}
>
<div
<StyledBaseContainer
onMouseLeave={handleContainerMouseLeave}
onMouseMove={handleContainerMouseMove}
onClick={handleContainerClick}
className={clsx({
[styles.cellBaseContainer]: true,
[styles.cellBaseContainerSoftFocus]: hasSoftFocus,
})}
backgroundColorTransparentSecondary={
theme.background.transparent.secondary
}
fontColorExtraLight={theme.font.color.extraLight}
hasSoftFocus={hasSoftFocus}
>
{isInEditMode ? (
<RecordTableCellEditMode>{editModeContent}</RecordTableCellEditMode>
@ -134,8 +169,8 @@ export const RecordTableCellContainer = ({
{nonEditModeContent}
</RecordTableCellDisplayMode>
)}
</div>
</StyledBaseContainer>
</CellHotkeyScopeContext.Provider>
</td>
</StyledTd>
);
};

View File

@ -1,21 +0,0 @@
.cell-display-outer-container {
align-items: center;
display: flex;
height: 100%;
overflow: hidden;
padding-left: 6px;
width: 100%;
}
.cell-display-with-soft-focus {
margin: -1px;
}
.cell-display-inner-container {
align-items: center;
display: flex;
height: 100%;
overflow: hidden;
width: 100%;
}

View File

@ -1,7 +1,26 @@
import { Ref } from 'react';
import clsx from 'clsx';
import { styled } from '@linaria/react';
import styles from './RecordTableCellDisplayContainer.module.css';
const StyledOuterContainer = styled.div<{
hasSoftFocus?: boolean;
}>`
align-items: center;
display: flex;
height: 100%;
overflow: hidden;
padding-left: 6px;
width: 100%;
margin: ${({ hasSoftFocus }) => (hasSoftFocus === true ? '-1px' : 'none')};
`;
const StyledInnerContainer = styled.div`
align-items: center;
display: flex;
height: 100%;
overflow: hidden;
width: 100%;
`;
export type EditableCellDisplayContainerProps = {
softFocus?: boolean;
@ -16,17 +35,14 @@ export const RecordTableCellDisplayContainer = ({
onClick,
scrollRef,
}: React.PropsWithChildren<EditableCellDisplayContainerProps>) => (
<div
<StyledOuterContainer
data-testid={
softFocus ? 'editable-cell-soft-focus-mode' : 'editable-cell-display-mode'
}
onClick={onClick}
className={clsx({
[styles.cellDisplayOuterContainer]: true,
[styles.cellDisplayWithSoftFocus]: softFocus,
})}
ref={scrollRef}
hasSoftFocus={softFocus}
>
<div className={clsx(styles.cellDisplayInnerContainer)}>{children}</div>
</div>
<StyledInnerContainer>{children}</StyledInnerContainer>
</StyledOuterContainer>
);

View File

@ -64,6 +64,9 @@ export default defineConfig(({ command, mode }) => {
'**/Tag.tsx',
'**/MultiSelectFieldDisplay.tsx',
'**/RatingInput.tsx',
'**/RecordTableCellContainer.tsx',
'**/RecordTableCellDisplayContainer.tsx',
'**/Avatar.tsx',
],
babelOptions: {
presets: ['@babel/preset-typescript', '@babel/preset-react'],

View File

@ -1,23 +0,0 @@
.avatar {
align-items: center;
border-radius: 2px;
display: flex;
flex-shrink: 0;
justify-content: center;
overflow: hidden;
user-select: none;
}
.rounded {
border-radius: 50%;
}
.avatar-on-click:hover {
box-shadow: 0 0 0 4px var(--twentycrm-background-transparent-light);
}
.avatar-image {
object-fit: cover;
width: 100%;
height: 100%;
}

View File

@ -1,15 +1,49 @@
import { useContext } from 'react';
import { styled } from '@linaria/react';
import { isNonEmptyString, isUndefined } from '@sniptt/guards';
import clsx from 'clsx';
import { useRecoilState } from 'recoil';
import { invalidAvatarUrlsState } from '@ui/display/avatar/components/states/isInvalidAvatarUrlState';
import { AVATAR_PROPERTIES_BY_SIZE } from '@ui/display/avatar/constants/AvatarPropertiesBySize';
import { AvatarSize } from '@ui/display/avatar/types/AvatarSize';
import { AvatarType } from '@ui/display/avatar/types/AvatarType';
import { ThemeContext } from '@ui/theme';
import { Nullable, stringToHslColor } from '@ui/utilities';
import styles from './Avatar.module.css';
const StyledAvatar = styled.div<{
size: AvatarSize;
rounded?: boolean;
clickable?: boolean;
color: string;
backgroundColor: string;
backgroundTransparentLight: string;
}>`
align-items: center;
flex-shrink: 0;
overflow: hidden;
user-select: none;
export type AvatarType = 'squared' | 'rounded';
border-radius: ${({ rounded }) => (rounded ? '50%' : '2px')};
display: flex;
font-size: ${({ size }) => AVATAR_PROPERTIES_BY_SIZE[size].fontSize};
height: ${({ size }) => AVATAR_PROPERTIES_BY_SIZE[size].width};
justify-content: center;
export type AvatarSize = 'xl' | 'lg' | 'md' | 'sm' | 'xs';
width: ${({ size }) => AVATAR_PROPERTIES_BY_SIZE[size].width};
color: ${({ color }) => color};
background: ${({ backgroundColor }) => backgroundColor};
&:hover {
box-shadow: ${({ clickable, backgroundTransparentLight }) =>
clickable ? `0 0 0 4px ${backgroundTransparentLight}` : 'none'};
}
`;
const StyledImage = styled.img`
height: 100%;
object-fit: cover;
width: 100%;
`;
export type AvatarProps = {
avatarUrl?: string | null;
@ -23,29 +57,7 @@ export type AvatarProps = {
onClick?: () => void;
};
const propertiesBySize = {
xl: {
fontSize: '16px',
width: '40px',
},
lg: {
fontSize: '13px',
width: '24px',
},
md: {
fontSize: '12px',
width: '16px',
},
sm: {
fontSize: '10px',
width: '14px',
},
xs: {
fontSize: '8px',
width: '12px',
},
};
// TODO: Remove recoil because we don't want it into twenty-ui and find a solution for invalid avatar urls
export const Avatar = ({
avatarUrl,
size = 'md',
@ -56,6 +68,7 @@ export const Avatar = ({
color,
backgroundColor,
}: AvatarProps) => {
const { theme } = useContext(ThemeContext);
const [invalidAvatarUrls, setInvalidAvatarUrls] = useRecoilState(
invalidAvatarUrlsState,
);
@ -79,31 +92,20 @@ export const Avatar = ({
const showBackgroundColor = showPlaceholder;
return (
<div
className={clsx({
[styles.avatar]: true,
[styles.rounded]: type === 'rounded',
[styles.avatarOnClick]: !isUndefined(onClick),
})}
<StyledAvatar
size={size}
backgroundColor={showBackgroundColor ? fixedBackgroundColor : 'none'}
color={fixedColor}
clickable={!isUndefined(onClick)}
rounded={type === 'rounded'}
onClick={onClick}
style={{
color: fixedColor,
backgroundColor: showBackgroundColor ? fixedBackgroundColor : 'none',
width: propertiesBySize[size].width,
height: propertiesBySize[size].width,
fontSize: propertiesBySize[size].fontSize,
}}
backgroundTransparentLight={theme.background.transparent.light}
>
{showPlaceholder ? (
placeholderChar
) : (
<img
src={avatarUrl}
className={styles.avatarImage}
onError={handleImageError}
alt=""
/>
<StyledImage src={avatarUrl} onError={handleImageError} alt="" />
)}
</div>
</StyledAvatar>
);
};

View File

@ -1,11 +1,8 @@
import { Meta, StoryObj } from '@storybook/react';
import {
Avatar,
AvatarProps,
AvatarSize,
AvatarType,
} from '@ui/display/avatar/components/Avatar';
import { Avatar, AvatarProps } from '@ui/display/avatar/components/Avatar';
import { AvatarSize } from '@ui/display/avatar/types/AvatarSize';
import { AvatarType } from '@ui/display/avatar/types/AvatarType';
import {
AVATAR_URL_MOCK,
CatalogDecorator,

View File

@ -0,0 +1,22 @@
export const AVATAR_PROPERTIES_BY_SIZE = {
xl: {
fontSize: '16px',
width: '40px',
},
lg: {
fontSize: '13px',
width: '24px',
},
md: {
fontSize: '12px',
width: '16px',
},
sm: {
fontSize: '10px',
width: '14px',
},
xs: {
fontSize: '8px',
width: '12px',
},
};

View File

@ -0,0 +1 @@
export type AvatarSize = 'xl' | 'lg' | 'md' | 'sm' | 'xs';

View File

@ -0,0 +1 @@
export type AvatarType = 'squared' | 'rounded';

View File

@ -3,7 +3,8 @@ import { useNavigate } from 'react-router-dom';
import { useTheme } from '@emotion/react';
import { isNonEmptyString } from '@sniptt/guards';
import { Avatar, AvatarType } from '@ui/display/avatar/components/Avatar';
import { Avatar } from '@ui/display/avatar/components/Avatar';
import { AvatarType } from '@ui/display/avatar/types/AvatarType';
import { Chip, ChipVariant } from '@ui/display/chip/components/Chip';
import { IconComponent } from '@ui/display/icon/types/IconComponent';
import { Nullable } from '@ui/utilities/types/Nullable';

View File

@ -1,6 +1,9 @@
export * from './avatar/components/Avatar';
export * from './avatar/components/AvatarGroup';
export * from './avatar/components/states/isInvalidAvatarUrlState';
export * from './avatar/constants/AvatarPropertiesBySize';
export * from './avatar/types/AvatarSize';
export * from './avatar/types/AvatarType';
export * from './checkmark/components/AnimatedCheckmark';
export * from './checkmark/components/Checkmark';
export * from './chip/components/Chip';

View File

@ -1,20 +0,0 @@
.main {
font-family: inherit;
font-size: inherit;
font-weight: inherit;
max-width: 100%;
overflow: hidden;
text-decoration: inherit;
text-overflow: ellipsis;
white-space: nowrap;
}
.cursor {
cursor: pointer;
}
.large {
height: calc(var(--twentycrm-spacing-multiplicator) * 4px);
}

View File

@ -1,7 +1,7 @@
export const BORDER_COMMON = {
radius: {
xs: '2px',
sm: 'var(--twentycrm-border-radius-sm)',
sm: '4px',
md: '8px',
xl: '20px',
pill: '999px',

View File

@ -10,7 +10,7 @@ export const FONT_COMMON = {
},
weight: {
regular: 400,
medium: 'var(--twentycrm-font-weight-medium)',
medium: 500,
semiBold: 600,
},
family: 'Inter, sans-serif',

View File

@ -1,23 +1,16 @@
import { ReactNode, useEffect } from 'react';
import { ReactNode } from 'react';
import { ThemeProvider as EmotionThemeProvider } from '@emotion/react';
import { ThemeContextProvider } from '@ui/theme/provider/ThemeContextProvider';
import { ThemeType } from '..';
import './theme.css';
type ThemeProviderProps = {
theme: ThemeType;
children: ReactNode;
};
const ThemeProvider = ({ theme, children }: ThemeProviderProps) => {
useEffect(() => {
document.documentElement.className =
theme.name === 'dark' ? 'dark' : 'light';
}, [theme]);
return (
<EmotionThemeProvider theme={theme}>
<ThemeContextProvider theme={theme}>{children}</ThemeContextProvider>

View File

@ -1,93 +0,0 @@
/*
!! DEPRECATED !!
Please do not use those variables anymore. They are deprecated and will be removed soon.
*/
:root {
--twentycrm-spacing-multiplicator: 4;
--twentycrm-border-radius-sm: 4px;
--twentycrm-font-weight-medium: 500;
/* Grays */
--twentycrm-gray-100: #000000;
--twentycrm-gray-100-4: #0000000A;
--twentycrm-gray-100-10: #00000019;
--twentycrm-gray-100-16: #00000029;
--twentycrm-gray-90: #141414;
--twentycrm-gray-85: #171717;
--twentycrm-gray-85-80: #171717CC;
--twentycrm-gray-80: #1b1b1b;
--twentycrm-gray-80-80: #1b1b1bCC;
--twentycrm-gray-75: #1d1d1d;
--twentycrm-gray-70: #222222;
--twentycrm-gray-65: #292929;
--twentycrm-gray-60: #333333;
--twentycrm-gray-55: #4c4c4c;
--twentycrm-gray-50: #666666;
--twentycrm-gray-45: #818181;
--twentycrm-gray-40: #999999;
--twentycrm-gray-35: #b3b3b3;
--twentycrm-gray-30: #cccccc;
--twentycrm-gray-25: #d6d6d6;
--twentycrm-gray-20: #ebebeb;
--twentycrm-gray-15: #f1f1f1;
--twentycrm-gray-10: #fcfcfc;
--twentycrm-gray-10-80: #fcfcfcCC;
--twentycrm-gray-0: #ffffff;
--twentycrm-gray-0-6: #ffffff0f;
--twentycrm-gray-0-10: #ffffff19;
--twentycrm-gray-0-14: #ffffff23;
/* Blues */
--twentycrm-blue-accent-90: #141a25;
--twentycrm-blue-accent-10: #f5f9fd;
}
:root.dark {
/* Accent color */
--twentycrm-accent-quaternary: var(--twentycrm-blue-accent-90);
/* Font color */
--twentycrm-font-color-secondary: var(--twentycrm-gray-35);
--twentycrm-font-color-primary: var(--twentycrm-gray-20);
--twentycrm-font-color-light: var(--twentycrm-gray-50);
--twentycrm-font-color-extra-light: var(--twentycrm-gray-55);
/* Background color */
--twentycrm-background-primary: var(--twentycrm-gray-85);
/* Background transparent color */
--twentycrm-background-transparent-secondary: var(--twentycrm-gray-80-80);
--twentycrm-background-transparent-light: var(--twentycrm-gray-0-6);
--twentycrm-background-transparent-medium: var(--twentycrm-gray-0-10);
--twentycrm-background-transparent-strong: var(--twentycrm-gray-0-14);
/* Border color */
--twentycrm-border-color-medium: var(--twentycrm-gray-65);
}
:root.light {
/* Accent color */
--twentycrm-accent-quaternary: var(--twentycrm-blue-accent-10);
/* Colors */
--twentycrm-font-color-primary: var(--twentycrm-gray-60);
--twentycrm-font-color-secondary: var(--twentycrm-gray-50);
--twentycrm-font-color-light: var(--twentycrm-gray-35);
--twentycrm-font-color-extra-light: var(--twentycrm-gray-30);
/* Background color */
--twentycrm-background-primary: var(--twentycrm-gray-0);
/* Background transparent color */
--twentycrm-background-transparent-secondary: var(--twentycrm-gray-10-80);
--twentycrm-background-transparent-light: var(--twentycrm-gray-100-4);
--twentycrm-background-transparent-medium: var(--twentycrm-gray-100-10);
--twentycrm-background-transparent-strong: var(--twentycrm-gray-100-16);
/* Border color */
--twentycrm-border-color-medium: var(--twentycrm-gray-20);
}

View File

@ -33,6 +33,7 @@ export default defineConfig({
'**/OverflowingTextWithTooltip.tsx',
'**/Chip.tsx',
'**/Tag.tsx',
'**/Avatar.tsx',
],
babelOptions: {
presets: ['@babel/preset-typescript', '@babel/preset-react'],