glam/frontend/src/components/layout/Navigation.tsx
2025-12-01 23:55:55 +01:00

236 lines
8.3 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>
{/* Mobile Hamburger Button */}
<button
className="nav-mobile-toggle"
onClick={() => setMobileMenuOpen(!mobileMenuOpen)}
aria-label={mobileMenuOpen ? 'Close menu' : 'Open menu'}
aria-expanded={mobileMenuOpen}
>
<span className={`hamburger ${mobileMenuOpen ? 'open' : ''}`}>
<span></span>
<span></span>
<span></span>
</span>
</button>
{/* Desktop Navigation Links */}
<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 - Desktop */}
<button
className="nav-lang-toggle nav-lang-toggle--desktop"
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 - Desktop */}
{user && (
<div className="nav-user nav-user--desktop" 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>
)}
{/* Mobile Menu Overlay */}
<div
className={`nav-mobile-menu ${mobileMenuOpen ? 'open' : ''}`}
ref={mobileMenuRef}
>
<div className="nav-mobile-links">
<Link to="/" className={`nav-mobile-link ${isActive('/') ? 'active' : ''}`}>
{t('home')}
</Link>
<Link to="/visualize" className={`nav-mobile-link ${isActive('/visualize') ? 'active' : ''}`}>
{t('visualize')}
</Link>
<Link to="/database" className={`nav-mobile-link ${isActive('/database') ? 'active' : ''}`}>
{t('database')}
</Link>
<Link to="/query-builder" className={`nav-mobile-link ${isActive('/query-builder') ? 'active' : ''}`}>
{t('query')}
</Link>
<Link to="/linkml" className={`nav-mobile-link ${isActive('/linkml') ? 'active' : ''}`}>
{t('linkml')}
</Link>
<Link to="/ontology" className={`nav-mobile-link ${isActive('/ontology') ? 'active' : ''}`}>
{t('ontology')}
</Link>
<Link to="/map" className={`nav-mobile-link ${isActive('/map') ? 'active' : ''}`}>
{t('map')}
</Link>
<Link to="/stats" className={`nav-mobile-link ${isActive('/stats') ? 'active' : ''}`}>
{t('stats')}
</Link>
<Link to="/settings" className={`nav-mobile-link ${isActive('/settings') ? 'active' : ''}`}>
{t('settings')}
</Link>
</div>
{/* Language Toggle - Mobile */}
<div className="nav-mobile-footer">
<button
className="nav-lang-toggle"
onClick={toggleLanguage}
aria-label={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 - Mobile */}
{user && (
<div className="nav-mobile-user">
<span className="nav-user-icon">👤</span>
<span className="nav-user-name">{user.username}</span>
<span className="nav-user-role">({user.role})</span>
<button onClick={logout} className="nav-mobile-logout">
{t('signOut')}
</button>
</div>
)}
</div>
</div>
</div>
</nav>
);
}