src/avatar-stack/avatar-stack.tsx (65 lines of code) (raw):
import {Children, type HTMLAttributes, useState} from 'react';
import classNames from 'classnames';
import {type ListDataItem} from '../list/consts';
import DropdownMenu, {type DropdownMenuProps} from '../dropdown-menu/dropdown-menu';
import Avatar, {Size} from '../avatar/avatar';
import {fontSizes} from '../avatar/avatar-info';
import styles from './avatar-stack.css';
declare module 'csstype' {
interface Properties {
'--ring-avatar-stack-index'?: number;
}
}
export interface AvatarProps<T = unknown> extends HTMLAttributes<HTMLDivElement> {
size?: Size | undefined;
extraItems?: readonly ListDataItem<T>[] | null | undefined;
dropdownMenuProps?: DropdownMenuProps<T> | null | undefined;
}
export default function AvatarStack({
children,
className,
size = Size.Size20,
extraItems,
dropdownMenuProps,
...restProps
}: AvatarProps) {
const [dropdownOpen, setDropdownOpen] = useState(false);
return (
<div
className={classNames(styles.avatarStack, className, styles[`size${size}`], {
[styles.hovered]: dropdownOpen,
})}
{...restProps}
style={{height: size, ...restProps.style}}
>
{Children.map(children, (child, index) => (
<div className={styles.item} style={{'--ring-avatar-stack-index': index}}>
{child}
</div>
))}
{extraItems?.length ? (
<DropdownMenu
hoverMode
hoverShowTimeOut={10}
onShow={() => setDropdownOpen(true)}
onHide={() => setDropdownOpen(false)}
className={styles.extra}
style={{
width: size,
height: size,
'--ring-avatar-stack-index': Children.count(children),
fontSize: fontSizes[size],
}}
anchor={(
<button type='button' className={styles.extraButton}>
<Avatar size={size} info={<span className={styles.extraText}>{`+${extraItems.length}`}</span>} />
</button>
)}
data={extraItems}
menuProps={{offset: 4, ...dropdownMenuProps?.menuProps}}
{...dropdownMenuProps}
/>
) : null}
</div>
);
}