Replace native selects with NovaSelect

This commit is contained in:
2026-05-01 07:45:37 +02:00
parent 67be537c86
commit 35011001ba
55 changed files with 3136 additions and 1662 deletions

View File

@@ -1,5 +1,6 @@
import React, { useEffect, useState } from 'react'
import { Link, usePage } from '@inertiajs/react'
import NovaSelect from '../components/ui/NovaSelect'
import { studioModule, studioSurface, trackStudioEvent } from '../utils/studioEvents'
const baseNavGroups = [
@@ -15,8 +16,9 @@ const baseNavGroups = [
label: 'Create',
items: [
{ label: 'New Artwork', href: '/upload', icon: 'fa-solid fa-cloud-arrow-up' },
{ label: 'Upload Queue', href: '/studio/upload-queue', icon: 'fa-solid fa-layer-group' },
{ label: 'New Card', href: '/studio/cards/create', icon: 'fa-solid fa-id-card' },
{ label: 'New Story', href: '/creator/stories/create', icon: 'fa-solid fa-feather-pointed' },
{ label: 'New Story', href: '/studio/stories/create', icon: 'fa-solid fa-feather-pointed', fullLoad: true },
{ label: 'New Collection', href: '/settings/collections/create', icon: 'fa-solid fa-layer-group' },
],
},
@@ -34,8 +36,9 @@ const baseNavGroups = [
label: 'Library',
items: [
{ label: 'Drafts', href: '/studio/drafts', icon: 'fa-solid fa-file-pen' },
{ label: 'Upload Queue', href: '/studio/upload-queue', icon: 'fa-solid fa-list-check' },
{ label: 'Scheduled', href: '/studio/scheduled', icon: 'fa-solid fa-calendar-days' },
{ label: 'Calendar', href: '/studio/calendar', icon: 'fa-solid fa-calendar-range' },
{ label: 'Calendar', href: '/studio/calendar', icon: 'fa-solid fa-calendar-days' },
{ label: 'Archived', href: '/studio/archived', icon: 'fa-solid fa-box-archive' },
{ label: 'Assets', href: '/studio/assets', icon: 'fa-solid fa-photo-film' },
],
@@ -71,7 +74,7 @@ const baseNavGroups = [
const baseQuickCreateItems = [
{ label: 'Artwork', href: '/upload', icon: 'fa-solid fa-cloud-arrow-up' },
{ label: 'Card', href: '/studio/cards/create', icon: 'fa-solid fa-id-card' },
{ label: 'Story', href: '/creator/stories/create', icon: 'fa-solid fa-feather-pointed' },
{ label: 'Story', href: '/studio/stories/create', icon: 'fa-solid fa-feather-pointed', fullLoad: true },
{ label: 'Collection', href: '/settings/collections/create', icon: 'fa-solid fa-layer-group' },
]
@@ -145,14 +148,25 @@ function navigateToStudioUrl(targetUrl) {
}
function NavLink({ item, active }) {
const className = `flex items-center gap-3 px-4 py-2.5 rounded-xl text-sm font-medium transition-all duration-200 ${
active
? 'bg-accent/20 text-accent shadow-sm shadow-accent/10'
: 'text-slate-400 hover:text-white hover:bg-white/5'
}`
if (item.fullLoad) {
return (
<a href={item.href} className={className}>
<i className={`${item.icon} w-5 text-center text-base`} />
<span>{item.label}</span>
</a>
)
}
return (
<Link
href={item.href}
className={`flex items-center gap-3 px-4 py-2.5 rounded-xl text-sm font-medium transition-all duration-200 ${
active
? 'bg-accent/20 text-accent shadow-sm shadow-accent/10'
: 'text-slate-400 hover:text-white hover:bg-white/5'
}`}
className={className}
>
<i className={`${item.icon} w-5 text-center text-base`} />
<span>{item.label}</span>
@@ -169,6 +183,7 @@ export default function StudioLayout({ children, title, subtitle, actions }) {
const currentGroup = props.studioGroup || null
const canManageNews = Boolean(props.auth?.user?.is_admin || props.auth?.user?.is_moderator)
const canManageWorlds = canManageNews
const isStaff = Boolean(props.auth?.user?.is_staff)
const navGroups = baseNavGroups.map((group) => {
if ((!canManageNews && !canManageWorlds) || group.label !== 'Content') {
@@ -312,6 +327,7 @@ export default function StudioLayout({ children, title, subtitle, actions }) {
navGroups={navGroups}
quickCreateItems={quickCreateItems}
isActive={isActive}
isStaff={isStaff}
onNavigate={() => setMobileOpen(false)}
onQuickCreate={handleQuickCreateClick}
onContextChange={handleContextChange}
@@ -328,6 +344,7 @@ export default function StudioLayout({ children, title, subtitle, actions }) {
navGroups={navGroups}
quickCreateItems={quickCreateItems}
isActive={isActive}
isStaff={isStaff}
onQuickCreate={handleQuickCreateClick}
onContextChange={handleContextChange}
/>
@@ -384,25 +401,22 @@ export default function StudioLayout({ children, title, subtitle, actions }) {
function ContextSwitcher({ currentGroup, studioGroups, onContextChange }) {
return (
<label className="inline-flex items-center gap-2 rounded-full border border-white/10 bg-black/20 px-3 py-2 text-sm text-slate-200">
<div className="inline-flex items-center gap-2 rounded-full border border-white/10 bg-black/20 px-3 py-2 text-sm text-slate-200">
<i className="fa-solid fa-people-group text-sky-200" />
<select
<NovaSelect
value={currentGroup?.slug || ''}
onChange={(event) => onContextChange?.(event.target.value)}
className="bg-transparent text-sm text-white outline-none"
>
<option value="" className="bg-slate-950 text-white">Personal studio</option>
{studioGroups.map((group) => (
<option key={group.slug} value={group.slug} className="bg-slate-950 text-white">
{group.name}
</option>
))}
</select>
</label>
onChange={(value) => onContextChange?.(value)}
options={[
{ value: '', label: 'Personal studio' },
...studioGroups.map((group) => ({ value: group.slug, label: group.name })),
]}
searchable={false}
/>
</div>
)
}
function StudioSidebarContent({ currentGroup, studioGroups, navGroups, quickCreateItems, isActive, onNavigate, onQuickCreate, onContextChange }) {
function StudioSidebarContent({ currentGroup, studioGroups, navGroups, quickCreateItems, isActive, isStaff, onNavigate, onQuickCreate, onContextChange }) {
return (
<>
<div className="mb-6 rounded-[26px] border border-white/10 bg-white/[0.04] p-4">
@@ -412,16 +426,16 @@ function StudioSidebarContent({ currentGroup, studioGroups, navGroups, quickCrea
{studioGroups.length > 0 ? (
<div className="mt-4 rounded-2xl border border-white/10 bg-black/20 p-3">
<p className="text-[11px] font-semibold uppercase tracking-[0.18em] text-slate-500">Context</p>
<select
<NovaSelect
value={currentGroup?.slug || ''}
onChange={(event) => onContextChange?.(event.target.value)}
className="mt-2 w-full rounded-xl border border-white/10 bg-slate-950/80 px-3 py-2 text-sm text-white outline-none"
>
<option value="">Personal studio</option>
{studioGroups.map((group) => (
<option key={group.slug} value={group.slug}>{group.name}</option>
))}
</select>
onChange={(value) => onContextChange?.(value)}
className="mt-2"
options={[
{ value: '', label: 'Personal studio' },
...studioGroups.map((group) => ({ value: group.slug, label: group.name })),
]}
searchable={false}
/>
</div>
) : null}
</div>
@@ -437,6 +451,20 @@ function StudioSidebarContent({ currentGroup, studioGroups, navGroups, quickCrea
</div>
</div>
))}
{isStaff && (
<div>
<h3 className="mb-2 px-3 text-[11px] font-semibold uppercase tracking-[0.22em] text-rose-500/70">Administration</h3>
<div className="space-y-1">
<a
href="/moderation"
className="flex items-center gap-3 rounded-xl px-4 py-2.5 text-sm font-medium text-rose-300/70 transition hover:bg-rose-500/10 hover:text-rose-300"
>
<i className="fa-solid fa-shield-halved w-5 text-center text-base" />
<span>Admin Panel</span>
</a>
</div>
</div>
)}
</nav>
<div className="mt-6 rounded-[24px] border border-white/10 bg-[linear-gradient(135deg,_rgba(15,23,42,0.95),_rgba(12,74,110,0.4))] p-4">