{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "avatar",
  "title": "Avatar",
  "description": "Shadcn avatar primitives with deterministic Blobz fallbacks, images, status, and groups.",
  "dependencies": [
    "@base-ui/react",
    "@blobz/react@^0.1.0",
    "class-variance-authority",
    "cn"
  ],
  "files": [
    {
      "path": "packages/ui/src/components/avatar.tsx",
      "content": "\"use client\";\n\nimport { Avatar as AvatarPrimitive } from \"@base-ui/react/avatar\";\nimport {\n\tAvatar as GeneratedAvatar,\n\ttype AvatarProps as GeneratedAvatarProps,\n} from \"@blobz/react\";\nimport { cva, type VariantProps } from \"class-variance-authority\";\nimport { cn } from \"cn\";\nimport { type ComponentProps, createContext, use } from \"react\";\n\nexport type AvatarFrame = NonNullable<GeneratedAvatarProps[\"frame\"]>;\nexport type AvatarStatus = \"online\" | \"away\" | \"busy\" | \"offline\";\n\nexport interface AvatarSource\n\textends Pick<GeneratedAvatarProps, \"name\" | \"palette\" | \"choices\"> {\n\treadonly frame?: AvatarFrame;\n\treadonly src?: string;\n}\n\nconst AvatarFrameContext = createContext<AvatarFrame>(\"rounded\");\n\nconst avatarVariants = cva(\n\t\"relative inline-flex shrink-0 select-none items-center justify-center align-middle text-sm [--avatar-inset:0px]\",\n\t{\n\t\tdefaultVariants: { frame: \"rounded\", size: \"default\", variant: \"default\" },\n\t\tvariants: {\n\t\t\tframe: {\n\t\t\t\tcircle: \"rounded-full\",\n\t\t\t\t// The generated SVG uses an 18px radius in an 80px viewBox.\n\t\t\t\trounded: \"rounded-[22.5%]\",\n\t\t\t\tsquare: \"rounded-none\",\n\t\t\t},\n\t\t\tsize: {\n\t\t\t\tdefault: \"size-10 [--avatar-badge-size:--spacing(3)]\",\n\t\t\t\tlg: \"size-12 [--avatar-badge-size:--spacing(3.5)]\",\n\t\t\t\tsm: \"size-8 [--avatar-badge-size:--spacing(2.5)]\",\n\t\t\t\txl: \"size-16 [--avatar-badge-size:--spacing(4)]\",\n\t\t\t},\n\t\t\tvariant: {\n\t\t\t\tdefault: \"bg-transparent\",\n\t\t\t\televated:\n\t\t\t\t\t\"bg-card p-1 shadow-lg ring-1 ring-border [--avatar-inset:--spacing(1)]\",\n\t\t\t\toutline:\n\t\t\t\t\t\"bg-transparent ring-2 ring-foreground/40 ring-offset-2 ring-offset-background\",\n\t\t\t},\n\t\t},\n\t}\n);\n\nexport interface AvatarProps\n\textends AvatarPrimitive.Root.Props,\n\t\tVariantProps<typeof avatarVariants> {}\n\nfunction Avatar({\n\tclassName,\n\tframe = \"rounded\",\n\tsize = \"default\",\n\tvariant = \"default\",\n\t...props\n}: AvatarProps) {\n\treturn (\n\t\t<AvatarFrameContext value={frame ?? \"rounded\"}>\n\t\t\t<AvatarPrimitive.Root\n\t\t\t\tclassName={cn(avatarVariants({ frame, size, variant }), className)}\n\t\t\t\tdata-frame={frame}\n\t\t\t\tdata-size={size}\n\t\t\t\tdata-slot=\"avatar\"\n\t\t\t\tdata-variant={variant}\n\t\t\t\t{...props}\n\t\t\t/>\n\t\t</AvatarFrameContext>\n\t);\n}\n\nfunction AvatarImage({ className, ...props }: AvatarPrimitive.Image.Props) {\n\treturn (\n\t\t<AvatarPrimitive.Image\n\t\t\tclassName={cn(\n\t\t\t\t\"absolute inset-(--avatar-inset) size-[calc(100%_-_var(--avatar-inset)_*_2)] rounded-[inherit] object-cover aria-hidden:hidden data-[error]:hidden data-[loading]:hidden\",\n\t\t\t\tclassName\n\t\t\t)}\n\t\t\tdata-slot=\"avatar-image\"\n\t\t\t{...props}\n\t\t/>\n\t);\n}\n\nexport interface AvatarFallbackProps\n\textends AvatarPrimitive.Fallback.Props,\n\t\tPick<GeneratedAvatarProps, \"alt\" | \"choices\" | \"palette\"> {\n\treadonly name?: GeneratedAvatarProps[\"name\"];\n}\n\nfunction AvatarFallback({\n\talt,\n\tchildren,\n\tchoices,\n\tclassName,\n\tname,\n\tpalette,\n\t...props\n}: AvatarFallbackProps) {\n\tconst frame = use(AvatarFrameContext);\n\tconst generated = name !== undefined && children === undefined;\n\treturn (\n\t\t<AvatarPrimitive.Fallback\n\t\t\tclassName={cn(\n\t\t\t\t\"flex size-full items-center justify-center overflow-hidden rounded-[inherit] font-medium text-muted-foreground text-sm\",\n\t\t\t\tgenerated ? \"bg-transparent\" : \"bg-muted\",\n\t\t\t\tclassName\n\t\t\t)}\n\t\t\tdata-slot=\"avatar-fallback\"\n\t\t\t{...props}\n\t\t>\n\t\t\t{generated ? (\n\t\t\t\t<GeneratedAvatar\n\t\t\t\t\talt={alt}\n\t\t\t\t\tchoices={choices}\n\t\t\t\t\tclassName=\"block size-full object-cover\"\n\t\t\t\t\tframe={frame}\n\t\t\t\t\tname={name}\n\t\t\t\t\tpalette={palette}\n\t\t\t\t\tsize=\"100%\"\n\t\t\t\t/>\n\t\t\t) : (\n\t\t\t\tchildren\n\t\t\t)}\n\t\t</AvatarPrimitive.Fallback>\n\t);\n}\n\nconst badgeVariants = cva(\n\t\"absolute z-10 inline-flex size-(--avatar-badge-size) items-center justify-center rounded-full bg-primary text-primary-foreground ring-2 ring-background [&>svg]:size-2\",\n\t{\n\t\tdefaultVariants: { position: \"bottom-end\" },\n\t\tvariants: {\n\t\t\tposition: {\n\t\t\t\t\"bottom-end\": \"end-0 bottom-0\",\n\t\t\t\t\"bottom-start\": \"start-0 bottom-0\",\n\t\t\t\t\"top-end\": \"end-0 top-0\",\n\t\t\t\t\"top-start\": \"start-0 top-0\",\n\t\t\t},\n\t\t\tstatus: {\n\t\t\t\taway: \"bg-[var(--avatar-status-away)]\",\n\t\t\t\tbusy: \"bg-destructive\",\n\t\t\t\toffline: \"bg-muted-foreground\",\n\t\t\t\tonline: \"bg-[var(--avatar-status-online)]\",\n\t\t\t},\n\t\t},\n\t}\n);\n\nconst STATUS_LABELS: Record<AvatarStatus, string> = {\n\taway: \"Away\",\n\tbusy: \"Busy\",\n\toffline: \"Offline\",\n\tonline: \"Online\",\n};\n\nfunction AvatarBadge({\n\t\"aria-label\": ariaLabel,\n\tclassName,\n\tposition,\n\tstatus,\n\t...props\n}: ComponentProps<\"span\"> & VariantProps<typeof badgeVariants>) {\n\treturn (\n\t\t<span\n\t\t\taria-label={ariaLabel ?? (status ? STATUS_LABELS[status] : undefined)}\n\t\t\tclassName={cn(badgeVariants({ position, status }), className)}\n\t\t\tdata-slot=\"avatar-badge\"\n\t\t\trole=\"status\"\n\t\t\t{...props}\n\t\t/>\n\t);\n}\n\nfunction AvatarCount({\n\t\"aria-label\": ariaLabel,\n\tclassName,\n\tcount,\n\t...props\n}: Omit<ComponentProps<\"span\">, \"children\"> & { readonly count: number }) {\n\tif (!Number.isFinite(count) || count < 1) {\n\t\treturn null;\n\t}\n\tconst unreadCount = Math.floor(count);\n\treturn (\n\t\t<span\n\t\t\taria-label={ariaLabel ?? `${unreadCount} unread messages`}\n\t\t\tclassName={cn(\n\t\t\t\t\"absolute -end-1.5 -top-1.5 z-10 flex h-5 min-w-5 items-center justify-center rounded-full bg-foreground px-1 font-medium text-[10px] text-background tabular-nums ring-2 ring-background\",\n\t\t\t\tclassName\n\t\t\t)}\n\t\t\tdata-slot=\"avatar-count\"\n\t\t\trole=\"status\"\n\t\t\t{...props}\n\t\t>\n\t\t\t<span aria-hidden=\"true\">\n\t\t\t\t{unreadCount > 99 ? \"99+\" : String(unreadCount)}\n\t\t\t</span>\n\t\t</span>\n\t);\n}\n\nfunction AvatarGroup({ className, ...props }: ComponentProps<\"div\">) {\n\treturn (\n\t\t// biome-ignore lint/a11y/useSemanticElements: This names a collection of images, not a group of form controls.\n\t\t<div\n\t\t\tclassName={cn(\n\t\t\t\t\"group/avatar-group flex items-center [&>[data-slot=avatar]:not(:first-child)]:-ms-2 [&>[data-slot=avatar]]:ring-2 [&>[data-slot=avatar]]:ring-background\",\n\t\t\t\tclassName\n\t\t\t)}\n\t\t\tdata-slot=\"avatar-group\"\n\t\t\trole=\"group\"\n\t\t\t{...props}\n\t\t/>\n\t);\n}\n\nfunction AvatarGroupCount({\n\t\"aria-label\": ariaLabel,\n\tchildren,\n\tclassName,\n\tcount,\n\tframe = \"rounded\",\n\tsize = \"default\",\n\t...props\n}: ComponentProps<\"span\"> &\n\tPick<AvatarProps, \"frame\" | \"size\"> & { readonly count?: number }) {\n\tif (count !== undefined && (!Number.isFinite(count) || count < 1)) {\n\t\treturn null;\n\t}\n\tconst label =\n\t\tariaLabel ||\n\t\t(count === undefined ? undefined : `${Math.floor(count)} more people`);\n\treturn (\n\t\t<span\n\t\t\taria-label={label}\n\t\t\tclassName={cn(\n\t\t\t\tavatarVariants({ frame, size }),\n\t\t\t\t\"not-first:-ms-2 bg-muted font-medium text-muted-foreground tabular-nums ring-2 ring-background\",\n\t\t\t\tclassName\n\t\t\t)}\n\t\t\tdata-slot=\"avatar-group-count\"\n\t\t\trole=\"img\"\n\t\t\t{...props}\n\t\t>\n\t\t\t{children ??\n\t\t\t\t(count === undefined ? null : (\n\t\t\t\t\t<span aria-hidden=\"true\">+{Math.floor(count)}</span>\n\t\t\t\t))}\n\t\t</span>\n\t);\n}\n\nexport {\n\tAvatar,\n\tAvatarBadge,\n\tAvatarCount,\n\tAvatarFallback,\n\tAvatarGroup,\n\tAvatarGroupCount,\n\tAvatarImage,\n\tavatarVariants,\n};\n",
      "type": "registry:ui",
      "target": "@ui/avatar.tsx"
    }
  ],
  "cssVars": {
    "light": {
      "avatar-status-online": "oklch(0.627 0.194 149.214)",
      "avatar-status-away": "oklch(0.666 0.179 58.318)"
    },
    "dark": {
      "avatar-status-online": "oklch(0.723 0.219 149.579)",
      "avatar-status-away": "oklch(0.828 0.189 84.429)"
    }
  },
  "type": "registry:ui"
}
