Dataset file import dialog with drag-and-drop upload zone and a list of queued files. Each file shows a color-coded type icon (CSV, JSON, Parquet), filename, size, and a three-dot context menu for removal. Features hover and drag-active states, fully themed with shadcn tokens. Perfect for analytics platforms, data pipelines, ETL tools, and any SaaS that requires structured file ingestion.
Files
"use client"
import * as React from "react"
import {
FileJson,
FileSpreadsheet,
FileText,
MoreVertical,
Trash2,
Upload,
X,
} from "lucide-react"
import { Button } from "@/components/ui/button"
type DataFile = {
id: string
name: string
size: string
type: "csv" | "json" | "parquet"
}
const INITIAL_FILES: DataFile[] = [
{ id: "1", name: "sales-report-q1.csv", size: "2.9 MB", type: "csv" },
{ id: "2", name: "user-events-may.json", size: "3.2 MB", type: "json" },
{ id: "3", name: "churn-analysis.csv", size: "2.6 MB", type: "csv" },
]
function FileTypeIcon({ type }: { type: DataFile["type"] }) {
if (type === "json") {
return (
<div className="flex h-10 w-10 shrink-0 items-center justify-center rounded-lg bg-amber-100 dark:bg-amber-900/30">
<FileJson className="h-5 w-5 text-amber-600 dark:text-amber-400" />
</div>
)
}
if (type === "parquet") {
return (
<div className="flex h-10 w-10 shrink-0 items-center justify-center rounded-lg bg-violet-100 dark:bg-violet-900/30">
<FileText className="h-5 w-5 text-violet-600 dark:text-violet-400" />
</div>
)
}
return (
<div className="flex h-10 w-10 shrink-0 items-center justify-center rounded-lg bg-emerald-100 dark:bg-emerald-900/30">
<FileSpreadsheet className="h-5 w-5 text-emerald-600 dark:text-emerald-400" />
</div>
)
}
export default function DatasetUpload() {
const [files, setFiles] = React.useState<DataFile[]>(INITIAL_FILES)
const [dragging, setDragging] = React.useState(false)
const [openMenu, setOpenMenu] = React.useState<string | null>(null)
function removeFile(id: string) {
setFiles((f) => f.filter((file) => file.id !== id))
setOpenMenu(null)
}
React.useEffect(() => {
function handleClick() {
setOpenMenu(null)
}
document.addEventListener("click", handleClick)
return () => document.removeEventListener("click", handleClick)
}, [])
return (
<div className="flex min-h-screen items-center justify-center p-4">
<div className="bg-background w-full max-w-md rounded-2xl shadow-xl">
{/* Header */}
<div className="flex items-start justify-between p-6 pb-4">
<div>
<h2 className="text-foreground text-xl font-medium tracking-tight">
Import dataset files
</h2>
<p className="text-muted-foreground mt-1 text-sm font-light">
Add CSV, JSON, or Parquet files to power your analytics.
</p>
</div>
<button
className="text-muted-foreground hover:bg-muted hover:text-foreground mt-0.5 ml-4 rounded-md p-1 transition-colors"
aria-label="Close"
>
<X className="h-4 w-4" />
</button>
</div>
<div className="space-y-4 px-6 pb-6">
{/* Drop zone */}
<div
onDragOver={(e) => {
e.preventDefault()
setDragging(true)
}}
onDragLeave={() => setDragging(false)}
onDrop={(e) => {
e.preventDefault()
setDragging(false)
}}
className={`flex flex-col items-center justify-center gap-2 rounded-xl border-2 border-dashed px-6 py-8 text-center transition-colors ${
dragging
? "border-primary bg-primary/5"
: "border-border bg-muted/30 hover:border-muted-foreground/40 hover:bg-muted/50"
}`}
>
<div className="bg-muted flex h-12 w-12 items-center justify-center rounded-xl">
<Upload className="text-muted-foreground h-6 w-6" />
</div>
<p className="text-foreground text-sm font-light">
Drag and drop a file or{" "}
<button className="hover:text-primary font-normal underline underline-offset-2">
Browse
</button>
</p>
<p className="text-muted-foreground text-xs font-light">
CSV, JSON, XLSX or Parquet up to 50 MB
</p>
</div>
{/* File list */}
{files.length > 0 && (
<ul className="space-y-2">
{files.map((file) => (
<li
key={file.id}
className="border-border bg-background flex items-center gap-3 rounded-xl border px-3 py-3"
>
<FileTypeIcon type={file.type} />
<div className="min-w-0 flex-1">
<p className="text-foreground truncate text-sm font-normal">
{file.name}
</p>
<p className="text-muted-foreground text-xs font-light">
{file.size}
</p>
</div>
<div className="relative">
<button
onClick={(e) => {
e.stopPropagation()
setOpenMenu(openMenu === file.id ? null : file.id)
}}
className="text-muted-foreground hover:bg-muted hover:text-foreground rounded-md p-1.5 transition-colors"
aria-label="File options"
>
<MoreVertical className="h-4 w-4" />
</button>
{openMenu === file.id && (
<div className="border-border bg-background absolute right-0 z-10 mt-1 w-36 rounded-lg border py-1 shadow-lg">
<button
onClick={() => removeFile(file.id)}
className="text-destructive hover:bg-muted flex w-full items-center gap-2 px-3 py-1.5 text-sm"
>
<Trash2 className="h-3.5 w-3.5" />
Remove file
</button>
</div>
)}
</div>
</li>
))}
</ul>
)}
{/* Continue button */}
<Button className="w-full" size="lg">
Continue
</Button>
</div>
</div>
</div>
)
}
Dataset file uploader with drag-and-drop zone and file list
dataset-upload-01
Import dataset files
Add CSV, JSON, or Parquet files to power your analytics.
Drag and drop a file or
CSV, JSON, XLSX or Parquet up to 50 MB
sales-report-q1.csv
2.9 MB
user-events-may.json
3.2 MB
churn-analysis.csv
2.6 MB
"use client"
import * as React from "react"
import {
FileJson,
FileSpreadsheet,
FileText,
MoreVertical,
Trash2,
Upload,
X,
} from "lucide-react"
import { Button } from "@/components/ui/button"
type DataFile = {
id: string
name: string
size: string
type: "csv" | "json" | "parquet"
}
const INITIAL_FILES: DataFile[] = [
{ id: "1", name: "sales-report-q1.csv", size: "2.9 MB", type: "csv" },
{ id: "2", name: "user-events-may.json", size: "3.2 MB", type: "json" },
{ id: "3", name: "churn-analysis.csv", size: "2.6 MB", type: "csv" },
]
function FileTypeIcon({ type }: { type: DataFile["type"] }) {
if (type === "json") {
return (
<div className="flex h-10 w-10 shrink-0 items-center justify-center rounded-lg bg-amber-100 dark:bg-amber-900/30">
<FileJson className="h-5 w-5 text-amber-600 dark:text-amber-400" />
</div>
)
}
if (type === "parquet") {
return (
<div className="flex h-10 w-10 shrink-0 items-center justify-center rounded-lg bg-violet-100 dark:bg-violet-900/30">
<FileText className="h-5 w-5 text-violet-600 dark:text-violet-400" />
</div>
)
}
return (
<div className="flex h-10 w-10 shrink-0 items-center justify-center rounded-lg bg-emerald-100 dark:bg-emerald-900/30">
<FileSpreadsheet className="h-5 w-5 text-emerald-600 dark:text-emerald-400" />
</div>
)
}
export default function DatasetUpload() {
const [files, setFiles] = React.useState<DataFile[]>(INITIAL_FILES)
const [dragging, setDragging] = React.useState(false)
const [openMenu, setOpenMenu] = React.useState<string | null>(null)
function removeFile(id: string) {
setFiles((f) => f.filter((file) => file.id !== id))
setOpenMenu(null)
}
React.useEffect(() => {
function handleClick() {
setOpenMenu(null)
}
document.addEventListener("click", handleClick)
return () => document.removeEventListener("click", handleClick)
}, [])
return (
<div className="flex min-h-screen items-center justify-center p-4">
<div className="bg-background w-full max-w-md rounded-2xl shadow-xl">
{/* Header */}
<div className="flex items-start justify-between p-6 pb-4">
<div>
<h2 className="text-foreground text-xl font-medium tracking-tight">
Import dataset files
</h2>
<p className="text-muted-foreground mt-1 text-sm font-light">
Add CSV, JSON, or Parquet files to power your analytics.
</p>
</div>
<button
className="text-muted-foreground hover:bg-muted hover:text-foreground mt-0.5 ml-4 rounded-md p-1 transition-colors"
aria-label="Close"
>
<X className="h-4 w-4" />
</button>
</div>
<div className="space-y-4 px-6 pb-6">
{/* Drop zone */}
<div
onDragOver={(e) => {
e.preventDefault()
setDragging(true)
}}
onDragLeave={() => setDragging(false)}
onDrop={(e) => {
e.preventDefault()
setDragging(false)
}}
className={`flex flex-col items-center justify-center gap-2 rounded-xl border-2 border-dashed px-6 py-8 text-center transition-colors ${
dragging
? "border-primary bg-primary/5"
: "border-border bg-muted/30 hover:border-muted-foreground/40 hover:bg-muted/50"
}`}
>
<div className="bg-muted flex h-12 w-12 items-center justify-center rounded-xl">
<Upload className="text-muted-foreground h-6 w-6" />
</div>
<p className="text-foreground text-sm font-light">
Drag and drop a file or{" "}
<button className="hover:text-primary font-normal underline underline-offset-2">
Browse
</button>
</p>
<p className="text-muted-foreground text-xs font-light">
CSV, JSON, XLSX or Parquet up to 50 MB
</p>
</div>
{/* File list */}
{files.length > 0 && (
<ul className="space-y-2">
{files.map((file) => (
<li
key={file.id}
className="border-border bg-background flex items-center gap-3 rounded-xl border px-3 py-3"
>
<FileTypeIcon type={file.type} />
<div className="min-w-0 flex-1">
<p className="text-foreground truncate text-sm font-normal">
{file.name}
</p>
<p className="text-muted-foreground text-xs font-light">
{file.size}
</p>
</div>
<div className="relative">
<button
onClick={(e) => {
e.stopPropagation()
setOpenMenu(openMenu === file.id ? null : file.id)
}}
className="text-muted-foreground hover:bg-muted hover:text-foreground rounded-md p-1.5 transition-colors"
aria-label="File options"
>
<MoreVertical className="h-4 w-4" />
</button>
{openMenu === file.id && (
<div className="border-border bg-background absolute right-0 z-10 mt-1 w-36 rounded-lg border py-1 shadow-lg">
<button
onClick={() => removeFile(file.id)}
className="text-destructive hover:bg-muted flex w-full items-center gap-2 px-3 py-1.5 text-sm"
>
<Trash2 className="h-3.5 w-3.5" />
Remove file
</button>
</div>
)}
</div>
</li>
))}
</ul>
)}
{/* Continue button */}
<Button className="w-full" size="lg">
Continue
</Button>
</div>
</div>
</div>
)
}