UNPKG

@spaced-out/ui-design-system

Version:
270 lines (252 loc) 6.45 kB
// @flow strict import * as React from 'react'; import { JumboSmall, SubTitleLarge, SubTitleMedium, SubTitleSmall, } from '../../components/Text'; import * as COLORS from '../../styles/variables/_color'; import { colorDangerLightest, colorGrayLightest, colorInformationLightest, colorNeutralLightest, colorSuccessLightest, colorWarningLightest, } from '../../styles/variables/_color'; import classify from '../../utils/classify'; import type {IconType} from '../Icon'; import {Icon} from '../Icon'; import type {StatusSemanticType} from '../StatusIndicator'; import {StatusIndicator} from '../StatusIndicator'; import css from './Avatar.module.css'; type ClassNames = $ReadOnly<{ wrapper?: string, ring?: string, content?: string, image?: string, icon?: string, text?: string, }>; export const AVATAR_SIZE = Object.freeze({ small: 'small', medium: 'medium', large: 'large', extraLarge: 'extraLarge', }); export const avatarSizeOptions: Array<mixed> = [...Object.keys(AVATAR_SIZE)]; export type AvatarSize = $Values<typeof AVATAR_SIZE>; export const AVATAR_COLOR = Object.freeze({ red: colorDangerLightest, orange: colorWarningLightest, green: colorSuccessLightest, blue: colorInformationLightest, gray: colorGrayLightest, indigo: colorNeutralLightest, }); export type AvatarColorType = $Keys<typeof AVATAR_COLOR>; export type AvatarProps = { classNames?: ClassNames, size?: AvatarSize, imageSrc?: string, ring?: boolean, icon?: boolean, text?: string, color?: AvatarColorType, style?: mixed, iconName?: string, iconType?: IconType, status?: StatusSemanticType, statusBorderColorToken?: $Keys<typeof COLORS>, }; const getAvatarText = (text = '') => { const [firstWord, secondWord] = text.split(' '); const firstChar = firstWord ? firstWord[0].toUpperCase() : ''; const secondChar = secondWord ? secondWord[0].toUpperCase() : ''; return firstChar + secondChar; }; const AvatarTextDecorator = ({ avatarText, size, className, }: { avatarText: string, size: AvatarSize, className?: string, }) => { switch (size) { case 'small': return <SubTitleSmall className={className}>{avatarText}</SubTitleSmall>; case 'medium': return ( <SubTitleMedium className={className}>{avatarText}</SubTitleMedium> ); case 'large': return <SubTitleLarge className={className}>{avatarText}</SubTitleLarge>; case 'extraLarge': return <JumboSmall className={className}>{avatarText}</JumboSmall>; } }; // mapping of iconSize for each avatar size; const iconSize = Object.freeze({ small: 'small', medium: 'medium', large: 'medium', extraLarge: 'large', }); export type AvatarContentProps = { imageSrc?: string, text?: string, iconName?: string, iconType?: IconType, size: AvatarSize, color?: AvatarColorType, ring?: boolean, status?: StatusSemanticType, statusBorderColorToken?: $Keys<typeof COLORS>, classNames?: ClassNames, }; const AvatarConditionalContent = ({ imageSrc, text, iconName, size, iconType, color = 'blue', status, statusBorderColorToken, classNames, }: AvatarContentProps) => ( <div className={classify( css.innerContainer, {[css.alignCenter]: !imageSrc}, classNames?.content, )} style={{ '--background-color': AVATAR_COLOR[color], }} > {!!status && ( <div className={classify(css.statusIndicatorWrapper, css[size])}> <StatusIndicator status={status} withBorder borderColorToken={statusBorderColorToken} /> </div> )} {imageSrc ? ( <img src={imageSrc} alt="Avatar" className={classify(css.avatar, classNames?.image)} ></img> ) : iconName ? ( <Icon name={iconName} type={iconType} size={iconSize[size]} className={classNames?.icon} /> ) : text ? ( <AvatarTextDecorator avatarText={getAvatarText(text)} size={size} className={classNames?.text} /> ) : null} </div> ); export const Avatar: React$AbstractComponent<AvatarProps, HTMLDivElement> = React.forwardRef<AvatarProps, HTMLDivElement>( ( { classNames, size = AVATAR_SIZE.medium, imageSrc, text, style, iconName, iconType, color, ring = false, status, statusBorderColorToken, }: AvatarProps, ref, ) => ( <div className={classify( css.container, { [css.extraLargeSize]: size === 'extraLarge', [css.largeSize]: size === 'large', [css.mediumSize]: size === 'medium', [css.smallSize]: size === 'small', }, classNames?.wrapper, )} style={style} ref={ref} > {ring ? ( <div className={classify(css.ring, classNames?.ring)}> <div className={css.whiteCircle}> <AvatarConditionalContent imageSrc={imageSrc} text={text} iconName={iconName} iconType={iconType} color={color} size={size} status={status} statusBorderColorToken={statusBorderColorToken} classNames={classNames} /> </div> </div> ) : ( <AvatarConditionalContent imageSrc={imageSrc} text={text} iconName={iconName} iconType={iconType} color={color} size={size} status={status} statusBorderColorToken={statusBorderColorToken} classNames={classNames} /> )} </div> ), ); export const BaseAvatar = ({ classNames, size = AVATAR_SIZE.medium, text = '', style, color = 'blue', }: AvatarProps): React.Node => ( <div className={classify( css.container, { [css.baseExtraLargeSize]: size === 'extraLarge', [css.baseLargeSize]: size === 'large', [css.baseMediumSize]: size === 'medium', [css.baseSmallSize]: size === 'small', }, classNames?.wrapper, )} style={{...style, background: AVATAR_COLOR[color]}} > <AvatarTextDecorator avatarText={text} size={size} className={classNames?.text} /> </div> );