236 lines
8.3 KiB
TypeScript
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>
|
|
);
|
|
}
|