Files
news_all_spo/web/editor-ui/src/components/Sidebar.jsx
2026-05-14 16:47:58 +05:00

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>
)
}