[BUGFIX] Account owner should not be clickable & [Refactor] Chip.tsx links (#10359)

# Introduction

closes #10196 
Initially fixing the `Account Owner` record field value should not be
clickable and redirects on current page bug.
This has been fixed computing whereas the current filed is a workspace
member dynamically rendering a stale Chip components instead of an
interactive one

## Refactor
Refactored the `AvatarChip` `to` props logic to be scoped to lower level
scope `Chip`.
Now we have `LinkChip` `Chip`, `LinkAvatarChip` and `AvatarChip` all
exported from twenty-ui.

The caller has to determine which one to call from the design system

## New rule regarding chip links
As discussed with @charlesBochet and @FelixMalfait 
A chip link will now ***always*** have `to` defined. ( and optionally an
`onClick` ).
`ChipLinks` cannot be used as buttons anymore

## Factorization
Deleted the `RecordIndexRecordChip.tsx` file ( aka
`RecordIdentifierChip` component ) that was duplicating some logic,
refactored the `RecordChip` in order to handle what was covered by
`RecordIdentifierChip`

## Conclusion
As always any suggestions are more than welcomed ! Took few opinionated
decision/refactor regarding nested long ternaries rendering `ReactNode`
elements

## Misc


https://github.com/user-attachments/assets/8ef11fb2-7ba6-4e96-bd59-b0be5a425156

---------

Co-authored-by: Mohammed Razak <mohammedrazak2001@gmail.com>
Co-authored-by: Félix Malfait <felix.malfait@gmail.com>
This commit is contained in:
Paul Rastoin
2025-02-25 15:36:17 +01:00
committed by GitHub
parent fc0e98b53e
commit 89e11b4626
24 changed files with 387 additions and 302 deletions

View File

@ -1,121 +0,0 @@
import { styled } from '@linaria/react';
import { Avatar } from '@ui/display/avatar/components/Avatar';
import { AvatarType } from '@ui/display/avatar/types/AvatarType';
import { Chip, ChipSize, ChipVariant } from '@ui/display/chip/components/Chip';
import { IconComponent } from '@ui/display/icon/types/IconComponent';
import { ThemeContext } from '@ui/theme';
import { Nullable } from '@ui/utilities/types/Nullable';
import { MouseEvent, useContext } from 'react';
import { isDefined } from 'twenty-shared';
// Import Link from react-router-dom instead of UndecoratedLink
import { Link } from 'react-router-dom';
export type AvatarChipProps = {
name: string;
avatarUrl?: string;
avatarType?: Nullable<AvatarType>;
variant?: AvatarChipVariant;
size?: ChipSize;
LeftIcon?: IconComponent;
LeftIconColor?: string;
isIconInverted?: boolean;
className?: string;
placeholderColorSeed?: string;
onClick?: (event: MouseEvent) => void;
to?: string;
maxWidth?: number;
};
export enum AvatarChipVariant {
Regular = 'regular',
Transparent = 'transparent',
}
const StyledInvertedIconContainer = styled.div<{ backgroundColor: string }>`
display: flex;
align-items: center;
justify-content: center;
width: 14px;
height: 14px;
border-radius: 4px;
background-color: ${({ backgroundColor }) => backgroundColor};
`;
// Ideally we would use the UndecoratedLink component from @ui/navigation
// but it led to a bug probably linked to circular dependencies, which was hard to solve
const StyledLink = styled(Link)`
text-decoration: none;
`;
export const AvatarChip = ({
name,
avatarUrl,
avatarType = 'rounded',
variant = AvatarChipVariant.Regular,
LeftIcon,
LeftIconColor,
isIconInverted,
className,
placeholderColorSeed,
onClick,
to,
size = ChipSize.Small,
maxWidth,
}: AvatarChipProps) => {
const { theme } = useContext(ThemeContext);
const chip = (
<Chip
label={name}
variant={
isDefined(onClick) || isDefined(to)
? variant === AvatarChipVariant.Regular
? ChipVariant.Highlighted
: ChipVariant.Regular
: ChipVariant.Transparent
}
size={size}
leftComponent={
isDefined(LeftIcon) ? (
isIconInverted === true ? (
<StyledInvertedIconContainer
backgroundColor={theme.background.invertedSecondary}
>
<LeftIcon
color="white"
size={theme.icon.size.sm}
stroke={theme.icon.stroke.sm}
/>
</StyledInvertedIconContainer>
) : (
<LeftIcon
size={theme.icon.size.sm}
stroke={theme.icon.stroke.sm}
color={LeftIconColor || 'currentColor'}
/>
)
) : (
<Avatar
avatarUrl={avatarUrl}
placeholderColorSeed={placeholderColorSeed}
placeholder={name}
size="sm"
type={avatarType}
/>
)
}
clickable={isDefined(onClick) || isDefined(to)}
onClick={to ? undefined : onClick}
className={className}
maxWidth={maxWidth}
/>
);
if (!isDefined(to)) return chip;
return (
<StyledLink to={to} onClick={onClick}>
{chip}
</StyledLink>
);
};

View File

@ -1,6 +1,6 @@
import { Theme, withTheme } from '@emotion/react';
import { styled } from '@linaria/react';
import { MouseEvent, ReactNode } from 'react';
import { ReactNode } from 'react';
import { OverflowingTextWithTooltip } from '@ui/display/tooltip/OverflowingTextWithTooltip';
@ -21,7 +21,7 @@ export enum ChipVariant {
Rounded = 'rounded',
}
type ChipProps = {
export type ChipProps = {
size?: ChipSize;
disabled?: boolean;
clickable?: boolean;
@ -29,10 +29,9 @@ type ChipProps = {
maxWidth?: number;
variant?: ChipVariant;
accent?: ChipAccent;
leftComponent?: ReactNode;
rightComponent?: ReactNode;
leftComponent?: (() => ReactNode) | null;
rightComponent?: (() => ReactNode) | null;
className?: string;
onClick?: (event: MouseEvent<HTMLDivElement>) => void;
};
const StyledContainer = withTheme(styled.div<
@ -128,10 +127,9 @@ export const Chip = ({
disabled = false,
clickable = true,
variant = ChipVariant.Regular,
leftComponent,
rightComponent,
leftComponent = null,
rightComponent = null,
accent = ChipAccent.TextPrimary,
onClick,
className,
maxWidth,
}: ChipProps) => {
@ -143,13 +141,12 @@ export const Chip = ({
disabled={disabled}
size={size}
variant={variant}
onClick={onClick}
className={className}
maxWidth={maxWidth}
>
{leftComponent}
{leftComponent?.()}
<OverflowingTextWithTooltip size={size} text={label} />
{rightComponent}
{rightComponent?.()}
</StyledContainer>
);
};

View File

@ -0,0 +1,53 @@
import styled from '@emotion/styled';
import {
Chip,
ChipAccent,
ChipProps,
ChipSize,
ChipVariant,
} from '@ui/display/chip/components/Chip';
import { MouseEvent } from 'react';
import { Link } from 'react-router-dom';
export type LinkChipProps = Omit<
ChipProps,
'onClick' | 'disabled' | 'clickable'
> & {
to: string;
onClick?: (event: MouseEvent<HTMLAnchorElement>) => void;
};
// Ideally we would use the UndecoratedLink component from @ui/navigation
// but it led to a bug probably linked to circular dependencies, which was hard to solve
const StyledLink = styled(Link)`
text-decoration: none;
`;
export const LinkChip = ({
to,
size = ChipSize.Small,
label,
variant = ChipVariant.Regular,
leftComponent = null,
rightComponent = null,
accent = ChipAccent.TextPrimary,
className,
maxWidth,
onClick,
}: LinkChipProps) => {
return (
<StyledLink to={to} onClick={onClick}>
<Chip
size={size}
label={label}
clickable={true}
variant={variant}
leftComponent={leftComponent}
rightComponent={rightComponent}
accent={accent}
className={className}
maxWidth={maxWidth}
/>
</StyledLink>
);
};

View File

@ -1,5 +1,5 @@
import { Meta, StoryObj } from '@storybook/react';
import { AvatarChip } from '@ui/display/chip/components/AvatarChip';
import { AvatarChip } from '@ui/display/avatar-chip/components/AvatarChip';
import { ComponentDecorator, RouterDecorator } from '@ui/testing';