157 lines
5.8 KiB
JavaScript
157 lines
5.8 KiB
JavaScript
import { useRef } from 'react'
|
|
import { X, ImageIcon, ChevronDown, BarChart2 } from 'lucide-react'
|
|
import { TagInput } from './TagInput'
|
|
|
|
export function Sidebar({ meta, categories, onChange, onUploadCover, wordCount, charCount, fonts }) {
|
|
const coverRef = useRef(null)
|
|
|
|
return (
|
|
<aside className="w-[280px] flex-shrink-0 bg-slate-900 border-l border-slate-800 flex flex-col overflow-y-auto">
|
|
|
|
{/* Stats bar */}
|
|
<div className="flex items-center gap-4 px-4 py-3 border-b border-slate-800">
|
|
<BarChart2 size={14} className="text-slate-500 flex-shrink-0" />
|
|
<div className="flex gap-4 text-xs">
|
|
<span>
|
|
<span className="font-semibold text-white">{wordCount}</span>
|
|
<span className="text-slate-400 ml-1">слов</span>
|
|
</span>
|
|
<span>
|
|
<span className="font-semibold text-white">{charCount}</span>
|
|
<span className="text-slate-400 ml-1">симв.</span>
|
|
</span>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Status */}
|
|
<div className="px-4 pt-4 pb-1">
|
|
<span className={`inline-flex items-center gap-1.5 px-2.5 py-1 rounded-full text-xs font-medium
|
|
${meta.status === 'published'
|
|
? 'bg-emerald-900/60 text-emerald-300 border border-emerald-800'
|
|
: 'bg-amber-900/40 text-amber-300 border border-amber-800/60'}`}
|
|
>
|
|
<span className={`w-1.5 h-1.5 rounded-full ${meta.status === 'published' ? 'bg-emerald-400' : 'bg-amber-400'}`} />
|
|
{meta.status === 'published' ? 'Опубликовано' : 'Черновик'}
|
|
</span>
|
|
</div>
|
|
|
|
<div className="p-4 space-y-5">
|
|
|
|
{/* Slug */}
|
|
<div>
|
|
<Label>URL (slug)</Label>
|
|
<input
|
|
className="input-dark font-mono text-xs"
|
|
placeholder="url-stati"
|
|
value={meta.slug}
|
|
onChange={e => onChange({ slug: e.target.value })}
|
|
/>
|
|
</div>
|
|
|
|
{/* Excerpt */}
|
|
<div>
|
|
<Label>Анонс</Label>
|
|
<textarea
|
|
className="input-dark resize-none text-sm"
|
|
placeholder="Краткое описание статьи…"
|
|
rows={3}
|
|
value={meta.excerpt}
|
|
onChange={e => onChange({ excerpt: e.target.value })}
|
|
/>
|
|
</div>
|
|
|
|
{/* Category */}
|
|
<div>
|
|
<Label>Категория</Label>
|
|
<div className="relative">
|
|
<select
|
|
className="input-dark appearance-none cursor-pointer pr-8 text-sm"
|
|
value={meta.category_id || ''}
|
|
onChange={e => onChange({ category_id: e.target.value || null })}
|
|
>
|
|
<option value="">Без категории</option>
|
|
{categories.map(c => (
|
|
<option key={c.id} value={c.id}>{c.name}</option>
|
|
))}
|
|
</select>
|
|
<ChevronDown size={13} className="absolute right-2.5 top-1/2 -translate-y-1/2 text-slate-400 pointer-events-none" />
|
|
</div>
|
|
</div>
|
|
|
|
{/* Tags */}
|
|
<div>
|
|
<Label>Теги</Label>
|
|
<TagInput tags={meta.tag_names} onChange={tags => onChange({ tag_names: tags })} />
|
|
<p className="text-[11px] text-slate-500 mt-1.5">Enter или запятая для добавления</p>
|
|
</div>
|
|
|
|
{/* Font */}
|
|
<div>
|
|
<Label>Шрифт статьи</Label>
|
|
<div className="relative">
|
|
<select
|
|
className="input-dark appearance-none cursor-pointer pr-8 text-sm"
|
|
style={{ fontFamily: meta.font_family }}
|
|
value={meta.font_family}
|
|
onChange={e => onChange({ font_family: e.target.value })}
|
|
>
|
|
{fonts.map(f => (
|
|
<option key={f} value={f} style={{ fontFamily: f }}>{f}</option>
|
|
))}
|
|
</select>
|
|
<ChevronDown size={13} className="absolute right-2.5 top-1/2 -translate-y-1/2 text-slate-400 pointer-events-none" />
|
|
</div>
|
|
</div>
|
|
|
|
{/* Cover image */}
|
|
<div>
|
|
<Label>Обложка</Label>
|
|
{meta.cover_url ? (
|
|
<div className="relative rounded-xl overflow-hidden ring-1 ring-slate-700">
|
|
<img
|
|
src={meta.cover_url}
|
|
alt="cover"
|
|
className="w-full h-36 object-cover"
|
|
/>
|
|
<button
|
|
type="button"
|
|
onClick={() => onChange({ cover_url: null })}
|
|
className="absolute top-2 right-2 p-1.5 bg-slate-900/80 backdrop-blur-sm rounded-full hover:bg-red-600 transition-colors"
|
|
>
|
|
<X size={12} className="text-white" />
|
|
</button>
|
|
</div>
|
|
) : (
|
|
<button
|
|
type="button"
|
|
onClick={() => coverRef.current?.click()}
|
|
className="w-full h-28 border-2 border-dashed border-slate-700 rounded-xl flex flex-col items-center justify-center gap-2 hover:border-indigo-500 hover:bg-slate-800/50 transition-all group"
|
|
>
|
|
<ImageIcon size={20} className="text-slate-500 group-hover:text-indigo-400 transition-colors" />
|
|
<span className="text-xs text-slate-500 group-hover:text-slate-300 transition-colors">
|
|
Загрузить обложку
|
|
</span>
|
|
</button>
|
|
)}
|
|
<input
|
|
ref={coverRef}
|
|
type="file"
|
|
accept="image/*"
|
|
className="hidden"
|
|
onChange={e => e.target.files[0] && onUploadCover(e.target.files[0])}
|
|
/>
|
|
</div>
|
|
|
|
</div>
|
|
</aside>
|
|
)
|
|
}
|
|
|
|
function Label({ children }) {
|
|
return (
|
|
<p className="text-[11px] font-semibold uppercase tracking-wider text-slate-400 mb-1.5">
|
|
{children}
|
|
</p>
|
|
)
|
|
}
|