{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "editor-upload",
  "type": "registry:block",
  "title": "Editor Upload",
  "description": "A component for handling file uploads within the editor.",
  "files": [
    {
      "path": "src/components/deso/editor-upload.tsx",
      "content": "'use client';\n\nimport React, { useState, useCallback } from 'react';\nimport { useDropzone } from 'react-dropzone';\nimport { UploadCloud } from 'lucide-react';\nimport {\n  DndContext,\n  closestCenter,\n  KeyboardSensor,\n  PointerSensor,\n  useSensor,\n  useSensors,\n  DragEndEvent,\n} from '@dnd-kit/core';\nimport {\n  arrayMove,\n  SortableContext,\n  sortableKeyboardCoordinates,\n  rectSortingStrategy,\n  useSortable,\n} from '@dnd-kit/sortable';\nimport { CSS } from '@dnd-kit/utilities';\nimport { Button } from '../ui/button';\nimport { X, File, Music, Video } from 'lucide-react';\nimport { cn } from '@/lib/utils/deso';\n\nexport interface UploadedFile {\n  id: string;\n  file: File;\n  preview: string;\n  progress?: number;\n}\n\nexport type UploadType = 'image' | 'video' | 'audio';\n\ninterface EditorUploadProps {\n  files: UploadedFile[];\n  onFilesChange: (files: UploadedFile[]) => void;\n  onFileUpload: (files: File[]) => void;\n  uploadType: UploadType;\n  className?: string;\n  mainText?: string;\n  subText?: string;\n}\n\n\ninterface UploadAreaProps {\n  onFileUpload: (files: File[]) => void;\n  accept?: Record<string, string[]>;\n  multiple?: boolean;\n  className?: string;\n  mainText?: string;\n  subText?: string;\n}\n\nexport function UploadArea({\n  onFileUpload,\n  accept,\n  multiple = true,\n  className,\n  mainText,\n  subText,\n}: UploadAreaProps) {\n  const [isDragging, setIsDragging] = useState(false);\n\n  const onDrop = useCallback(\n    (acceptedFiles: File[]) => {\n      onFileUpload(acceptedFiles);\n      setIsDragging(false);\n    },\n    [onFileUpload]\n  );\n\n  const { getRootProps, getInputProps, isDragActive } = useDropzone({\n    onDrop,\n    accept,\n    multiple,\n    onDragOver: () => setIsDragging(true),\n    onDragEnter: () => setIsDragging(true),\n    onDragLeave: () => setIsDragging(false),\n  });\n\n  return (\n    <div\n      {...getRootProps()}\n      className={cn(\n        'border-2 border-dashed border-muted-foreground/30 rounded-lg p-8 text-center cursor-pointer transition-colors',\n        isDragActive || isDragging ? 'border-primary bg-primary/10' : 'hover:border-muted-foreground/50 hover:bg-muted/50',\n        className\n      )}\n    >\n      <input type=\"file\" {...getInputProps()} />\n      <div className=\"flex flex-col items-center gap-2 text-muted-foreground\">\n        <UploadCloud className=\"w-10 h-10\" />\n        <p className=\"font-semibold text-foreground\">{mainText}</p>\n        <p className=\"text-xs text-muted-foreground\">\n          {subText}\n        </p>\n      </div>\n    </div>\n  );\n} \n\nconst uploadAreaTexts = {\n    image: {\n      mainText: 'Drag & drop images here, or click to select files',\n      subText: 'Max file size: 100MB, Allowed types: .jpeg, .jpg, .png, .gif',\n    },\n    video: {\n      mainText: 'Drag & drop videos here, or click to select files',\n      subText: 'Max file size: 100MB, Allowed types: .mp4, .mov, .avi',\n    },\n    audio: {\n      mainText: 'Drag & drop audio files here, or click to select files',\n      subText: 'Max file size: 100MB, Allowed types: .mp3, .wav, .ogg',\n    },\n  }\n\nfunction FilePreview({\n  file,\n  onRemove,\n  uploadType,\n}: {\n  file: UploadedFile;\n  onRemove: (id: string) => void;\n  uploadType: UploadType;\n}) {\n  return (\n    <div className=\"relative aspect-square\">\n      {uploadType === 'image' ? (\n        <img\n          src={file.preview}\n          alt={file.file.name}\n          className=\"w-full h-full object-cover rounded-lg\"\n          onLoad={() => URL.revokeObjectURL(file.preview)}\n        />\n      ) : (\n        <div className=\"w-full h-full bg-muted rounded-lg flex items-center justify-center\">\n          {uploadType === 'video' && <Video className=\"w-10 h-10\" />}\n          {uploadType === 'audio' && <Music className=\"w-10 h-10\" />}\n          {!['image', 'video', 'audio'].includes(uploadType) && (\n            <File className=\"w-10 h-10\" />\n          )}\n        </div>\n      )}\n\n      {file.progress !== undefined && file.progress < 100 && (\n        <div className=\"absolute inset-0 bg-black/50 flex items-center justify-center rounded-lg\">\n          <div className=\"w-16 h-1.5 bg-white/30 rounded-full overflow-hidden\">\n            <div\n              className=\"h-full bg-white transition-all duration-150\"\n              style={{ width: `${file.progress}%` }}\n            />\n          </div>\n        </div>\n      )}\n      <Button\n        variant=\"destructive\"\n        size=\"icon\"\n        className=\"absolute -top-2 -right-2 w-6 h-6 rounded-full\"\n        onClick={() => onRemove(file.id)}\n      >\n        <X size={12} />\n      </Button>\n    </div>\n  );\n}\n\nfunction SortableFilePreview({\n  id,\n  file,\n  onRemove,\n  uploadType,\n}: {\n  id: string;\n  file: UploadedFile;\n  onRemove: (id: string) => void;\n  uploadType: UploadType;\n}) {\n  const {\n    attributes,\n    listeners,\n    setNodeRef,\n    transform,\n    transition,\n    isDragging,\n  } = useSortable({ id });\n\n  const style = {\n    transform: CSS.Transform.toString(transform),\n    transition,\n    zIndex: isDragging ? 10 : undefined,\n    opacity: isDragging ? 0.5 : 1,\n  };\n\n  return (\n    <div ref={setNodeRef} style={style} {...attributes} {...listeners}>\n      <FilePreview file={file} onRemove={onRemove} uploadType={uploadType} />\n    </div>\n  );\n}\n\nconst uploadConfig: Record<\n  UploadType,\n  { accept: Record<string, string[]>; typeName: string }\n> = {\n  image: { accept: { 'image/*': ['.jpeg', '.jpg', '.png', '.gif'] }, typeName: 'Images' },\n  video: { accept: { 'video/*': ['.mp4', '.mov', '.avi'] }, typeName: 'Videos' },\n  audio: { accept: { 'audio/*': ['.mp3', '.wav', '.ogg'] }, typeName: 'Audio' },\n};\n\nexport function EditorUpload({\n  files,\n  onFilesChange,\n  onFileUpload,\n  uploadType,\n  className,\n}: EditorUploadProps) {\n  const sensors = useSensors(\n    useSensor(PointerSensor),\n    useSensor(KeyboardSensor, {\n      coordinateGetter: sortableKeyboardCoordinates,\n    })\n  );\n\n  const handleRemoveFile = (id: string) => {\n    const updatedFiles = files.filter((f) => f.id !== id);\n    onFilesChange(updatedFiles);\n  };\n\n  const handleDragEnd = (event: DragEndEvent) => {\n    const { active, over } = event;\n    if (over && active.id !== over.id) {\n      const oldIndex = files.findIndex((item) => item.id === active.id);\n      const newIndex = files.findIndex((item) => item.id === over.id);\n      onFilesChange(arrayMove(files, oldIndex, newIndex));\n    }\n  };\n\n  const { accept } = uploadConfig[uploadType];\n\n  return (\n    <div className={cn('flex flex-col gap-4', className)}>\n      {files.length > 0 && (\n        <DndContext\n          sensors={sensors}\n          collisionDetection={closestCenter}\n          onDragEnd={handleDragEnd}\n        >\n          <SortableContext items={files} strategy={rectSortingStrategy}>\n            <div className=\"grid grid-cols-3 sm:grid-cols-4 md:grid-cols-5 gap-2\">\n              {files.map((file) => (\n                <SortableFilePreview\n                  key={file.id}\n                  id={file.id}\n                  file={file}\n                  onRemove={handleRemoveFile}\n                  uploadType={uploadType}\n                />\n              ))}\n            </div>\n          </SortableContext>\n        </DndContext>\n      )}\n      <UploadArea\n        onFileUpload={onFileUpload}\n        accept={accept}\n        mainText={uploadAreaTexts[uploadType].mainText}\n        subText={uploadAreaTexts[uploadType].subText}\n      />\n    </div>\n  );\n} ",
      "type": "registry:component",
      "target": "src/components/deso-ui/editor-upload.tsx"
    }
  ]
}