103 lines
2.9 KiB
TypeScript
103 lines
2.9 KiB
TypeScript
"use client";
|
|
|
|
import { useCallback, useState } from "react";
|
|
import { useDropzone } from "react-dropzone";
|
|
import { Upload, X, File } from "lucide-react";
|
|
import { Button } from "@/components/ui/button";
|
|
import { cn } from "@/lib/utils";
|
|
|
|
interface FileUploadProps {
|
|
onFilesSelected: (files: File[]) => void;
|
|
maxFiles?: number;
|
|
accept?: string;
|
|
maxSize?: number; // bytes
|
|
}
|
|
|
|
export function FileUpload({
|
|
onFilesSelected,
|
|
maxFiles = 5,
|
|
accept = ".pdf,.doc,.docx",
|
|
maxSize = 10485760, // 10MB
|
|
}: FileUploadProps) {
|
|
const [files, setFiles] = useState<File[]>([]);
|
|
|
|
const onDrop = useCallback(
|
|
(acceptedFiles: File[]) => {
|
|
setFiles((prev) => {
|
|
const newFiles = [...prev, ...acceptedFiles].slice(0, maxFiles);
|
|
onFilesSelected(newFiles);
|
|
return newFiles;
|
|
});
|
|
},
|
|
[maxFiles, onFilesSelected]
|
|
);
|
|
|
|
const { getRootProps, getInputProps, isDragActive } = useDropzone({
|
|
onDrop,
|
|
maxFiles,
|
|
accept: accept.split(",").reduce((acc, ext) => ({ ...acc, [ext]: [] }), {}),
|
|
maxSize,
|
|
});
|
|
|
|
const removeFile = (index: number) => {
|
|
setFiles((prev) => {
|
|
const newFiles = prev.filter((_, i) => i !== index);
|
|
onFilesSelected(newFiles);
|
|
return newFiles;
|
|
});
|
|
};
|
|
|
|
return (
|
|
<div className="space-y-4">
|
|
<div
|
|
{...getRootProps()}
|
|
className={cn(
|
|
"border-2 border-dashed rounded-lg p-8 text-center cursor-pointer transition-colors",
|
|
isDragActive
|
|
? "border-primary bg-primary/5"
|
|
: "border-gray-300 hover:border-gray-400"
|
|
)}
|
|
>
|
|
<input {...getInputProps()} />
|
|
<Upload className="mx-auto h-12 w-12 text-gray-400" />
|
|
<p className="mt-2 text-sm text-gray-600">
|
|
{isDragActive
|
|
? "Drop files here"
|
|
: "Drag & drop files or click to browse"}
|
|
</p>
|
|
<p className="mt-1 text-xs text-gray-500">
|
|
Maximum {maxFiles} files, {(maxSize / 1024 / 1024).toFixed(0)}MB each
|
|
</p>
|
|
</div>
|
|
|
|
{files.length > 0 && (
|
|
<div className="space-y-2">
|
|
{files.map((file, index) => (
|
|
<div
|
|
key={index}
|
|
className="flex items-center justify-between p-3 bg-gray-50 rounded-lg"
|
|
>
|
|
<div className="flex items-center gap-3">
|
|
<File className="h-5 w-5 text-gray-500" />
|
|
<div>
|
|
<p className="text-sm font-medium">{file.name}</p>
|
|
<p className="text-xs text-gray-500">
|
|
{(file.size / 1024).toFixed(1)} KB
|
|
</p>
|
|
</div>
|
|
</div>
|
|
<Button
|
|
variant="ghost"
|
|
size="sm"
|
|
onClick={() => removeFile(index)}
|
|
>
|
|
<X className="h-4 w-4" />
|
|
</Button>
|
|
</div>
|
|
))}
|
|
</div>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|