openvisengine/src/components/ava/control-panel.tsx
2026-11-05 06:53:07 +00:00

122 lines
4.3 KiB
TypeScript
Executable file

// OVE — right-hand control panel: the six tabs.
//
// Desktop: fixed width, content scrolls inside the panel. Mobile: natural
// height (the page scrolls and the footer is pushed down naturally).
'use client'
import { motion } from 'framer-motion'
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
import { PresetsTab } from '@/components/ava/presets-tab'
import { ScriptTab } from '@/components/ava/script-tab'
import { DirectoryTab } from '@/components/ava/directory-tab'
import { DetectTab } from '@/components/ava/detect-tab'
import { BeatTab } from '@/components/ava/beat-tab'
import { NativeTab } from '@/components/ava/native-tab'
import { SettingsTab } from '@/components/ava/settings-tab'
import { useAvaStore } from '@/lib/store'
import { thinScrollbar } from '@/components/ava/shared'
import { cn } from '@/lib/utils'
// v6.5 — the Native tab is PARKED (hidden) so the Settings tab can take its
// slot; it returns unchanged when the project nears completion. Flip this
// flag to bring it back — the tab row widens to 7 columns automatically and
// the component + /api/rust-scaffold endpoint are untouched.
//
// v7.11 — the Directory tab (community script browser) joined between Script
// and Detect; the row is 3 columns on phones (two stacked rows) and one row
// from sm up.
const NATIVE_TAB_ENABLED = false
function TabBody({ children }: { children: React.ReactNode }) {
return (
<motion.div
initial={{ opacity: 0, y: 4 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.16, ease: 'easeOut' }}
className={cn(thinScrollbar, 'min-h-0 flex-1 overflow-y-auto pr-1 lg:h-full')}
>
{children}
</motion.div>
)
}
export function ControlPanel() {
const tab = useAvaStore((s) => s.tab)
const setTab = useAvaStore((s) => s.setTab)
return (
<Tabs value={tab} onValueChange={setTab} className="flex min-h-0 flex-1 flex-col gap-3">
<TabsList
className={cn(
'h-11 w-full shrink-0 bg-zinc-900/80',
NATIVE_TAB_ENABLED ? 'grid-cols-3 sm:grid-cols-7' : 'grid-cols-3 sm:grid-cols-6',
)}
>
<TabsTrigger value="presets" className="text-xs">
Presets
</TabsTrigger>
<TabsTrigger value="script" className="text-xs">
Script
</TabsTrigger>
<TabsTrigger value="directory" className="text-xs">
Directory
</TabsTrigger>
<TabsTrigger value="detect" className="text-xs">
Detect
</TabsTrigger>
<TabsTrigger value="beat" className="text-xs">
Beat
</TabsTrigger>
{NATIVE_TAB_ENABLED && (
<TabsTrigger value="native" className="text-xs">
Native
</TabsTrigger>
)}
<TabsTrigger value="settings" className="text-xs">
Settings
</TabsTrigger>
</TabsList>
<div className="flex min-h-0 flex-1 flex-col rounded-xl border border-zinc-800/80 bg-zinc-900/60 p-4 lg:overflow-hidden">
<TabsContent value="presets" className="flex min-h-0 flex-1 flex-col outline-none">
<TabBody>
<PresetsTab />
</TabBody>
</TabsContent>
<TabsContent value="script" className="flex min-h-0 flex-1 flex-col outline-none">
<TabBody>
<ScriptTab />
</TabBody>
</TabsContent>
<TabsContent value="directory" className="flex min-h-0 flex-1 flex-col outline-none">
<TabBody>
<DirectoryTab />
</TabBody>
</TabsContent>
<TabsContent value="detect" className="flex min-h-0 flex-1 flex-col outline-none">
<TabBody>
<DetectTab />
</TabBody>
</TabsContent>
<TabsContent value="beat" className="flex min-h-0 flex-1 flex-col outline-none">
<TabBody>
<BeatTab />
</TabBody>
</TabsContent>
{NATIVE_TAB_ENABLED && (
<TabsContent value="native" className="flex min-h-0 flex-1 flex-col outline-none">
<TabBody>
<NativeTab />
</TabBody>
</TabsContent>
)}
<TabsContent value="settings" className="flex min-h-0 flex-1 flex-col outline-none">
<TabBody>
<SettingsTab />
</TabBody>
</TabsContent>
</div>
</Tabs>
)
}