| // 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.