{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "image-upload-dropzone",
  "title": "Image Upload / Dropzone",
  "description": "Drag-and-drop image upload with preview, center crop, and progress feedback.",
  "dependencies": [
    "lucide-react"
  ],
  "registryDependencies": [
    "button"
  ],
  "files": [
    {
      "path": "components/ui/image-upload-dropzone.tsx",
      "content": "'use client'\n\nimport * as React from 'react'\nimport { ImagePlus, Upload } from 'lucide-react'\n\nimport { Button } from '@/components/ui/button'\n\ntype ImageUploadDropzoneProps = {\n  value?: string | null\n  onChange?: (value: string | null) => void\n}\n\nfunction cropCenterSquare(dataUrl: string): Promise<string> {\n  return new Promise((resolve, reject) => {\n    const img = new Image()\n    img.onload = () => {\n      const side = Math.min(img.width, img.height)\n      const sx = (img.width - side) / 2\n      const sy = (img.height - side) / 2\n      const canvas = document.createElement('canvas')\n      canvas.width = side\n      canvas.height = side\n      const ctx = canvas.getContext('2d')\n\n      if (!ctx) {\n        reject(new Error('Unable to crop image'))\n        return\n      }\n\n      ctx.drawImage(img, sx, sy, side, side, 0, 0, side, side)\n      resolve(canvas.toDataURL('image/png'))\n    }\n    img.onerror = () => reject(new Error('Failed to load image'))\n    img.src = dataUrl\n  })\n}\n\nexport function ImageUploadDropzone({ value, onChange }: ImageUploadDropzoneProps) {\n  const [isDragging, setIsDragging] = React.useState(false)\n  const [preview, setPreview] = React.useState<string | null>(value ?? null)\n  const [progress, setProgress] = React.useState(0)\n  const inputRef = React.useRef<HTMLInputElement>(null)\n\n  React.useEffect(() => {\n    setPreview(value ?? null)\n  }, [value])\n\n  const readFile = (file: File) => {\n    const reader = new FileReader()\n    reader.onload = () => {\n      const data = reader.result as string\n      setPreview(data)\n      onChange?.(data)\n\n      setProgress(0)\n      const timer = window.setInterval(() => {\n        setProgress((prev) => {\n          if (prev >= 100) {\n            window.clearInterval(timer)\n            return 100\n          }\n          return prev + 20\n        })\n      }, 120)\n    }\n    reader.readAsDataURL(file)\n  }\n\n  const handleFiles = (files: FileList | null) => {\n    const file = files?.[0]\n    if (!file || !file.type.startsWith('image/')) return\n    readFile(file)\n  }\n\n  return (\n    <div className=\"space-y-3\">\n      <div\n        className={`rounded-lg border-2 border-dashed p-6 text-center ${isDragging ? 'border-primary bg-muted' : 'border-muted-foreground/30'}`}\n        onDragOver={(event) => {\n          event.preventDefault()\n          setIsDragging(true)\n        }}\n        onDragLeave={() => setIsDragging(false)}\n        onDrop={(event) => {\n          event.preventDefault()\n          setIsDragging(false)\n          handleFiles(event.dataTransfer.files)\n        }}\n      >\n        <input\n          ref={inputRef}\n          type=\"file\"\n          accept=\"image/*\"\n          className=\"hidden\"\n          onChange={(event) => handleFiles(event.target.files)}\n        />\n        <div className=\"mb-2 flex justify-center\">\n          <ImagePlus className=\"h-6 w-6 text-muted-foreground\" />\n        </div>\n        <p className=\"text-sm text-muted-foreground\">Drop image here or click to upload</p>\n        <Button type=\"button\" variant=\"outline\" className=\"mt-3\" onClick={() => inputRef.current?.click()}>\n          <Upload className=\"mr-2 h-4 w-4\" />\n          Choose Image\n        </Button>\n      </div>\n\n      {progress > 0 && progress < 100 ? (\n        <div className=\"rounded border p-2 text-sm\">Uploading... {progress}%</div>\n      ) : null}\n\n      {preview ? (\n        <div className=\"space-y-2 rounded border p-3\">\n          {/* eslint-disable-next-line @next/next/no-img-element */}\n          <img src={preview} alt=\"Preview\" className=\"max-h-56 w-full rounded object-contain\" />\n          <div className=\"flex gap-2\">\n            <Button\n              type=\"button\"\n              variant=\"outline\"\n              onClick={async () => {\n                const cropped = await cropCenterSquare(preview)\n                setPreview(cropped)\n                onChange?.(cropped)\n              }}\n            >\n              Crop Center Square\n            </Button>\n            <Button\n              type=\"button\"\n              variant=\"destructive\"\n              onClick={() => {\n                setPreview(null)\n                setProgress(0)\n                onChange?.(null)\n              }}\n            >\n              Remove\n            </Button>\n          </div>\n        </div>\n      ) : null}\n    </div>\n  )\n}\n",
      "type": "registry:ui"
    }
  ],
  "categories": [
    "input",
    "upload"
  ],
  "type": "registry:ui"
}