TradingAgents/ui/components/Sidebar.tsx

248 lines
8.5 KiB
TypeScript

'use client'
import Link from 'next/link'
import { usePathname } from 'next/navigation'
const NAV = [
{
href: '/new-run',
label: 'New Analysis',
tag: 'RUN',
icon: (
<svg width="16" height="16" viewBox="0 0 16 16" fill="none">
<polygon points="4,3 13,8 4,13" fill="currentColor" opacity=".9"/>
</svg>
),
},
{
href: '/history',
label: 'Run History',
tag: 'LOG',
icon: (
<svg width="16" height="16" viewBox="0 0 16 16" fill="none">
<rect x="2" y="2" width="12" height="2.5" rx="1.25" fill="currentColor" opacity=".9"/>
<rect x="2" y="6.75" width="8" height="2.5" rx="1.25" fill="currentColor" opacity=".65"/>
<rect x="2" y="11.5" width="10" height="2.5" rx="1.25" fill="currentColor" opacity=".8"/>
</svg>
),
},
{
href: '/settings',
label: 'Settings',
tag: 'CFG',
icon: (
<svg width="16" height="16" viewBox="0 0 16 16" fill="none">
<circle cx="8" cy="8" r="2.5" stroke="currentColor" strokeWidth="1.4"/>
<path d="M8 1v2M8 13v2M1 8h2M13 8h2M3.05 3.05l1.42 1.42M11.53 11.53l1.42 1.42M12.95 3.05l-1.42 1.42M4.47 11.53l-1.42 1.42" stroke="currentColor" strokeWidth="1.4" strokeLinecap="round"/>
</svg>
),
},
]
export default function Sidebar() {
const path = usePathname()
return (
<aside
className="w-[240px] min-h-screen flex flex-col shrink-0 relative"
style={{
background: 'var(--bg-sidebar)',
borderRight: '1px solid var(--border)',
}}
>
{/* Subtle dot grid texture */}
<div
className="absolute inset-0 pointer-events-none"
style={{
backgroundImage: 'radial-gradient(circle, rgba(80,80,200,0.08) 1px, transparent 1px)',
backgroundSize: '20px 20px',
}}
/>
{/* Top glow */}
<div
className="absolute top-0 left-0 right-0 h-px pointer-events-none"
style={{
background: 'linear-gradient(90deg, transparent 10%, var(--accent) 50%, transparent 90%)',
opacity: 0.3,
}}
/>
{/* Logo */}
<div className="relative px-5 pt-6 pb-5" style={{ borderBottom: '1px solid var(--border)' }}>
<div className="flex items-center gap-3">
{/* Logo mark */}
<div
className="relative w-9 h-9 rounded-xl shrink-0 flex items-center justify-center overflow-hidden"
style={{
background: 'var(--accent-dim)',
border: '1px solid rgba(0,196,232,0.35)',
boxShadow: '0 0 20px rgba(0,196,232,0.15), inset 0 1px 0 rgba(255,255,255,0.06)',
}}
>
<svg width="18" height="18" viewBox="0 0 18 18" fill="none">
<polyline
points="1,13 5,8 9,10 13,5 17,2"
stroke="var(--accent)"
strokeWidth="1.8"
strokeLinecap="round"
strokeLinejoin="round"
/>
<circle cx="17" cy="2" r="1.5" fill="var(--accent-light)"/>
</svg>
{/* Inner glow */}
<div
className="absolute inset-0"
style={{
background: 'radial-gradient(ellipse at 50% 0%, rgba(0,196,232,0.15) 0%, transparent 60%)',
}}
/>
</div>
<div>
<div
className="font-bold leading-none tracking-tight"
style={{
fontFamily: 'var(--font-syne)',
fontSize: '15px',
color: 'var(--text-high)',
letterSpacing: '-0.02em',
}}
>
TradingAgents
</div>
<div
className="mt-1 flex items-center gap-1.5"
style={{ color: 'var(--accent)', fontFamily: 'var(--font-mono)', fontSize: '9px', letterSpacing: '0.12em' }}
>
<span
className="w-1.5 h-1.5 rounded-full inline-block"
style={{ background: 'var(--buy)', boxShadow: '0 0 5px var(--buy)', animation: 'shimmer 2s ease-in-out infinite' }}
/>
MULTI-AGENT AI
</div>
</div>
</div>
</div>
{/* Nav */}
<div className="relative px-3 pt-5 flex-1">
<div className="apex-label px-2 mb-3">Workspace</div>
<nav className="flex flex-col gap-0.5">
{NAV.map(({ href, label, tag, icon }) => {
const active = path === href || path.startsWith(href + '/')
return (
<Link
key={href}
href={href}
className="group relative flex items-center gap-3 px-3 py-2.5 rounded-lg transition-all duration-200"
style={
active
? {
background: 'var(--accent-glow)',
color: 'var(--accent-light)',
}
: {
color: 'var(--text-mid)',
}
}
onMouseEnter={(e) => {
if (!active) {
(e.currentTarget as HTMLElement).style.background = 'var(--bg-hover)'
;(e.currentTarget as HTMLElement).style.color = 'var(--text-high)'
}
}}
onMouseLeave={(e) => {
if (!active) {
(e.currentTarget as HTMLElement).style.background = ''
;(e.currentTarget as HTMLElement).style.color = 'var(--text-mid)'
}
}}
>
{/* Active indicator */}
{active && (
<div
className="absolute left-0 top-1/2 -translate-y-1/2 w-0.5 rounded-r"
style={{
height: '60%',
background: 'var(--accent)',
boxShadow: '0 0 8px var(--accent)',
}}
/>
)}
{/* Icon */}
<span
className="shrink-0 w-4 h-4 flex items-center justify-center transition-opacity"
style={{ opacity: active ? 1 : 0.5 }}
>
{icon}
</span>
{/* Label */}
<span
className="flex-1 text-[13px] font-medium"
style={{ fontFamily: 'var(--font-manrope)' }}
>
{label}
</span>
{/* Tag */}
<span
className="shrink-0 px-1.5 py-0.5 rounded text-[9px] font-bold"
style={{
fontFamily: 'var(--font-mono)',
letterSpacing: '0.08em',
background: active ? 'rgba(0,196,232,0.15)' : 'var(--bg-elevated)',
color: active ? 'var(--accent)' : 'var(--text-low)',
border: `1px solid ${active ? 'rgba(0,196,232,0.25)' : 'var(--border)'}`,
}}
>
{tag}
</span>
</Link>
)
})}
</nav>
</div>
{/* Divider */}
<div className="mx-3 h-px" style={{ background: 'var(--border)' }} />
{/* Footer */}
<div className="relative px-5 py-4">
<div className="flex items-center gap-2.5">
<div
className="w-6 h-6 rounded-lg flex items-center justify-center shrink-0"
style={{ background: 'var(--bg-elevated)', border: '1px solid var(--border-raised)' }}
>
<svg width="12" height="12" viewBox="0 0 12 12" fill="none">
<rect x="1" y="1" width="10" height="10" rx="2" stroke="var(--text-low)" strokeWidth="1.2"/>
<circle cx="6" cy="6" r="1.5" fill="var(--text-low)"/>
</svg>
</div>
<div>
<div
className="text-[11px] font-semibold"
style={{ color: 'var(--text-mid)', fontFamily: 'var(--font-manrope)' }}
>
Local Instance
</div>
<div
className="text-[9px] flex items-center gap-1"
style={{ color: 'var(--text-low)', fontFamily: 'var(--font-mono)', letterSpacing: '0.06em' }}
>
<span
className="w-1 h-1 rounded-full inline-block"
style={{ background: 'var(--buy)', animation: 'shimmer 3s ease-in-out infinite' }}
/>
DEV · PORT 8000
</div>
</div>
</div>
</div>
</aside>
)
}