|
| 1 | +import type { VariantProps } from 'class-variance-authority'; |
| 2 | +import type { ImgHTMLAttributes, RefObject, SVGAttributes } from 'react'; |
| 3 | + |
| 4 | +import { cva, cx } from 'class-variance-authority'; |
| 5 | + |
| 6 | +import styles from './styles/Avatar.module.css'; |
| 7 | +import { useImageLoadingStatus } from './utils'; |
| 8 | + |
| 9 | +const avatar = cva(styles.base, { |
| 10 | + variants: { |
| 11 | + size: { |
| 12 | + small: styles.small, |
| 13 | + medium: styles.medium, |
| 14 | + large: styles.large, |
| 15 | + }, |
| 16 | + }, |
| 17 | + defaultVariants: { |
| 18 | + size: 'medium', |
| 19 | + }, |
| 20 | +}); |
| 21 | + |
| 22 | +const colors = cva(null, { |
| 23 | + variants: { |
| 24 | + color: { |
| 25 | + 0: styles.yellow, |
| 26 | + 1: styles.blue, |
| 27 | + 2: styles.pink, |
| 28 | + 3: styles.cyan, |
| 29 | + 4: styles.purple, |
| 30 | + }, |
| 31 | + }, |
| 32 | +}); |
| 33 | + |
| 34 | +interface AvatarVariants extends VariantProps<typeof avatar> {} |
| 35 | + |
| 36 | +interface AvatarProps extends ImgHTMLAttributes<HTMLImageElement>, AvatarVariants { |
| 37 | + ref?: RefObject<HTMLImageElement | null>; |
| 38 | +} |
| 39 | + |
| 40 | +interface InitialsAvatarProps extends SVGAttributes<SVGElement>, AvatarVariants {} |
| 41 | + |
| 42 | +const Avatar = ({ className, children, size = 'medium', ref, src, ...props }: AvatarProps) => { |
| 43 | + const status = useImageLoadingStatus(src); |
| 44 | + |
| 45 | + if (status !== 'loaded') { |
| 46 | + return <InitialsAvatar size={size}>{children}</InitialsAvatar>; |
| 47 | + } |
| 48 | + |
| 49 | + // biome-ignore lint/a11y/useAltText: <explanation> |
| 50 | + return <img ref={ref} src={src} {...props} className={avatar({ size, className })} />; |
| 51 | +}; |
| 52 | + |
| 53 | +const InitialsAvatar = ({ |
| 54 | + className, |
| 55 | + size = 'medium', |
| 56 | + children, |
| 57 | + ...props |
| 58 | +}: InitialsAvatarProps) => { |
| 59 | + const color = children |
| 60 | + ? (children.toString().charCodeAt(0) + children.toString().charCodeAt(1)) % 5 |
| 61 | + : 0; |
| 62 | + return ( |
| 63 | + // biome-ignore lint/a11y/noSvgWithoutTitle: <explanation> |
| 64 | + <svg |
| 65 | + role="img" |
| 66 | + className={cx( |
| 67 | + avatar({ size, className }), |
| 68 | + colors({ color: color as keyof typeof colors }), |
| 69 | + styles.initials, |
| 70 | + )} |
| 71 | + viewBox="0 0 24 24" |
| 72 | + {...props} |
| 73 | + > |
| 74 | + <text x="50%" y="50%" className={styles.text}> |
| 75 | + {children} |
| 76 | + </text> |
| 77 | + </svg> |
| 78 | + ); |
| 79 | +}; |
| 80 | + |
| 81 | +export { Avatar, InitialsAvatar }; |
| 82 | +export type { AvatarProps, InitialsAvatarProps }; |
0 commit comments