import { Link } from '@tanstack/react-router'; import { useEffect, useRef, useState } from 'react'; import type { LucideIcon } from 'lucide-react'; import { Home, Music, Disc3, Users, Tag, Compass, Terminal, ShieldAlert, Zap, Settings, Sparkles, X, } from 'lucide-react'; interface NavItem { to: string; icon: LucideIcon; label: string; exact?: boolean; } interface NavGroup { label: string; items: NavItem[]; } const NAV_GROUPS: NavGroup[] = [ { label: 'Workspace', items: [ { to: '/', icon: Home, label: 'Home', exact: true }, { to: '/tracks', icon: Music, label: 'Songs' }, { to: '/albums', icon: Disc3, label: 'Albums' }, { to: '/artists', icon: Users, label: 'Artists' }, { to: '/genres', icon: Tag, label: 'Genres' }, ], }, { label: 'AI', items: [ { to: '/vibe', icon: Zap, label: 'Vibe' }, { to: '/recommendations', icon: Compass, label: 'Found' }, ], }, { label: 'Infrastructure', items: [ { to: '/jobs', icon: Terminal, label: 'Jobs' }, { to: '/quarantine', icon: ShieldAlert, label: 'Quarantine' }, ], }, { label: 'Settings', items: [ { to: '/settings', icon: Settings, label: 'Settings' }, ], }, ]; const base = 'group relative flex items-center gap-2.5 px-3 py-1.5 rounded-md text-xs w-full transition-all duration-100'; const inactive = 'text-secondary hover:bg-surface0 hover:text-text'; const active = 'bg-accent/10 text-accent font-medium ' + "before:content-[''] before:absolute before:left-0 before:top-1 before:bottom-1 before:w-0.5 before:rounded-full before:bg-accent"; interface NavRailProps { open: boolean; onClose: () => void; } export function NavRail({ open, onClose }: NavRailProps) { const [desktop, setDesktop] = useState(false); const drawerRef = useRef(null); const closeRef = useRef(null); useEffect(() => { const query = window.matchMedia('(min-width: 1024px)'); const update = () => setDesktop(query.matches); update(); query.addEventListener('change', update); return () => query.removeEventListener('change', update); }, []); useEffect(() => { if (open && !desktop) closeRef.current?.focus(); }, [open, desktop]); useEffect(() => { if (!open || desktop || !drawerRef.current) return; const previous = document.activeElement as HTMLElement | null; const trap = (event: KeyboardEvent) => { if (event.key !== 'Tab' || !drawerRef.current) return; const focusable = [...drawerRef.current.querySelectorAll( 'a[href], button:not([disabled]), input:not([disabled]), [tabindex]:not([tabindex="-1"])' )].filter((element) => !element.hasAttribute('hidden')); if (focusable.length === 0) return; const first = focusable[0]; const last = focusable[focusable.length - 1]; if (event.shiftKey && document.activeElement === first) { event.preventDefault(); last.focus(); } else if (!event.shiftKey && document.activeElement === last) { event.preventDefault(); first.focus(); } }; document.addEventListener('keydown', trap); return () => { document.removeEventListener('keydown', trap); previous?.focus(); }; }, [open, desktop]); // Do not leave an off-screen mobile drawer in the tab order. The desktop // rail remains mounted independently of the drawer state. if (!desktop && !open) return null; return ( <> {open && ( {/* Navigation */} {/* Footer */}
muzick ยท v0.1
); }