Retour au catalogue

Navbar Breadcrumb Integrated

Navbar qui se transforme en fil d'Ariane au scroll vers le bas. Les liens classiques laissent place au chemin de navigation contextuel.

navbarmedium Both Responsive a11y
corporateminimaluniversalsaasecommercesticky
Theme
"use client";

import { useState, useEffect } from "react";
import { motion, AnimatePresence } from "framer-motion";
import { Menu, X, ChevronRight } from "lucide-react";

interface NavLink {
  label: string;
  href: string;
}

interface Breadcrumb {
  label: string;
  href: string;
}

interface NavbarBreadcrumbIntegratedProps {
  brandName?: string;
  links?: NavLink[];
  breadcrumbs?: Breadcrumb[];
  ctaLabel?: string;
  ctaUrl?: string;
}

const THRESHOLD = 200;

export default function NavbarBreadcrumbIntegrated({
  brandName = "Brand",
  links = [],
  breadcrumbs = [],
  ctaLabel = "Contact",
  ctaUrl = "#contact",
}: NavbarBreadcrumbIntegratedProps) {
  const [scrolled, setScrolled] = useState(false);
  const [mobileOpen, setMobileOpen] = useState(false);

  useEffect(() => {
    const onScroll = () => setScrolled(window.scrollY > THRESHOLD);
    onScroll();
    window.addEventListener("scroll", onScroll, { passive: true });
    return () => window.removeEventListener("scroll", onScroll);
  }, []);

  return (
    <>
      <header
        style={{
          position: "fixed",
          top: 0,
          left: 0,
          right: 0,
          zIndex: 50,
          background: "var(--color-background)",
          borderBottom: "1px solid var(--color-border)",
        }}
      >
        <div
          style={{
            maxWidth: "var(--container-max-width)",
            margin: "0 auto",
            padding: "0 var(--container-padding-x)",
            display: "flex",
            alignItems: "center",
            justifyContent: "space-between",
            height: "56px",
          }}
        >
          <a
            href="/"
            style={{
              fontSize: "1.125rem",
              fontWeight: 700,
              color: "var(--color-foreground)",
              textDecoration: "none",
              letterSpacing: "-0.02em",
              flexShrink: 0,
            }}
          >
            {brandName}
          </a>

          {/* Center: links or breadcrumbs */}
          <div
            style={{
              display: "none",
              alignItems: "center",
              gap: "1.5rem",
              flex: 1,
              justifyContent: "center",
              overflow: "hidden",
            }}
            className="lg:!flex"
          >
            <AnimatePresence mode="wait">
              {scrolled && breadcrumbs.length > 0 ? (
                <motion.nav
                  key="breadcrumbs"
                  initial={{ opacity: 0, y: -8 }}
                  animate={{ opacity: 1, y: 0 }}
                  exit={{ opacity: 0, y: 8 }}
                  transition={{ duration: 0.25 }}
                  aria-label="Fil d'Ariane"
                  style={{ display: "flex", alignItems: "center", gap: "0.5rem" }}
                >
                  {breadcrumbs.map((crumb, i) => (
                    <span key={crumb.href} style={{ display: "flex", alignItems: "center", gap: "0.5rem" }}>
                      {i > 0 && (
                        <ChevronRight
                          style={{ width: 14, height: 14, color: "var(--color-foreground-light)" }}
                        />
                      )}
                      <a
                        href={crumb.href}
                        style={{
                          fontSize: "0.8125rem",
                          fontWeight: i === breadcrumbs.length - 1 ? 600 : 400,
                          color: i === breadcrumbs.length - 1
                            ? "var(--color-foreground)"
                            : "var(--color-foreground-muted)",
                          textDecoration: "none",
                          whiteSpace: "nowrap",
                        }}
                      >
                        {crumb.label}
                      </a>
                    </span>
                  ))}
                </motion.nav>
              ) : (
                <motion.div
                  key="links"
                  initial={{ opacity: 0, y: -8 }}
                  animate={{ opacity: 1, y: 0 }}
                  exit={{ opacity: 0, y: 8 }}
                  transition={{ duration: 0.25 }}
                  style={{ display: "flex", alignItems: "center", gap: "2rem" }}
                >
                  {links.map((link) => (
                    <a
                      key={link.href}
                      href={link.href}
                      style={{
                        fontSize: "0.875rem",
                        fontWeight: 500,
                        color: "var(--color-foreground-muted)",
                        textDecoration: "none",
                      }}
                    >
                      {link.label}
                    </a>
                  ))}
                </motion.div>
              )}
            </AnimatePresence>
          </div>

          <div style={{ display: "flex", alignItems: "center", gap: "0.75rem" }}>
            <a
              href={ctaUrl}
              style={{
                display: "none",
                alignItems: "center",
                padding: "0.5rem 1.25rem",
                borderRadius: "var(--radius-full)",
                background: "var(--color-accent)",
                color: "var(--color-foreground)",
                fontWeight: 600,
                fontSize: "0.8125rem",
                textDecoration: "none",
              }}
              className="lg:!inline-flex"
            >
              {ctaLabel}
            </a>

            <button
              onClick={() => setMobileOpen(!mobileOpen)}
              aria-label={mobileOpen ? "Fermer" : "Menu"}
              style={{
                display: "flex",
                alignItems: "center",
                justifyContent: "center",
                width: "40px",
                height: "40px",
                border: "none",
                background: "transparent",
                cursor: "pointer",
                color: "var(--color-foreground)",
              }}
              className="lg:!hidden"
            >
              {mobileOpen ? <X style={{ width: 20, height: 20 }} /> : <Menu style={{ width: 20, height: 20 }} />}
            </button>
          </div>
        </div>
      </header>

      {/* Mobile menu */}
      <AnimatePresence>
        {mobileOpen && (
          <motion.div
            initial={{ opacity: 0, y: -10 }}
            animate={{ opacity: 1, y: 0 }}
            exit={{ opacity: 0, y: -10 }}
            transition={{ duration: 0.25 }}
            style={{
              position: "fixed",
              top: "56px",
              left: 0,
              right: 0,
              zIndex: 49,
              background: "var(--color-background)",
              borderBottom: "1px solid var(--color-border)",
              padding: "1rem var(--container-padding-x)",
              display: "flex",
              flexDirection: "column",
              gap: "0.75rem",
            }}
            className="lg:!hidden"
          >
            {links.map((link) => (
              <a
                key={link.href}
                href={link.href}
                onClick={() => setMobileOpen(false)}
                style={{
                  fontSize: "1rem",
                  color: "var(--color-foreground)",
                  textDecoration: "none",
                  padding: "0.5rem 0",
                }}
              >
                {link.label}
              </a>
            ))}
          </motion.div>
        )}
      </AnimatePresence>
    </>
  );
}

Avis

Navbar Breadcrumb Integrated — React Navbar Section — Incubator