Files
whispering-tree/components/ui/mobile-navbar.tsx
2025-11-10 17:10:34 +05:30

181 lines
5.8 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"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;