{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "avatar",
  "type": "registry:ui",
  "title": "Avatar",
  "description": "A compact identity marker for people and accounts.",
  "categories": [
    "data"
  ],
  "dependencies": [
    "motion"
  ],
  "registryDependencies": [
    "https://irisui.dev/r/foundation.json",
    "https://irisui.dev/r/provider.json",
    "https://irisui.dev/r/utils.json",
    "https://irisui.dev/r/motion-tokens.json"
  ],
  "files": [
    {
      "path": "src/registry/components/avatar/avatar.tsx",
      "content": "\"use client\";\n\nimport { useLayoutEffect, useRef, useState, type HTMLAttributes } from \"react\";\nimport { AnimatePresence, motion, useReducedMotion } from \"motion/react\";\nimport { ProvidedImage } from \"../lib/provider\";\nimport { cn } from \"../lib/utils\";\nimport { motionTokens } from \"../lib/motion-tokens\";\nexport interface AvatarProps extends HTMLAttributes<HTMLSpanElement> {\n  name: string;\n  src?: string;\n  size?: \"sm\" | \"md\" | \"lg\" | \"xl\";\n  status?: \"online\" | \"offline\";\n}\nconst sizes = {\n  sm: \"size-7 text-[10px]/[inherit]\",\n  md: \"size-9\",\n  lg: \"size-12 text-sm/[inherit]\",\n  xl: \"size-[88px] text-xl/[inherit]\",\n} as const;\nconst statusSizes = { sm: \"size-2 border-[1.5px]\", md: \"\", lg: \"size-3\", xl: \"right-1 bottom-1 size-3.5\" } as const;\nconst statusTones = { online: \"bg-green\", offline: \"bg-ink-3\" } as const;\n\nexport function Avatar({ name, src, size = \"md\", status, className, ...props }: AvatarProps) {\n  const initials = name\n    .trim()\n    .split(/\\s+/)\n    .slice(0, 2)\n    .map((part) => part[0]?.toUpperCase())\n    .join(\"\");\n  const reduceMotion = !!useReducedMotion();\n  const image = useRef<HTMLImageElement>(null);\n  const [failedSrc, setFailedSrc] = useState<string>();\n  // A photo that is already decoded shows at once. One that is still loading waits, then fades in from a soft blur.\n  useLayoutEffect(() => {\n    const node = image.current;\n    if (node && !node.complete) node.dataset.loading = \"\";\n  }, [src]);\n  const showImage = src && failedSrc !== src;\n  return (\n    <span\n      {...props}\n      className={cn(\n        \"relative inline-grid flex-none place-items-center overflow-visible rounded-full border border-line bg-hover text-xs/[inherit] font-medium text-ink\",\n        sizes[size],\n        className,\n      )}\n      role=\"img\"\n      aria-label={`${name}${status ? `, ${status}` : \"\"}`}\n    >\n      {showImage ? (\n        <ProvidedImage\n          key={src}\n          ref={image}\n          src={src}\n          alt=\"\"\n          className=\"pointer-events-none rounded-[inherit] object-cover transition-[opacity,filter] duration-standard ease-enter data-loading:opacity-0 data-loading:blur-[4px] data-loading:transition-none motion-reduce:transition-none motion-reduce:data-loading:filter-none\"\n          fill\n          sizes={size === \"xl\" ? \"88px\" : size === \"lg\" ? \"48px\" : size === \"md\" ? \"36px\" : \"28px\"}\n          onLoad={(event) => {\n            delete event.currentTarget.dataset.loading;\n          }}\n          onError={() => setFailedSrc(src)}\n        />\n      ) : (\n        <span className={src ? \"animate-avatar-fallback-in motion-reduce:animate-none\" : undefined} aria-hidden=\"true\">\n          {initials}\n        </span>\n      )}\n      <AnimatePresence initial={false}>\n        {status && (\n          <motion.i\n            key={status}\n            className={cn(\n              \"absolute right-0 bottom-0 z-2 size-2.5 rounded-full border-2 border-surface shadow-[0_0_0_1px_color-mix(in_oklab,var(--line)_70%,transparent)]\",\n              statusSizes[size],\n              statusTones[status],\n            )}\n            aria-hidden=\"true\"\n            initial={{ opacity: 0, scale: 0.6 }}\n            animate={{ opacity: 1, scale: 1 }}\n            exit={{ opacity: 0, scale: 0.6, transition: { duration: reduceMotion ? 0 : motionTokens.duration.fast } }}\n            transition={reduceMotion ? { duration: 0 } : motionTokens.spring.snappy}\n          />\n        )}\n      </AnimatePresence>\n    </span>\n  );\n}\n",
      "type": "registry:component",
      "target": "@components/iris/avatar/avatar.tsx"
    }
  ],
  "docs": "Docs and live preview: https://irisui.dev/components/avatar",
  "meta": {
    "kind": "component",
    "group": "Data display",
    "export": "Avatar",
    "tags": [
      "identity",
      "people"
    ],
    "docs": "https://irisui.dev/components/avatar"
  }
}
