{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "uploadthing-avatar",
  "title": "UploadThing Avatar",
  "description": "A circular avatar uploader with preview, hover state, and upload progress. Perfect for profile pictures.",
  "dependencies": [
    "@uploadthing/react",
    "uploadthing"
  ],
  "registryDependencies": [],
  "files": [
    {
      "path": "registry/default/blocks/uploadthing/uploadthing-avatar/components/elements/uploadthing-avatar.tsx",
      "content": "\"use client\";\n\nimport { useCallback, useState } from \"react\";\nimport { cn } from \"@/lib/utils\";\n\ninterface UploadThingAvatarProps {\n  value?: string;\n  onChange?: (url: string) => void;\n  onUpload?: (file: File) => Promise<string>;\n  size?: \"sm\" | \"md\" | \"lg\" | \"xl\";\n  fallback?: string;\n  className?: string;\n  disabled?: boolean;\n}\n\nconst SIZE_CLASSES = {\n  sm: \"w-16 h-16\",\n  md: \"w-24 h-24\",\n  lg: \"w-32 h-32\",\n  xl: \"w-40 h-40\",\n};\n\nconst ICON_SIZES = {\n  sm: \"w-4 h-4\",\n  md: \"w-6 h-6\",\n  lg: \"w-8 h-8\",\n  xl: \"w-10 h-10\",\n};\n\nfunction CameraIcon({ className }: { className?: string }) {\n  return (\n    <svg\n      className={className}\n      viewBox=\"0 0 24 24\"\n      fill=\"none\"\n      stroke=\"currentColor\"\n      strokeWidth={2}\n      strokeLinecap=\"round\"\n      strokeLinejoin=\"round\"\n    >\n      <path d=\"M14.5 4h-5L7 7H4a2 2 0 0 0-2 2v9a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2V9a2 2 0 0 0-2-2h-3l-2.5-3z\" />\n      <circle cx=\"12\" cy=\"13\" r=\"3\" />\n    </svg>\n  );\n}\n\nfunction UserIcon({ className }: { className?: string }) {\n  return (\n    <svg\n      className={className}\n      viewBox=\"0 0 24 24\"\n      fill=\"none\"\n      stroke=\"currentColor\"\n      strokeWidth={2}\n      strokeLinecap=\"round\"\n      strokeLinejoin=\"round\"\n    >\n      <path d=\"M19 21v-2a4 4 0 0 0-4-4H9a4 4 0 0 0-4 4v2\" />\n      <circle cx=\"12\" cy=\"7\" r=\"4\" />\n    </svg>\n  );\n}\n\nfunction LoadingSpinner({ className }: { className?: string }) {\n  return (\n    <svg\n      className={cn(\"animate-spin\", className)}\n      viewBox=\"0 0 24 24\"\n      fill=\"none\"\n    >\n      <circle\n        className=\"opacity-25\"\n        cx=\"12\"\n        cy=\"12\"\n        r=\"10\"\n        stroke=\"currentColor\"\n        strokeWidth=\"4\"\n      />\n      <path\n        className=\"opacity-75\"\n        fill=\"currentColor\"\n        d=\"M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z\"\n      />\n    </svg>\n  );\n}\n\nexport function UploadThingAvatar({\n  value,\n  onChange,\n  onUpload,\n  size = \"lg\",\n  fallback,\n  className,\n  disabled = false,\n}: UploadThingAvatarProps) {\n  const [isUploading, setIsUploading] = useState(false);\n  const [previewUrl, setPreviewUrl] = useState<string | null>(null);\n  const [error, setError] = useState<string | null>(null);\n\n  const handleFileChange = useCallback(\n    async (e: React.ChangeEvent<HTMLInputElement>) => {\n      const file = e.target.files?.[0];\n      if (!file) return;\n\n      if (!file.type.startsWith(\"image/\")) {\n        setError(\"Please select an image file\");\n        return;\n      }\n\n      setError(null);\n      setPreviewUrl(URL.createObjectURL(file));\n\n      if (onUpload) {\n        try {\n          setIsUploading(true);\n          const url = await onUpload(file);\n          onChange?.(url);\n        } catch (err) {\n          setError(err instanceof Error ? err.message : \"Upload failed\");\n          setPreviewUrl(null);\n        } finally {\n          setIsUploading(false);\n        }\n      }\n\n      e.target.value = \"\";\n    },\n    [onChange, onUpload]\n  );\n\n  const displayUrl = previewUrl || value;\n\n  return (\n    <div\n      data-slot=\"uploadthing-avatar\"\n      className={cn(\"relative inline-block\", className)}\n    >\n      <div\n        className={cn(\n          \"relative rounded-full overflow-hidden bg-muted border-2 border-border\",\n          \"transition-all duration-200\",\n          !disabled && \"hover:border-primary/50 cursor-pointer\",\n          disabled && \"opacity-50 cursor-not-allowed\",\n          SIZE_CLASSES[size]\n        )}\n      >\n        {displayUrl ? (\n          <img\n            src={displayUrl}\n            alt=\"Avatar\"\n            className=\"w-full h-full object-cover\"\n          />\n        ) : (\n          <div className=\"w-full h-full flex items-center justify-center bg-muted\">\n            {fallback ? (\n              <span className=\"text-2xl font-medium text-muted-foreground\">\n                {fallback}\n              </span>\n            ) : (\n              <UserIcon\n                className={cn(\"text-muted-foreground\", ICON_SIZES[size])}\n              />\n            )}\n          </div>\n        )}\n\n        {isUploading && (\n          <div className=\"absolute inset-0 bg-background/80 flex items-center justify-center\">\n            <LoadingSpinner className={ICON_SIZES[size]} />\n          </div>\n        )}\n\n        {!disabled && !isUploading && (\n          <div\n            className={cn(\n              \"absolute inset-0 bg-black/50 flex items-center justify-center\",\n              \"opacity-0 hover:opacity-100 transition-opacity\"\n            )}\n          >\n            <CameraIcon className={cn(\"text-white\", ICON_SIZES[size])} />\n          </div>\n        )}\n\n        <input\n          type=\"file\"\n          accept=\"image/*\"\n          onChange={handleFileChange}\n          disabled={disabled || isUploading}\n          className=\"absolute inset-0 w-full h-full opacity-0 cursor-pointer disabled:cursor-not-allowed\"\n          aria-label=\"Upload avatar\"\n        />\n      </div>\n\n      {error && (\n        <p className=\"mt-2 text-xs text-destructive text-center\">{error}</p>\n      )}\n    </div>\n  );\n}\n",
      "type": "registry:component"
    }
  ],
  "docs": "Circular avatar uploader with camera icon overlay. Supports size variants (sm, md, lg, xl), fallback initials, and custom upload handlers.",
  "categories": [
    "uploadthing"
  ],
  "type": "registry:block"
}