- Add Nova UI library: Button, TextInput, Textarea, FormField, Select, NovaSelect, Checkbox, Radio/RadioGroup, Toggle, DatePicker, DateRangePicker, Modal + barrel index.js - Replace all native <select> in Studio with NovaSelect (StudioFilters, StudioToolbar, BulkActionsBar) including frosted-glass portal and category group headers - Replace native checkboxes in StudioGridCard, StudioTable, UploadSidebar, UploadWizard, Upload/Index with custom Checkbox component - Add nova-scrollbar CSS utility (thin 4px, semi-transparent) - Fix portal position drift: use viewport-relative coords (no scrollY offset) for NovaSelect, DatePicker and DateRangePicker - Close portals on external scroll instead of remeasuring - Improve hover highlight visibility in NovaSelect (bg-white/[0.13]) - Move search icon to right side in NovaSelect dropdown - Reduce Studio layout top spacing (py-6 -> pt-4 pb-8) - Add StudioCheckbox and SquareCheckbox backward-compat shims - Add sync.sh rsync deploy script
89 lines
3.1 KiB
JavaScript
89 lines
3.1 KiB
JavaScript
import React from 'react'
|
|
import NovaSelect from '../ui/NovaSelect'
|
|
|
|
const sortOptions = [
|
|
{ value: 'created_at:desc', label: 'Latest' },
|
|
{ value: 'ranking_score:desc', label: 'Trending' },
|
|
{ value: 'heat_score:desc', label: 'Rising' },
|
|
{ value: 'views:desc', label: 'Most viewed' },
|
|
{ value: 'favorites_count:desc', label: 'Most favourited' },
|
|
{ value: 'shares_count:desc', label: 'Most shared' },
|
|
{ value: 'downloads:desc', label: 'Most downloaded' },
|
|
]
|
|
|
|
export default function StudioToolbar({
|
|
search,
|
|
onSearchChange,
|
|
sort,
|
|
onSortChange,
|
|
viewMode,
|
|
onViewModeChange,
|
|
onFilterToggle,
|
|
selectedCount,
|
|
onUpload,
|
|
}) {
|
|
return (
|
|
<div className="flex flex-wrap items-center gap-3 mb-4">
|
|
{/* Search */}
|
|
<div className="relative flex-1 min-w-[200px] max-w-md">
|
|
<i className="fa-solid fa-magnifying-glass absolute left-3.5 top-1/2 -translate-y-1/2 text-slate-500 text-sm pointer-events-none" />
|
|
<input
|
|
type="text"
|
|
value={search}
|
|
onChange={(e) => onSearchChange(e.target.value)}
|
|
placeholder="Search title or tags…"
|
|
style={{ paddingLeft: '3rem' }}
|
|
className="w-full pr-4 py-2.5 rounded-xl bg-nova-900/60 border border-white/10 text-white placeholder-slate-500 text-sm focus:outline-none focus:ring-2 focus:ring-accent/50 focus:border-accent/50 transition-all"
|
|
/>
|
|
</div>
|
|
|
|
{/* Sort */}
|
|
<div className="min-w-[160px]">
|
|
<NovaSelect
|
|
options={sortOptions}
|
|
value={sort}
|
|
onChange={onSortChange}
|
|
searchable={false}
|
|
/>
|
|
</div>
|
|
|
|
{/* Filter toggle */}
|
|
<button
|
|
onClick={onFilterToggle}
|
|
className="flex items-center gap-2 px-4 py-2.5 rounded-xl border border-white/10 text-slate-400 hover:text-white hover:bg-white/5 text-sm transition-all"
|
|
aria-label="Toggle filters"
|
|
>
|
|
<i className="fa-solid fa-filter" />
|
|
<span className="hidden sm:inline">Filters</span>
|
|
</button>
|
|
|
|
{/* View toggle */}
|
|
<div className="flex items-center bg-nova-900/60 border border-white/10 rounded-xl overflow-hidden">
|
|
<button
|
|
onClick={() => onViewModeChange('grid')}
|
|
className={`px-3 py-2.5 text-sm transition-all ${viewMode === 'grid' ? 'bg-accent/20 text-accent' : 'text-slate-400 hover:text-white'}`}
|
|
aria-label="Grid view"
|
|
>
|
|
<i className="fa-solid fa-table-cells" />
|
|
</button>
|
|
<button
|
|
onClick={() => onViewModeChange('list')}
|
|
className={`px-3 py-2.5 text-sm transition-all ${viewMode === 'list' ? 'bg-accent/20 text-accent' : 'text-slate-400 hover:text-white'}`}
|
|
aria-label="List view"
|
|
>
|
|
<i className="fa-solid fa-list" />
|
|
</button>
|
|
</div>
|
|
|
|
{/* Upload */}
|
|
<a
|
|
href="/upload"
|
|
className="flex items-center gap-2 px-4 py-2.5 rounded-xl bg-accent hover:bg-accent/90 text-white font-semibold text-sm transition-all shadow-lg shadow-accent/25"
|
|
>
|
|
<i className="fa-solid fa-cloud-arrow-up" />
|
|
<span className="hidden sm:inline">Upload</span>
|
|
</a>
|
|
</div>
|
|
)
|
|
}
|