Files
pinkudex/components/collections/CollectionPicker.tsx
T
2026-05-26 22:46:00 +02:00

119 lines
4.1 KiB
TypeScript

"use client";
import { useState, useTransition } from "react";
import Link from "next/link";
import { FolderHeart, X, Plus } from "lucide-react";
import { addImageToCollection, removeImageFromCollection, createCollection } from "@/app/actions/collections";
interface PickedCollection { id: number; name: string; slug?: string }
export function CollectionPicker({
imageId,
current,
available,
}: {
imageId: number;
current: PickedCollection[];
available: PickedCollection[];
}) {
const [picked, setPicked] = useState(current);
const [open, setOpen] = useState(false);
const [draft, setDraft] = useState("");
const [pool, setPool] = useState(available);
const [, start] = useTransition();
const add = (c: PickedCollection) => {
if (picked.some(p => p.id === c.id)) return;
setPicked((cur) => [...cur, c]);
start(async () => { await addImageToCollection(c.id, imageId); });
};
const remove = (id: number) => {
setPicked((cur) => cur.filter(c => c.id !== id));
start(async () => { await removeImageFromCollection(id, imageId); });
};
const createNew = async (e: React.FormEvent) => {
e.preventDefault();
const name = draft.trim();
if (!name) return;
setDraft("");
start(async () => {
const created = await createCollection(name);
if (created) {
setPool((cur) => [...cur, { id: created.id, name, slug: created.slug }]);
setPicked((cur) => [...cur, { id: created.id, name, slug: created.slug }]);
await addImageToCollection(created.id, imageId);
}
});
};
const remaining = pool.filter(p => !picked.some(pp => pp.id === p.id));
return (
<div>
<div className="text-[10px] uppercase tracking-wider font-mono text-[var(--color-fg-muted)] mb-2">
Collections
</div>
<div className="flex flex-wrap gap-1.5">
{picked.map((c) => (
<span
key={c.id}
className="group flex items-center gap-1 px-2 py-1 rounded-full text-xs glass border-[var(--color-cyan)]/30 text-[var(--color-cyan)] bg-[color-mix(in_oklch,var(--color-cyan)_10%,transparent)]"
>
<FolderHeart className="w-3 h-3" />
{c.slug ? (
<Link
href={`/collection/${c.slug}`}
className="hover:underline"
>
{c.name}
</Link>
) : (
c.name
)}
<button onClick={() => remove(c.id)} className="opacity-50 hover:opacity-100">
<X className="w-3 h-3" />
</button>
</span>
))}
<button
onClick={() => setOpen((o) => !o)}
className="flex items-center gap-1 px-2 py-1 rounded-full text-xs border border-dashed border-[var(--color-glass-border)] text-[var(--color-fg-dim)] hover:text-[var(--color-cyan)] hover:border-[var(--color-cyan)]"
>
<Plus className="w-3 h-3" />
Add to collection
</button>
</div>
{open && (
<div className="mt-2 glass rounded-xl p-3 space-y-2">
{remaining.length > 0 && (
<div className="flex flex-wrap gap-1.5">
{remaining.map((c) => (
<button
key={c.id}
onClick={() => add(c)}
className="text-xs px-2 py-1 rounded-full glass-strong hover:text-[var(--color-cyan)]"
>
{c.name}
</button>
))}
</div>
)}
<form onSubmit={createNew} className="flex gap-1.5">
<input
value={draft}
onChange={(e) => setDraft(e.target.value)}
placeholder="New collection…"
className="flex-1 bg-transparent text-xs px-2 py-1 rounded-md border border-[var(--color-glass-border)] outline-none focus:border-[var(--color-cyan)]"
/>
<button type="submit" className="text-xs px-2 py-1 rounded-md bg-[var(--color-cyan)] text-black font-medium">
Create
</button>
</form>
</div>
)}
</div>
);
}