"use client" import { cva, type VariantProps } from "class-variance-authority" import * as React from "react" import { Slot } from "@radix-ui/react-slot" import { cn } from "../../../lib/utils" const avatarGroupVariants = cva("flex items-center", { variants: { orientation: { horizontal: "flex-row", vertical: "flex-col", }, dir: { ltr: "", rtl: "", }, }, compoundVariants: [ { orientation: "horizontal", dir: "ltr", className: "-space-x-1", }, { orientation: "horizontal", dir: "rtl", className: "flex-row-reverse -space-x-1 space-x-reverse", }, { orientation: "vertical", dir: "ltr", className: "-space-y-1", }, { orientation: "vertical", dir: "rtl", className: "flex-col-reverse -space-y-1 space-y-reverse", }, ], defaultVariants: { orientation: "horizontal", dir: "ltr", }, }) interface AvatarGroupProps extends Omit, "dir">, VariantProps { size?: number max?: number asChild?: boolean reverse?: boolean renderOverflow?: (count: number) => React.ReactNode } function AvatarGroup(props: AvatarGroupProps) { const { orientation = "horizontal", dir = "ltr", size = 40, max, asChild, reverse = false, renderOverflow, className, children, ...rootProps } = props const childrenArray = React.Children.toArray(children).filter( React.isValidElement, ) const itemCount = childrenArray.length const shouldTruncate = max && itemCount > max const visibleItems = shouldTruncate ? childrenArray.slice(0, max - 1) : childrenArray const overflowCount = shouldTruncate ? itemCount - (max - 1) : 0 const totalRenderedItems = shouldTruncate ? max : itemCount const RootPrimitive = asChild ? Slot : "div" return ( {visibleItems.map((child, index) => ( ))} {shouldTruncate && ( +{overflowCount} ) } index={visibleItems.length} itemCount={totalRenderedItems} orientation={orientation} dir={dir} size={size} reverse={reverse} /> )} ) } interface AvatarGroupItemProps extends Omit, "dir">, VariantProps { child: React.ReactNode index: number itemCount: number size: number reverse: boolean } function AvatarGroupItem(props: AvatarGroupItemProps) { const { child, index, size, orientation, dir = "ltr", reverse = false, itemCount, className, style, ...itemProps } = props const maskStyle = React.useMemo(() => { let maskImage = "" let shouldMask = false if (orientation === "vertical" && dir === "rtl" && reverse) { shouldMask = index !== itemCount - 1 } else { shouldMask = reverse ? index < itemCount - 1 : index > 0 } if (shouldMask) { const maskRadius = size / 2 const maskOffset = size / 4 + size / 10 if (orientation === "vertical") { if (dir === "ltr") { if (reverse) { maskImage = `radial-gradient(circle ${maskRadius}px at 50% ${size + maskOffset}px, transparent 99%, white 100%)` } else { maskImage = `radial-gradient(circle ${maskRadius}px at 50% -${maskOffset}px, transparent 99%, white 100%)` } } else { if (reverse) { maskImage = `radial-gradient(circle ${maskRadius}px at 50% -${maskOffset}px, transparent 99%, white 100%)` } else { maskImage = `radial-gradient(circle ${maskRadius}px at 50% ${size + maskOffset}px, transparent 99%, white 100%)` } } } else { if (dir === "ltr") { if (reverse) { maskImage = `radial-gradient(circle ${maskRadius}px at ${size + maskOffset}px 50%, transparent 99%, white 100%)` } else { maskImage = `radial-gradient(circle ${maskRadius}px at -${maskOffset}px 50%, transparent 99%, white 100%)` } } else { if (reverse) { maskImage = `radial-gradient(circle ${maskRadius}px at -${maskOffset}px 50%, transparent 99%, white 100%)` } else { maskImage = `radial-gradient(circle ${maskRadius}px at ${size + maskOffset}px 50%, transparent 99%, white 100%)` } } } } return { width: size, height: size, maskImage, } }, [size, index, orientation, dir, reverse, itemCount]) return (
{child}
) } export { AvatarGroup } export type { AvatarGroupProps }