fix: apply designer agent improvements to src/components/Header.tsx
This commit is contained in:
parent
f5fdc9a370
commit
961d48c59d
|
|
@ -1,70 +1,136 @@
|
||||||
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 = [
|
||||||
{ label: 'Accueil', href: '#accueil' },
|
{ label: 'Accueil', href: '#accueil' },
|
||||||
{ label: 'À Propos', href: '#apropos' },
|
{ label: 'À Propos', href: '#apropos' },
|
||||||
{ label: 'Histoire', href: '#histoire' },
|
{ label: 'Histoire', href: '#histoire' },
|
||||||
{ label: 'Galerie', href: '#galerie' },
|
{ label: 'Galerie', href: '#galerie' },
|
||||||
{ label: 'Événements', href: '#evenements' },
|
{ label: 'Événements', href: '#evenements' },
|
||||||
{ 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
|
||||||
{navLinks.map((link) => (
|
className="bg-marine-900 border-t border-marine-700 px-4 py-4"
|
||||||
<a
|
aria-label="Navigation mobile"
|
||||||
key={link.href}
|
>
|
||||||
href={link.href}
|
<ul className="flex flex-col gap-1">
|
||||||
onClick={(e) => { e.preventDefault(); handleNavClick(link.href); }}
|
{navLinks.map((link) => (
|
||||||
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"
|
<li key={link.href}>
|
||||||
>
|
<a
|
||||||
{link.label}
|
href={link.href}
|
||||||
</a>
|
onClick={(e) => { e.preventDefault(); handleNavClick(link.href); }}
|
||||||
))}
|
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"
|
||||||
<a
|
tabIndex={menuOpen ? 0 : -1}
|
||||||
href="#contact"
|
>
|
||||||
onClick={(e) => { e.preventDefault(); handleNavClick('#contact'); }}
|
{link.label}
|
||||||
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"
|
</a>
|
||||||
>
|
</li>
|
||||||
Nous rejoindre
|
))}
|
||||||
</a>
|
<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="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>
|
</nav>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue