fix: apply designer agent improvements to src/components/Header.tsx
This commit is contained in:
parent
f5fdc9a370
commit
961d48c59d
|
|
@ -1,8 +1,12 @@
|
|||
import React, { useState } from 'react';
|
||||
import { Menu, X, Anchor } from 'lucide-react';
|
||||
import React, { useState, useEffect, useRef } from 'react';
|
||||
import { Menu, X, Anchor, BookOpen } from 'lucide-react';
|
||||
|
||||
type Page = 'home' | 'musee';
|
||||
|
||||
interface HeaderProps {
|
||||
scrolled: boolean;
|
||||
currentPage: Page;
|
||||
navigateTo: (page: Page) => void;
|
||||
}
|
||||
|
||||
const navLinks = [
|
||||
|
|
@ -14,57 +18,119 @@ const navLinks = [
|
|||
{ label: 'Contact', href: '#contact' },
|
||||
];
|
||||
|
||||
const Header: React.FC<HeaderProps> = ({ scrolled }) => {
|
||||
const Header: React.FC<HeaderProps> = ({ scrolled, currentPage, navigateTo }) => {
|
||||
const [menuOpen, setMenuOpen] = useState(false);
|
||||
const mobileMenuRef = useRef<HTMLDivElement>(null);
|
||||
const hamburgerRef = useRef<HTMLButtonElement>(null);
|
||||
|
||||
// Close mobile menu on resize to desktop
|
||||
useEffect(() => {
|
||||
const onResize = () => {
|
||||
if (window.innerWidth >= 1024) setMenuOpen(false);
|
||||
};
|
||||
window.addEventListener('resize', onResize);
|
||||
return () => window.removeEventListener('resize', onResize);
|
||||
}, []);
|
||||
|
||||
// Trap focus in mobile menu
|
||||
useEffect(() => {
|
||||
if (!menuOpen) return;
|
||||
const focusable = mobileMenuRef.current?.querySelectorAll<HTMLElement>(
|
||||
'a, button, [tabindex]:not([tabindex="-1"])'
|
||||
);
|
||||
focusable?.[0]?.focus();
|
||||
|
||||
const handleKey = (e: KeyboardEvent) => {
|
||||
if (e.key === 'Escape') {
|
||||
setMenuOpen(false);
|
||||
hamburgerRef.current?.focus();
|
||||
}
|
||||
};
|
||||
document.addEventListener('keydown', handleKey);
|
||||
return () => document.removeEventListener('keydown', handleKey);
|
||||
}, [menuOpen]);
|
||||
|
||||
const handleNavClick = (href: string) => {
|
||||
setMenuOpen(false);
|
||||
const el = document.querySelector(href);
|
||||
if (el) el.scrollIntoView({ behavior: 'smooth' });
|
||||
if (currentPage !== 'home') {
|
||||
navigateTo('home');
|
||||
setTimeout(() => {
|
||||
document.querySelector(href)?.scrollIntoView({ behavior: 'smooth' });
|
||||
}, 100);
|
||||
} else {
|
||||
document.querySelector(href)?.scrollIntoView({ behavior: 'smooth' });
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<header
|
||||
className={`fixed top-0 left-0 right-0 z-50 transition-all duration-400 ${
|
||||
className={`fixed top-0 left-0 right-0 z-50 transition-all duration-300 ${
|
||||
scrolled
|
||||
? 'bg-marine-900 shadow-xl py-2'
|
||||
: 'bg-marine-900/80 backdrop-blur-sm py-4'
|
||||
: 'bg-marine-900/85 backdrop-blur-md py-4'
|
||||
}`}
|
||||
>
|
||||
{/* Tricolore accent */}
|
||||
<div className="tricolore-bar h-0.5 absolute top-0 left-0 right-0" aria-hidden="true" />
|
||||
|
||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 flex items-center justify-between">
|
||||
{/* Logo */}
|
||||
<a
|
||||
href="#accueil"
|
||||
onClick={(e) => { e.preventDefault(); handleNavClick('#accueil'); }}
|
||||
className="flex items-center gap-3 group"
|
||||
aria-label="Accueil AATMTC"
|
||||
className="flex items-center gap-3 group focus-visible:ring-2 focus-visible:ring-gold-400 rounded"
|
||||
aria-label="Retour à l'accueil AATMTC"
|
||||
>
|
||||
<div className="bg-gold-500 group-hover:bg-gold-400 transition-colors p-2 rounded-sm">
|
||||
<div className="bg-gold-500 group-hover:bg-gold-400 transition-colors p-2 rounded-sm" aria-hidden="true">
|
||||
<Anchor className="w-6 h-6 text-white" />
|
||||
</div>
|
||||
<div className="leading-tight">
|
||||
<div className="text-white font-bold text-sm tracking-widest uppercase">AATMTC</div>
|
||||
<div className="text-gold-400 text-xs tracking-wide hidden sm:block">Troupes de Marine · Télégraphistes</div>
|
||||
<div className="text-gold-400 text-xs tracking-wide hidden sm:block">
|
||||
Troupes de Marine · Télégraphistes
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
|
||||
{/* Desktop Nav */}
|
||||
<nav className="hidden lg:flex items-center gap-1" role="navigation" aria-label="Navigation principale">
|
||||
<nav
|
||||
className="hidden lg:flex items-center gap-1"
|
||||
role="navigation"
|
||||
aria-label="Navigation principale"
|
||||
>
|
||||
{navLinks.map((link) => (
|
||||
<a
|
||||
key={link.href}
|
||||
href={link.href}
|
||||
onClick={(e) => { e.preventDefault(); handleNavClick(link.href); }}
|
||||
className="text-gray-200 hover:text-gold-400 transition-colors px-4 py-2 text-sm font-medium tracking-wide uppercase relative group"
|
||||
className="text-gray-200 hover:text-gold-400 focus-visible:text-gold-400 transition-colors px-3 py-2 text-sm font-medium tracking-wide uppercase relative group rounded"
|
||||
>
|
||||
{link.label}
|
||||
<span className="absolute bottom-1 left-4 right-4 h-0.5 bg-gold-400 scale-x-0 group-hover:scale-x-100 transition-transform origin-left duration-300" />
|
||||
<span
|
||||
className="absolute bottom-1 left-3 right-3 h-0.5 bg-gold-400 scale-x-0 group-hover:scale-x-100 transition-transform origin-left duration-300"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</a>
|
||||
))}
|
||||
|
||||
{/* Museum page link */}
|
||||
<button
|
||||
onClick={() => navigateTo('musee')}
|
||||
className={`ml-2 flex items-center gap-2 px-3 py-2 text-sm font-medium tracking-wide uppercase rounded transition-colors ${
|
||||
currentPage === 'musee'
|
||||
? 'text-gold-400'
|
||||
: 'text-gray-200 hover:text-gold-400'
|
||||
}`}
|
||||
aria-current={currentPage === 'musee' ? 'page' : undefined}
|
||||
>
|
||||
<BookOpen className="w-4 h-4" aria-hidden="true" />
|
||||
Musée
|
||||
</button>
|
||||
|
||||
<a
|
||||
href="#contact"
|
||||
onClick={(e) => { e.preventDefault(); handleNavClick('#contact'); }}
|
||||
className="ml-4 bg-gold-500 hover:bg-gold-400 text-white text-sm font-semibold px-5 py-2 rounded-sm transition-colors tracking-wide uppercase"
|
||||
className="ml-4 bg-gold-500 hover:bg-gold-600 focus-visible:ring-2 focus-visible:ring-gold-300 focus-visible:ring-offset-2 focus-visible:ring-offset-marine-900 text-white text-sm font-semibold px-5 py-2 rounded-sm transition-colors tracking-wide uppercase"
|
||||
>
|
||||
Nous rejoindre
|
||||
</a>
|
||||
|
|
@ -72,39 +138,72 @@ const Header: React.FC<HeaderProps> = ({ scrolled }) => {
|
|||
|
||||
{/* Mobile Hamburger */}
|
||||
<button
|
||||
className="lg:hidden text-white p-2 focus:outline-none focus:ring-2 focus:ring-gold-400 rounded"
|
||||
ref={hamburgerRef}
|
||||
className="lg:hidden text-white p-2 focus-visible:ring-2 focus-visible:ring-gold-400 rounded"
|
||||
onClick={() => setMenuOpen(!menuOpen)}
|
||||
aria-label={menuOpen ? 'Fermer le menu' : 'Ouvrir le menu'}
|
||||
aria-expanded={menuOpen}
|
||||
aria-controls="mobile-menu"
|
||||
aria-label={menuOpen ? 'Fermer le menu' : 'Ouvrir le menu'}
|
||||
>
|
||||
{menuOpen ? <X className="w-6 h-6" /> : <Menu className="w-6 h-6" />}
|
||||
{menuOpen
|
||||
? <X className="w-6 h-6" aria-hidden="true" />
|
||||
: <Menu className="w-6 h-6" aria-hidden="true" />
|
||||
}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Mobile Menu */}
|
||||
<div
|
||||
id="mobile-menu"
|
||||
ref={mobileMenuRef}
|
||||
className={`lg:hidden overflow-hidden transition-all duration-300 ${
|
||||
menuOpen ? 'max-h-screen opacity-100' : 'max-h-0 opacity-0'
|
||||
menuOpen ? 'max-h-[500px] opacity-100' : 'max-h-0 opacity-0'
|
||||
}`}
|
||||
aria-hidden={!menuOpen}
|
||||
>
|
||||
<nav className="bg-marine-900 border-t border-marine-700 px-4 pb-4 pt-2 flex flex-col gap-1">
|
||||
<nav
|
||||
className="bg-marine-900 border-t border-marine-700 px-4 py-4"
|
||||
aria-label="Navigation mobile"
|
||||
>
|
||||
<ul className="flex flex-col gap-1">
|
||||
{navLinks.map((link) => (
|
||||
<li key={link.href}>
|
||||
<a
|
||||
key={link.href}
|
||||
href={link.href}
|
||||
onClick={(e) => { e.preventDefault(); handleNavClick(link.href); }}
|
||||
className="text-gray-200 hover:text-gold-400 hover:bg-marine-800 transition-colors px-4 py-3 text-sm font-medium uppercase tracking-wide rounded"
|
||||
className="block text-gray-200 hover:text-gold-400 hover:bg-marine-800 transition-colors px-4 py-3 text-sm font-medium tracking-wide uppercase rounded"
|
||||
tabIndex={menuOpen ? 0 : -1}
|
||||
>
|
||||
{link.label}
|
||||
</a>
|
||||
</li>
|
||||
))}
|
||||
<li>
|
||||
<button
|
||||
onClick={() => { navigateTo('musee'); setMenuOpen(false); }}
|
||||
className={`w-full text-left flex items-center gap-2 px-4 py-3 text-sm font-medium tracking-wide uppercase rounded transition-colors ${
|
||||
currentPage === 'musee'
|
||||
? 'text-gold-400 bg-marine-800'
|
||||
: 'text-gray-200 hover:text-gold-400 hover:bg-marine-800'
|
||||
}`}
|
||||
tabIndex={menuOpen ? 0 : -1}
|
||||
aria-current={currentPage === 'musee' ? 'page' : undefined}
|
||||
>
|
||||
<BookOpen className="w-4 h-4" aria-hidden="true" />
|
||||
Musée en ligne
|
||||
</button>
|
||||
</li>
|
||||
<li className="pt-2 border-t border-marine-700">
|
||||
<a
|
||||
href="#contact"
|
||||
onClick={(e) => { e.preventDefault(); handleNavClick('#contact'); }}
|
||||
className="mt-2 bg-gold-500 hover:bg-gold-400 text-white text-sm font-semibold px-4 py-3 rounded-sm text-center uppercase tracking-wide"
|
||||
className="block w-full text-center bg-gold-500 hover:bg-gold-600 text-white text-sm font-semibold px-5 py-3 rounded-sm transition-colors tracking-wide uppercase"
|
||||
tabIndex={menuOpen ? 0 : -1}
|
||||
>
|
||||
Nous rejoindre
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
</div>
|
||||
</header>
|
||||
|
|
|
|||
Loading…
Reference in New Issue