{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "avatar-group",
  "type": "registry:ui",
  "title": "AvatarGroup",
  "description": "Compact Avatar composition primitive with RTL-safe overlap, max visible members, overflow summary, programmatic group/member names, and optional Motion-powered reveal polish.",
  "dependencies": [
    "motion@^12.43.0"
  ],
  "devDependencies": [],
  "registryDependencies": [
    "https://components.dethink.co.uk/r/dethink-base.json",
    "https://components.dethink.co.uk/r/avatar.json"
  ],
  "files": [
    {
      "path": "packages/components/src/components/avatar-group/avatar-group.tsx",
      "type": "registry:file",
      "target": "~/components/dethink/components/avatar-group/avatar-group.tsx",
      "content": "import {\n  forwardRef,\n  useRef,\n  useState,\n  useId,\n  type FocusEventHandler,\n  type HTMLAttributes,\n  type MouseEventHandler,\n  type PointerEventHandler,\n  type ReactNode,\n} from \"react\";\nimport {\n  motion as motionElement,\n  useReducedMotion,\n  type HTMLMotionProps,\n  type Transition,\n} from \"motion/react\";\nimport {\n  Avatar,\n  type AvatarMotion,\n  type AvatarProps,\n  type AvatarRing,\n  type AvatarShape,\n  type AvatarSize,\n} from \"../avatar\";\nimport { cn } from \"../../utils/cn\";\n\nexport type AvatarGroupOverlap = \"none\" | \"sm\" | \"md\" | \"lg\";\nexport type AvatarGroupReveal = \"none\" | \"spread\" | \"names\";\nexport type AvatarGroupMotion = AvatarMotion;\nexport type AvatarGroupRevealLabelVisibility = \"hover\" | \"always\";\n\nexport interface AvatarGroupMember extends Pick<\n  AvatarProps,\n  | \"alt\"\n  | \"crossOrigin\"\n  | \"decoding\"\n  | \"fallbackIcon\"\n  | \"fetchPriority\"\n  | \"imageProps\"\n  | \"initials\"\n  | \"loading\"\n  | \"motion\"\n  | \"name\"\n  | \"onImageError\"\n  | \"onImageLoad\"\n  | \"reducedMotion\"\n  | \"referrerPolicy\"\n  | \"ring\"\n  | \"shape\"\n  | \"size\"\n  | \"sizes\"\n  | \"src\"\n  | \"srcSet\"\n  | \"tone\"\n> {\n  avatarClassName?: string;\n  id?: string | number;\n  label?: string;\n  metadata?: ReactNode;\n}\n\nexport interface AvatarGroupOverflowLabelContext {\n  count: number;\n  hiddenMembers: readonly AvatarGroupMember[];\n  label?: string;\n  members: readonly AvatarGroupMember[];\n  totalCount: number;\n  visibleCount: number;\n}\n\nexport type AvatarGroupOverflowLabel =\n  string | ((context: AvatarGroupOverflowLabelContext) => string);\n\nexport type AvatarGroupVisibleMemberLabel =\n  string | ((member: AvatarGroupMember, index: number) => string);\n\nexport interface AvatarGroupProps extends Omit<\n  HTMLAttributes<HTMLDivElement>,\n  \"aria-label\" | \"aria-labelledby\" | \"children\" | \"role\"\n> {\n  label?: string;\n  max?: number;\n  members: readonly AvatarGroupMember[];\n  motion?: AvatarGroupMotion;\n  overlap?: AvatarGroupOverlap;\n  overflowLabel?: AvatarGroupOverflowLabel;\n  reducedMotion?: boolean;\n  reveal?: AvatarGroupReveal;\n  revealLabelVisibility?: AvatarGroupRevealLabelVisibility;\n  ring?: AvatarRing;\n  shape?: AvatarShape;\n  size?: AvatarSize;\n  visibleMemberLabel?: AvatarGroupVisibleMemberLabel;\n}\n\nconst avatarGroupBaseClasses =\n  \"relative inline-flex max-w-full items-center align-middle outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background data-[reduced-motion=true]:will-change-auto\";\n\nconst avatarGroupOverlapClasses: Record<AvatarGroupOverlap, string> = {\n  none: \"[--avatar-group-overlap:0rem]\",\n  sm: \"[--avatar-group-overlap:calc(var(--dt-density-control)*0.16)]\",\n  md: \"[--avatar-group-overlap:calc(var(--dt-density-control)*0.28)]\",\n  lg: \"[--avatar-group-overlap:calc(var(--dt-density-control)*0.4)]\",\n};\n\nconst avatarGroupStackClasses =\n  \"isolate flex list-none items-center p-0 [margin-block:0] [margin-inline:0]\";\n\nconst avatarGroupItemClasses =\n  \"relative flex shrink-0 transform-gpu [margin-inline-start:calc(var(--avatar-group-overlap)*-1)] [rotate:0deg] [scale:1] [translate:0_0] first:[margin-inline-start:0]\";\n\nconst avatarGroupOverflowClasses = \"bg-background text-muted-foreground\";\n\nconst avatarGroupRevealLabelClasses =\n  \"pointer-events-none absolute start-1/2 top-full z-50 mt-[var(--dt-space-2)] max-w-48 -translate-x-1/2 truncate rounded-md border border-border bg-background px-[var(--dt-space-2)] py-[var(--dt-space-1)] text-xs font-medium text-foreground shadow-lg rtl:translate-x-1/2\";\n\nconst avatarGroupRevealSummaryClasses =\n  \"pointer-events-none absolute start-1/2 top-full z-50 mt-[var(--dt-space-2)] max-w-[min(20rem,calc(100vw-var(--dt-space-4)))] -translate-x-1/2 truncate rounded-md border border-border bg-background px-[var(--dt-space-3)] py-[var(--dt-space-1)] text-xs font-medium text-foreground shadow-lg rtl:translate-x-1/2\";\n\n// Per-step translate that fans the overlapped stack apart on hover so the\n// avatars separate cleanly and the zoomed one never collides with a neighbor.\nconst avatarGroupRevealSpread: Record<AvatarSize, number> = {\n  xs: 16,\n  sm: 18,\n  md: 22,\n  lg: 26,\n  xl: 32,\n  \"2xl\": 38,\n};\n\nconst avatarGroupLabelTransitions: Record<AvatarGroupMotion, Transition> = {\n  none: { duration: 0 },\n  subtle: { duration: 0.12, ease: \"easeOut\" },\n  standard: { duration: 0.16, ease: \"easeOut\" },\n};\n\n// Hovering the group expands the stack; only the avatar under the pointer zooms,\n// and moving to another avatar returns the previous one to its resting size.\nconst avatarGroupZoomScale: Record<AvatarGroupMotion, number> = {\n  none: 1,\n  subtle: 1.22,\n  standard: 1.3,\n};\n\nconst avatarGroupExpandTransitions: Record<AvatarGroupMotion, Transition> = {\n  none: { duration: 0 },\n  subtle: { type: \"spring\", stiffness: 420, damping: 34, mass: 0.8 },\n  standard: { type: \"spring\", stiffness: 380, damping: 30, mass: 0.8 },\n};\n\nconst avatarGroupItemScaleClasses =\n  \"inline-flex origin-bottom transform-gpu will-change-transform data-[reduced-motion=true]:will-change-auto\";\n\nexport function avatarGroupClassNames({\n  className,\n  overlap = \"md\",\n}: Pick<AvatarGroupProps, \"className\" | \"overlap\"> = {}) {\n  return cn(\n    avatarGroupBaseClasses,\n    avatarGroupOverlapClasses[overlap],\n    className,\n  );\n}\n\nfunction normalizeMax(max: number | undefined, totalCount: number) {\n  if (max === undefined || !Number.isFinite(max)) {\n    return totalCount;\n  }\n\n  return Math.max(0, Math.floor(max));\n}\n\nfunction getDirectionDataAttribute(dir: AvatarGroupProps[\"dir\"]) {\n  if (dir === \"ltr\" || dir === \"rtl\") {\n    return dir;\n  }\n\n  return \"inherit\";\n}\n\nfunction getMemberKey(member: AvatarGroupMember, index: number) {\n  return `${member.id ?? member.name ?? member.label ?? \"member\"}-${index}`;\n}\n\nfunction getMemberLabel({\n  fallback,\n  index,\n  member,\n  visibleMemberLabel,\n}: {\n  fallback?: string;\n  index: number;\n  member: AvatarGroupMember;\n  visibleMemberLabel?: AvatarGroupVisibleMemberLabel;\n}) {\n  if (typeof visibleMemberLabel === \"function\") {\n    return visibleMemberLabel(member, index);\n  }\n\n  if (typeof visibleMemberLabel === \"string\") {\n    return visibleMemberLabel;\n  }\n\n  if (member.label) {\n    return member.label;\n  }\n\n  if (member.name) {\n    return member.name;\n  }\n\n  if (member.alt) {\n    return member.alt;\n  }\n\n  if (member.initials) {\n    return member.initials;\n  }\n\n  return fallback ?? `Member ${index + 1}`;\n}\n\nfunction getAvatarName(member: AvatarGroupMember, index: number) {\n  return (\n    member.name ||\n    member.label ||\n    (member.alt && member.alt.length > 0 ? member.alt : undefined) ||\n    member.initials ||\n    `Member ${index + 1}`\n  );\n}\n\nfunction getOverflowDisplay(count: number) {\n  if (count > 99) {\n    return \"99+\";\n  }\n\n  return `+${count}`;\n}\n\nfunction getOverflowLabel({\n  context,\n  overflowLabel,\n}: {\n  context: AvatarGroupOverflowLabelContext;\n  overflowLabel?: AvatarGroupOverflowLabel;\n}) {\n  if (context.count <= 0) {\n    return undefined;\n  }\n\n  if (typeof overflowLabel === \"function\") {\n    return overflowLabel(context);\n  }\n\n  if (typeof overflowLabel === \"string\") {\n    return overflowLabel;\n  }\n\n  const noun = context.label ? context.label.toLowerCase() : \"members\";\n  const normalizedNoun =\n    context.count === 1 && noun.endsWith(\"s\") ? noun.slice(0, -1) : noun;\n\n  return `${context.count} more ${normalizedNoun}`;\n}\n\nfunction canRevealForHover() {\n  if (\n    typeof window === \"undefined\" ||\n    typeof window.matchMedia !== \"function\"\n  ) {\n    return true;\n  }\n\n  return window.matchMedia(\"(hover: hover)\").matches;\n}\n\nfunction getMotionBehavior({\n  motion,\n  reducedMotion,\n  reveal,\n}: {\n  motion: AvatarGroupMotion;\n  reducedMotion: boolean;\n  reveal: AvatarGroupReveal;\n}) {\n  if (reveal === \"none\" || motion === \"none\") {\n    return \"disabled\";\n  }\n\n  if (reducedMotion || reveal === \"names\") {\n    return \"opacity\";\n  }\n\n  return \"transform-opacity\";\n}\n\nfunction getRevealOffset({\n  count,\n  direction,\n  index,\n  motionBehavior,\n  reveal,\n  size,\n}: {\n  count: number;\n  direction: ReturnType<typeof getDirectionDataAttribute>;\n  index: number;\n  motionBehavior: ReturnType<typeof getMotionBehavior>;\n  reveal: AvatarGroupReveal;\n  size: AvatarSize;\n}) {\n  if (reveal !== \"spread\" || motionBehavior !== \"transform-opacity\") {\n    return 0;\n  }\n\n  const center = (count - 1) / 2;\n  const directionMultiplier = direction === \"rtl\" ? -1 : 1;\n\n  return (\n    Math.round((index - center) * avatarGroupRevealSpread[size]) *\n    directionMultiplier\n  );\n}\n\nfunction getRevealSummary({\n  hiddenMembers,\n  overflowAccessibleLabel,\n  visibleMemberLabel,\n  visibleMembers,\n}: {\n  hiddenMembers: readonly AvatarGroupMember[];\n  overflowAccessibleLabel?: string;\n  visibleMemberLabel?: AvatarGroupVisibleMemberLabel;\n  visibleMembers: readonly AvatarGroupMember[];\n}) {\n  const visibleLabels = visibleMembers.map((member, index) =>\n    getMemberLabel({ index, member, visibleMemberLabel }),\n  );\n\n  if (overflowAccessibleLabel && hiddenMembers.length > 0) {\n    visibleLabels.push(overflowAccessibleLabel);\n  }\n\n  return visibleLabels.length > 0 ? visibleLabels.join(\", \") : \"No members\";\n}\n\nfunction renderMemberMetadata(metadata: ReactNode) {\n  if (metadata === null || metadata === undefined || metadata === false) {\n    return null;\n  }\n\n  return <> - {metadata}</>;\n}\n\ntype AvatarGroupActiveKey = number | \"overflow\";\n\ninterface AvatarGroupItemProps {\n  active: boolean;\n  children: ReactNode;\n  expandTransition: Transition;\n  label?: string;\n  labelActive: boolean;\n  labelTransition: Transition;\n  motionBehavior: ReturnType<typeof getMotionBehavior>;\n  reducedMotion: boolean;\n  revealOffset: number;\n  revealState: \"revealed\" | \"collapsed\";\n  showLabel: boolean;\n  slot: \"item\" | \"overflow\";\n  stackIndex: number;\n  xTarget: number;\n  zIndex: number;\n  zoomScale: number;\n  itemIndex?: number;\n}\n\nfunction AvatarGroupItem({\n  active,\n  children,\n  expandTransition,\n  itemIndex,\n  label,\n  labelActive,\n  labelTransition,\n  motionBehavior,\n  reducedMotion,\n  revealOffset,\n  revealState,\n  showLabel,\n  slot,\n  stackIndex,\n  xTarget,\n  zIndex,\n  zoomScale,\n}: AvatarGroupItemProps) {\n  const slotProps =\n    slot === \"overflow\"\n      ? { \"data-overflow-item\": \"true\", \"data-slot\": \"avatar-group-overflow\" }\n      : { \"data-member-index\": itemIndex, \"data-slot\": \"avatar-group-item\" };\n  // Only the hovered avatar zooms; the stack fans apart via `x` so nothing\n  // overlaps while it is enlarged.\n  const scaleTarget = active && !reducedMotion ? zoomScale : 1;\n\n  return (\n    <motionElement.li\n      {...slotProps}\n      data-active={active ? \"true\" : undefined}\n      data-motion-behavior={motionBehavior}\n      data-reveal-offset={revealOffset}\n      data-stack-index={stackIndex}\n      data-state={revealState}\n      className={avatarGroupItemClasses}\n      style={{ zIndex }}\n      initial={false}\n      animate={{ x: xTarget }}\n      transition={expandTransition}\n    >\n      <motionElement.span\n        aria-hidden=\"true\"\n        data-slot=\"avatar-group-item-scale\"\n        data-reduced-motion={reducedMotion ? \"true\" : undefined}\n        className={avatarGroupItemScaleClasses}\n        initial={false}\n        animate={{ scale: scaleTarget }}\n        transition={expandTransition}\n      >\n        {children}\n      </motionElement.span>\n      {showLabel && label ? (\n        <motionElement.span\n          aria-hidden=\"true\"\n          data-slot=\"avatar-group-reveal-label\"\n          data-state={labelActive ? \"revealed\" : \"collapsed\"}\n          className={avatarGroupRevealLabelClasses}\n          initial={false}\n          animate={{\n            opacity: labelActive ? 1 : 0,\n            y: labelActive || reducedMotion ? 0 : -2,\n          }}\n          transition={labelTransition}\n        >\n          {label}\n        </motionElement.span>\n      ) : null}\n    </motionElement.li>\n  );\n}\n\nexport const AvatarGroup = forwardRef<HTMLDivElement, AvatarGroupProps>(\n  (\n    {\n      \"aria-describedby\": ariaDescribedBy,\n      className,\n      dir,\n      label = \"Avatar group\",\n      max = 5,\n      members,\n      motion = \"standard\",\n      onBlur,\n      onFocus,\n      onMouseEnter,\n      onMouseLeave,\n      onPointerEnter,\n      onPointerLeave,\n      onPointerMove,\n      overlap = \"md\",\n      overflowLabel,\n      reducedMotion: reducedMotionProp,\n      reveal = \"none\",\n      revealLabelVisibility = \"hover\",\n      ring = \"border\",\n      shape = \"circle\",\n      size = \"md\",\n      tabIndex,\n      visibleMemberLabel,\n      ...props\n    },\n    ref,\n  ) => {\n    const prefersReducedMotion = useReducedMotion();\n    const descriptionId = useId();\n    const memberListId = useId();\n    const [hovered, setHovered] = useState(false);\n    const [focused, setFocused] = useState(false);\n    const [activeKey, setActiveKey] = useState<AvatarGroupActiveKey | null>(\n      null,\n    );\n    const stackRef = useRef<HTMLUListElement>(null);\n    const totalCount = members.length;\n    const visibleLimit = Math.min(normalizeMax(max, totalCount), totalCount);\n    const visibleMembers = members.slice(0, visibleLimit);\n    const hiddenMembers = members.slice(visibleLimit);\n    const overflowCount = hiddenMembers.length;\n    const overflowContext: AvatarGroupOverflowLabelContext = {\n      count: overflowCount,\n      hiddenMembers,\n      label,\n      members,\n      totalCount,\n      visibleCount: visibleMembers.length,\n    };\n    const overflowAccessibleLabel = getOverflowLabel({\n      context: overflowContext,\n      overflowLabel,\n    });\n    const describedBy = [descriptionId, ariaDescribedBy]\n      .filter(Boolean)\n      .join(\" \");\n    const visualItems =\n      overflowCount > 0 ? visibleMembers.length + 1 : visibleMembers.length;\n    const direction = getDirectionDataAttribute(dir);\n    const reducedMotion =\n      motion === \"none\" ||\n      reducedMotionProp === true ||\n      prefersReducedMotion === true;\n    const motionBehavior = getMotionBehavior({\n      motion,\n      reducedMotion,\n      reveal,\n    });\n    const canReveal = reveal !== \"none\";\n    const revealActive =\n      canReveal && (revealLabelVisibility === \"always\" || hovered || focused);\n    const revealState = revealActive ? \"revealed\" : \"collapsed\";\n    // Hovering a spread group fans the stack apart and zooms only the avatar\n    // under the pointer, revealing its label. The static spread (all labels,\n    // permanently fanned apart) is kept for the always/reduced/no-motion paths\n    // where there is no pointer to follow.\n    // `reducedMotion` already folds in `motion === \"none\"`, so excluding it here\n    // also covers the no-motion path.\n    const magnifyMode =\n      reveal === \"spread\" &&\n      revealLabelVisibility === \"hover\" &&\n      !reducedMotion;\n    const zoomScale = avatarGroupZoomScale[motion];\n    const expandTransition = reducedMotion\n      ? avatarGroupExpandTransitions.none\n      : avatarGroupExpandTransitions[motion];\n    const labelTransition = reducedMotion\n      ? avatarGroupLabelTransitions.none\n      : avatarGroupLabelTransitions[motion];\n    const resolvedTabIndex =\n      canReveal && revealLabelVisibility !== \"always\" && tabIndex === undefined\n        ? 0\n        : tabIndex;\n    const revealSummary = getRevealSummary({\n      hiddenMembers,\n      overflowAccessibleLabel,\n      visibleMemberLabel,\n      visibleMembers,\n    });\n\n    const resetPointerState = () => {\n      setHovered(false);\n      setActiveKey(null);\n    };\n\n    // Track the avatar nearest the pointer so only it zooms and surfaces its\n    // label; moving the pointer resets the previously active avatar.\n    const updateActiveKeyFromPointer = (clientX: number) => {\n      const stack = stackRef.current;\n\n      if (!stack) {\n        return;\n      }\n\n      const nodes = stack.querySelectorAll<HTMLElement>(\n        '[data-slot=\"avatar-group-item\"], [data-slot=\"avatar-group-overflow\"]',\n      );\n      let nearest: AvatarGroupActiveKey | null = null;\n      let nearestDistance = Number.POSITIVE_INFINITY;\n\n      nodes.forEach((node) => {\n        const bounds = node.getBoundingClientRect();\n\n        if (bounds.width <= 0) {\n          return;\n        }\n\n        const distance = Math.abs(clientX - (bounds.left + bounds.width / 2));\n\n        if (distance < nearestDistance) {\n          nearestDistance = distance;\n          nearest = node.hasAttribute(\"data-overflow-item\")\n            ? \"overflow\"\n            : Number(node.getAttribute(\"data-member-index\"));\n        }\n      });\n\n      if (nearest !== null) {\n        setActiveKey((current) => (current === nearest ? current : nearest));\n      }\n    };\n\n    const handleFocus: FocusEventHandler<HTMLDivElement> = (event) => {\n      if (canReveal) {\n        setFocused(true);\n      }\n\n      onFocus?.(event);\n    };\n\n    const handleBlur: FocusEventHandler<HTMLDivElement> = (event) => {\n      setFocused(false);\n      onBlur?.(event);\n    };\n\n    const handlePointerEnter: PointerEventHandler<HTMLDivElement> = (event) => {\n      if (canReveal && event.pointerType !== \"touch\" && canRevealForHover()) {\n        setHovered(true);\n      }\n\n      onPointerEnter?.(event);\n    };\n\n    const handlePointerMove: PointerEventHandler<HTMLDivElement> = (event) => {\n      onPointerMove?.(event);\n\n      if (!magnifyMode || event.pointerType === \"touch\") {\n        return;\n      }\n\n      updateActiveKeyFromPointer(event.clientX);\n    };\n\n    const handlePointerLeave: PointerEventHandler<HTMLDivElement> = (event) => {\n      resetPointerState();\n      onPointerLeave?.(event);\n    };\n\n    const handleMouseEnter: MouseEventHandler<HTMLDivElement> = (event) => {\n      if (canReveal && canRevealForHover()) {\n        setHovered(true);\n      }\n\n      onMouseEnter?.(event);\n    };\n\n    const handleMouseLeave: MouseEventHandler<HTMLDivElement> = (event) => {\n      resetPointerState();\n      onMouseLeave?.(event);\n    };\n\n    // The names summary (and the spread summary shown on keyboard focus, where\n    // there is no pointer to drive the wave) fades as a single element.\n    const showSummary =\n      reveal === \"names\" || (reveal === \"spread\" && magnifyMode);\n    const summaryVisible = reveal === \"names\" ? revealActive : focused;\n\n    return (\n      <motionElement.div\n        {...(props as HTMLMotionProps<\"div\">)}\n        ref={ref}\n        aria-describedby={describedBy || undefined}\n        aria-label={label}\n        data-count={totalCount}\n        data-direction={direction}\n        data-motion={motion}\n        data-motion-behavior={motionBehavior}\n        data-overflow={overflowCount > 0 ? \"true\" : undefined}\n        data-overflow-count={overflowCount}\n        data-overlap={overlap}\n        data-reduced-motion={reducedMotion ? \"true\" : undefined}\n        data-reveal={reveal}\n        data-reveal-label-visibility={revealLabelVisibility}\n        data-ring={ring}\n        data-shape={shape}\n        data-size={size}\n        data-magnify={magnifyMode ? \"true\" : undefined}\n        data-slot=\"avatar-group\"\n        data-state={revealState}\n        data-visible-count={visibleMembers.length}\n        dir={dir}\n        role=\"group\"\n        tabIndex={resolvedTabIndex}\n        className={avatarGroupClassNames({ className, overlap })}\n        initial={false}\n        onBlur={handleBlur}\n        onFocus={handleFocus}\n        onMouseEnter={handleMouseEnter}\n        onMouseLeave={handleMouseLeave}\n        onPointerEnter={handlePointerEnter}\n        onPointerLeave={handlePointerLeave}\n        onPointerMove={handlePointerMove}\n      >\n        <span\n          id={descriptionId}\n          data-slot=\"avatar-group-description\"\n          className=\"sr-only\"\n        >\n          {members.length > 0\n            ? members.map((member, index) => (\n                <span key={getMemberKey(member, index)}>\n                  {getMemberLabel({\n                    index,\n                    member,\n                    visibleMemberLabel,\n                  })}\n                  {renderMemberMetadata(member.metadata)}\n                  {index < members.length - 1 ? \"; \" : \".\"}\n                </span>\n              ))\n            : \"No members.\"}\n          {overflowAccessibleLabel ? <> {overflowAccessibleLabel}.</> : null}\n        </span>\n        <ul\n          ref={stackRef}\n          aria-hidden=\"true\"\n          data-slot=\"avatar-group-stack\"\n          className={avatarGroupStackClasses}\n        >\n          {visibleMembers.map((member, index) => {\n            // Non-zero for spread (transform-opacity); zero for reduced/no-motion.\n            const spreadOffset = getRevealOffset({\n              count: visualItems,\n              direction,\n              index,\n              motionBehavior,\n              reveal,\n              size,\n            });\n            const itemActive = magnifyMode && hovered && activeKey === index;\n\n            return (\n              <AvatarGroupItem\n                key={getMemberKey(member, index)}\n                active={itemActive}\n                expandTransition={expandTransition}\n                itemIndex={index}\n                label={getMemberLabel({ index, member, visibleMemberLabel })}\n                labelActive={magnifyMode ? itemActive : revealActive}\n                labelTransition={labelTransition}\n                motionBehavior={motionBehavior}\n                reducedMotion={reducedMotion}\n                revealOffset={magnifyMode ? 0 : spreadOffset}\n                revealState={revealState}\n                showLabel={reveal === \"spread\"}\n                slot=\"item\"\n                stackIndex={index + 1}\n                xTarget={\n                  magnifyMode ? (hovered ? spreadOffset : 0) : spreadOffset\n                }\n                zIndex={itemActive ? visualItems + 10 : index + 1}\n                zoomScale={zoomScale}\n              >\n                <Avatar\n                  alt=\"\"\n                  className={member.avatarClassName}\n                  crossOrigin={member.crossOrigin}\n                  decoding={member.decoding}\n                  decorative\n                  fallbackIcon={member.fallbackIcon}\n                  fetchPriority={member.fetchPriority}\n                  imageProps={member.imageProps}\n                  initials={member.initials}\n                  loading={member.loading}\n                  motion={member.motion ?? \"none\"}\n                  name={getAvatarName(member, index)}\n                  onImageError={member.onImageError}\n                  onImageLoad={member.onImageLoad}\n                  reducedMotion={member.reducedMotion}\n                  referrerPolicy={member.referrerPolicy}\n                  ring={member.ring ?? ring}\n                  shape={member.shape ?? shape}\n                  size={member.size ?? size}\n                  sizes={member.sizes}\n                  src={member.src}\n                  srcSet={member.srcSet}\n                  tone={member.tone}\n                />\n              </AvatarGroupItem>\n            );\n          })}\n          {overflowCount > 0\n            ? (() => {\n                const spreadOffset = getRevealOffset({\n                  count: visualItems,\n                  direction,\n                  index: visualItems - 1,\n                  motionBehavior,\n                  reveal,\n                  size,\n                });\n                const overflowActive =\n                  magnifyMode && hovered && activeKey === \"overflow\";\n\n                return (\n                  <AvatarGroupItem\n                    active={overflowActive}\n                    expandTransition={expandTransition}\n                    label={overflowAccessibleLabel}\n                    labelActive={magnifyMode ? overflowActive : revealActive}\n                    labelTransition={labelTransition}\n                    motionBehavior={motionBehavior}\n                    reducedMotion={reducedMotion}\n                    revealOffset={magnifyMode ? 0 : spreadOffset}\n                    revealState={revealState}\n                    showLabel={\n                      reveal === \"spread\" && Boolean(overflowAccessibleLabel)\n                    }\n                    slot=\"overflow\"\n                    stackIndex={visualItems}\n                    xTarget={\n                      magnifyMode ? (hovered ? spreadOffset : 0) : spreadOffset\n                    }\n                    zIndex={overflowActive ? visualItems + 10 : visualItems}\n                    zoomScale={zoomScale}\n                  >\n                    <Avatar\n                      decorative\n                      className={avatarGroupOverflowClasses}\n                      initials={getOverflowDisplay(overflowCount)}\n                      motion=\"none\"\n                      name={overflowAccessibleLabel}\n                      ring={ring}\n                      shape={shape}\n                      size={size}\n                      tone=\"neutral\"\n                    />\n                  </AvatarGroupItem>\n                );\n              })()\n            : null}\n        </ul>\n        {showSummary ? (\n          <motionElement.span\n            aria-hidden=\"true\"\n            data-slot=\"avatar-group-reveal-summary\"\n            data-state={summaryVisible ? \"revealed\" : \"collapsed\"}\n            className={avatarGroupRevealSummaryClasses}\n            initial={false}\n            animate={{ opacity: summaryVisible ? 1 : 0 }}\n            transition={labelTransition}\n          >\n            {revealSummary}\n          </motionElement.span>\n        ) : null}\n        <ol\n          id={memberListId}\n          aria-label={`${label} members`}\n          data-slot=\"avatar-group-member-list\"\n          className=\"sr-only\"\n        >\n          {members.length > 0 ? (\n            members.map((member, index) => (\n              <li\n                key={getMemberKey(member, index)}\n                data-slot=\"avatar-group-member\"\n              >\n                {getMemberLabel({\n                  index,\n                  member,\n                  visibleMemberLabel,\n                })}\n                {renderMemberMetadata(member.metadata)}\n              </li>\n            ))\n          ) : (\n            <li data-slot=\"avatar-group-empty\">No members</li>\n          )}\n          {overflowAccessibleLabel ? (\n            <li data-slot=\"avatar-group-overflow-label\">\n              {overflowAccessibleLabel}\n            </li>\n          ) : null}\n        </ol>\n      </motionElement.div>\n    );\n  },\n);\n\nAvatarGroup.displayName = \"AvatarGroup\";\n"
    },
    {
      "path": "packages/components/src/components/avatar-group/index.ts",
      "type": "registry:file",
      "target": "~/components/dethink/components/avatar-group/index.ts",
      "content": "export {\n  AvatarGroup,\n  avatarGroupClassNames,\n  type AvatarGroupMember,\n  type AvatarGroupMotion,\n  type AvatarGroupOverflowLabel,\n  type AvatarGroupOverflowLabelContext,\n  type AvatarGroupOverlap,\n  type AvatarGroupProps,\n  type AvatarGroupReveal,\n  type AvatarGroupRevealLabelVisibility,\n  type AvatarGroupVisibleMemberLabel,\n} from \"./avatar-group\";\n"
    },
    {
      "path": "packages/components/src/utils/cn.ts",
      "type": "registry:file",
      "target": "~/components/dethink/utils/cn.ts",
      "content": "import { clsx, type ClassValue } from \"clsx\";\nimport { twMerge } from \"tailwind-merge\";\n\nexport function cn(...inputs: ClassValue[]) {\n  return twMerge(clsx(inputs));\n}\n"
    }
  ]
}
