import { Link, useMatchRoute } from '@tanstack/react-router'; import { AudioLines, Box, Mic, Server, Speaker, Volume2, Wand2 } from 'lucide-react'; import { useEffect, useState } from 'react'; import voiceboxLogo from '@/assets/voicebox-logo.png'; import { cn } from '@/lib/utils/cn'; import { usePlatform } from '@/platform/PlatformContext'; import type { UpdateStatus } from '@/platform/types'; import { usePlayerStore } from '@/stores/playerStore'; import { version } from '../../package.json'; interface SidebarProps { isMacOS?: boolean; } const tabs = [ { id: 'main', path: '/', icon: Volume2, label: 'Generate' }, { id: 'stories', path: '/stories', icon: AudioLines, label: 'Stories' }, { id: 'voices', path: '/voices', icon: Mic, label: 'Voices' }, { id: 'effects', path: '/effects', icon: Wand2, label: 'Effects' }, { id: 'audio', path: '/audio', icon: Speaker, label: 'Audio' }, { id: 'models', path: '/models', icon: Box, label: 'Models' }, { id: 'server', path: '/server', icon: Server, label: 'Server' }, ]; export function Sidebar({ isMacOS }: SidebarProps) { const matchRoute = useMatchRoute(); const isPlayerOpen = !!usePlayerStore((s) => s.audioUrl); const platform = usePlatform(); const [updateStatus, setUpdateStatus] = useState(platform.updater.getStatus()); useEffect(() => platform.updater.subscribe(setUpdateStatus), [platform.updater]); return (
{/* Logo */}
Voicebox
{/* Navigation Buttons */}
{tabs.map((tab, index) => { const Icon = tab.icon; // For index route, use exact match; for others, use default matching const isActive = tab.path === '/' ? matchRoute({ to: '/', exact: true }) : matchRoute({ to: tab.path }); // Accent fades as buttons get further from the logo const accentOpacity = Math.max(0.08, 0.5 - index * 0.07); return ( {isActive && (
)} ); })}
{/* Version */}
v{version} {updateStatus.available && ( Update )}
); }