"use client"; /** * File Upload Field * * COPIED VERBATIM FROM: components/onboarding/steps/file-upload-step.tsx:457-584 * Renders a file upload drop zone with file list */ import { AlertCircle, File, FileCode, FileImage, FileText, Upload, X, } from "lucide-react"; import { useRef, useState } from "react"; import { ANIMATION_CLASSES, getStaggerStyle, STAGGER_PRESETS, } from "../animations"; import { cx } from "../lib/utils"; import { OnboardingLabel } from "../primitives/onboarding-label"; import type { FileUploadFieldProps } from "../types/fields"; /** * Format file size * COPIED VERBATIM FROM: components/onboarding/steps/file-upload-step.tsx */ function formatFileSize(bytes: number): string { if (bytes === 0) return "0 Bytes"; const k = 1024; const sizes = ["Bytes", "KB", "MB", "GB"]; const i = Math.floor(Math.log(bytes) / Math.log(k)); return parseFloat((bytes / k ** i).toFixed(2)) + " " + sizes[i]; } /** * Get file icon based on mime type * COPIED VERBATIM FROM: components/onboarding/steps/file-upload-step.tsx */ function getFileIcon(file: File) { if (file.type.startsWith("image/")) return FileImage; if (file.type.startsWith("text/")) return FileText; if ( file.type.includes("json") || file.type.includes("javascript") || file.type.includes("typescript") ) return FileCode; return File; } export function FileUploadField({ id: _id, label, description, accept, maxSize, maxFiles = 1, required = false, dropZoneTitle = "Drop files here or click to upload", dropZoneSubtitle, files, onChange, onRemove, showFileSize = true, animationIndex = 0, disabled = false, error, }: FileUploadFieldProps) { const fileInputRef = useRef(null); const [dragOver, setDragOver] = useState(false); const canAddMore = !maxFiles || files.length < maxFiles; const handleDragOver = (e: React.DragEvent) => { e.preventDefault(); setDragOver(true); }; const handleDragLeave = (e: React.DragEvent) => { e.preventDefault(); setDragOver(false); }; const handleDrop = (e: React.DragEvent) => { e.preventDefault(); setDragOver(false); if (e.dataTransfer.files) { onChange(e.dataTransfer.files); } }; const handleFiles = (fileList: FileList | null) => { if (fileList) { onChange(fileList); } }; const defaultSubtitle = ( <> {accept && `Accepted: ${accept}`} {accept && maxSize && " · "} {maxSize && `Max size: ${formatFileSize(maxSize)}`} {(accept || maxSize) && maxFiles && " · "} {maxFiles && `Max files: ${maxFiles}`} ); return (
{label} {required && *} {description && (

{description}

)} {/* Drop zone */} {canAddMore && (
fileInputRef.current?.click()} className={cx( "relative cursor-pointer rounded-lg border-2 border-dashed p-6 transition-colors", "hover:border-primary/50 hover:bg-muted/50", dragOver ? "border-primary bg-primary/5" : "border-border bg-background", error && "border-destructive", disabled && "pointer-events-none opacity-50", )} > 1} onChange={(e) => handleFiles(e.target.files)} className="sr-only" disabled={disabled} />

{dropZoneTitle}

{dropZoneSubtitle || defaultSubtitle}

)} {/* Uploaded files list */} {files.length > 0 && (
{files.map((uploadedFile) => { const FileIcon = getFileIcon(uploadedFile.file); return (
{/* Thumbnail / Icon */}
{uploadedFile.preview ? ( {uploadedFile.file.name} ) : (
)}
{/* File info */}

{uploadedFile.file.name}

{showFileSize && (

{formatFileSize(uploadedFile.file.size)}

)} {/* Progress bar */} {uploadedFile.status === "uploading" && (
)} {/* Error message */} {uploadedFile.status === "error" && uploadedFile.error && (

{uploadedFile.error}

)}
{/* Remove button */}
); })}
)} {error &&

{error}

}
); }