Files
whispering-tree/components/ui/mobile-navbar.tsx

181 lines
5.8 KiB
TypeScript
Raw Permalink Normal View History

2025-11-10 17:10:34 +05:30
"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.outish
},
};
// ✅ 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;