UNPKG

@spaced-out/ui-design-system

Version:
165 lines (148 loc) 4.3 kB
// @flow strict import * as React from 'react'; import {colorBackgroundTertiary} from '../../styles/variables/_color'; import { spaceSmall, spaceXSmall, spaceXXSmall, } from '../../styles/variables/_space'; import classify from '../../utils/classify'; import type {AvatarSize} from '../Avatar'; import {BaseAvatar} from '../Avatar'; import type {ElevationType, PlacementType} from '../Tooltip'; import {Tooltip} from '../Tooltip'; import css from './AvatarGroup.module.css'; const COLOR_SEQUENCE = ['blue', 'green', 'orange', 'red', 'gray']; export type AvatarGroupProps = { children?: React.Node, size?: AvatarSize, borderColor?: string, maxTooltipLines?: number, placement?: PlacementType, maxAvatars?: number, tooltipElevation?: ElevationType, }; export const AvatarGroup: React$AbstractComponent< AvatarGroupProps, HTMLDivElement, > = React.forwardRef<AvatarGroupProps, HTMLDivElement>( ( { children, size = 'medium', borderColor = colorBackgroundTertiary, maxTooltipLines = 7, placement = 'top', maxAvatars = 5, tooltipElevation, }: AvatarGroupProps, ref, ): React.Node => { const childAvatarCount = React.Children.count(children); const leftOverlap = { small: spaceXXSmall, medium: `${parseInt(spaceSmall) / 2}px`, large: spaceXSmall, extraLarge: spaceSmall, }; let colorIndex = -1; const avatarInGroup = (child, index, color) => { const {classNames, text} = child.props; const {wrapper, ...restClassNames} = classNames || {}; const avatar = React.cloneElement(child, { size, classNames: { wrapper: classify(css.avatarInGroup, wrapper), ...restClassNames, }, style: { borderColor, marginLeft: `-${index !== 0 ? leftOverlap[size] : 0}`, }, color, }); return text ? ( <Tooltip body={text} placement={placement} elevation={tooltipElevation}> {avatar} </Tooltip> ) : ( avatar ); }; const childrenArray = React.Children.toArray(children); const totalAvatarCount = childrenArray.length; const plusAvatar = () => { const nameList = []; for (let i = maxAvatars - 1; i < totalAvatarCount; i++) { const child = childrenArray[i]; const {text} = child.props; if (text) { nameList.push(text); } } const NameListNode = () => ( <span> {nameList.map((name) => ( <React.Fragment key={name}> {name} <br /> </React.Fragment> ))} </span> ); return ( <Tooltip body={<NameListNode />} bodyMaxLines={maxTooltipLines} placement={placement} elevation={tooltipElevation} > <div className={css.plusAvatar}> <BaseAvatar size={size} color="gray" text={`+${childAvatarCount - maxAvatars + 1}`} classNames={{wrapper: css.avatarInGroup}} style={{ borderColor, marginLeft: `-${leftOverlap[size]}`, }} /> </div> </Tooltip> ); }; const childrenWithProps = React.Children.map(children, (child, index) => { const {imageSrc} = child.props; if (!imageSrc) { colorIndex++; if (colorIndex === COLOR_SEQUENCE.length) { colorIndex = 0; } } const color = COLOR_SEQUENCE[colorIndex]; if (childAvatarCount <= maxAvatars) { return avatarInGroup(child, index, color); } else { if (index < maxAvatars - 1) { return avatarInGroup(child, index, color); } else if (index === maxAvatars) { return plusAvatar(); } } }); return ( <div className={classify(css.avatarGroupContainer, { [css.extraLargeSize]: size === 'extraLarge', [css.largeSize]: size === 'large', [css.mediumSize]: size === 'medium', [css.smallSize]: size === 'small', })} ref={ref} > {childrenWithProps} </div> ); }, );