{"$schema":"https://ui.shadcn.com/schema/registry-item.json","author":"Eduardo Calvo <edu@smoothui.dev>","css":{},"dependencies":["@base-ui/react"],"description":"Avatar, AvatarGroup, and status dot for SmoothUI with Base UI / Radix twins.","devDependencies":[],"files":[{"content":"\"use client\";\n\nimport { Avatar as AvatarPrimitive } from \"@base-ui/react/avatar\";\nimport { cn } from \"@/lib/utils\";\nimport {\n  Children,\n  type ComponentProps,\n  createContext,\n  type ReactNode,\n  useContext,\n} from \"react\";\n\nexport type AvatarSize = \"sm\" | \"md\" | \"lg\";\nexport type AvatarStatusValue = \"online\" | \"away\" | \"busy\" | \"offline\";\n\nexport interface AvatarProps {\n  /** Alt text for the image (also used for the accessible name) */\n  alt?: string;\n  /** Optional CSS class names */\n  className?: string;\n  /** Content shown while the image loads or when it fails (e.g. initials) */\n  fallback?: ReactNode;\n  /** Avatar diameter: `sm` 24px, `md` 32px (default), `lg` 40px */\n  size?: AvatarSize;\n  /** Image URL */\n  src?: string;\n  /** Optional presence dot rendered at the bottom-right */\n  status?: AvatarStatusValue;\n}\n\nexport interface AvatarGroupProps {\n  /** Accessible label for the group, e.g. \"Project members\" */\n  \"aria-label\"?: string;\n  /** `Avatar` children (or any avatar-sized nodes) */\n  children?: ReactNode;\n  /** Optional CSS class names */\n  className?: string;\n  /** Show at most this many avatars, then a `+N` overflow chip */\n  max?: number;\n  /** Size applied to every avatar in the group */\n  size?: AvatarSize;\n}\n\nexport interface AvatarGroupOverflowProps {\n  /** Optional CSS class names */\n  className?: string;\n  /** Number of hidden avatars */\n  count: number;\n}\n\nexport interface AvatarStatusProps {\n  /** Optional CSS class names */\n  className?: string;\n  /** Presence state; also announced to assistive tech */\n  status: AvatarStatusValue;\n}\n\nconst SIZE_CLASS: Record<AvatarSize, string> = {\n  lg: \"size-10 text-sm\",\n  md: \"size-8 text-xs\",\n  sm: \"size-6 text-[10px]\",\n};\n\nconst STATUS_DOT_SIZE: Record<AvatarSize, string> = {\n  lg: \"size-3\",\n  md: \"size-2.5\",\n  sm: \"size-2\",\n};\n\nconst STATUS_COLOR: Record<AvatarStatusValue, string> = {\n  away: \"bg-amber-500\",\n  busy: \"bg-destructive\",\n  offline: \"bg-zinc-400 dark:bg-zinc-600\",\n  online: \"bg-emerald-500\",\n};\n\nconst GROUP_RING =\n  \"[&>[data-slot=avatar-group-overflow]]:ring-2 [&>[data-slot=avatar-group-overflow]]:ring-background [&>[data-slot=avatar]]:ring-2 [&>[data-slot=avatar]]:ring-background\";\n\ninterface AvatarGroupContextValue {\n  size: AvatarSize | undefined;\n}\n\nconst AvatarGroupContext = createContext<AvatarGroupContextValue>({\n  size: undefined,\n});\nconst AvatarSizeContext = createContext<AvatarSize>(\"md\");\n\nexport type AvatarRootProps = Omit<\n  ComponentProps<typeof AvatarPrimitive.Root>,\n  \"className\"\n> & {\n  className?: string;\n  size?: AvatarSize;\n};\nexport type AvatarImageProps = Omit<\n  ComponentProps<typeof AvatarPrimitive.Image>,\n  \"className\"\n> & {\n  className?: string;\n};\nexport type AvatarFallbackProps = Omit<\n  ComponentProps<typeof AvatarPrimitive.Fallback>,\n  \"className\"\n> & {\n  className?: string;\n};\n\nexport const AvatarRoot = ({ className, size, ...props }: AvatarRootProps) => {\n  const group = useContext(AvatarGroupContext);\n  const resolved = size ?? group.size ?? \"md\";\n\n  return (\n    <AvatarSizeContext.Provider value={resolved}>\n      <AvatarPrimitive.Root\n        className={cn(\n          \"relative inline-flex shrink-0 select-none items-center justify-center align-middle\",\n          SIZE_CLASS[resolved],\n          className\n        )}\n        data-size={resolved}\n        data-slot=\"avatar\"\n        {...props}\n      />\n    </AvatarSizeContext.Provider>\n  );\n};\n\nexport const AvatarImage = ({ className, ...props }: AvatarImageProps) => (\n  <AvatarPrimitive.Image\n    className={cn(\n      \"aspect-square size-full rounded-full object-cover transition-opacity duration-200 ease-out data-[starting-style]:opacity-0 motion-reduce:transition-none\",\n      className\n    )}\n    data-slot=\"avatar-image\"\n    {...props}\n  />\n);\n\nexport const AvatarFallback = ({\n  className,\n  ...props\n}: AvatarFallbackProps) => (\n  <AvatarPrimitive.Fallback\n    className={cn(\n      \"flex size-full items-center justify-center rounded-full bg-foreground/10 font-medium text-muted-foreground uppercase ring-1 ring-foreground/10 ring-inset\",\n      className\n    )}\n    data-slot=\"avatar-fallback\"\n    {...props}\n  />\n);\n\n/** Presence dot — place inside `AvatarRoot`. */\nexport const AvatarStatus = ({ className, status }: AvatarStatusProps) => {\n  const size = useContext(AvatarSizeContext);\n\n  return (\n    <span\n      aria-label={status}\n      className={cn(\n        \"absolute right-0 bottom-0 rounded-full ring-2 ring-background\",\n        STATUS_DOT_SIZE[size],\n        STATUS_COLOR[status],\n        className\n      )}\n      data-slot=\"avatar-status\"\n      data-status={status}\n      role=\"img\"\n    />\n  );\n};\n\n/** `+N` chip for avatars hidden by `AvatarGroup max`. */\nexport const AvatarGroupOverflow = ({\n  className,\n  count,\n}: AvatarGroupOverflowProps) => {\n  const group = useContext(AvatarGroupContext);\n\n  return (\n    <span\n      aria-label={`${count} more`}\n      className={cn(\n        \"inline-flex shrink-0 select-none items-center justify-center rounded-full bg-foreground/10 font-medium text-muted-foreground tabular-nums ring-1 ring-foreground/10 ring-inset\",\n        SIZE_CLASS[group.size ?? \"md\"],\n        className\n      )}\n      data-slot=\"avatar-group-overflow\"\n      role=\"img\"\n    >\n      +{count}\n    </span>\n  );\n};\n\n/**\n * Overlapping stack of avatars with an optional `+N` overflow chip.\n */\nexport const AvatarGroup = ({\n  \"aria-label\": ariaLabel,\n  children,\n  className,\n  max,\n  size,\n}: AvatarGroupProps) => {\n  const items = Children.toArray(children);\n  const hasLimit = typeof max === \"number\" && max >= 0 && items.length > max;\n  const visible = hasLimit ? items.slice(0, max) : items;\n  const hidden = hasLimit ? items.length - visible.length : 0;\n\n  return (\n    <AvatarGroupContext.Provider value={{ size }}>\n      <div\n        aria-label={ariaLabel}\n        className={cn(\n          \"flex min-w-0 items-center -space-x-2\",\n          GROUP_RING,\n          className\n        )}\n        data-slot=\"avatar-group\"\n        role=\"group\"\n      >\n        {visible}\n        {hidden > 0 ? <AvatarGroupOverflow count={hidden} /> : null}\n      </div>\n    </AvatarGroupContext.Provider>\n  );\n};\n\n/**\n * SmoothUI Avatar — Base UI twin (default).\n * Same public props as the Radix twin; headless via `@base-ui/react/avatar`.\n * Convenience API: `src` + `fallback` (+ optional `status`); use the compound\n * parts for custom layouts.\n */\nexport default function Avatar({\n  alt = \"\",\n  className,\n  fallback,\n  size,\n  src,\n  status,\n}: AvatarProps) {\n  return (\n    <AvatarRoot className={className} size={size}>\n      {src ? <AvatarImage alt={alt} src={src} /> : null}\n      <AvatarFallback>{fallback}</AvatarFallback>\n      {status ? <AvatarStatus status={status} /> : null}\n    </AvatarRoot>\n  );\n}\n\nexport { Avatar };\n","path":"index.tsx","target":"components/smoothui/avatar/index.tsx","type":"registry:ui"}],"name":"avatar","registryDependencies":[],"title":"Avatar","type":"registry:ui"}