"use client"; import { useGSAP } from "@gsap/react"; import { Book1Outlined, DashboardSquare1Outlined, Home2Outlined, } from "@lineiconshq/free-icons"; import { Lineicons } from "@lineiconshq/react-lineicons"; import gsap from "gsap"; import Image from "next/image"; import Link from "next/link"; import { usePathname } from "next/navigation"; import { useRef, useState } from "react"; // Constantes extraites du composant const NAV_LINKS = [ { href: "/", label: "Accueil", icon: Home2Outlined }, { href: "/tutos", label: "Tous les tutos", icon: Book1Outlined }, { href: "/dashboard", label: "Dashboard", icon: DashboardSquare1Outlined }, ] as const; // Types type NavLinkType = (typeof NAV_LINKS)[number]; interface NavLinkProps { link: NavLinkType; isActive: boolean; onClick?: () => void; mobile?: boolean; } // Composant NavLink réutilisable function NavLink({ link, isActive, onClick, mobile = false }: NavLinkProps) { const baseClass = mobile ? "px-4 py-3 text-sm font-medium rounded-lg transition-colors flex items-center gap-3" : "relative px-4 py-2 text-sm font-medium transition-colors flex items-center gap-2"; const activeClass = mobile ? "text-cyan-400 bg-cyan-500/10 border-l-2 border-cyan-400" : "text-cyan-400 active"; const inactiveClass = mobile ? "text-slate-400 hover:text-slate-100 hover:bg-slate-800/50" : "text-slate-400 hover:text-slate-100"; return ( {link.label} ); } // Composant SkipLink pour l'accessibilité function SkipLink() { return ( Aller au contenu principal ); } export function Header() { const pathname = usePathname(); const headerRef = useRef(null); const logoRef = useRef(null); const navRef = useRef(null); const indicatorRef = useRef(null); const mobileMenuRef = useRef(null); const [mobileOpen, setMobileOpen] = useState(false); const isActive = (href: string) => { if (href === "/") return pathname === "/"; return pathname.startsWith(href); }; // Animation d'entrée avec useGSAP useGSAP( () => { const prefersReducedMotion = window.matchMedia( "(prefers-reduced-motion: reduce)" ).matches; if (prefersReducedMotion) return; gsap.fromTo( logoRef.current, { opacity: 0, x: -20 }, { opacity: 1, x: 0, duration: 0.5, ease: "power2.out" } ); const links = navRef.current?.querySelectorAll("a"); if (links) { gsap.fromTo( links, { opacity: 0, y: -10 }, { opacity: 1, y: 0, duration: 0.4, stagger: 0.08, ease: "power2.out", delay: 0.2, } ); } }, { scope: headerRef } ); // Indicateur animé sur le lien actif useGSAP( () => { if (!navRef.current || !indicatorRef.current) return; const prefersReducedMotion = window.matchMedia( "(prefers-reduced-motion: reduce)" ).matches; const activeLink = navRef.current.querySelector( "a.active" ) as HTMLElement; if (activeLink) { gsap.to(indicatorRef.current, { x: activeLink.offsetLeft, width: activeLink.offsetWidth, duration: prefersReducedMotion ? 0 : 0.3, ease: "power2.out", }); } }, { scope: headerRef, dependencies: [pathname] } ); // Animation menu mobile useGSAP( () => { if (!mobileMenuRef.current) return; const prefersReducedMotion = window.matchMedia( "(prefers-reduced-motion: reduce)" ).matches; if (mobileOpen) { gsap.fromTo( mobileMenuRef.current, { height: 0, opacity: 0 }, { height: "auto", opacity: 1, duration: prefersReducedMotion ? 0 : 0.3, ease: "power2.out", } ); if (!prefersReducedMotion) { const links = mobileMenuRef.current.querySelectorAll("a"); gsap.fromTo( links, { opacity: 0, x: -15 }, { opacity: 1, x: 0, duration: 0.25, stagger: 0.05, delay: 0.1, } ); } } else { gsap.to(mobileMenuRef.current, { height: 0, opacity: 0, duration: prefersReducedMotion ? 0 : 0.2, ease: "power2.in", }); } }, { scope: headerRef, dependencies: [mobileOpen] } ); const handleLogoHover = (enter: boolean) => { const prefersReducedMotion = window.matchMedia( "(prefers-reduced-motion: reduce)" ).matches; if (prefersReducedMotion) return; const img = logoRef.current?.querySelector("img"); if (!img) return; gsap.to(img, { scale: enter ? 1.1 : 1, duration: 0.25, ease: "power2.out", }); }; return ( <> {/* Logo */} handleLogoHover(true)} onMouseLeave={() => handleLogoHover(false)} className="flex items-center gap-3" aria-label="Jessy David - Accueil" > Jessy David tutos.jessy-david.dev {/* Desktop Nav */} {/* Indicateur animé */} {NAV_LINKS.map((link) => ( ))} {/* Mobile Button */} setMobileOpen((prev) => !prev)} className="md:hidden w-10 h-10 flex flex-col items-center justify-center gap-1.5 rounded-lg bg-slate-800/50 border border-slate-700/50 text-slate-400 hover:text-cyan-400 hover:border-cyan-500/30 transition-colors" aria-label={ mobileOpen ? "Fermer le menu" : "Ouvrir le menu" } aria-expanded={mobileOpen} aria-controls="mobile-menu" type="button" > {/* Mobile Menu */} {NAV_LINKS.map((link) => ( setMobileOpen(false)} mobile /> ))} > ); }