SPB Git

spb/phi-conseil

Public
3.8 KB · 121 lines tsx
1"use client";23import { useEffect, useId, useRef, useState } from "react";4import { createPortal } from "react-dom";5import { Button } from "@/components/ui/Button";6import { NavLinks } from "./NavLinks";78type Item = { label: string; href: string };910export function MobileNav({11  items,12  cta,13  brandName,14}: {15  items: Item[];16  cta: Item;17  brandName: string;18}) {19  const [open, setOpen] = useState(false);20  const panelId = useId();21  const closeRef = useRef<HTMLButtonElement>(null);22  const openerRef = useRef<HTMLButtonElement>(null);2324  useEffect(() => {25    if (!open) return;26    const prev = document.body.style.overflow;27    const opener = openerRef.current;28    document.body.style.overflow = "hidden";29    closeRef.current?.focus();30    const onKey = (e: KeyboardEvent) => {31      if (e.key === "Escape") setOpen(false);32    };33    document.addEventListener("keydown", onKey);34    return () => {35      document.body.style.overflow = prev;36      document.removeEventListener("keydown", onKey);37      opener?.focus();38    };39  }, [open]);4041  // Le panneau est rendu dans <body> par un portail : l’en-tête utilise42  // backdrop-filter, qui ferait de lui le bloc conteneur d’un élément `fixed`.43  return (44    <div className="lg:hidden">45      <button46        ref={openerRef}47        type="button"48        className="text-navy-900 hover:bg-navy-900/5 inline-flex h-11 w-11 items-center justify-center rounded-md"49        aria-expanded={open}50        aria-controls={panelId}51        aria-label="Ouvrir le menu"52        onClick={() => setOpen(true)}53      >54        <svg55          viewBox="0 0 24 24"56          className="h-6 w-6"57          fill="none"58          stroke="currentColor"59          strokeWidth="1.6"60          strokeLinecap="round"61          aria-hidden="true"62        >63          <path d="M4 7h16M4 12h16M4 17h16" />64        </svg>65      </button>6667      {open68        ? createPortal(69            <div70              id={panelId}71              role="dialog"72              aria-modal="true"73              aria-label={`Menu ${brandName}`}74              className="bg-ivory-100 fixed inset-0 z-50 flex flex-col motion-safe:animate-[fade-in_var(--duration-base)_var(--ease-out)]"75            >76              <div className="container-site flex items-center justify-between py-4">77                <span className="text-navy-900 font-serif text-xl">{brandName}</span>78                <button79                  ref={closeRef}80                  type="button"81                  className="text-navy-900 hover:bg-navy-900/5 inline-flex h-11 w-11 items-center justify-center rounded-md"82                  aria-label="Fermer le menu"83                  onClick={() => setOpen(false)}84                >85                  <svg86                    viewBox="0 0 24 24"87                    className="h-6 w-6"88                    fill="none"89                    stroke="currentColor"90                    strokeWidth="1.6"91                    strokeLinecap="round"92                    aria-hidden="true"93                  >94                    <path d="m6 6 12 12M18 6 6 18" />95                  </svg>96                </button>97              </div>98              <nav99                aria-label="Navigation mobile"100                className="container-site flex-1 overflow-y-auto py-6"101              >102                <NavLinks103                  items={items}104                  vertical105                  className="flex flex-col gap-1"106                  onNavigate={() => setOpen(false)}107                />108              </nav>109              <div className="container-site pb-10">110                <Button href={cta.href} size="lg" className="w-full" onClick={() => setOpen(false)}>111                  {cta.label}112                </Button>113              </div>114            </div>,115            document.body,116          )117        : null}118    </div>119  );120}121
Files · main
Menu

Theme