181 lines
5.8 KiB
TypeScript
181 lines
5.8 KiB
TypeScript
|
|
"use client";
|
|||
|
|
|
|||
|
|
import React, { useEffect, useRef, useState } from "react";
|
|||
|
|
import Image from "next/image";
|
|||
|
|
import Link from "next/link";
|
|||
|
|
import { motion, Variants } from "framer-motion";
|
|||
|
|
import { cn } from "@/lib/utils";
|
|||
|
|
import Button from "../shared/Button";
|
|||
|
|
|
|||
|
|
const MobileNavbar = () => {
|
|||
|
|
const [isOpen, setIsOpen] = useState(false);
|
|||
|
|
const [scrolled, setScrolled] = useState(false);
|
|||
|
|
const mobileRef = useRef<HTMLDivElement>(null);
|
|||
|
|
|
|||
|
|
useEffect(() => {
|
|||
|
|
const handleScroll = () => {
|
|||
|
|
const currentScroll = window.scrollY;
|
|||
|
|
setScrolled(currentScroll > 30);
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
window.addEventListener("scroll", handleScroll);
|
|||
|
|
return () => window.removeEventListener("scroll", handleScroll);
|
|||
|
|
}, []);
|
|||
|
|
|
|||
|
|
const navLinks = [
|
|||
|
|
{ label: "Home", href: "#home" },
|
|||
|
|
{ label: "About Us", href: "#about" },
|
|||
|
|
{ label: "Features", href: "#features" },
|
|||
|
|
{ label: "Benefits", href: "#benefits" },
|
|||
|
|
{ label: "FAQ", href: "#faq" },
|
|||
|
|
];
|
|||
|
|
const containerVariants: Variants = {
|
|||
|
|
hidden: { y: -100, opacity: 0 },
|
|||
|
|
visible: {
|
|||
|
|
y: 0,
|
|||
|
|
opacity: 1,
|
|||
|
|
transition: { duration: 1.2, ease: [0.19, 1, 0.22, 1] }, // expo.out‑ish
|
|||
|
|
},
|
|||
|
|
};
|
|||
|
|
// ✅ Correctly typed Framer Motion variants
|
|||
|
|
const mobileNavVariants: Variants = {
|
|||
|
|
hidden: { opacity: 0, y: 20 },
|
|||
|
|
visible: {
|
|||
|
|
opacity: 1,
|
|||
|
|
y: 0,
|
|||
|
|
transition: {
|
|||
|
|
staggerChildren: 0.1,
|
|||
|
|
duration: 0.4,
|
|||
|
|
ease: "easeInOut", // ✅ typed correctly
|
|||
|
|
},
|
|||
|
|
},
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
const linkVariants: Variants = {
|
|||
|
|
hidden: { opacity: 0, y: 20 },
|
|||
|
|
visible: {
|
|||
|
|
opacity: 1,
|
|||
|
|
y: 0,
|
|||
|
|
transition: {
|
|||
|
|
duration: 0.3,
|
|||
|
|
ease: "easeInOut", // ✅ allowed by Framer
|
|||
|
|
},
|
|||
|
|
},
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
// ✅ Safe smooth scroll handler
|
|||
|
|
const handleClick = (
|
|||
|
|
e: React.MouseEvent<HTMLAnchorElement>,
|
|||
|
|
href: string
|
|||
|
|
) => {
|
|||
|
|
e.preventDefault();
|
|||
|
|
const targetId = href.replace("#", "");
|
|||
|
|
const section = document.getElementById(targetId);
|
|||
|
|
if (section) {
|
|||
|
|
const yOffset = -80;
|
|||
|
|
const y =
|
|||
|
|
section.getBoundingClientRect().top + window.pageYOffset + yOffset;
|
|||
|
|
window.scrollTo({ top: y, behavior: "smooth" });
|
|||
|
|
setIsOpen(false);
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
return (
|
|||
|
|
<motion.nav
|
|||
|
|
variants={containerVariants}
|
|||
|
|
initial={{ opacity: 0, y: -20 }}
|
|||
|
|
animate={{ opacity: 1, y: 0 }}
|
|||
|
|
transition={{ duration: 0.8, ease: "easeInOut" }}
|
|||
|
|
className={`fixed left-1/2 z-50 ${
|
|||
|
|
isOpen
|
|||
|
|
? "h-[480px] border-white/30 bg-white/80 shadow-xs backdrop-blur-lg"
|
|||
|
|
: "sm:h-[70px]"
|
|||
|
|
} ${
|
|||
|
|
scrolled
|
|||
|
|
? "top-0 border-white/30 bg-white/80 shadow-xs backdrop-blur-lg"
|
|||
|
|
: "top-2 border-transparent bg-transparent"
|
|||
|
|
} tab:hidden mx-auto w-full -translate-x-1/2 transform px-5 transition-[height] duration-300 sm:block sm:max-w-full sm:py-[15px]`}
|
|||
|
|
>
|
|||
|
|
{/* Header Bar */}
|
|||
|
|
<div className="relative flex w-full items-center justify-between">
|
|||
|
|
<Link href="/" className="logo">
|
|||
|
|
<div className="h-10 w-36">
|
|||
|
|
<Image
|
|||
|
|
src={
|
|||
|
|
scrolled || isOpen
|
|||
|
|
? "/images/svg/dark-logo.svg"
|
|||
|
|
: "/images/svg/whispering-logo.svg"
|
|||
|
|
}
|
|||
|
|
width={144}
|
|||
|
|
height={40}
|
|||
|
|
alt="Whispering Tree"
|
|||
|
|
className="h-full w-full object-contain"
|
|||
|
|
/>
|
|||
|
|
</div>
|
|||
|
|
</Link>
|
|||
|
|
|
|||
|
|
{/* Mobile Menu Toggle */}
|
|||
|
|
<button
|
|||
|
|
className="menu__icon relative z-10 flex h-max w-[28px] flex-col items-center justify-center gap-1.5 p-[3px] md:hidden"
|
|||
|
|
onClick={() => setIsOpen(!isOpen)}
|
|||
|
|
>
|
|||
|
|
<span
|
|||
|
|
className={`h-[0.1rem] w-full rounded-[0.125rem] transition-all duration-300 ${
|
|||
|
|
scrolled || isOpen ? "bg-black" : "bg-white"
|
|||
|
|
} ${isOpen ? "translate-y-[7.5px] rotate-[-45deg]" : ""}`}
|
|||
|
|
></span>
|
|||
|
|
<span
|
|||
|
|
className={`h-[0.1rem] w-[60%] rounded-[0.125rem] transition-all duration-300 ${
|
|||
|
|
scrolled || isOpen ? "bg-black" : "bg-white"
|
|||
|
|
} ${isOpen ? "w-0 opacity-0" : ""}`}
|
|||
|
|
></span>
|
|||
|
|
<span
|
|||
|
|
className={`h-[0.1rem] w-full rounded-[0.125rem] transition-all duration-300 ${
|
|||
|
|
scrolled || isOpen ? "bg-black" : "bg-white"
|
|||
|
|
} ${isOpen ? "translate-y-[-7.5px] rotate-[45deg]" : ""}`}
|
|||
|
|
></span>
|
|||
|
|
</button>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
{/* Dropdown Mobile Menu */}
|
|||
|
|
<motion.div
|
|||
|
|
initial="hidden"
|
|||
|
|
animate={isOpen ? "visible" : "hidden"}
|
|||
|
|
variants={mobileNavVariants}
|
|||
|
|
className={`mt-12 flex flex-col items-center gap-5 ${
|
|||
|
|
isOpen ? "flex" : "hidden"
|
|||
|
|
} md:hidden`}
|
|||
|
|
>
|
|||
|
|
{navLinks.map((link, i) => (
|
|||
|
|
<motion.div
|
|||
|
|
key={i}
|
|||
|
|
variants={linkVariants}
|
|||
|
|
className="flex w-full items-center justify-start"
|
|||
|
|
>
|
|||
|
|
<a
|
|||
|
|
href={link.href}
|
|||
|
|
onClick={(e) => handleClick(e, link.href)}
|
|||
|
|
className={cn(
|
|||
|
|
"font-switzer text-h-4-34 group relative inline-block h-full cursor-pointer leading-9 font-medium transition-all duration-700 ease-[cubic-bezier(0.19,1,0.22,1)]"
|
|||
|
|
)}
|
|||
|
|
>
|
|||
|
|
{/* Text hover animation */}
|
|||
|
|
<span className="relative block overflow-hidden">
|
|||
|
|
<span className="block leading-9 transition-transform duration-[1.125s] ease-[cubic-bezier(0.19,1,0.22,1)] group-hover:-translate-y-6">
|
|||
|
|
{link.label}
|
|||
|
|
</span>
|
|||
|
|
<span className="absolute top-8 left-0 leading-9 transition-all duration-[1.125s] ease-[cubic-bezier(0.19,1,0.22,1)] group-hover:top-0">
|
|||
|
|
{link.label}
|
|||
|
|
</span>
|
|||
|
|
</span>
|
|||
|
|
</a>
|
|||
|
|
</motion.div>
|
|||
|
|
))}
|
|||
|
|
<Button text="Book A Demo" className="mt-5 w-full!" />
|
|||
|
|
</motion.div>
|
|||
|
|
</motion.nav>
|
|||
|
|
);
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
export default MobileNavbar;
|