"use client";
import { useState, useMemo } from "react";
import {
FolderPlus,
Search,
Plus,
Check,
Folder,
Loader2,
X,
} from "lucide-react";
import { Button } from "@/components/ui/button";
import { Badge } from "@/components/ui/badge";
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { Checkbox } from "@/components/ui/checkbox";
import { ScrollArea } from "@/components/ui/scroll-area";
import { Separator } from "@/components/ui/separator";
import { cn } from "@/lib/utils";
type Collection = {
id: string;
name: string;
itemCount: number;
color: string;
};
const initialCollections: Collection[] = [
{ id: "1", name: "Favorites", itemCount: 24, color: "bg-red-500" },
{ id: "2", name: "Read Later", itemCount: 12, color: "bg-blue-500" },
{ id: "3", name: "Work Resources", itemCount: 45, color: "bg-green-500" },
{ id: "4", name: "Inspiration", itemCount: 89, color: "bg-purple-500" },
{ id: "5", name: "Tutorials", itemCount: 33, color: "bg-yellow-500" },
{ id: "6", name: "Design Assets", itemCount: 67, color: "bg-pink-500" },
];
// Simulate that item is already in some collections
const alreadyInCollections = ["1", "3"];
export const title = "React Dialog Block Add To Collection";
export default function DialogAddToCollection() {
const [open, setOpen] = useState(false);
const [searchQuery, setSearchQuery] = useState("");
const [selectedIds, setSelectedIds] = useState<string[]>(alreadyInCollections);
const [collections, setCollections] = useState<Collection[]>(initialCollections);
const [isCreating, setIsCreating] = useState(false);
const [newCollectionName, setNewCollectionName] = useState("");
const [isSaving, setIsSaving] = useState(false);
const filteredCollections = useMemo(() => {
if (!searchQuery) return collections;
return collections.filter((c) =>
c.name.toLowerCase().includes(searchQuery.toLowerCase())
);
}, [collections, searchQuery]);
const handleToggleCollection = (collectionId: string) => {
setSelectedIds((prev) =>
prev.includes(collectionId)
? prev.filter((id) => id !== collectionId)
: [...prev, collectionId]
);
};
const handleCreateCollection = () => {
if (!newCollectionName.trim()) return;
const colors = ["bg-red-500", "bg-blue-500", "bg-green-500", "bg-purple-500", "bg-yellow-500", "bg-pink-500", "bg-orange-500", "bg-teal-500"];
const randomColor = colors[Math.floor(Math.random() * colors.length)];
const newCollection: Collection = {
id: Date.now().toString(),
name: newCollectionName.trim(),
itemCount: 0,
color: randomColor,
};
setCollections((prev) => [newCollection, ...prev]);
setSelectedIds((prev) => [...prev, newCollection.id]);
setNewCollectionName("");
setIsCreating(false);
};
const handleSave = async () => {
setIsSaving(true);
await new Promise((resolve) => setTimeout(resolve, 1000));
setIsSaving(false);
setOpen(false);
};
const handleClose = () => {
setOpen(false);
setTimeout(() => {
setSearchQuery("");
setIsCreating(false);
setNewCollectionName("");
}, 200);
};
const newSelections = selectedIds.filter((id) => !alreadyInCollections.includes(id));
const removedSelections = alreadyInCollections.filter((id) => !selectedIds.includes(id));
const hasChanges = newSelections.length > 0 || removedSelections.length > 0;
return (
<Dialog open={open} onOpenChange={setOpen}>
<div className="flex min-h-[350px] items-center justify-center">
<DialogTrigger asChild>
<Button variant="outline">
<FolderPlus className="mr-2 h-4 w-4" />
Add to Collection
</Button>
</DialogTrigger>
</div>
<DialogContent className="sm:max-w-md">
<DialogHeader>
<DialogTitle className="flex items-center gap-2">
<FolderPlus className="h-5 w-5" />
Add to Collection
</DialogTitle>
<DialogDescription>
Select collections to add this item to.
</DialogDescription>
</DialogHeader>
<div className="space-y-4 py-4">
<div className="relative">
<Search className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground" />
<Input
placeholder="Search collections..."
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
className="pl-9"
/>
</div>
{isCreating ? (
<div className="flex gap-2">
<Input
placeholder="Collection name"
value={newCollectionName}
onChange={(e) => setNewCollectionName(e.target.value)}
onKeyDown={(e) => e.key === "Enter" && handleCreateCollection()}
autoFocus
/>
<Button size="icon" onClick={handleCreateCollection} disabled={!newCollectionName.trim()}>
<Check className="h-4 w-4" />
</Button>
<Button size="icon" variant="outline" onClick={() => setIsCreating(false)}>
<X className="h-4 w-4" />
</Button>
</div>
) : (
<Button
variant="outline"
className="w-full justify-start"
onClick={() => setIsCreating(true)}
>
<Plus className="mr-2 h-4 w-4" />
Create New Collection
</Button>
)}
<Separator />
<ScrollArea className="h-[240px]">
{filteredCollections.length === 0 ? (
<div className="flex flex-col items-center justify-center py-8 text-center">
<Folder className="h-8 w-8 text-muted-foreground mb-2" />
<p className="text-sm text-muted-foreground">
{searchQuery ? "No collections found" : "No collections yet"}
</p>
{!searchQuery && (
<Button
variant="link"
size="sm"
onClick={() => setIsCreating(true)}
>
Create your first collection
</Button>
)}
</div>
) : (
<div className="space-y-1">
{filteredCollections.map((collection) => {
const isSelected = selectedIds.includes(collection.id);
const wasAlreadyIn = alreadyInCollections.includes(collection.id);
return (
<label
key={collection.id}
className={cn(
"w-full flex items-center gap-3 p-3 rounded-lg transition-colors text-left cursor-pointer",
isSelected ? "bg-primary/5" : "hover:bg-muted"
)}
>
<Checkbox
checked={isSelected}
onCheckedChange={() => handleToggleCollection(collection.id)}
/>
<div className={cn("w-3 h-3 rounded-full", collection.color)} />
<div className="flex-1 min-w-0">
<p className="text-sm font-medium truncate">
{collection.name}
</p>
<p className="text-xs text-muted-foreground">
{collection.itemCount} items
</p>
</div>
{wasAlreadyIn && isSelected && (
<Badge variant="secondary" className="text-xs">
Added
</Badge>
)}
</label>
);
})}
</div>
)}
</ScrollArea>
{selectedIds.length > 0 && (
<div className="flex items-center justify-between text-sm">
<span className="text-muted-foreground">
{selectedIds.length} collection{selectedIds.length !== 1 ? "s" : ""} selected
</span>
{hasChanges && (
<Badge variant="secondary">
{newSelections.length > 0 && `+${newSelections.length}`}
{newSelections.length > 0 && removedSelections.length > 0 && " / "}
{removedSelections.length > 0 && `-${removedSelections.length}`}
</Badge>
)}
</div>
)}
</div>
<DialogFooter>
<Button variant="outline" onClick={handleClose}>
Cancel
</Button>
<Button onClick={handleSave} disabled={!hasChanges || isSaving}>
{isSaving ? (
<>
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
Saving...
</>
) : (
<>
<Check className="mr-2 h-4 w-4" />
Save Changes
</>
)}
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
);
}