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.
112 lines
3.2 KiB
TypeScript
112 lines
3.2 KiB
TypeScript
import { useContext } from 'react';
|
|
import { styled } from '@linaria/react';
|
|
import { isNonEmptyString, isUndefined } from '@sniptt/guards';
|
|
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';
|
|
|
|
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;
|
|
entityId?: 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,
|
|
entityId = placeholder,
|
|
onClick,
|
|
type = 'squared',
|
|
color,
|
|
backgroundColor,
|
|
}: AvatarProps) => {
|
|
const { theme } = useContext(ThemeContext);
|
|
const [invalidAvatarUrls, setInvalidAvatarUrls] = useRecoilState(
|
|
invalidAvatarUrlsState,
|
|
);
|
|
|
|
const noAvatarUrl = !isNonEmptyString(avatarUrl);
|
|
|
|
const placeholderChar = placeholder?.[0]?.toLocaleUpperCase();
|
|
|
|
const showPlaceholder = noAvatarUrl || invalidAvatarUrls.includes(avatarUrl);
|
|
|
|
const handleImageError = () => {
|
|
if (isNonEmptyString(avatarUrl)) {
|
|
setInvalidAvatarUrls((prev) => [...prev, avatarUrl]);
|
|
}
|
|
};
|
|
|
|
const fixedColor = color ?? stringToHslColor(entityId ?? '', 75, 25);
|
|
const fixedBackgroundColor =
|
|
backgroundColor ?? stringToHslColor(entityId ?? '', 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={avatarUrl} onError={handleImageError} alt="" />
|
|
)}
|
|
</StyledAvatar>
|
|
);
|
|
};
|