import React, { useContext, useMemo, useState } from "react";
import md5 from "md5";
import classNames from "classnames";
import { bemHOF } from "../../utilities/bem";
import { colors } from "../../tokens";
import { Box, BoxProps } from "../Box";
import { Icon, ICON_TYPE } from "../Icon";
import { Flex } from "../Flex";
import { Text } from "../Text";
import { Image } from "../Image";
import { Tooltip } from "../Tooltip";
import { Svg, SvgProps } from "../Svg";
import { AvatarGroup } from "./AvatarGroup";
import { AvatarSizesForGravatar, AvatarSize, AVATAR_TYPE } from "./types";
import { AvatarContext } from "./context";
const cn = bemHOF("Avatar");
const SystemIcon = (props: SvgProps) => {
return (
);
};
// Deprecated; Remove after dual-branding in Comply is no longer needed
// Exported to create a dual-branded system avatar in Comply
export const SystemIconConveyor = (props: SvgProps) => (
);
// Deprecated; Remove after dual-branding in Comply is no longer needed
// Exported to create a dual-branded system avatar in Comply
export const SystemIconAptible = (props: SvgProps) => (
);
export interface SharedAvatarProps extends BoxProps {
/**
* The size to render the avatar.
*/
size?: AvatarSize;
/**
* Makes the avatar a square with rounded corners and a box shadow; good to
* use when overlapping avatars.
*/
square?: boolean;
/**
* Adds a border around the avatar.
*/
bordered?: boolean;
/**
* Alternate text for accessibility; most times it’s better to leave this
* null.
*/
alt?: string;
}
type AnonymousAvatarProps = SharedAvatarProps & {
type: AVATAR_TYPE.ANONYMOUS;
};
type SystemAvatarProps = SharedAvatarProps & {
type: AVATAR_TYPE.SYSTEM;
};
interface UserAvatarComponentProps extends GravatarOrInitialsProps {
/**
* Path to an image.
*/
imgSrc?: string;
/**
* Whether or not to use a tooltip to display the user’s name; it
* only applies when `type=user`. If `name` is not provided, it’ll
* use `email` if it exists. The tooltip won’t show if either is
* missing.
*/
enableTooltip?: boolean;
}
type UserAvatarProps = SharedAvatarProps &
UserAvatarComponentProps & {
type: AVATAR_TYPE.USER;
};
export type AvatarProps =
| AnonymousAvatarProps
| SystemAvatarProps
| UserAvatarProps;
const avatarInitialBackgroundColors: string[] = [
colors.green["600"],
colors.gold["600"],
colors.orange["600"],
colors.red["600"],
colors.pink["600"],
colors.purple["600"],
colors.blue["600"],
colors.cyan["600"],
];
interface InitialsProps {
/**
* Email address for the user; this is used to look up a Gravatar
* image or as a key to provide a unique color when the initials fallback
* is used.
*/
email: string;
/**
* The user’s name; this will be used to automatically create the
* user’s initials. It’ll also display in a tooltip when
* `enableTooltip=true`.
*/
name?: string;
/**
* The user’s first name initial; this can be omitted if a `name` is passed.
*/
firstInitial?: string;
/**
* The user’s last name initial; this can be omitted if a `name` is passed.
*/
lastInitial?: string;
}
interface GravatarOrInitialsProps extends InitialsProps {
size?: AvatarSize;
alt?: string;
}
const Initials = ({
email,
name,
firstInitial,
lastInitial,
}: InitialsProps) => {
const initialsBgColor: string = useMemo(() => {
const fallbackColor = colors.gray["600"];
if (!email) return fallbackColor;
let hash = 0;
for (let i = 0; i < email.length; i += 1) {
// eslint-disable-next-line no-bitwise
hash = email.charCodeAt(i) + ((hash << 5) - hash);
}
const colorIndex = Math.abs(hash % avatarInitialBackgroundColors.length);
return avatarInitialBackgroundColors[colorIndex] || fallbackColor;
}, [email]);
// Generate the initials from the name
const initialsFromName: string = name
? name
.split(" ") // Separate first and last names
.map((n: string) => n.charAt(0)) // Get the first letter of each name
.join("") // Join them back together
.slice(0, 2) // Take the first two initials (in case of multiple word surnames)
: "";
// Use the first letter of the email address instead of initials if not provided
const initials =
firstInitial === undefined && lastInitial === undefined
? email.charAt(0)
: `${firstInitial}${lastInitial}`;
return (
{initialsFromName || initials}
);
};
const GravatarOrInitials = ({
email,
size = "medium",
name,
firstInitial,
lastInitial,
alt,
}: GravatarOrInitialsProps) => {
const [hasImageError, setImageError] = useState(false);
const src = useMemo(() => {
const md5Email = email
? md5(email.trim().toLowerCase(), { encoding: "binary" })
: null;
return `https://www.gravatar.com/avatar/${md5Email}?d=404&r=pg&s=${AvatarSizesForGravatar[size]}`;
}, [email, size]);
if (hasImageError) {
return (
);
}
return (
setImageError(true)}
onLoad={() => setImageError(false)}
/>
);
};
const UserAvatar = ({
imgSrc,
email = "",
alt,
...rest
}: UserAvatarComponentProps) => {
// If an imgSrc is provided, just use that as the avatar
if (imgSrc) {
return ;
}
// If no imgSrc, then use the Gravatar
return ;
};
const AnonymousAvatar = () => (
);
const SystemAvatar = () => {
return (
);
};
// Deprecated; Remove after dual-branding in Comply is no longer needed
// Exported to create a dual-branded system avatar in Comply
export const SystemAvatarConveyor = () => {
return ;
};
// Deprecated; Remove after dual-branding in Comply is no longer needed
// Exported to create a dual-branded system avatar in Comply
export const SystemAvatarAptible = () => {
return (
);
};
const NonUserAvatarComponent = (type: AVATAR_TYPE) => {
return {
anonymous: ,
user: ,
system: ,
}[type];
};
export const AvatarWrapper = ({
children,
size,
square,
bordered,
className,
...rest
}: SharedAvatarProps) => (
{children}
);
export const Avatar = ({
size: sizeProp = "medium",
square,
bordered,
className,
style,
alt,
...rest
}: AvatarProps) => {
const avatarContext = useContext(AvatarContext);
const size = avatarContext ? avatarContext.size : sizeProp;
if (rest.type === AVATAR_TYPE.USER && (rest.email || rest.imgSrc)) {
const {
type,
email,
imgSrc,
name,
firstInitial,
lastInitial,
enableTooltip = false,
...userRest
} = rest;
return (
{enableTooltip && (name || email) ? (
}
>
{name || email}
) : (
)}
);
}
const { type, ...nonUserRest } = rest;
if (type === AVATAR_TYPE.USER) {
if ("name" in nonUserRest) {
delete nonUserRest.name;
}
if ("firstInitial" in nonUserRest) {
delete nonUserRest.firstInitial;
}
if ("lastInitial" in nonUserRest) {
delete nonUserRest.lastInitial;
}
}
return (
{NonUserAvatarComponent(type)}
);
};
Avatar.Group = AvatarGroup;