@spaced-out/ui-design-system
Version:
Sense UI components library
165 lines (148 loc) • 4.3 kB
Flow
// @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>
);
},
);