{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "uploadthing-paste",
  "title": "UploadThing Paste",
  "description": "A clipboard paste zone that accepts pasted images and files. Perfect for quick screenshot uploads.",
  "dependencies": [
    "@uploadthing/react",
    "uploadthing"
  ],
  "registryDependencies": [],
  "files": [
    {
      "path": "registry/default/blocks/uploadthing/uploadthing-paste/components/elements/uploadthing-paste.tsx",
      "content": "\"use client\";\n\nimport { useCallback, useEffect, useRef, useState } from \"react\";\nimport { cn } from \"@/lib/utils\";\n\ninterface UploadThingPasteProps {\n  onPaste?: (files: File[]) => void;\n  onUpload?: (files: File[]) => Promise<void>;\n  accept?: string[];\n  className?: string;\n  disabled?: boolean;\n  children?: React.ReactNode;\n}\n\nfunction ClipboardIcon({ 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      <rect x=\"8\" y=\"2\" width=\"8\" height=\"4\" rx=\"1\" ry=\"1\" />\n      <path d=\"M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2\" />\n    </svg>\n  );\n}\n\nfunction ImageIcon({ 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      <rect x=\"3\" y=\"3\" width=\"18\" height=\"18\" rx=\"2\" ry=\"2\" />\n      <circle cx=\"9\" cy=\"9\" r=\"2\" />\n      <path d=\"m21 15-3.086-3.086a2 2 0 0 0-2.828 0L6 21\" />\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\nfunction CheckIcon({ 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      <polyline points=\"20 6 9 17 4 12\" />\n    </svg>\n  );\n}\n\nexport function UploadThingPaste({\n  onPaste,\n  onUpload,\n  accept = [\"image/*\"],\n  className,\n  disabled = false,\n  children,\n}: UploadThingPasteProps) {\n  const [isFocused, setIsFocused] = useState(false);\n  const [isUploading, setIsUploading] = useState(false);\n  const [lastPasted, setLastPasted] = useState<string | null>(null);\n  const [success, setSuccess] = useState(false);\n  const containerRef = useRef<HTMLDivElement>(null);\n\n  const handlePaste = useCallback(\n    async (e: ClipboardEvent) => {\n      if (disabled || isUploading) return;\n\n      const items = e.clipboardData?.items;\n      if (!items) return;\n\n      const files: File[] = [];\n      for (const item of items) {\n        if (item.kind === \"file\") {\n          const file = item.getAsFile();\n          if (file) {\n            const isAccepted = accept.some((pattern) => {\n              if (pattern === \"*\" || pattern === \"*/*\") return true;\n              if (pattern.endsWith(\"/*\")) {\n                return file.type.startsWith(pattern.replace(\"/*\", \"/\"));\n              }\n              return file.type === pattern;\n            });\n            if (isAccepted) {\n              files.push(file);\n            }\n          }\n        }\n      }\n\n      if (files.length === 0) return;\n\n      e.preventDefault();\n      onPaste?.(files);\n\n      if (files[0]) {\n        setLastPasted(files[0].name);\n      }\n\n      if (onUpload) {\n        try {\n          setIsUploading(true);\n          await onUpload(files);\n          setSuccess(true);\n          setTimeout(() => setSuccess(false), 2000);\n        } catch (err) {\n          console.error(\"Upload failed:\", err);\n        } finally {\n          setIsUploading(false);\n        }\n      }\n    },\n    [accept, disabled, isUploading, onPaste, onUpload]\n  );\n\n  useEffect(() => {\n    const container = containerRef.current;\n    if (!container || !isFocused) return;\n\n    const handleGlobalPaste = (e: ClipboardEvent) => {\n      if (document.activeElement === container || container.contains(document.activeElement)) {\n        handlePaste(e);\n      }\n    };\n\n    document.addEventListener(\"paste\", handleGlobalPaste);\n    return () => document.removeEventListener(\"paste\", handleGlobalPaste);\n  }, [isFocused, handlePaste]);\n\n  const getStatusContent = () => {\n    if (isUploading) {\n      return (\n        <>\n          <LoadingSpinner className=\"w-8 h-8 text-primary\" />\n          <span className=\"text-sm text-muted-foreground\">Uploading...</span>\n        </>\n      );\n    }\n\n    if (success) {\n      return (\n        <>\n          <div className=\"w-12 h-12 rounded-full bg-green-500/10 flex items-center justify-center\">\n            <CheckIcon className=\"w-6 h-6 text-green-500\" />\n          </div>\n          <span className=\"text-sm text-green-600 dark:text-green-400\">\n            Uploaded successfully!\n          </span>\n        </>\n      );\n    }\n\n    if (lastPasted) {\n      return (\n        <>\n          <ImageIcon className=\"w-8 h-8 text-muted-foreground\" />\n          <span className=\"text-sm text-muted-foreground truncate max-w-[200px]\">\n            {lastPasted}\n          </span>\n        </>\n      );\n    }\n\n    return (\n      <>\n        <ClipboardIcon className=\"w-8 h-8 text-muted-foreground\" />\n        <span className=\"text-sm text-muted-foreground\">\n          {isFocused ? (\n            <span>\n              Press <kbd className=\"px-1.5 py-0.5 rounded bg-muted text-xs font-mono\">⌘V</kbd> to paste\n            </span>\n          ) : (\n            \"Click to focus, then paste\"\n          )}\n        </span>\n      </>\n    );\n  };\n\n  if (children) {\n    return (\n      <div\n        ref={containerRef}\n        data-slot=\"uploadthing-paste\"\n        tabIndex={disabled ? -1 : 0}\n        onFocus={() => setIsFocused(true)}\n        onBlur={() => setIsFocused(false)}\n        className={cn(\n          \"outline-none\",\n          isFocused && \"ring-2 ring-primary ring-offset-2 rounded-lg\",\n          className\n        )}\n      >\n        {children}\n      </div>\n    );\n  }\n\n  return (\n    <div\n      ref={containerRef}\n      data-slot=\"uploadthing-paste\"\n      tabIndex={disabled ? -1 : 0}\n      onFocus={() => setIsFocused(true)}\n      onBlur={() => setIsFocused(false)}\n      className={cn(\n        \"flex flex-col items-center justify-center gap-3 p-8\",\n        \"border-2 border-dashed rounded-lg transition-all cursor-pointer\",\n        \"outline-none focus-visible:ring-2 focus-visible:ring-primary focus-visible:ring-offset-2\",\n        isFocused ? \"border-primary bg-primary/5\" : \"border-border hover:border-primary/50\",\n        disabled && \"opacity-50 cursor-not-allowed\",\n        className\n      )}\n    >\n      {getStatusContent()}\n    </div>\n  );\n}\n",
      "type": "registry:component"
    }
  ],
  "docs": "Focus the zone and paste (Cmd+V) to upload. Shows upload progress and success state. Supports file type filtering via accept prop.",
  "categories": [
    "uploadthing"
  ],
  "type": "registry:block"
}