"use client";
import { useState } from "react";
import {
Layers,
Trash2,
Archive,
Download,
FolderInput,
Tag,
Loader2,
AlertTriangle,
Check,
X,
} from "lucide-react";
import { Button } from "@/components/ui/button";
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog";
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
import { Label } from "@/components/ui/label";
import { Progress } from "@/components/ui/progress";
import { cn } from "@/lib/utils";
type BulkAction = {
id: string;
label: string;
description: string;
icon: React.ElementType;
destructive?: boolean;
};
const bulkActions: BulkAction[] = [
{
id: "archive",
label: "Archive",
description: "Move to archive",
icon: Archive,
},
{
id: "export",
label: "Export",
description: "Download as file",
icon: Download,
},
{
id: "move",
label: "Move",
description: "Move to folder",
icon: FolderInput,
},
{
id: "tag",
label: "Tag",
description: "Apply tags",
icon: Tag,
},
{
id: "duplicate",
label: "Duplicate",
description: "Create copies",
icon: Layers,
},
{
id: "delete",
label: "Delete",
description: "Remove permanently",
icon: Trash2,
destructive: true,
},
];
type Status = "idle" | "processing" | "completed" | "error";
export const title = "React Dialog Block Bulk Actions";
export default function DialogBulkActions() {
const [open, setOpen] = useState(false);
const [selectedAction, setSelectedAction] = useState<string>("archive");
const [status, setStatus] = useState<Status>("idle");
const [progress, setProgress] = useState(0);
const [processedCount, setProcessedCount] = useState(0);
// Simulated selection
const selectedItems = 24;
const totalItems = selectedItems;
const handleExecute = async () => {
setStatus("processing");
setProgress(0);
setProcessedCount(0);
// Simulate bulk operation
for (let i = 1; i <= totalItems; i++) {
await new Promise((resolve) => setTimeout(resolve, 80));
setProgress((i / totalItems) * 100);
setProcessedCount(i);
}
setStatus("completed");
};
const handleClose = () => {
setOpen(false);
setTimeout(() => {
setStatus("idle");
setProgress(0);
setProcessedCount(0);
}, 200);
};
const currentAction = bulkActions.find((a) => a.id === selectedAction);
return (
<Dialog open={open} onOpenChange={setOpen}>
<div className="flex min-h-[350px] items-center justify-center">
<DialogTrigger asChild>
<Button variant="outline">
<Layers className="mr-2 h-4 w-4" />
Bulk Actions
</Button>
</DialogTrigger>
</div>
<DialogContent className="sm:max-w-md">
<DialogHeader>
<DialogTitle className="flex items-center gap-2">
<Layers className="h-5 w-5" />
Bulk Actions
</DialogTitle>
<DialogDescription>
Apply an action to {selectedItems} selected items.
</DialogDescription>
</DialogHeader>
{status === "idle" && (
<>
<div className="space-y-4 py-4">
<div className="space-y-3">
<Label>Choose action</Label>
<RadioGroup
value={selectedAction}
onValueChange={setSelectedAction}
className="grid grid-cols-2 gap-2"
>
{bulkActions.map((action) => {
const Icon = action.icon;
const isSelected = selectedAction === action.id;
return (
<label
key={action.id}
htmlFor={action.id}
className={cn(
"flex items-center gap-3 rounded-lg border p-3 cursor-pointer transition-colors",
isSelected
? action.destructive
? "border-destructive bg-destructive/5"
: "border-primary bg-primary/5"
: "hover:bg-muted"
)}
>
<RadioGroupItem value={action.id} id={action.id} />
<div>
<p className="text-sm font-medium">{action.label}</p>
<p className="text-xs text-muted-foreground">
{action.description}
</p>
</div>
</label>
);
})}
</RadioGroup>
</div>
{currentAction?.destructive && (
<p className="text-sm text-destructive flex items-center gap-2">
<AlertTriangle className="h-4 w-4" />
This will permanently delete {selectedItems} items.
</p>
)}
</div>
<DialogFooter>
<Button variant="outline" onClick={handleClose}>
Cancel
</Button>
<Button
onClick={handleExecute}
variant={currentAction?.destructive ? "destructive" : "default"}
>
{currentAction?.label} {selectedItems} Items
</Button>
</DialogFooter>
</>
)}
{status === "processing" && (
<div className="py-8 space-y-6">
<div className="space-y-3">
<Progress value={progress} className="h-1.5" />
<p className="text-sm text-muted-foreground text-center">
Processing {processedCount} of {totalItems} items...
</p>
</div>
</div>
)}
{status === "completed" && (
<div className="py-8 space-y-6">
<div className="flex flex-col items-center text-center">
<Check className="h-10 w-10 text-primary" />
<h3 className="mt-4 text-lg font-semibold">Complete</h3>
<p className="mt-1 text-sm text-muted-foreground">
{totalItems} items {currentAction?.label.toLowerCase()}d successfully.
</p>
</div>
<DialogFooter className="sm:justify-center">
<Button onClick={handleClose}>Done</Button>
</DialogFooter>
</div>
)}
{status === "error" && (
<div className="py-8 space-y-6">
<div className="flex flex-col items-center text-center">
<X className="h-10 w-10 text-destructive" />
<h3 className="mt-4 text-lg font-semibold">Failed</h3>
<p className="mt-1 text-sm text-muted-foreground">
Some items could not be processed.
</p>
</div>
<DialogFooter className="sm:justify-center gap-2">
<Button variant="outline" onClick={handleClose}>
Close
</Button>
<Button onClick={handleExecute}>Retry</Button>
</DialogFooter>
</div>
)}
</DialogContent>
</Dialog>
);
}