"use client" import * as React from "react" import { cn } from "@/lib/utils" export type AvatarSize = "xs" | "sm" | "default" | "lg" | "xl" export type AvatarShape = "circle" | "rounded" | "square" export type AvatarProps = React.ComponentProps<"span"> & { src?: string alt?: string name?: string fallback?: React.ReactNode size?: AvatarSize shape?: AvatarShape status?: "online" | "offline" | "busy" | "away" } export type AvatarGroupItem = AvatarProps & { key: string } export type AvatarGroupProps = React.ComponentProps<"div"> & { items: AvatarGroupItem[] max?: number size?: AvatarSize shape?: AvatarShape stacked?: boolean overflowLabel?: (count: number) => React.ReactNode } const sizeClassName: Record = { xs: "size-6 text-[10px]", sm: "size-8 text-xs", default: "size-10 text-sm", lg: "size-12 text-base", xl: "size-16 text-lg", } const shapeClassName: Record = { circle: "rounded-full", rounded: "rounded-xl", square: "rounded-md", } const statusClassName = { online: "bg-emerald-500", offline: "bg-muted-foreground", busy: "bg-destructive", away: "bg-amber-500", } function getInitials(name?: string) { if (!name) return "?" const words = name.trim().split(/\s+/).filter(Boolean) if (!words.length) return "?" return words.slice(0, 2).map((word) => word[0]?.toUpperCase()).join("") } function Avatar({ src, alt, name, fallback, size = "default", shape = "circle", status, className, ...props }: AvatarProps) { const [imageError, setImageError] = React.useState(false) const showImage = Boolean(src && !imageError) return ( {showImage ? ( {alt setImageError(true)} /> ) : ( {fallback ?? getInitials(name)} )} {status && ( )} ) } function AvatarGroup({ items, max = 5, size = "default", shape = "circle", stacked = true, overflowLabel = (count) => `+${count}`, className, ...props }: AvatarGroupProps) { const visibleItems = items.slice(0, max) const overflowCount = Math.max(items.length - max, 0) return (
{visibleItems.map((item) => { const { key: itemKey, ...avatarProps } = item return ( ) })} {overflowCount > 0 && ( )}
) } export { Avatar, AvatarGroup }