File size: 3,622 Bytes
a4bdc89 3993320 a4bdc89 3993320 d87c1fb 3993320 d87c1fb 3acde0a 3993320 3acde0a a4bdc89 d87c1fb a4bdc89 3993320 a4bdc89 d87c1fb a4bdc89 3993320 a4bdc89 3993320 d87c1fb a4bdc89 3993320 a4bdc89 3acde0a a4bdc89 3acde0a a4bdc89 3993320 a4bdc89 3acde0a a4bdc89 3acde0a a4bdc89 3993320 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 | import { useEffect, useState } from 'react';
import { BookOpen, Database, Download, Menu, X } from 'lucide-react';
import { Link, useLocation } from 'react-router-dom';
export function Header() {
const location = useLocation();
const [menuOpen, setMenuOpen] = useState(false);
// Close the mobile menu whenever the route changes
useEffect(() => {
setMenuOpen(false);
}, [location.pathname]);
const navItemClass = (active: boolean) =>
`flex items-center gap-1.5 px-3 py-2 text-sm rounded-lg transition-colors ${
active ? 'bg-slate-100 text-slate-900 font-semibold' : 'text-slate-600 hover:bg-slate-50'
}`;
return (
<header className="border-b border-gray-200 bg-white sticky top-0 z-50 shadow-sm">
<div className="max-w-7xl mx-auto px-4 sm:px-6 py-4 flex items-center justify-between gap-3 sm:gap-6">
<Link to="/" className="flex items-center gap-3 hover:opacity-80 transition-opacity min-w-0">
<div className="w-9 h-9 bg-slate-900 rounded-lg flex items-center justify-center shrink-0">
<Database className="w-5 h-5 text-white" />
</div>
<div className="min-w-0">
<div className="text-xl font-semibold text-slate-900 truncate">MuSProt</div>
<div className="header-nav-subtitle text-xs text-slate-500 truncate">Multistate Protein Structure Database</div>
</div>
</Link>
{/* Full nav — shown in landscape (W >= H) */}
<nav className="header-nav-full items-center gap-2">
<Link to="/" className={navItemClass(location.pathname === '/')}>
<Database className="w-4 h-4 shrink-0" />
Explorer
</Link>
<Link to="/docs" className={navItemClass(location.pathname === '/docs')}>
<BookOpen className="w-4 h-4 shrink-0" />
Docs
</Link>
<a
href="/api/protein/download/db"
className="flex items-center gap-1.5 px-4 py-2 text-sm font-medium text-white bg-slate-900 rounded-lg hover:bg-slate-700 transition-colors"
>
<Download className="w-4 h-4 shrink-0" />
Download DB
</a>
</nav>
{/* Menu toggle — shown only in portrait (W < H) */}
<button
type="button"
aria-label="Toggle navigation menu"
aria-expanded={menuOpen}
onClick={() => setMenuOpen((open) => !open)}
className="header-nav-toggle items-center justify-center p-2 rounded-lg text-slate-700 hover:bg-slate-100 transition-colors shrink-0"
>
{menuOpen ? <X className="w-6 h-6" /> : <Menu className="w-6 h-6" />}
</button>
</div>
{/* Dropdown menu — portrait only */}
{menuOpen && (
<nav className="header-nav-dropdown border-t border-gray-200 bg-white px-4 py-3 flex-col gap-1">
<Link to="/" className={navItemClass(location.pathname === '/')}>
<Database className="w-4 h-4 shrink-0" />
Explorer
</Link>
<Link to="/docs" className={navItemClass(location.pathname === '/docs')}>
<BookOpen className="w-4 h-4 shrink-0" />
Docs
</Link>
<a
href="/api/protein/download/db"
onClick={() => setMenuOpen(false)}
className="flex items-center gap-1.5 px-3 py-2 text-sm font-medium text-white bg-slate-900 rounded-lg hover:bg-slate-700 transition-colors"
>
<Download className="w-4 h-4 shrink-0" />
Download DB
</a>
</nav>
)}
</header>
);
}
|