1 import type { ForwardedRef, ComponentPropsWithoutRef } from 'react'
2 import { useMemo, forwardRef } from 'react'
3 import clsx from '@proton/utils/clsx'
4 import tinycolor from 'tinycolor2'
5 import { getAccentColorForUsername } from './getAccentColorForUsername'
7 function parseHueFromHSLstring(hsl: string): number | undefined {
8 const NumberRegex = /(\d+)/
9 const numberMatch = hsl.match(NumberRegex)?.[0]
10 return numberMatch ? parseInt(numberMatch) : undefined
13 /** A number between 0 to 360 */
14 type HueValue = number
16 const UserAvatarHueCache = new Map<string, number>()
18 interface UserAvatarProps extends Omit<ComponentPropsWithoutRef<'div'>, 'color'> {
21 color?: { hue: HueValue } | { hsl: string }
24 export const UserAvatar = forwardRef(function UserAvatar(
25 { name, className, color, ...rest }: UserAvatarProps,
26 ref: ForwardedRef<HTMLDivElement>,
29 throw new Error('UserAvatar requires a name prop')
32 const hue = useMemo(() => {
36 const parsed = parseHueFromHSLstring(color.hsl)
37 if (parsed && !isNaN(parsed)) {
41 } else if (!isNaN(color.hue)) {
46 const cachedHue = UserAvatarHueCache.get(name)
51 const hue = tinycolor(getAccentColorForUsername(name)).toHsl().h
52 UserAvatarHueCache.set(name, hue)
56 const letter = useMemo(() => {
57 return name.substring(0, 1).toUpperCase()
64 'h-custom w-custom relative flex items-center justify-center overflow-hidden rounded-lg',
72 backgroundColor: `hsl(${hue}, 100%, 90%)`,
73 color: `hsl(${hue}, 100%, 10%)`,
75 borderRadius: '0.5rem',
76 '--h-custom': '1.75rem',
77 '--w-custom': '1.75rem',
78 } as React.CSSProperties