fixing headers anchor links
This commit is contained in:
@@ -1,62 +1,91 @@
|
|||||||
'use client';
|
"use client";
|
||||||
|
|
||||||
import Link from 'next/link';
|
import Link from "next/link";
|
||||||
import React, { useState, useEffect, MouseEvent } from 'react';
|
import React, { useState, useEffect, MouseEvent } from "react";
|
||||||
|
import { useParams, usePathname } from "next/navigation";
|
||||||
import { CiMenuFries } from "react-icons/ci";
|
import { CiMenuFries } from "react-icons/ci";
|
||||||
import { IoCloseOutline } from "react-icons/io5";
|
import { IoCloseOutline } from "react-icons/io5";
|
||||||
|
|
||||||
export default function Header() {
|
export default function Header() {
|
||||||
|
const params = useParams<{ locale?: string }>();
|
||||||
|
const pathname = usePathname();
|
||||||
|
const locale = params?.locale ?? "fr";
|
||||||
|
const homePath = `/${locale}`;
|
||||||
|
|
||||||
const links = [
|
const links = [
|
||||||
{ "text": "Accueil", "target": "/" },
|
{ text: "Accueil", target: homePath },
|
||||||
{ "text": "Travaux", "target": "/#works" },
|
{ text: "Travaux", target: `${homePath}#works` },
|
||||||
{ "text": "À propos", "target": "/#about" },
|
{ text: "A propos", target: `${homePath}#about` },
|
||||||
{ "text": "Blog", "target": "/blog" },
|
{ text: "Blog", target: `${homePath}/blog` },
|
||||||
];
|
];
|
||||||
|
|
||||||
const [isOpen, setIsOpen] = useState(false);
|
const [isOpen, setIsOpen] = useState(false);
|
||||||
const [showText, setShowText] = useState(false);
|
const [showText, setShowText] = useState(false);
|
||||||
|
|
||||||
const handleAnchorClick = (e: MouseEvent<HTMLLIElement>, target: string) => {
|
const scrollToSection = (id: string) => {
|
||||||
if (target.startsWith("/#")) { // only anchor links
|
|
||||||
e.preventDefault();
|
|
||||||
const id = target.slice(2);
|
|
||||||
const element = document.getElementById(id);
|
const element = document.getElementById(id);
|
||||||
if (element) {
|
if (!element) return false;
|
||||||
|
|
||||||
element.scrollIntoView({ behavior: "smooth" });
|
element.scrollIntoView({ behavior: "smooth" });
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleAnchorClick = (e: MouseEvent<HTMLAnchorElement>, target: string) => {
|
||||||
|
const isAnchor = target.includes("#");
|
||||||
|
const isOnHome = pathname === homePath || pathname === `${homePath}/`;
|
||||||
|
|
||||||
|
if (isAnchor && isOnHome) {
|
||||||
|
e.preventDefault();
|
||||||
|
const sectionId = target.split("#")[1];
|
||||||
|
scrollToSection(sectionId);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
setIsOpen(false);
|
setIsOpen(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const hash = window.location.hash.replace("#", "");
|
||||||
|
if (!hash) return;
|
||||||
|
|
||||||
|
if (scrollToSection(hash)) return;
|
||||||
|
|
||||||
|
const timer = window.setTimeout(() => {
|
||||||
|
scrollToSection(hash);
|
||||||
|
}, 120);
|
||||||
|
|
||||||
|
return () => window.clearTimeout(timer);
|
||||||
|
}, [pathname]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isOpen) {
|
if (isOpen) {
|
||||||
const timer = setTimeout(() => setShowText(true), 450);
|
const timer = setTimeout(() => setShowText(true), 450);
|
||||||
return () => clearTimeout(timer);
|
return () => clearTimeout(timer);
|
||||||
} else {
|
|
||||||
setShowText(false);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setShowText(false);
|
||||||
}, [isOpen]);
|
}, [isOpen]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="bg-brand-beige h-20 fixed left-2.75 right-2.75 z-50 rounded-b-[20px]">
|
<div className="bg-brand-beige h-20 fixed left-2.75 right-2.75 z-50 rounded-b-[20px]">
|
||||||
<header className="h-20 bg-brand-brown text-brand-beige py-6 px-10 flex justify-between items-center rounded-[10px]">
|
<header className="h-20 bg-brand-brown text-brand-beige py-6 px-10 flex justify-between items-center rounded-[10px]">
|
||||||
<Link href="/#">
|
<Link href={homePath}>
|
||||||
<div className="hidden md:block font-serif text-2xl font-light cursor-pointer">M. Guilbert</div>
|
<div className="hidden md:block font-serif text-2xl font-light cursor-pointer">M. Guilbert</div>
|
||||||
<img className="block md:hidden w-auto h-15 cursor-pointer" src="/icons/logo.svg" alt="logo mg dev" />
|
<img className="block md:hidden w-auto h-15 cursor-pointer" src="/icons/logo.svg" alt="logo mg dev" />
|
||||||
</Link>
|
</Link>
|
||||||
|
|
||||||
{/* Desktop */}
|
|
||||||
<nav className="hidden sm:block">
|
<nav className="hidden sm:block">
|
||||||
<ul className="flex gap-8 font-sans text-base font-light">
|
<ul className="flex gap-8 font-sans text-base font-light">
|
||||||
{links.map((link, index) => (
|
{links.map((link, index) => (
|
||||||
<li key={index} className="cursor-pointer" onClick={(e) => handleAnchorClick(e, link.target)}><a href={link.target}>{link.text}</a></li>
|
<li key={index} className="cursor-pointer">
|
||||||
|
<Link href={link.target} onClick={(e) => handleAnchorClick(e, link.target)}>
|
||||||
|
{link.text}
|
||||||
|
</Link>
|
||||||
|
</li>
|
||||||
))}
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
</nav>
|
</nav>
|
||||||
<div className="hidden sm:block font-sans text-base font-light">France</div>
|
<div className="hidden sm:block font-sans text-base font-light">France</div>
|
||||||
|
|
||||||
{/* Mobile */}
|
|
||||||
<nav className="block sm:hidden">
|
<nav className="block sm:hidden">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
@@ -69,15 +98,10 @@ export default function Header() {
|
|||||||
</nav>
|
</nav>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
{/* Overlay mobile menu */}
|
|
||||||
<div className={`sm:hidden fixed inset-2.75 z-60 overflow-hidden rounded-[10px] ${isOpen ? "pointer-events-auto" : "pointer-events-none"}`}>
|
<div className={`sm:hidden fixed inset-2.75 z-60 overflow-hidden rounded-[10px] ${isOpen ? "pointer-events-auto" : "pointer-events-none"}`}>
|
||||||
{/* wave / circle that fills the screen */}
|
|
||||||
<div className={`absolute right-6 top-2 h-14 w-14 rounded-full bg-[#5a4336] transition-transform duration-900 ease-[cubic-bezier(0.22,1,0.36,1)] ${isOpen ? "scale-[38]" : "scale-0"} origin-center`} />
|
<div className={`absolute right-6 top-2 h-14 w-14 rounded-full bg-[#5a4336] transition-transform duration-900 ease-[cubic-bezier(0.22,1,0.36,1)] ${isOpen ? "scale-[38]" : "scale-0"} origin-center`} />
|
||||||
|
|
||||||
{/* content */}
|
|
||||||
<div className={`relative z-10 flex h-full flex-col justify-between px-10 pt-5 pb-10 transition-opacity duration-300 ${isOpen ? "opacity-100" : "opacity-0"}`}>
|
<div className={`relative z-10 flex h-full flex-col justify-between px-10 pt-5 pb-10 transition-opacity duration-300 ${isOpen ? "opacity-100" : "opacity-0"}`}>
|
||||||
|
|
||||||
{/* top bar */}
|
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<div className="font-serif text-2xl font-light text-brand-beige">
|
<div className="font-serif text-2xl font-light text-brand-beige">
|
||||||
M. Guilbert
|
M. Guilbert
|
||||||
@@ -93,13 +117,11 @@ export default function Header() {
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* liens */}
|
|
||||||
<nav>
|
<nav>
|
||||||
<ul className="flex flex-col gap-4">
|
<ul className="flex flex-col gap-4">
|
||||||
{links.map((link, index) => (
|
{links.map((link, index) => (
|
||||||
<li
|
<li
|
||||||
key={index}
|
key={index}
|
||||||
onClick={(e) => handleAnchorClick(e, link.target)}
|
|
||||||
className={`
|
className={`
|
||||||
cursor-pointer text-brand-beige font-serif text-[clamp(2.4rem,9vw,5rem)] leading-[0.95]
|
cursor-pointer text-brand-beige font-serif text-[clamp(2.4rem,9vw,5rem)] leading-[0.95]
|
||||||
transition-all duration-700 ease-out
|
transition-all duration-700 ease-out
|
||||||
@@ -109,18 +131,16 @@ export default function Header() {
|
|||||||
transitionDelay: showText ? `${index * 140}ms` : "0ms",
|
transitionDelay: showText ? `${index * 140}ms` : "0ms",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<a href={link.target} className="inline-block">
|
<Link href={link.target} onClick={(e) => handleAnchorClick(e, link.target)} className="inline-block">
|
||||||
{link.text}
|
{link.text}
|
||||||
</a>
|
</Link>
|
||||||
</li>
|
</li>
|
||||||
))}
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
{/* footer */}
|
|
||||||
<div
|
<div
|
||||||
className={`text-brand-beige/80 text-sm font-sans transition-all duration-700 ${showText ? "translate-x-0 opacity-100" : "-translate-x-8 opacity-0"
|
className={`text-brand-beige/80 text-sm font-sans transition-all duration-700 ${showText ? "translate-x-0 opacity-100" : "-translate-x-8 opacity-0"}`}
|
||||||
}`}
|
|
||||||
style={{ transitionDelay: showText ? "650ms" : "0ms" }}
|
style={{ transitionDelay: showText ? "650ms" : "0ms" }}
|
||||||
>
|
>
|
||||||
France
|
France
|
||||||
@@ -128,6 +148,5 @@ export default function Header() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user