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>
</>
);
}