'use client' import { forwardRef, useCallback, useMemo } from 'react' import * as React from 'react' import { MoreIcon } from '@channel.io/bezier-icons' import classNames from 'classnames' import { isLastIndex } from '~/src/utils/array' import { px } from '~/src/utils/style' import { type AlphaAvatarProps, type AlphaAvatarSize, useAlphaAvatarRadiusToken, } from '~/src/components/AlphaAvatar' import { AvatarContextProvider } from '~/src/components/AlphaAvatar/AvatarSizeContext' import { Icon } from '~/src/components/Icon' import { SmoothCornersBox } from '~/src/components/SmoothCornersBox' import { Text } from '~/src/components/Text' import { type AvatarGroupProps } from './AvatarGroup.types' import styles from './AvatarGroup.module.scss' const MAX_AVATAR_LIST_COUNT = 99 const AVATAR_GROUP_DEFAULT_SPACING = 4 export const AVATAR_GROUP_ELLIPSIS_ICON_TEST_ID = 'bezier-avatar-group-ellipsis-icon' function getRestAvatarListCountText(count: number, max: number) { const restCount = count - max return `+${restCount > MAX_AVATAR_LIST_COUNT ? MAX_AVATAR_LIST_COUNT : restCount}` } // TODO: Not specified function getProperIconSize(size: AlphaAvatarSize) { return ( { 16: 'xxs', 20: 'xxs', 24: 'xs', 30: 's', 36: 'm', 42: 'm', 48: 'l', 72: 'l', 90: 'l', 120: 'l', } as const )[size] } // TODO: Not specified function getProperTypoSize(size: AlphaAvatarSize) { return ( { 16: '12', 20: '12', 24: '13', 30: '15', 36: '16', 42: '18', 48: '24', 72: '24', 90: '24', 120: '24', } as const )[size] } /** * `AvatarGroup` is a component for grouping `Avatar` components * @example * * ```tsx * * * * * * ``` */ export const AvatarGroup = forwardRef( function AvatarGroup( { max = 5, size = '24', spacing = AVATAR_GROUP_DEFAULT_SPACING, ellipsisType = 'icon', style, className, children, ...rest }, forwardedRef ) { const AVATAR_BORDER_RADIUS = useAlphaAvatarRadiusToken() const avatarListCount = React.Children.count(children) const renderAvatarElement = useCallback( (avatar: React.ReactElement) => { const key = avatar.key ?? `${avatar.props.name}-${avatar.props.avatarUrl}` const shouldShowBorder = max > 1 && avatarListCount > 1 && spacing < 0 const showBorder = avatar.props.showBorder || shouldShowBorder return React.cloneElement(avatar, { key, showBorder }) }, [avatarListCount, max, spacing] ) const AvatarListComponent = useMemo(() => { return React.Children.toArray(children) .slice(0, max) .map((avatar, index, arr) => { if (!React.isValidElement(avatar)) { return null } const AvatarElement = renderAvatarElement(avatar) if (!isLastIndex(arr, index) || avatarListCount <= max) { return AvatarElement } if (ellipsisType === 'icon') { return (
{AvatarElement}
) } if (ellipsisType === 'count') { return ( {AvatarElement}
{getRestAvatarListCountText(avatarListCount, max)}
) } return null }) }, [ avatarListCount, max, children, renderAvatarElement, ellipsisType, AVATAR_BORDER_RADIUS, size, spacing, ]) return ( ({ size, }), [size] )} >
{AvatarListComponent}
) } )