musealpha / uiprototype2 /src /components /LibraryPanel.tsx
asdf98's picture
Upload 112 files
3d7d9b5 verified
import { useState } from "react";
import { X, Search, Folder, Grid, Filter, Plus, Tag } from "lucide-react";
import { useAppStore } from "../store";
const ALL_TAGS = [
"anatomy",
"environment",
"character",
"lighting",
"cyberpunk",
"fantasy",
"sci-fi",
];
const mockLibrary = Array.from({ length: 24 }).map((_, i) => ({
url: `https://picsum.photos/id/${100 + i}/400/400`,
tags: [ALL_TAGS[i % ALL_TAGS.length], ALL_TAGS[(i + 2) % ALL_TAGS.length]],
}));
export const LibraryPanel = () => {
const { isLibraryOpen, setIsLibraryOpen, setImages, pan, zoom } =
useAppStore();
const [search, setSearch] = useState("");
const [activeTag, setActiveTag] = useState<string | null>(null);
const handleAdd = (src: string) => {
const newImg = {
id: Math.random().toString(36).substr(2, 9),
url: src,
x: (-pan.x + window.innerWidth / 4) / zoom,
y: (-pan.y + window.innerHeight / 4) / zoom,
width: 250,
height: 250,
aspectRatio: 1,
};
setImages((prev) => [...prev, newImg]);
};
const filteredLibrary = mockLibrary.filter((img) => {
if (activeTag && !img.tags.includes(activeTag)) return false;
if (search && !img.tags.some((t) => t.includes(search.toLowerCase())))
return false;
return true;
});
return (
<div
className={`absolute left-0 top-0 h-full w-[45%] max-w-[500px] bg-panel-bg shadow-2xl flex flex-col z-[60] transform transition-transform duration-500 ease-[cubic-bezier(0.19,1,0.22,1)] ${isLibraryOpen ? "translate-x-0" : "-translate-x-full"}`}
>
<div className="flex items-center justify-between p-4 bg-panel-bg z-10 border-b border-[#3A3A3E]">
<div className="flex items-center gap-2 text-[#E0E0E0] text-[14px] font-medium tracking-tight">
<Folder size={16} className="text-[#0A84FF]" />
Asset Library
</div>
<button
onClick={() => setIsLibraryOpen(false)}
className="text-[#A0A0A0] hover:text-[#E0E0E0] p-1.5 rounded-md hover:bg-white/5 transition-colors"
>
<X size={16} />
</button>
</div>
<div className="px-4 py-3 bg-[#2A2A2E] flex flex-col gap-3">
<div className="relative group">
<Search
size={14}
className="absolute left-3 top-1/2 -translate-y-1/2 text-[#808080] group-focus-within:text-[#0A84FF] transition-colors"
/>
<input
type="text"
value={search}
onChange={(e) => setSearch(e.target.value)}
placeholder="Search assets or tags..."
className="w-full bg-[#1C1C1E] text-[#E0E0E0] pl-9 pr-3 py-2 text-[13px] rounded-lg border border-[#3A3A3E] focus:border-[#0A84FF] outline-none transition-all placeholder:text-[#808080]"
/>
</div>
{/* Tag Pills */}
<div className="flex items-center gap-1.5 overflow-x-auto hide-scrollbar pb-1">
<button
onClick={() => setActiveTag(null)}
className={`whitespace-nowrap px-3 py-1 rounded-full text-[11px] font-medium transition-colors ${!activeTag ? "bg-[#0A84FF] text-white" : "bg-[#3A3A3E] text-[#C0C0C0] hover:bg-[#4A4A4E]"}`}
>
All
</button>
{ALL_TAGS.map((tag) => (
<button
key={tag}
onClick={() => setActiveTag(tag)}
className={`whitespace-nowrap px-3 py-1 rounded-full text-[11px] font-medium transition-colors flex items-center gap-1 ${activeTag === tag ? "bg-[#0A84FF] text-white" : "bg-[#3A3A3E] text-[#C0C0C0] hover:bg-[#4A4A4E]"}`}
>
<Tag size={10} />
{tag}
</button>
))}
</div>
</div>
<div className="flex-1 overflow-y-auto bg-[#1C1C1E] p-4 custom-scrollbar">
<div className="flex justify-between items-center mb-4">
<span className="text-[11px] font-medium text-[#808080] uppercase tracking-widest">
Images ({filteredLibrary.length})
</span>
<Grid size={14} className="text-[#808080]" />
</div>
<div className="grid grid-cols-3 gap-3">
<div className="aspect-square bg-white/5 hover:bg-white/10 border border-dashed border-white/20 hover:border-white/30 rounded-xl cursor-pointer flex flex-col items-center justify-center text-[#A0A0A0] hover:text-[#E0E0E0] transition-all group shadow-sm">
<div className="w-8 h-8 rounded-full bg-black/20 flex items-center justify-center mb-2 group-hover:scale-110 transition-transform">
<Plus size={16} />
</div>
<span className="text-[11px] font-medium">Upload File</span>
</div>
{filteredLibrary.map((img, i) => (
<div
key={i}
className="aspect-square bg-[#2A2A2E] rounded-xl cursor-pointer group relative overflow-hidden shadow-sm ring-1 ring-[#3A3A3E] hover:ring-[#0A84FF] transition-all"
onClick={() => handleAdd(img.url)}
>
<img
src={img.url}
className="w-full h-full object-cover opacity-90 group-hover:opacity-100 group-hover:scale-105 transition-all duration-500 ease-out"
/>
<div className="absolute inset-0 bg-gradient-to-t from-black/80 via-transparent to-transparent opacity-0 group-hover:opacity-100 transition-opacity flex flex-col justify-end p-2 pointer-events-none">
<span className="text-[10px] text-white font-medium truncate mb-1">
IMG_{100 + i}.jpg
</span>
<div className="flex items-center gap-1 overflow-hidden">
{img.tags.map((t) => (
<span
key={t}
className="text-[9px] bg-white/20 text-white/90 px-1.5 py-0.5 rounded backdrop-blur"
>
{t}
</span>
))}
</div>
</div>
</div>
))}
</div>
</div>
</div>
);
};