glam/frontend/src/components/layout/Navigation.tsx

160 lines
5.1 KiB
TypeScript

/**
* Navigation Component
* Styled following Netwerk Digitaal Erfgoed (NDE) house style
* With bilingual support (NL/EN)
*/
import { useState, useRef, useEffect } from 'react';
import { Link, useLocation } from 'react-router-dom';
import { useAuth } from '../../contexts/AuthContext';
import { useLanguage, translations } from '../../contexts/LanguageContext';
import './Navigation.css';
export function Navigation() {
const location = useLocation();
const { user, logout } = useAuth();
const { language, toggleLanguage } = useLanguage();
const [userMenuOpen, setUserMenuOpen] = useState(false);
const [mobileMenuOpen, setMobileMenuOpen] = useState(false);
const userMenuRef = useRef<HTMLDivElement>(null);
const mobileMenuRef = useRef<HTMLDivElement>(null);
// Close menus when clicking outside
useEffect(() => {
const handleClickOutside = (event: MouseEvent) => {
if (userMenuRef.current && !userMenuRef.current.contains(event.target as Node)) {
setUserMenuOpen(false);
}
if (mobileMenuRef.current && !mobileMenuRef.current.contains(event.target as Node)) {
setMobileMenuOpen(false);
}
};
document.addEventListener('mousedown', handleClickOutside);
return () => document.removeEventListener('mousedown', handleClickOutside);
}, []);
// Close mobile menu on route change
useEffect(() => {
setMobileMenuOpen(false);
}, [location.pathname]);
const isActive = (path: string) => {
return location.pathname === path;
};
// Get translated nav text
const t = (key: keyof typeof translations.nav) => {
return language === 'en' ? translations.nav[key].en : translations.nav[key].nl;
};
return (
<nav className="navigation">
<div className="nav-container">
<Link to="/" className="nav-brand">
{/* NDE Logo - Blue sunburst "e" icon */}
<img
src="/nde-icon-square.png"
alt="Netwerk Digitaal Erfgoed Logo"
className="nav-logo"
/>
<span className="nav-title">Bronhouder</span>
</Link>
<div className="nav-links">
<Link
to="/"
className={`nav-link ${isActive('/') ? 'active' : ''}`}
>
{t('home')}
</Link>
<Link
to="/visualize"
className={`nav-link ${isActive('/visualize') ? 'active' : ''}`}
>
{t('visualize')}
</Link>
<Link
to="/database"
className={`nav-link ${isActive('/database') ? 'active' : ''}`}
>
{t('database')}
</Link>
<Link
to="/query-builder"
className={`nav-link ${isActive('/query-builder') ? 'active' : ''}`}
>
{t('query')}
</Link>
<Link
to="/linkml"
className={`nav-link ${isActive('/linkml') ? 'active' : ''}`}
>
{t('linkml')}
</Link>
<Link
to="/ontology"
className={`nav-link ${isActive('/ontology') ? 'active' : ''}`}
>
{t('ontology')}
</Link>
<Link
to="/map"
className={`nav-link ${isActive('/map') ? 'active' : ''}`}
>
{t('map')}
</Link>
<Link
to="/stats"
className={`nav-link ${isActive('/stats') ? 'active' : ''}`}
>
{t('stats')}
</Link>
<Link
to="/settings"
className={`nav-link ${isActive('/settings') ? 'active' : ''}`}
>
{t('settings')}
</Link>
</div>
{/* Language Toggle */}
<button
className="nav-lang-toggle"
onClick={toggleLanguage}
aria-label={language === 'nl' ? 'Switch to English' : 'Schakel naar Nederlands'}
title={language === 'nl' ? 'Switch to English' : 'Schakel naar Nederlands'}
>
<span className={language === 'nl' ? 'lang-active' : 'lang-inactive'}>NL</span>
<span className="lang-separator">|</span>
<span className={language === 'en' ? 'lang-active' : 'lang-inactive'}>EN</span>
</button>
{/* User Account Dropdown */}
{user && (
<div className="nav-user" ref={userMenuRef}>
<button
className="nav-user-btn"
onClick={() => setUserMenuOpen(!userMenuOpen)}
aria-expanded={userMenuOpen}
aria-haspopup="true"
>
<span className="nav-user-icon">👤</span>
<span className="nav-user-name">{user.username}</span>
<span className="nav-user-chevron">{userMenuOpen ? '▲' : '▼'}</span>
</button>
{userMenuOpen && (
<div className="nav-user-menu">
<div className="nav-user-info">
<span className="nav-user-role">{user.role}</span>
</div>
<button onClick={logout} className="nav-user-logout">
{t('signOut')}
</button>
</div>
)}
</div>
)}
</div>
</nav>
);
}