Spaces:
Running
Running
| import { useCallback, useState } from "react"; | |
| import { Upload, File, X } from "lucide-react"; | |
| import { Button } from "@/components/ui/button"; | |
| import { Card } from "@/components/ui/card"; | |
| type UploadedFile = { | |
| name: string; | |
| size: number; | |
| type: string; | |
| }; | |
| type FileUploadZoneProps = { | |
| onFilesSelected: (files: File[]) => void; | |
| onClose?: () => void; | |
| }; | |
| export function FileUploadZone({ onFilesSelected, onClose }: FileUploadZoneProps) { | |
| const [dragActive, setDragActive] = useState(false); | |
| const [selectedFiles, setSelectedFiles] = useState<UploadedFile[]>([]); | |
| const handleDrag = useCallback((e: React.DragEvent) => { | |
| e.preventDefault(); | |
| e.stopPropagation(); | |
| if (e.type === "dragenter" || e.type === "dragover") { | |
| setDragActive(true); | |
| } else if (e.type === "dragleave") { | |
| setDragActive(false); | |
| } | |
| }, []); | |
| const handleDrop = useCallback((e: React.DragEvent) => { | |
| e.preventDefault(); | |
| e.stopPropagation(); | |
| setDragActive(false); | |
| const files = Array.from(e.dataTransfer.files); | |
| handleFiles(files); | |
| }, []); | |
| const handleFiles = (files: File[]) => { | |
| const uploadedFiles = files.map(f => ({ | |
| name: f.name, | |
| size: f.size, | |
| type: f.type | |
| })); | |
| setSelectedFiles(prev => [...prev, ...uploadedFiles]); | |
| onFilesSelected(files); | |
| }; | |
| const handleFileInput = (e: React.ChangeEvent<HTMLInputElement>) => { | |
| if (e.target.files) { | |
| handleFiles(Array.from(e.target.files)); | |
| } | |
| }; | |
| const removeFile = (index: number) => { | |
| setSelectedFiles(prev => prev.filter((_, i) => i !== index)); | |
| }; | |
| const formatFileSize = (bytes: number) => { | |
| if (bytes < 1024) return bytes + " B"; | |
| if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(1) + " KB"; | |
| return (bytes / (1024 * 1024)).toFixed(1) + " MB"; | |
| }; | |
| return ( | |
| <Card className="p-6"> | |
| <div className="flex items-center justify-between mb-4"> | |
| <h3 className="text-lg font-semibold">Upload Files</h3> | |
| {onClose && ( | |
| <Button variant="ghost" size="icon" onClick={onClose} data-testid="button-close-upload"> | |
| <X className="h-5 w-5" /> | |
| </Button> | |
| )} | |
| </div> | |
| <div | |
| className={`border-2 border-dashed rounded-xl p-8 text-center transition-colors ${ | |
| dragActive ? "border-primary bg-primary/5" : "border-border" | |
| }`} | |
| onDragEnter={handleDrag} | |
| onDragLeave={handleDrag} | |
| onDragOver={handleDrag} | |
| onDrop={handleDrop} | |
| data-testid="dropzone" | |
| > | |
| <Upload className="h-12 w-12 mx-auto mb-4 text-muted-foreground" /> | |
| <p className="text-sm font-medium mb-2">Drag and drop files here</p> | |
| <p className="text-xs text-muted-foreground mb-4">or</p> | |
| <label htmlFor="file-upload"> | |
| <Button variant="outline" asChild data-testid="button-browse-files"> | |
| <span className="cursor-pointer">Browse Files</span> | |
| </Button> | |
| </label> | |
| <input | |
| id="file-upload" | |
| type="file" | |
| multiple | |
| onChange={handleFileInput} | |
| className="hidden" | |
| /> | |
| <p className="text-xs text-muted-foreground mt-4"> | |
| Supports: .html, .js, .css, .py, .json, and more | |
| </p> | |
| </div> | |
| {selectedFiles.length > 0 && ( | |
| <div className="mt-4 space-y-2"> | |
| <p className="text-sm font-medium">Selected Files:</p> | |
| {selectedFiles.map((file, index) => ( | |
| <div | |
| key={index} | |
| className="flex items-center justify-between p-3 bg-muted rounded-lg" | |
| data-testid={`file-item-${index}`} | |
| > | |
| <div className="flex items-center gap-3"> | |
| <File className="h-4 w-4 text-muted-foreground" /> | |
| <div> | |
| <p className="text-sm font-medium">{file.name}</p> | |
| <p className="text-xs text-muted-foreground">{formatFileSize(file.size)}</p> | |
| </div> | |
| </div> | |
| <Button | |
| variant="ghost" | |
| size="icon" | |
| className="h-8 w-8" | |
| onClick={() => removeFile(index)} | |
| data-testid={`button-remove-file-${index}`} | |
| > | |
| <X className="h-4 w-4" /> | |
| </Button> | |
| </div> | |
| ))} | |
| </div> | |
| )} | |
| </Card> | |
| ); | |
| } | |