From 27d35b185df37f89fb08c387dcceb6f1719fe6c2 Mon Sep 17 00:00:00 2001 From: louis Lazare Date: Wed, 18 Jun 2025 10:33:14 +0200 Subject: [PATCH 1/2] =?UTF-8?q?=F0=9F=93=9D=20navbar=20optimization=20&=20?= =?UTF-8?q?commentary?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/marketing/navbar/index.tsx | 447 ++++++++++++---------- 1 file changed, 236 insertions(+), 211 deletions(-) diff --git a/src/components/marketing/navbar/index.tsx b/src/components/marketing/navbar/index.tsx index 2a1477a05..10711c83e 100644 --- a/src/components/marketing/navbar/index.tsx +++ b/src/components/marketing/navbar/index.tsx @@ -7,230 +7,255 @@ import Projects from "@/constants/projects"; import { getMajorAgencies } from "@/constants/agencies"; import { OnRuntimeWordMark } from "@/logos/components"; import Link from "next/link"; -import type React from "react"; import { useState, useRef, useEffect } from "react"; import Navigation from "./navigation"; import { ChevronDown, Menu } from "lucide-react"; import { cn } from "@/lib/utils"; +// Types pour les éléments de navigation interface SubNavItem { - title: string; - path: string; + title: string; + path: string; } interface DropdownItem { - title: string; - path: string; - items?: SubNavItem[]; + title: string; + path: string; + items?: SubNavItem[]; } interface NavItem { - title: string; - path: string; - dropdown?: DropdownItem[]; + title: string; + path: string; + dropdown?: DropdownItem[]; } -const Navbar: React.FC = () => { - const [mobileMenuOpen, setMobileMenuOpen] = useState(false); - const [expandedSection, setExpandedSection] = useState(null); - - const navRef = useRef(null); - - const [navWidth, setNavWidth] = useState(undefined); - - const majorAgencies = getMajorAgencies(5); - - const navItems: NavItem[] = [ - { - title: "Nos services", - path: Routes.services, - dropdown: Services.map((service) => ({ - title: service.name, - path: Routes.service[service.id].root, - items: service.subServices.map((subService) => ({ - title: subService.name, - path: subService.route, - })), - })), - }, - { - title: "Nos projets", - path: Routes.unknown, - dropdown: Projects.slice(0, 5).map((project) => ({ - title: project.name, - path: Routes.project(project.id), - })), - }, - { - title: "Nos agences", - path: Routes.agency.root, - dropdown: [ - { - title: "Toutes nos agences", - path: Routes.agency.root, - }, - ...majorAgencies.map((agency) => ({ - title: `Agence ${agency.name}`, - path: Routes.agency.city(agency.id), - })), - ], - }, - { - title: "L'association", - path: Routes.npo, - }, - ]; - - useEffect(() => { - if (!navRef.current) return; - - const resizeObserver = new ResizeObserver((entries) => { - for (const entry of entries) { - const { width } = entry.contentRect; - setNavWidth(width); - } - }); - - resizeObserver.observe(navRef.current); - - return () => { - resizeObserver.disconnect(); - }; - }, []); - - useEffect(() => { - if (navWidth && navWidth >= 768 && mobileMenuOpen) { - setMobileMenuOpen(false); - setExpandedSection(null); - } - }, [navWidth, mobileMenuOpen]); - - const toggleMobileMenu = () => { - setMobileMenuOpen(!mobileMenuOpen); - setExpandedSection(null); - }; - - const toggleSection = (section: string) => { - setExpandedSection(expandedSection === section ? null : section); - }; - - const closeMenu = () => { - setMobileMenuOpen(false); - setExpandedSection(null); - }; - - return ( - - ); +/** + * Composant pour un item du menu mobile avec sous-menus + */ +const MobileMenuItem = ({ + item, + isExpanded, + onToggle, + onClose +}: { + item: NavItem; + isExpanded: boolean; + onToggle: () => void; + onClose: () => void; +}) => { + // Item simple sans dropdown + if (!item.dropdown) { + return ( + + {item.title} + + ); + } + + // Item avec dropdown + return ( +
+ + + {isExpanded && ( +
+ {item.dropdown.map((dropdown) => ( +
+ + {dropdown.title} + + + {dropdown.items?.map((subItem) => ( + + {subItem.title} + + ))} +
+ ))} +
+ )} +
+ ); +}; + +/** + * Menu mobile complet + */ +const MobileMenu = ({ + items, + expandedItem, + onItemToggle, + onClose +}: { + items: NavItem[]; + expandedItem: string | null; + onItemToggle: (title: string) => void; + onClose: () => void; +}) => ( +
+ {items.map((item) => ( +
+ onItemToggle(item.title)} + onClose={onClose} + /> +
+ ))} + + + + +
+); + +/** + * Barre de navigation principale + */ +const Navbar = () => { + // État pour le menu mobile et les sections développées + const [mobileMenuOpen, setMobileMenuOpen] = useState(false); + const [expandedItem, setExpandedItem] = useState(null); + const navRef = useRef(null); + const [navWidth, setNavWidth] = useState(); + + // Configuration des items de navigation + const navItems: NavItem[] = [ + { + title: "Nos services", + path: Routes.services, + dropdown: Services.map((service) => ({ + title: service.name, + path: Routes.service[service.id].root, + items: service.subServices.map((subService) => ({ + title: subService.name, + path: subService.route, + })), + })), + }, + { + title: "Nos projets", + path: Routes.unknown, + dropdown: Projects.slice(0, 5).map((project) => ({ + title: project.name, + path: Routes.project(project.id), + })), + }, + { + title: "Nos agences", + path: Routes.agency.root, + dropdown: [ + { + title: "Toutes nos agences", + path: Routes.agency.root, + }, + ...getMajorAgencies(5).map((agency) => ({ + title: `Agence ${agency.name}`, + path: Routes.agency.city(agency.id), + })), + ], + }, + { + title: "L'association", + path: Routes.npo, + }, + ]; + + // Observateur de redimensionnement pour le responsive + useEffect(() => { + if (!navRef.current) return; + + const observer = new ResizeObserver(([entry]) => { + setNavWidth(entry.contentRect.width); + }); + + observer.observe(navRef.current); + return () => observer.disconnect(); + }, []); + + // Fermeture du menu mobile en desktop + useEffect(() => { + if (navWidth && navWidth >= 768 && mobileMenuOpen) { + setMobileMenuOpen(false); + setExpandedItem(null); + } + }, [navWidth, mobileMenuOpen]); + + // Gestionnaires d'événements + const toggleMobileMenu = () => { + setMobileMenuOpen(!mobileMenuOpen); + setExpandedItem(null); + }; + + const toggleItem = (title: string) => { + setExpandedItem(expandedItem === title ? null : title); + }; + + const closeMenu = () => { + setMobileMenuOpen(false); + setExpandedItem(null); + }; + + return ( + + ); }; -export default Navbar; +export default Navbar; \ No newline at end of file From 15cc6637b1adb7db7af823b5e11073f9345eee01 Mon Sep 17 00:00:00 2001 From: louis Lazare Date: Wed, 18 Jun 2025 10:40:28 +0200 Subject: [PATCH 2/2] =?UTF-8?q?=F0=9F=93=9D=20footer=20optimization=20&=20?= =?UTF-8?q?commentary?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/layout/footer/footer.tsx | 116 +++++++++++++++--------- 1 file changed, 75 insertions(+), 41 deletions(-) diff --git a/src/components/layout/footer/footer.tsx b/src/components/layout/footer/footer.tsx index 0f50e2e28..f17d062f2 100644 --- a/src/components/layout/footer/footer.tsx +++ b/src/components/layout/footer/footer.tsx @@ -3,6 +3,10 @@ import { OnRuntimeWordMark } from "@/logos/components"; import Link from "next/link"; import React from "react"; +/** + * Configuration de la navigation du footer + * Organisée par catégories pour une meilleure maintenabilité + */ const navigation = { Navigation: [ { name: "Nos services", href: Routes.services }, @@ -11,15 +15,15 @@ const navigation = { { name: "Carrières", href: Routes.unknown }, { name: "Blog", href: Routes.unknown }, ], - ["Ressources"]: [ + Ressources: [ { name: "Glossaire", href: Routes.glossary }, { name: "Communauté", href: Routes.unknown }, { name: "Status", href: Routes.unknown }, ], - ["Autres"]: [ + Autres: [ { name: "Contact", href: Routes.contact }, { name: "Conditions générales", href: Routes.legals.terms }, - { name: "Politique de confidentialité", href: Routes.legals.privacy }, + { name: "Politique de confidentialité", href: Routes.legals.privacy }, { name: "Détails de l'entreprise", href: Routes.legals.company }, ], ["Réseaux sociaux"]: [ @@ -31,16 +35,73 @@ const navigation = { ], }; +/** + * Composant StatusIndicator - Indicateur visuel de statut + */ +const StatusIndicator = () => ( + + + + +); + +/** + * Composant NavigationSection - Affiche une section de navigation + */ +const NavigationSection = ({ + title, + links, +}: { + title: string; + links: { name: string; href: string }[]; +}) => ( +
+

{title}

+
    + {links.map((link) => ( +
  • + + {link.name} + +
  • + ))} +
+
+); + +/** + * Composant Footer - Pied de page principal + */ const Footer: React.FC = () => { const currentYear = new Date().getFullYear(); return ( -