Feat/performance-refactor-styled-component (#5516)

In this PR I'm optimizing a whole RecordTableCell in real conditions
with a complex RelationFieldDisplay component :
- Broke down getObjectRecordIdentifier into multiple utils
- Precompute memoized function for getting chip data per field with
useRecordChipDataGenerator()
- Refactored RelationFieldDisplay
- Use CSS modules where performance is needed instead of styled
components
- Create a CSS theme with global CSS variables to be used by CSS modules
This commit is contained in:
Lucas Bordeau
2024-05-24 18:53:37 +02:00
committed by GitHub
parent 3680647c9a
commit a0178478d4
39 changed files with 1045 additions and 462 deletions

View File

@ -0,0 +1,84 @@
.label {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.chip {
--chip-horizontal-padding: calc(var(--twentycrm-spacing-multiplicator) * 1px);
--chip-vertical-padding: calc(var(--twentycrm-spacing-multiplicator) * 1px);
align-items: center;
border-radius: var(--twentycrm-border-radius-sm);
color: var(--twentycrm-font-color-secondary);
display: inline-flex;
justify-content: center;
gap: calc(var(--twentycrm-spacing-multiplicator) * 1px);
height: calc(var(--twentycrm-spacing-multiplicator) * 3px);
max-width: calc(100% - var(--chip-horizontal-padding) * 2px);
overflow: hidden;
padding: var(--chip-vertical-padding) var(--chip-horizontal-padding);
user-select: none;
}
.disabled {
cursor: not-allowed;
color: var(--twentycrm-font-color-light);
}
.clickable {
cursor: pointer;
}
.accent-text-primary {
color: var(--twentycrm-font-color-primary);
}
.accent-text-secondary {
font-weight: var(--twentycrm-font-weight-medium);
}
.size-large {
height: calc(var(--twentycrm-spacing-multiplicator) * 4px);
}
.variant-regular:hover {
background-color: var(--twentycrm-background-transparent-light);
}
.variant-regular:active {
background-color: var(--twentycrm-background-transparent-medium);
}
.variant-highlighted {
background-color: var(--twentycrm-background-transparent-light);
}
.variant-highlighted:hover {
background-color: var(--twentycrm-background-transparent-medium);
}
.variant-highlighted:active {
background-color: var(--twentycrm-background-transparent-strong);
}
.variant-rounded {
--chip-horizontal-padding: calc(var(--twentycrm-spacing-multiplicator) * 2px);
--chip-vertical-padding: 3px;
background-color: var(--twentycrm-background-transparent-light);
border: 1px solid var(--twentycrm-border-color-medium);
border-radius: 50px;
}
.variant-transparent {
cursor: inherit;
}

View File

@ -1,11 +1,10 @@
import { MouseEvent, ReactNode } from 'react';
import { Link } from 'react-router-dom';
import isPropValid from '@emotion/is-prop-valid';
import { css } from '@emotion/react';
import styled from '@emotion/styled';
import { clsx } from 'clsx';
import { OverflowingTextWithTooltip } from '@ui/display/tooltip/OverflowingTextWithTooltip';
import styles from './Chip.module.css';
export enum ChipSize {
Large = 'large',
Small = 'small',
@ -38,121 +37,6 @@ type ChipProps = {
to?: string;
};
const StyledContainer = styled('div', {
shouldForwardProp: (prop) =>
!['clickable', 'maxWidth'].includes(prop) && isPropValid(prop),
})<
Pick<
ChipProps,
'accent' | 'clickable' | 'disabled' | 'maxWidth' | 'size' | 'variant' | 'to'
>
>`
--chip-horizontal-padding: ${({ theme }) => theme.spacing(1)};
--chip-vertical-padding: ${({ theme }) => theme.spacing(1)};
text-decoration: none;
align-items: center;
border-radius: ${({ theme }) => theme.border.radius.sm};
color: ${({ theme, disabled }) =>
disabled ? theme.font.color.light : theme.font.color.secondary};
cursor: ${({ clickable, disabled }) =>
clickable ? 'pointer' : disabled ? 'not-allowed' : 'inherit'};
display: inline-flex;
justify-content: center;
gap: ${({ theme }) => theme.spacing(1)};
height: ${({ theme }) => theme.spacing(3)};
max-width: ${({ maxWidth }) =>
maxWidth
? `calc(${maxWidth}px - 2 * var(--chip-horizontal-padding))`
: '200px'};
overflow: hidden;
padding: var(--chip-vertical-padding) var(--chip-horizontal-padding);
user-select: none;
// Accent style overrides
${({ accent, disabled, theme }) => {
if (accent === ChipAccent.TextPrimary) {
return (
!disabled &&
css`
color: ${theme.font.color.primary};
`
);
}
if (accent === ChipAccent.TextSecondary) {
return css`
font-weight: ${theme.font.weight.medium};
`;
}
}}
// Variant style overrides
${({ disabled, theme, variant }) => {
if (variant === ChipVariant.Regular) {
return (
!disabled &&
css`
:hover {
background-color: ${theme.background.transparent.light};
}
:active {
background-color: ${theme.background.transparent.medium};
}
`
);
}
if (variant === ChipVariant.Highlighted) {
return css`
background-color: ${theme.background.transparent.light};
${!disabled &&
css`
:hover {
background-color: ${theme.background.transparent.medium};
}
:active {
background-color: ${theme.background.transparent.strong};
}
`}
`;
}
if (variant === ChipVariant.Rounded) {
return css`
--chip-horizontal-padding: ${theme.spacing(2)};
--chip-vertical-padding: 3px;
background-color: ${theme.background.transparent.lighter};
border: 1px solid ${theme.border.color.medium};
border-radius: 50px;
`;
}
if (variant === ChipVariant.Transparent) {
return css`
cursor: inherit;
`;
}
}}
`;
const StyledLabel = styled.span`
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
`;
const StyledOverflowingTextWithTooltip = styled(OverflowingTextWithTooltip)<{
size?: ChipSize;
}>`
height: ${({ theme, size }) =>
size === ChipSize.Large ? theme.spacing(4) : 'auto'};
`;
export const Chip = ({
size = ChipSize.Small,
label,
@ -162,30 +46,33 @@ export const Chip = ({
leftComponent,
rightComponent,
accent = ChipAccent.TextPrimary,
maxWidth,
className,
onClick,
to,
}: ChipProps) => {
return (
<StyledContainer
<div
data-testid="chip"
clickable={clickable}
variant={variant}
accent={accent}
size={size}
disabled={disabled}
className={className}
maxWidth={maxWidth}
className={clsx({
[styles.chip]: true,
[styles.clickable]: clickable,
[styles.disabled]: disabled,
[styles.accentTextPrimary]: accent === ChipAccent.TextPrimary,
[styles.accentTextSecondary]: accent === ChipAccent.TextSecondary,
[styles.sizeLarge]: size === ChipSize.Large,
[styles.variantRegular]: variant === ChipVariant.Regular,
[styles.variantHighlighted]: variant === ChipVariant.Highlighted,
[styles.variantRounded]: variant === ChipVariant.Rounded,
[styles.variantTransparent]: variant === ChipVariant.Transparent,
})}
onClick={onClick}
as={to ? Link : 'div'}
to={to ? to : undefined}
>
{leftComponent}
<StyledLabel>
<StyledOverflowingTextWithTooltip size={size} text={label} />
</StyledLabel>
<div className={styles.label}>
<OverflowingTextWithTooltip
size={size === ChipSize.Large ? 'large' : 'small'}
text={label}
/>
</div>
{rightComponent}
</StyledContainer>
</div>
);
};

View File

@ -3,11 +3,10 @@ import { useTheme } from '@emotion/react';
import { isNonEmptyString } from '@sniptt/guards';
import { Avatar, AvatarType } from '@ui/display/avatar/components/Avatar';
import { Chip, ChipVariant } from '@ui/display/chip/components/Chip';
import { IconComponent } from '@ui/display/icon/types/IconComponent';
import { Nullable } from '@ui/utilities/types/Nullable';
import { Chip, ChipVariant } from './Chip';
export type EntityChipProps = {
linkToEntity?: string;
entityId: string;
@ -17,7 +16,6 @@ export type EntityChipProps = {
variant?: EntityChipVariant;
LeftIcon?: IconComponent;
className?: string;
maxWidth?: number;
};
export enum EntityChipVariant {
@ -34,7 +32,6 @@ export const EntityChip = ({
variant = EntityChipVariant.Regular,
LeftIcon,
className,
maxWidth,
}: EntityChipProps) => {
const theme = useTheme();
@ -70,8 +67,6 @@ export const EntityChip = ({
clickable={!!linkToEntity}
onClick={handleLinkClick}
className={className}
maxWidth={maxWidth}
to={linkToEntity}
/>
);
};