### Description 1. This PR is a continuation of a previous PR: https://github.com/twentyhq/twenty/pull/6201#pullrequestreview-2175601222 2. One test case was removed here: `packages/twenty-front/src/utils/image/__tests__/getImageAbsoluteURI.test.ts` because since we are not handling base64 images anymore, the result is the same of the last test case. Would you rather we update the test instead? ### Refs - #3514 - https://github.com/twentyhq/twenty/pull/6201 ### Demo https://www.loom.com/share/4f32b535c77a4d418e319b095d09452c?sid=df34adf8-b013-44ef-b794-d54846f52d2d Fixes #3514 --------- Co-authored-by: gitstart-twenty <gitstart-twenty@users.noreply.github.com>
119 lines
3.4 KiB
TypeScript
119 lines
3.4 KiB
TypeScript
import { styled } from '@linaria/react';
|
|
import { isNonEmptyString, isUndefined } from '@sniptt/guards';
|
|
import { useContext, useMemo } from 'react';
|
|
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, getImageAbsoluteURI, stringToHslColor } from '@ui/utilities';
|
|
|
|
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;
|
|
|
|
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;
|
|
|
|
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;
|
|
className?: string;
|
|
size?: AvatarSize;
|
|
placeholder: string | undefined;
|
|
placeholderColorSeed?: string;
|
|
type?: Nullable<AvatarType>;
|
|
color?: string;
|
|
backgroundColor?: string;
|
|
onClick?: () => void;
|
|
};
|
|
|
|
// 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',
|
|
placeholder,
|
|
placeholderColorSeed = placeholder,
|
|
onClick,
|
|
type = 'squared',
|
|
color,
|
|
backgroundColor,
|
|
}: AvatarProps) => {
|
|
const { theme } = useContext(ThemeContext);
|
|
const [invalidAvatarUrls, setInvalidAvatarUrls] = useRecoilState(
|
|
invalidAvatarUrlsState,
|
|
);
|
|
|
|
const avatarImageURI = useMemo(
|
|
() => getImageAbsoluteURI(avatarUrl),
|
|
[avatarUrl],
|
|
);
|
|
|
|
const noAvatarUrl = !isNonEmptyString(avatarImageURI);
|
|
|
|
const placeholderChar = placeholder?.[0]?.toLocaleUpperCase();
|
|
|
|
const showPlaceholder =
|
|
noAvatarUrl || invalidAvatarUrls.includes(avatarImageURI);
|
|
|
|
const handleImageError = () => {
|
|
if (isNonEmptyString(avatarImageURI)) {
|
|
setInvalidAvatarUrls((prev) => [...prev, avatarImageURI]);
|
|
}
|
|
};
|
|
|
|
const fixedColor =
|
|
color ?? stringToHslColor(placeholderColorSeed ?? '', 75, 25);
|
|
const fixedBackgroundColor =
|
|
backgroundColor ?? stringToHslColor(placeholderColorSeed ?? '', 75, 85);
|
|
|
|
const showBackgroundColor = showPlaceholder;
|
|
|
|
return (
|
|
<StyledAvatar
|
|
size={size}
|
|
backgroundColor={showBackgroundColor ? fixedBackgroundColor : 'none'}
|
|
color={fixedColor}
|
|
clickable={!isUndefined(onClick)}
|
|
rounded={type === 'rounded'}
|
|
onClick={onClick}
|
|
backgroundTransparentLight={theme.background.transparent.light}
|
|
>
|
|
{showPlaceholder ? (
|
|
placeholderChar
|
|
) : (
|
|
<StyledImage src={avatarImageURI} onError={handleImageError} alt="" />
|
|
)}
|
|
</StyledAvatar>
|
|
);
|
|
};
|