Files
lcbp3/frontend/components/common/file-upload.tsx

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>
);
}