tahamajs/IE / Projects /front /src /components /authoring /TemplateLibraryPanel.tsx
tahamajs's picture
download
raw
4.32 kB
// src/components/authoring/TemplateLibraryPanel.tsx
import { Search, Layout, Check, Loader2, ExternalLink, Star } from "lucide-react";
interface TemplateLibraryPanelProps {
templates: any[];
loading: boolean;
search: string;
setSearch: (v: string) => void;
categoryFilter: string;
setCategoryFilter: (v: string) => void;
categories: string[];
selectedTemplateId: string | null;
setSelectedTemplateId: (v: string | null) => void;
selectedTemplate: any;
applyTemplate: (id: string, docId: string) => Promise<boolean>;
applying: boolean;
docId: string;
}
const CATEGORY_ICONS: Record<string, string> = {
journal: "๐Ÿ“„", conference: "๐ŸŽค", thesis: "๐ŸŽ“", grant: "๐Ÿ’ฐ", poster: "๐Ÿ–ผ๏ธ", letter: "โœ‰๏ธ",
};
export function TemplateLibraryPanel({ templates, loading, search, setSearch, categoryFilter, setCategoryFilter, categories, selectedTemplateId, setSelectedTemplateId, selectedTemplate, applyTemplate, applying, docId }: TemplateLibraryPanelProps) {
return (
<div className="grid grid-cols-1 lg:grid-cols-4 gap-4 h-full">
{/* Filters */}
<div className="space-y-3">
<div className="relative">
<Search className="absolute left-2 top-1/2 -translate-y-1/2 w-3.5 h-3.5 text-gray-400" />
<input type="text" value={search} onChange={e => setSearch(e.target.value)} placeholder="Search templates..."
className="w-full pl-7 pr-3 py-1.5 text-sm border border-white/20 rounded-lg bg-white/10" />
</div>
<div className="space-y-1">
{categories.map(cat => (
<button key={cat} onClick={() => setCategoryFilter(cat)}
className={`w-full text-left px-3 py-1.5 rounded-lg text-sm transition capitalize ${
categoryFilter === cat ? "bg-primary/20 text-primary" : "hover:bg-white/10 text-gray-600 dark:text-gray-400"
}`}>
{CATEGORY_ICONS[cat] ?? "๐Ÿ“"} {cat}
</button>
))}
</div>
</div>
{/* Template grid */}
<div className="lg:col-span-3 overflow-y-auto">
{loading ? (
<div className="flex justify-center py-8"><Loader2 className="w-6 h-6 animate-spin text-gray-400"/></div>
) : (
<div className="grid grid-cols-1 sm:grid-cols-2 gap-3">
{templates.map(tpl => (
<button key={tpl.id} onClick={() => setSelectedTemplateId(tpl.id)}
className={`text-left p-3 rounded-xl border-2 transition ${
selectedTemplateId === tpl.id ? "border-primary bg-primary/5" : "border-white/20 hover:border-primary/50"
}`}>
<div className="flex items-start justify-between mb-2">
<span className="text-2xl">{CATEGORY_ICONS[tpl.category] ?? "๐Ÿ“"}</span>
{tpl.isOfficial && <Star className="w-4 h-4 text-amber-500"/>}
</div>
<h4 className="font-semibold text-sm">{tpl.name}</h4>
<p className="text-xs text-gray-500 mt-0.5">{tpl.publisher}</p>
<p className="text-xs text-gray-400 mt-1 line-clamp-2">{tpl.description}</p>
<div className="flex flex-wrap gap-1 mt-2">
{tpl.tags.slice(0, 3).map((tag: string) => (
<span key={tag} className="text-[9px] bg-white/10 px-1.5 py-0.5 rounded-full">{tag}</span>
))}
</div>
</button>
))}
</div>
)}
{/* Selected template detail */}
{selectedTemplate && (
<div className="mt-4 p-4 rounded-xl border border-primary/30 bg-primary/5">
<h4 className="font-bold">{selectedTemplate.name}</h4>
<p className="text-sm text-gray-500 mt-1">{selectedTemplate.description}</p>
<p className="text-xs text-gray-400 mt-2">Files: {selectedTemplate.files.map((f: any) => f.name).join(", ")}</p>
<button onClick={() => applyTemplate(selectedTemplate.id, docId)} disabled={applying}
className="btn btn-primary btn-sm gap-1.5 mt-3">
{applying ? <Loader2 className="w-3.5 h-3.5 animate-spin"/> : <Check className="w-3.5 h-3.5"/>}
Apply Template
</button>
</div>
)}
</div>
</div>
);
}

Xet Storage Details

Size:
4.32 kB
ยท
Xet hash:
dee7aa428d96fdcfda38c2a9cf73f719cb5b3a11911fa642658ec93e26822ee7

Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.