{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "avatar-group",
  "type": "registry:ui",
  "title": "Avatar group",
  "description": "Shown under the name in the tooltip and overflow list.",
  "dependencies": [
    "@base-ui/react"
  ],
  "registryDependencies": [
    "https://ui.minidev.pro/r/popover.json",
    "https://ui.minidev.pro/r/tooltip.json",
    "utils"
  ],
  "files": [
    {
      "path": "registry/ui/avatar-group.tsx",
      "type": "registry:ui",
      "target": "components/ui/avatar-group.tsx",
      "content": "\"use client\"\nimport * as React from \"react\"\nimport { Avatar as AvatarPrimitive } from \"@base-ui/react/avatar\"\nimport { cn } from \"@/lib/utils\"\nimport { Popover, PopoverContent, PopoverTrigger } from \"@/components/ui/popover\"\nimport { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from \"@/components/ui/tooltip\"\n\ntype AvatarGroupPerson = {\n  name: string\n  /** Shown under the name in the tooltip and overflow list. */\n  role?: string\n  /** Image URL. Initials on a tinted background are used while it loads or if it fails. */\n  src?: string\n}\n\ntype Size = \"sm\" | \"md\" | \"lg\"\n\nconst SIZE: Record<Size, { avatar: string; overlap: string; text: string }> = {\n  sm: { avatar: \"size-6\", overlap: \"-ml-1\", text: \"text-[0.625rem]\" },\n  md: { avatar: \"size-8\", overlap: \"-ml-1.5\", text: \"text-xs\" },\n  lg: { avatar: \"size-10\", overlap: \"-ml-2\", text: \"text-sm\" },\n}\n\n// Tints are mixed from semantic tokens so they follow the theme and stay opaque where avatars overlap.\nconst TINTS = [\n  \"bg-[color-mix(in_oklch,var(--accent)_20%,var(--surface))] text-[color-mix(in_oklch,var(--accent)_78%,var(--fg))]\",\n  \"bg-[color-mix(in_oklch,var(--info)_20%,var(--surface))] text-[color-mix(in_oklch,var(--info)_78%,var(--fg))]\",\n  \"bg-[color-mix(in_oklch,var(--success)_20%,var(--surface))] text-[color-mix(in_oklch,var(--success)_78%,var(--fg))]\",\n  \"bg-[color-mix(in_oklch,var(--warning)_20%,var(--surface))] text-[color-mix(in_oklch,var(--warning)_62%,var(--fg))]\",\n  \"bg-[color-mix(in_oklch,var(--danger)_18%,var(--surface))] text-[color-mix(in_oklch,var(--danger)_78%,var(--fg))]\",\n  \"bg-[color-mix(in_oklch,var(--accent-2)_20%,var(--surface))] text-[color-mix(in_oklch,var(--accent-2)_74%,var(--fg))]\",\n  \"bg-[color-mix(in_oklch,var(--chart-2)_22%,var(--surface))] text-[color-mix(in_oklch,var(--chart-2)_62%,var(--fg))]\",\n]\n\nfunction tint(name: string) {\n  let h = 0\n  for (const c of name) h = (h * 31 + c.charCodeAt(0)) >>> 0\n  return TINTS[h % TINTS.length]\n}\n\nfunction initials(name: string) {\n  const parts = name.trim().split(/\\s+/)\n  return ((parts[0]?.[0] ?? \"\") + (parts.length > 1 ? parts[parts.length - 1][0] : \"\")).toUpperCase()\n}\n\nconst TEAM: AvatarGroupPerson[] = [\n  { name: \"Maya Chen\", role: \"Head of Finance\" },\n  { name: \"Theo Okafor\", role: \"Engineering lead\" },\n  { name: \"Priya Raman\", role: \"Product designer\" },\n  { name: \"Lucas Moreau\", role: \"Revenue ops\" },\n  { name: \"Sofia Alvarez\", role: \"Controller\" },\n  { name: \"Jonah Weiss\", role: \"Platform engineer\" },\n  { name: \"Amara Nwosu\", role: \"Customer success\" },\n  { name: \"Kenji Sato\", role: \"Data analyst\" },\n  { name: \"Elena Petrova\", role: \"Accounts payable\" },\n]\n\n/**\n * Overlapping avatars for a team, each ringed in the surface colour so the\n * stack reads cleanly on cards. Hovering or focusing an avatar lifts it above\n * its neighbours without moving the layout and shows a tooltip with the name.\n * Past `max`, a \"+N\" chip opens a popover listing everyone else. Initials on\n * token-tinted backgrounds stand in for missing photos. Sizes sm, md and lg.\n */\ntype AvatarGroupProps = {\n  people?: AvatarGroupPerson[]\n  /** Avatars shown before the \"+N\" chip. If only one person would overflow, they are shown instead of \"+1\". */\n  max?: number\n  size?: Size\n  /** Accessible name for the group, also the overflow popover title. */\n  label?: string\n  /** Show a \"Maya, Theo and 7 others\" line after the stack. */\n  summary?: boolean\n  className?: string\n}\n\nfunction AvatarGroup({ people = TEAM, max = 5, size = \"md\", label = \"Northwind team\", summary = true, className }: AvatarGroupProps) {\n  const s = SIZE[size]\n  const overflowing = people.length > max\n  // Never hide a single person behind \"+1\": show them instead.\n  const visible = overflowing && people.length - max > 1 ? people.slice(0, max) : people\n  const rest = people.slice(visible.length)\n  const first = people.slice(0, 2).map((p) => p.name.split(\" \")[0])\n  const line =\n    people.length === 0\n      ? \"No one yet\"\n      : people.length === 1\n        ? first[0]\n        : people.length === 2\n          ? `${first[0]} and ${first[1]}`\n          : `${first[0]}, ${first[1]} and ${people.length - 2} other${people.length - 2 === 1 ? \"\" : \"s\"}`\n\n  return (\n    <TooltipProvider delay={120}>\n      <div data-slot=\"avatar-group\" className={cn(\"flex flex-wrap items-center gap-x-3 gap-y-2\", className)}>\n        <div role=\"group\" aria-label={`${label}, ${people.length} ${people.length === 1 ? \"person\" : \"people\"}`} className=\"flex items-center ps-0.5\">\n          {visible.map((p, i) => (\n            <Tooltip key={p.name + i}>\n              <TooltipTrigger\n                render={<span />}\n                tabIndex={0}\n                role=\"img\"\n                aria-label={p.role ? `${p.name}, ${p.role}` : p.name}\n                className={cn(\n                  \"relative block rounded-full ring-2 ring-surface outline-none\",\n                  \"transition-transform duration-[140ms] ease-spring\",\n                  \"hover:z-10 hover:-translate-y-1 focus-visible:z-10 focus-visible:-translate-y-1\",\n                  \"focus-visible:ring-accent\",\n                  i > 0 && s.overlap\n                )}\n              >\n                <Face person={p} className={s.avatar} text={s.text} />\n              </TooltipTrigger>\n              <TooltipContent>\n                <span className=\"flex flex-col\">\n                  <span>{p.name}</span>\n                  {p.role ? <span className=\"font-normal text-on-ink/70\">{p.role}</span> : null}\n                </span>\n              </TooltipContent>\n            </Tooltip>\n          ))}\n          {rest.length ? (\n            <Popover>\n              <PopoverTrigger\n                aria-label={`Show ${rest.length} more`}\n                className={cn(\n                  \"relative grid shrink-0 cursor-pointer place-items-center rounded-full bg-sunken font-medium text-fg-muted tabular-nums ring-2 ring-surface outline-none\",\n                  \"transition-[color,background-color] duration-[70ms] hover:bg-border hover:text-fg\",\n                  \"focus-visible:ring-accent data-[popup-open]:bg-border data-[popup-open]:text-fg\",\n                  s.avatar,\n                  s.text,\n                  s.overlap\n                )}\n              >\n                +{rest.length}\n              </PopoverTrigger>\n              <PopoverContent align=\"start\" className=\"w-64 gap-2 p-2\">\n                <p className=\"px-2 pt-1 text-xs font-medium text-fg-muted\">\n                  {rest.length} more on {label}\n                </p>\n                <ul className=\"flex max-h-60 flex-col overflow-y-auto\">\n                  {rest.map((p, i) => (\n                    <li key={p.name + i} className=\"flex items-center gap-2.5 rounded-lg px-2 py-1.5\">\n                      <Face person={p} className=\"size-7\" text=\"text-[0.625rem]\" />\n                      <span className=\"flex min-w-0 flex-col\">\n                        <span className=\"truncate text-sm text-fg\">{p.name}</span>\n                        {p.role ? <span className=\"truncate text-xs text-fg-subtle\">{p.role}</span> : null}\n                      </span>\n                    </li>\n                  ))}\n                </ul>\n              </PopoverContent>\n            </Popover>\n          ) : null}\n        </div>\n        {summary ? <span className={cn(\"whitespace-nowrap text-fg-muted\", size === \"lg\" ? \"text-sm\" : \"text-xs\")}>{line}</span> : null}\n      </div>\n    </TooltipProvider>\n  )\n}\n\nfunction Face({ person, className, text }: { person: AvatarGroupPerson; className: string; text: string }) {\n  return (\n    <AvatarPrimitive.Root data-slot=\"avatar\" className={cn(\"relative flex shrink-0 overflow-hidden rounded-full select-none\", className)}>\n      {person.src ? <AvatarPrimitive.Image src={person.src} alt=\"\" className=\"size-full object-cover\" /> : null}\n      <AvatarPrimitive.Fallback className={cn(\"grid size-full place-items-center font-medium tracking-normal\", text, tint(person.name))}>\n        {initials(person.name)}\n      </AvatarPrimitive.Fallback>\n    </AvatarPrimitive.Root>\n  )\n}\n\nexport { AvatarGroup }\nexport type { AvatarGroupProps, AvatarGroupPerson }\n"
    }
  ]
}
