fix: apply designer agent improvements to src/components/Header.tsx

This commit is contained in:
cupadev-admin 2026-03-09 07:11:13 +00:00
parent f5fdc9a370
commit 961d48c59d
1 changed files with 141 additions and 42 deletions

View File

@ -1,8 +1,12 @@
import React, { useState } from 'react'; import React, { useState, useEffect, useRef } from 'react';
import { Menu, X, Anchor } from 'lucide-react'; import { Menu, X, Anchor, BookOpen } from 'lucide-react';
type Page = 'home' | 'musee';
interface HeaderProps { interface HeaderProps {
scrolled: boolean; scrolled: boolean;
currentPage: Page;
navigateTo: (page: Page) => void;
} }
const navLinks = [ const navLinks = [
@ -14,57 +18,119 @@ const navLinks = [
{ label: 'Contact', href: '#contact' }, { label: 'Contact', href: '#contact' },
]; ];
const Header: React.FC<HeaderProps> = ({ scrolled }) => { const Header: React.FC<HeaderProps> = ({ scrolled, currentPage, navigateTo }) => {
const [menuOpen, setMenuOpen] = useState(false); 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) => { const handleNavClick = (href: string) => {
setMenuOpen(false); setMenuOpen(false);
const el = document.querySelector(href); if (currentPage !== 'home') {
if (el) el.scrollIntoView({ behavior: 'smooth' }); navigateTo('home');
setTimeout(() => {
document.querySelector(href)?.scrollIntoView({ behavior: 'smooth' });
}, 100);
} else {
document.querySelector(href)?.scrollIntoView({ behavior: 'smooth' });
}
}; };
return ( return (
<header <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 scrolled
? 'bg-marine-900 shadow-xl py-2' ? '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"> <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 flex items-center justify-between">
{/* Logo */} {/* Logo */}
<a <a
href="#accueil" href="#accueil"
onClick={(e) => { e.preventDefault(); handleNavClick('#accueil'); }} onClick={(e) => { e.preventDefault(); handleNavClick('#accueil'); }}
className="flex items-center gap-3 group" className="flex items-center gap-3 group focus-visible:ring-2 focus-visible:ring-gold-400 rounded"
aria-label="Accueil AATMTC" 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" /> <Anchor className="w-6 h-6 text-white" />
</div> </div>
<div className="leading-tight"> <div className="leading-tight">
<div className="text-white font-bold text-sm tracking-widest uppercase">AATMTC</div> <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> </div>
</a> </a>
{/* Desktop Nav */} {/* 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) => ( {navLinks.map((link) => (
<a <a
key={link.href} key={link.href}
href={link.href} href={link.href}
onClick={(e) => { e.preventDefault(); handleNavClick(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} {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> </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 <a
href="#contact" href="#contact"
onClick={(e) => { e.preventDefault(); handleNavClick('#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 Nous rejoindre
</a> </a>
@ -72,39 +138,72 @@ const Header: React.FC<HeaderProps> = ({ scrolled }) => {
{/* Mobile Hamburger */} {/* Mobile Hamburger */}
<button <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)} onClick={() => setMenuOpen(!menuOpen)}
aria-label={menuOpen ? 'Fermer le menu' : 'Ouvrir le menu'}
aria-expanded={menuOpen} 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> </button>
</div> </div>
{/* Mobile Menu */} {/* Mobile Menu */}
<div <div
id="mobile-menu"
ref={mobileMenuRef}
className={`lg:hidden overflow-hidden transition-all duration-300 ${ 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) => ( {navLinks.map((link) => (
<li key={link.href}>
<a <a
key={link.href}
href={link.href} href={link.href}
onClick={(e) => { e.preventDefault(); handleNavClick(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} {link.label}
</a> </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 <a
href="#contact" href="#contact"
onClick={(e) => { e.preventDefault(); handleNavClick('#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 Nous rejoindre
</a> </a>
</li>
</ul>
</nav> </nav>
</div> </div>
</header> </header>