{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "avatar-group",
  "title": "Avatar Group",
  "description": "An overlapping avatar stack with ring cutouts, letter fallbacks for broken images, and a computed +N overflow chip.",
  "registryDependencies": [
    "utils"
  ],
  "files": [
    {
      "path": "src/registry/ui/avatar-group.tsx",
      "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { cn } from \"@/lib/utils\"\n\nconst SIZES = {\n  sm: { avatar: \"size-6 text-[10px]\", overlap: \"-space-x-1.5\" },\n  md: { avatar: \"size-8 text-xs\", overlap: \"-space-x-2\" },\n  lg: { avatar: \"size-10 text-sm\", overlap: \"-space-x-2.5\" },\n} as const\n\n/** Shared shell for a disc: ring-background matches the page, so the overlap reads as a cut-out */\nconst CIRCLE =\n  \"relative flex shrink-0 select-none items-center justify-center overflow-hidden rounded-full bg-muted font-medium text-muted-foreground ring-2 ring-background\"\n\n/** Hovering one disc: raising z reveals it and stays, the lift is decoration (off under reduced-motion) */\nconst LIFT =\n  \"transition-transform duration-200 ease-out hover:z-10 hover:-translate-y-1 motion-reduce:transition-none motion-reduce:hover:translate-y-0\"\n\nexport interface AvatarGroupItem {\n  src?: string\n  alt: string\n  /** Fallback letter; defaults to the first character of `alt`, uppercased. */\n  fallback?: string\n}\n\nexport interface AvatarGroupProps extends React.HTMLAttributes<HTMLDivElement> {\n  items: AvatarGroupItem[]\n  /** Anything beyond this collapses into a \"+N\" disc, N computed live. */\n  max?: number\n  size?: keyof typeof SIZES\n}\n\nfunction AvatarItem({ item, sizeClass }: { item: AvatarGroupItem; sizeClass: string }) {\n  const [failed, setFailed] = React.useState(false)\n  const showImage = Boolean(item.src) && !failed\n\n  return (\n    <span className={cn(CIRCLE, LIFT, sizeClass)}>\n      {showImage ? (\n        // eslint-disable-next-line @next/next/no-img-element -- registry components stay framework-agnostic, no next/image\n        <img\n          alt={item.alt}\n          className=\"size-full object-cover\"\n          onError={() => setFailed(true)}\n          src={item.src}\n        />\n      ) : (\n        <span aria-label={item.alt} role=\"img\">\n          {item.fallback ?? item.alt.charAt(0).toUpperCase()}\n        </span>\n      )}\n    </span>\n  )\n}\n\nexport const AvatarGroup = React.forwardRef<HTMLDivElement, AvatarGroupProps>(\n  ({ items, max = 5, size = \"md\", className, ...props }, ref) => {\n    const { avatar, overlap } = SIZES[size]\n    const visible = items.length > max ? items.slice(0, max) : items\n    const hidden = items.length - visible.length\n\n    return (\n      <div className={cn(\"flex items-center\", overlap, className)} ref={ref} {...props}>\n        {visible.map((item, i) => (\n          <AvatarItem item={item} key={`${item.alt}-${i}`} sizeClass={avatar} />\n        ))}\n        {hidden > 0 && (\n          <span className={cn(CIRCLE, avatar)}>\n            +{hidden}\n            <span className=\"sr-only\"> more</span>\n          </span>\n        )}\n      </div>\n    )\n  },\n)\n\nAvatarGroup.displayName = \"AvatarGroup\"\n\nexport default AvatarGroup\n",
      "type": "registry:ui"
    }
  ],
  "type": "registry:ui"
}