fix: apply designer agent improvements to src/components/public/SiteHeader.tsx
This commit is contained in:
parent
4e960ba152
commit
64d1d67eb6
|
|
@ -0,0 +1,263 @@
|
||||||
|
'use client'
|
||||||
|
import { useState, useEffect, useRef, useCallback } from 'react'
|
||||||
|
import Link from 'next/link'
|
||||||
|
import Image from 'next/image'
|
||||||
|
import { Menu, X, ChevronDown, Phone, Mail } from 'lucide-react'
|
||||||
|
import { useSettingsStore } from '@/store/settingsStore'
|
||||||
|
import { useNavigationStore } from '@/store/navigationStore'
|
||||||
|
import clsx from 'clsx'
|
||||||
|
|
||||||
|
export default function SiteHeader() {
|
||||||
|
const { settings } = useSettingsStore()
|
||||||
|
const { items } = useNavigationStore()
|
||||||
|
const [mobileOpen, setMobileOpen] = useState(false)
|
||||||
|
const [scrolled, setScrolled] = useState(false)
|
||||||
|
const mobileMenuRef = useRef<HTMLDivElement>(null)
|
||||||
|
const menuButtonRef = useRef<HTMLButtonElement>(null)
|
||||||
|
|
||||||
|
const visibleItems = items
|
||||||
|
.filter((i) => i.isVisible && i.parentId === null)
|
||||||
|
.sort((a, b) => a.order - b.order)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const handleScroll = () => setScrolled(window.scrollY > 10)
|
||||||
|
window.addEventListener('scroll', handleScroll, { passive: true })
|
||||||
|
return () => window.removeEventListener('scroll', handleScroll)
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!mobileOpen) return
|
||||||
|
const handleKey = (e: KeyboardEvent) => {
|
||||||
|
if (e.key === 'Escape') {
|
||||||
|
setMobileOpen(false)
|
||||||
|
menuButtonRef.current?.focus()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
document.addEventListener('keydown', handleKey)
|
||||||
|
return () => document.removeEventListener('keydown', handleKey)
|
||||||
|
}, [mobileOpen])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
settings.applyThemeVars?.()
|
||||||
|
}, [settings])
|
||||||
|
|
||||||
|
const closeMobile = useCallback(() => {
|
||||||
|
setMobileOpen(false)
|
||||||
|
menuButtonRef.current?.focus()
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
const headerStyle = {
|
||||||
|
backgroundColor: settings.headerBgColor,
|
||||||
|
color: settings.headerTextColor,
|
||||||
|
position: settings.headerPosition as 'sticky' | 'static' | 'fixed',
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{/* Top Bar */}
|
||||||
|
{settings.showTopBar && settings.topBarText && (
|
||||||
|
<div
|
||||||
|
className="py-2 px-4 text-center text-sm font-medium"
|
||||||
|
style={{ backgroundColor: settings.topBarBgColor, color: '#1a1a2e' }}
|
||||||
|
role="banner"
|
||||||
|
aria-label="Annonce"
|
||||||
|
>
|
||||||
|
{settings.topBarText}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Tricolor stripe */}
|
||||||
|
<div className="tricolor-bar" aria-hidden="true" />
|
||||||
|
|
||||||
|
{/* Main Header */}
|
||||||
|
<header
|
||||||
|
style={headerStyle}
|
||||||
|
className={clsx(
|
||||||
|
'top-0 z-50 w-full transition-shadow duration-300',
|
||||||
|
scrolled && 'shadow-lg'
|
||||||
|
)}
|
||||||
|
role="banner"
|
||||||
|
>
|
||||||
|
<div className="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
|
||||||
|
<div
|
||||||
|
className={clsx(
|
||||||
|
'flex h-20 items-center gap-4',
|
||||||
|
settings.headerLayout === 'center'
|
||||||
|
? 'justify-center'
|
||||||
|
: settings.headerLayout === 'right'
|
||||||
|
? 'justify-end'
|
||||||
|
: 'justify-between'
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{/* Logo / Site Name */}
|
||||||
|
<Link
|
||||||
|
href="/"
|
||||||
|
className="flex items-center gap-3 shrink-0 group focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-fr-gold rounded-md"
|
||||||
|
aria-label={`${settings.siteName} - Retour à l'accueil`}
|
||||||
|
>
|
||||||
|
{settings.logoUrl ? (
|
||||||
|
<Image
|
||||||
|
src={settings.logoUrl}
|
||||||
|
alt={settings.siteName}
|
||||||
|
width={48}
|
||||||
|
height={48}
|
||||||
|
className="rounded-full object-cover"
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<div
|
||||||
|
className="flex h-12 w-12 shrink-0 items-center justify-center rounded-full border-2 border-fr-gold text-lg font-bold"
|
||||||
|
style={{ color: settings.headerTextColor }}
|
||||||
|
aria-hidden="true"
|
||||||
|
>
|
||||||
|
🎖️
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<div>
|
||||||
|
<span
|
||||||
|
className="block font-serif text-lg font-bold leading-tight group-hover:opacity-90 transition-opacity"
|
||||||
|
style={{ color: settings.headerTextColor }}
|
||||||
|
>
|
||||||
|
{settings.siteName}
|
||||||
|
</span>
|
||||||
|
{settings.siteTagline && (
|
||||||
|
<span
|
||||||
|
className="block text-xs opacity-80 tracking-wide"
|
||||||
|
style={{ color: settings.headerTextColor }}
|
||||||
|
>
|
||||||
|
{settings.siteTagline}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</Link>
|
||||||
|
|
||||||
|
{/* Desktop Navigation */}
|
||||||
|
<nav
|
||||||
|
className="hidden lg:flex items-center gap-1"
|
||||||
|
aria-label="Navigation principale"
|
||||||
|
>
|
||||||
|
{visibleItems.map((item) => (
|
||||||
|
<Link
|
||||||
|
key={item.id}
|
||||||
|
href={item.href}
|
||||||
|
target={item.target}
|
||||||
|
rel={item.target === '_blank' ? 'noopener noreferrer' : undefined}
|
||||||
|
className={clsx(
|
||||||
|
'relative px-4 py-2 text-sm font-semibold tracking-wide rounded-md transition-all duration-200',
|
||||||
|
'hover:bg-white/10 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-fr-gold',
|
||||||
|
'after:absolute after:bottom-0 after:left-1/2 after:-translate-x-1/2 after:h-0.5 after:w-0 after:bg-fr-gold after:transition-all after:duration-200',
|
||||||
|
'hover:after:w-3/4'
|
||||||
|
)}
|
||||||
|
style={{ color: settings.headerTextColor }}
|
||||||
|
>
|
||||||
|
{item.label}
|
||||||
|
</Link>
|
||||||
|
))}
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
{/* Contact info (desktop) */}
|
||||||
|
<div className="hidden xl:flex items-center gap-4">
|
||||||
|
{settings.footerPhone && (
|
||||||
|
<a
|
||||||
|
href={`tel:${settings.footerPhone}`}
|
||||||
|
className="flex items-center gap-1.5 text-xs opacity-80 hover:opacity-100 transition-opacity"
|
||||||
|
style={{ color: settings.headerTextColor }}
|
||||||
|
aria-label={`Téléphone: ${settings.footerPhone}`}
|
||||||
|
>
|
||||||
|
<Phone size={14} aria-hidden="true" />
|
||||||
|
{settings.footerPhone}
|
||||||
|
</a>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Mobile hamburger */}
|
||||||
|
<button
|
||||||
|
ref={menuButtonRef}
|
||||||
|
type="button"
|
||||||
|
className={clsx(
|
||||||
|
'lg:hidden flex items-center justify-center w-10 h-10 rounded-md',
|
||||||
|
'hover:bg-white/10 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-fr-gold',
|
||||||
|
'transition-colors duration-200'
|
||||||
|
)}
|
||||||
|
style={{ color: settings.headerTextColor }}
|
||||||
|
onClick={() => setMobileOpen((v) => !v)}
|
||||||
|
aria-expanded={mobileOpen}
|
||||||
|
aria-controls="mobile-menu"
|
||||||
|
aria-label={mobileOpen ? 'Fermer le menu' : 'Ouvrir le menu'}
|
||||||
|
>
|
||||||
|
{mobileOpen ? (
|
||||||
|
<X size={24} aria-hidden="true" />
|
||||||
|
) : (
|
||||||
|
<Menu size={24} aria-hidden="true" />
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Mobile Menu */}
|
||||||
|
<div
|
||||||
|
id="mobile-menu"
|
||||||
|
ref={mobileMenuRef}
|
||||||
|
className={clsx(
|
||||||
|
'lg:hidden overflow-hidden transition-all duration-300',
|
||||||
|
mobileOpen ? 'max-h-screen opacity-100' : 'max-h-0 opacity-0'
|
||||||
|
)}
|
||||||
|
aria-hidden={!mobileOpen}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="border-t border-white/20 py-4 px-4 space-y-1"
|
||||||
|
style={{ backgroundColor: settings.headerBgColor }}
|
||||||
|
>
|
||||||
|
<nav aria-label="Navigation mobile">
|
||||||
|
{visibleItems.map((item) => (
|
||||||
|
<Link
|
||||||
|
key={item.id}
|
||||||
|
href={item.href}
|
||||||
|
target={item.target}
|
||||||
|
rel={item.target === '_blank' ? 'noopener noreferrer' : undefined}
|
||||||
|
className={clsx(
|
||||||
|
'flex items-center px-4 py-3 rounded-md text-sm font-semibold',
|
||||||
|
'hover:bg-white/10 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-fr-gold',
|
||||||
|
'transition-colors duration-200'
|
||||||
|
)}
|
||||||
|
style={{ color: settings.headerTextColor }}
|
||||||
|
onClick={closeMobile}
|
||||||
|
>
|
||||||
|
{item.label}
|
||||||
|
</Link>
|
||||||
|
))}
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
{/* Mobile contact */}
|
||||||
|
{(settings.footerPhone || settings.footerEmail) && (
|
||||||
|
<div className="mt-4 pt-4 border-t border-white/20 space-y-2">
|
||||||
|
{settings.footerPhone && (
|
||||||
|
<a
|
||||||
|
href={`tel:${settings.footerPhone}`}
|
||||||
|
className="flex items-center gap-2 px-4 py-2 text-sm opacity-80"
|
||||||
|
style={{ color: settings.headerTextColor }}
|
||||||
|
>
|
||||||
|
<Phone size={14} aria-hidden="true" />
|
||||||
|
{settings.footerPhone}
|
||||||
|
</a>
|
||||||
|
)}
|
||||||
|
{settings.footerEmail && (
|
||||||
|
<a
|
||||||
|
href={`mailto:${settings.footerEmail}`}
|
||||||
|
className="flex items-center gap-2 px-4 py-2 text-sm opacity-80"
|
||||||
|
style={{ color: settings.headerTextColor }}
|
||||||
|
>
|
||||||
|
<Mail size={14} aria-hidden="true" />
|
||||||
|
{settings.footerEmail}
|
||||||
|
</a>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
{/* Bottom tricolor stripe */}
|
||||||
|
<div className="tricolor-bar" aria-hidden="true" />
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue