{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "avatar",
  "title": "Avatar",
  "description": "아바타 — 이미지(브리지 Image onError 폴백 전환)·Fallback(이니셜/글리프)·배지·그룹 6종. sm/default/lg 크기.",
  "dependencies": [
    "@unikit-bridge/ui",
    "class-variance-authority",
    "clsx",
    "tailwind-merge",
    "tailwindcss"
  ],
  "registryDependencies": [
    "utils"
  ],
  "files": [
    {
      "path": "registry/ui/avatar.tsx",
      "content": "// Avatar — shadcn/ui new-york-v4 avatar（Radix Avatar 기반）를 UniKit UI 유니버설로 이식.\n//\n// 포팅 판단（radix 엔진 의존 제거）:\n// - 로드 실패 상태 공유는 React context로 재현 — Avatar（루트）가\n//   { size, hasImage, imageFailed }를 제공하고, AvatarImage가 마운트 등록/\n//   실패 보고（브리지 Image onError）, AvatarFallback이\n//   \"소스 없음 또는 로드 실패\"일 때만 렌더된다.\n// - 브리지 Image에 onLoad 계약이 없어 '로드 성공'을 알 수 없으므로 폴백\n//   표시 조건을 Radix의 \"로드 전+실패\"에서 \"소스 없음+실패\"로 단순화했다.\n//   실패한 AvatarImage는 렌더를 중단한다（Radix는 깨진 img를 남긴다）.\n// - 원본의 data-size + group-data size/avatar 셀렉터（폴백 글자 크기·배지\n//   크기 연동）는 아래 조회 테이블로 치환 — 각 컴포넌트가 context size를 읽어\n//   자기 크기 클래스를 적용한다.\n// - AvatarGroup의 겹침（-space-x-2）과 data-slot=avatar 자식 링은 자식 셀렉터가\n//   필요하다. 겹침은 웹 전용 클래스로 유지（네이티브은 나란히 배치）하고, 링은\n//   Avatar가 AvatarGroupContext를 읽어 자신에게 ring-2를 건다.\n// - AvatarGroupCount의 그룹 크기 반응（group-has-data-）은 고정 토큰（size-8）으로\n//   단순화. AvatarBadge 내용은 글리프 Text 또는 비움（배경 자체가 상태 점）.\nimport {\n  createContext,\n  forwardRef,\n  useContext,\n  useEffect,\n  useMemo,\n  useState,\n  type ReactNode,\n} from \"react\";\nimport {\n  Image,\n  Text,\n  View,\n  type BridgeImageRef,\n  type BridgeViewRef,\n  type ImageProps,\n  type ViewProps,\n} from \"@unikit-bridge/ui/primitives\";\nimport { cva, type VariantProps } from \"class-variance-authority\";\n\nimport { cn } from \"@/lib/utils\";\n\nexport type AvatarSize = \"default\" | \"sm\" | \"lg\";\n\n// 루트 크기 — 원본 data-size 셀렉터（size-8 · sm:size-6 · lg:size-10） 치환.\nconst avatarVariants = cva(\n  \"relative flex shrink-0 overflow-hidden rounded-full web:select-none\",\n  {\n    variants: {\n      size: {\n        default: \"size-8\",\n        sm: \"size-6\",\n        lg: \"size-10\",\n      },\n    },\n    defaultVariants: {\n      size: \"default\",\n    },\n  },\n);\n\n// 폴백 글자 크기（원본의 group-data size=sm → text-xs）·배지 크기\n// （sm 2 · default 2.5 · lg 3） — context size 값으로 조회하는 치환 테이블.\nconst AVATAR_FALLBACK_TEXT: Record<AvatarSize, string> = {\n  default: \"text-sm\",\n  sm: \"text-xs\",\n  lg: \"text-sm\",\n};\nconst AVATAR_BADGE_SIZE: Record<AvatarSize, string> = {\n  default: \"size-2.5\",\n  sm: \"size-2\",\n  lg: \"size-3\",\n};\n\n// 루트가 제공하는 상태 — 폴백 전환 판단의 단일 출처.\ntype AvatarContextValue = {\n  size: AvatarSize;\n  /** AvatarImage 마운트 여부 — false면 \"소스 없음\"으로 폴백 렌더 */\n  hasImage: boolean;\n  /** AvatarImage 로드 실패 — 폴백으로 전환 */\n  imageFailed: boolean;\n  reportImageMounted: (mounted: boolean) => void;\n  reportImageFailed: () => void;\n};\n\nconst AvatarContext = createContext<AvatarContextValue | null>(null);\n\n// AvatarGroup 내부 여부 — Avatar가 겹침 경계 링을 자신에게 적용한다.\nconst AvatarGroupContext = createContext(false);\n\n// ---------------------------------------------------------------------------\n// Avatar — 루트 컨테이너. 상태 제공자역도 겸한다（원본 AvatarPrimitive.Root）.\n// ---------------------------------------------------------------------------\nexport type AvatarProps = ViewProps &\n  VariantProps<typeof avatarVariants> & {\n    children?: ReactNode;\n  };\n\nconst Avatar = forwardRef<BridgeViewRef, AvatarProps>(\n  ({ className, size, ...props }, ref) => {\n    const [hasImage, setHasImage] = useState(false);\n    const [imageFailed, setImageFailed] = useState(false);\n    const grouped = useContext(AvatarGroupContext);\n    const resolvedSize = size ?? \"default\";\n    const contextValue = useMemo<AvatarContextValue>(\n      () => ({\n        size: resolvedSize,\n        hasImage,\n        imageFailed,\n        // setState 설정자는 참조가 안정적이라 그대로 노출해도 안전하다.\n        reportImageMounted: setHasImage,\n        reportImageFailed: () => setImageFailed(true),\n      }),\n      [resolvedSize, hasImage, imageFailed],\n    );\n    return (\n      <AvatarContext.Provider value={contextValue}>\n        <View\n          ref={ref}\n          className={cn(\n            avatarVariants({ size }),\n            grouped && \"ring-2 ring-background\",\n            className,\n          )}\n          {...props}\n        />\n      </AvatarContext.Provider>\n    );\n  },\n);\nAvatar.displayName = \"Avatar\";\n\n// ---------------------------------------------------------------------------\n// AvatarImage — 브리지 Image（원격 {uri}만）. 실패 보고는 context로.\n// ---------------------------------------------------------------------------\n// onError는 폴백 전환의 통로이므로 props에서 제외하고 컴포넌트가 소유한다.\nexport type AvatarImageProps = Omit<ImageProps, \"onError\">;\n\nconst AvatarImage = forwardRef<BridgeImageRef, AvatarImageProps>(\n  ({ className, source, ...props }, ref) => {\n    const avatar = useContext(AvatarContext);\n    // 마운트 등록/해제 — 폴백의 \"소스 없음\" 판단 근거（provider는 안정적）.\n    useEffect(() => {\n      avatar?.reportImageMounted(true);\n      return () => avatar?.reportImageMounted(false);\n    }, [avatar]);\n    // 실패한 이미지는 렌더하지 않는다（포팅 노트 — Radix는 깨진 img를 남김）.\n    if (avatar?.imageFailed) {\n      return null;\n    }\n    return (\n      <Image\n        ref={ref}\n        source={source}\n        onError={avatar ? avatar.reportImageFailed : undefined}\n        className={cn(\"aspect-square size-full\", className)}\n        {...props}\n      />\n    );\n  },\n);\nAvatarImage.displayName = \"AvatarImage\";\n\n// ---------------------------------------------------------------------------\n// AvatarFallback — 이니셜/글리프. \"소스 없음 또는 실패\"일 때만 렌더.\n// ---------------------------------------------------------------------------\nexport type AvatarFallbackProps = ViewProps & {\n  children?: ReactNode;\n};\n\nconst AvatarFallback = forwardRef<BridgeViewRef, AvatarFallbackProps>(\n  ({ className, children, ...props }, ref) => {\n    const avatar = useContext(AvatarContext);\n    // Avatar 밖 단독 사용（문맥 없음）이면 항상 렌드한다.\n    const visible = avatar ? !avatar.hasImage || avatar.imageFailed : true;\n    if (!visible) {\n      return null;\n    }\n    return (\n      <View\n        ref={ref}\n        className={cn(\n          \"flex size-full items-center justify-center rounded-full bg-muted\",\n          className,\n        )}\n        {...props}\n      >\n        {/* 이중 지정 원칙: 전경색은 내부 Text에 — children은 이니셜/글리프 문자열 권장 */}\n        <Text\n          className={cn(\n            \"font-sans text-muted-foreground\",\n            avatar && AVATAR_FALLBACK_TEXT[avatar.size],\n          )}\n        >\n          {children}\n        </Text>\n      </View>\n    );\n  },\n);\nAvatarFallback.displayName = \"AvatarFallback\";\n\n// ---------------------------------------------------------------------------\n// AvatarBadge — 우하단 상태 점. 크기는 context size 연동.\n// ---------------------------------------------------------------------------\nexport type AvatarBadgeProps = ViewProps;\n\nconst AvatarBadge = forwardRef<BridgeViewRef, AvatarBadgeProps>(\n  ({ className, ...props }, ref) => {\n    const avatar = useContext(AvatarContext);\n    return (\n      <View\n        ref={ref}\n        className={cn(\n          \"absolute right-0 bottom-0 z-10 flex items-center justify-center rounded-full bg-primary ring-2 ring-background web:select-none\",\n          avatar && AVATAR_BADGE_SIZE[avatar.size],\n          className,\n        )}\n        {...props}\n      />\n    );\n  },\n);\nAvatarBadge.displayName = \"AvatarBadge\";\n\n// ---------------------------------------------------------------------------\n// AvatarGroup — 아바타 무리. 겹침은 웹 전용（네이티브은 나란히）.\n// ---------------------------------------------------------------------------\nexport type AvatarGroupProps = ViewProps;\n\nconst AvatarGroup = forwardRef<BridgeViewRef, AvatarGroupProps>(\n  ({ className, ...props }, ref) => (\n    <AvatarGroupContext.Provider value={true}>\n      <View\n        ref={ref}\n        className={cn(\"flex flex-row items-center web:-space-x-2\", className)}\n        {...props}\n      />\n    </AvatarGroupContext.Provider>\n  ),\n);\nAvatarGroup.displayName = \"AvatarGroup\";\n\n// ---------------------------------------------------------------------------\n// AvatarGroupCount — 넘친 인원 표시（\"+9\" 등 문자열 children）.\n// ---------------------------------------------------------------------------\nexport type AvatarGroupCountProps = ViewProps & {\n  children?: ReactNode;\n};\n\nconst AvatarGroupCount = forwardRef<BridgeViewRef, AvatarGroupCountProps>(\n  ({ className, children, ...props }, ref) => (\n    <View\n      ref={ref}\n      className={cn(\n        \"relative flex size-8 shrink-0 items-center justify-center rounded-full bg-muted ring-2 ring-background\",\n        className,\n      )}\n      {...props}\n    >\n      {/* 이중 지정 원칙: 전경색은 내부 Text에 */}\n      <Text className=\"font-sans text-sm text-muted-foreground\">\n        {children}\n      </Text>\n    </View>\n  ),\n);\nAvatarGroupCount.displayName = \"AvatarGroupCount\";\n\nexport {\n  Avatar,\n  AvatarImage,\n  AvatarFallback,\n  AvatarBadge,\n  AvatarGroup,\n  AvatarGroupCount,\n};\n",
      "type": "registry:ui",
      "target": "components/unikit/avatar.tsx"
    }
  ],
  "type": "registry:ui"
}
