Navbar component code
import Humburger from "./ui/Humburger";
const navlins = ["HOME", "PAGES", "PRODUCTS", "ARTICLES", "CONTACT"];
const Navbar = () => {
const [isNavbarActive, setIsNavbarActive] = useState(false);
const toogleNavbar = () => setIsNavbarActive(!isNavbarActive);
return (
<>
<div className="relative bg-white h-[4.5rem] z-20">
<div className="w-full h-full flex justify-between items-center px-4">
<Image
width={120}
height={70}
src="https://cdn.prod.website-files.com/65478d390c8996a757a4faaa/65c4aacccb9a41d80d0d66f7_Stride-logo-dark.svg"
alt="nav-logo"
/>
<div className="hidden">
<ul className="flex w-full border border-green-500">
<li>Home</li>
<li>Pages</li>
<li>Product</li>
<li>Article</li>
<li>contact</li>
</ul>
</div>
<div className="flex items-center gap-4">
<div className="relative h-10 w-10 p-2">
<Image
className="z-0 inset-0"
src="https://cdn.prod.website-files.com/65478d390c8996a757a4faaa/659e5cb3e032b694713f63c0_Add%20to%20basket.svg"
width={20}
height={20}
alt="cart-icon"
/>
<div className="z-20 absolute shadow-xl top-1 -right-[2px] bg-gray-200 text-black rounded-full text-[0.6em] h-[1.1rem] w-[1.1rem] grid place-content-center font-bold">
5
</div>
</div>
<button className="border flex items-center justify-center border-slate-400 hover:focus-within:none bg-black text-white rounded-full px-4 py-1.5 hover:bg-white transition-all ease-in hover:text-black">
Login
</button>
<Humburger onClick={() => setIsNavbarActive(!isNavbarActive)} />
</div>
</div>
</div>
<AnimatePresence mode="popLayout">
{isNavbarActive && <NavbarDropdown />}
</AnimatePresence>
</>
);
};
export default Navbar;
const NavbarDropdown = () => {
const dropdownVariants = {
initial: {
opacity: 0,
height: 0,
},
enter: {
opacity: 1,
height: "14rem",
transition: {
ease: [0.83, 0, 0.17, 1],
duration: 0.5,
staggerChildren: 0.1,
staggerDirection: 1,
delayChildren: 0.2,
// Stagger the li children after the dropdown opens
},
},
exit: {
height: 0,
transition: {
ease: [0.83, 0, 0.17, 1],
staggerChildren: 0.1, // Stagger the li children on exit as well
staggerDirection: -1,
delay: 0.3,
// Reverse the direction (exit from bottom to top)
// Delay before shrinking the dropdown after the li items
},
},
};
const liVariants = {
initial: {
opacity: 0,
x: -50,
},
enter: {
opacity: 1,
x: 0,
transition: {
ease: [0.22, 1, 0.36, 1],
duration: 0.5,
},
},
exit: {
opacity: 0,
x: -50,
transition: {
ease: [0.22, 1, 0.36, 1],
},
},
};
return (
<motion.div
variants={dropdownVariants}
initial="initial"
animate="enter"
exit="exit"
className="absolute origin-top top-[4.6rem] w-full bg-white drop-shadow-lg"
>
<motion.ul className="space-y-5 px-3">
{navlins.map((navlin, index) => (
<motion.li
custom={index}
key={index}
variants={liVariants} // Apply variants to each li item
className="text-gray-600 text-[1em] font-normal flex items-center gap-5"
>
{navlin}
{index === 0 || index === 1 ? (
<IoMdArrowDown size={24} color="gray" />
) : (
<></>
)}
</motion.li>
))}
</motion.ul>
</motion.div>
);
};
Humburger Component code
"use client";
import { useAnimate } from "framer-motion";
import React, { forwardRef, useState } from "react";
const Humburger = forwardRef<HTMLDivElement, { onClick: () => void }>(
({ onClick }, forwardedRef) => {
const [scope, animate] = useAnimate();
const [isHumburgOpen, setIsHumburgOpen] = useState(false);
const [isAnimating, setIsAnimating] = useState(false);
const animateHumburger = async () => {
if (isAnimating) return null;
setIsAnimating(!isAnimating);
onClick();
if (!isHumburgOpen) {
animate(
"#top",
{
y: "0.45rem",
},
{
ease: [0.36, 0, 0.66, -0.56],
duration: 0.5,
}
);
await animate(
"#bottom",
{
y: `-${0.45}rem`,
opacity: 0,
},
{
ease: [0.36, 0, 0.66, -0.56],
duration: 0.5,
}
);
animate(
"#top",
{
rotate: "136deg",
},
{
ease: [0.22, 1, 0.36, 1],
duration: 0.5,
}
);
animate(
"#middle",
{
rotate: "43deg",
},
{
ease: [0.22, 1, 0.36, 1],
duration: 0.5,
}
);
setIsHumburgOpen(!isHumburgOpen);
} else {
animate(
"#top",
{
rotate: "0deg",
},
{
ease: [0.36, 0, 0.66, -0.56],
duration: 0.5,
}
);
await animate(
"#middle",
{
rotate: "0deg",
},
{
ease: [0.36, 0, 0.66, -0.56],
duration: 0.5,
}
);
animate(
"#top",
{
y: "0rem",
},
{
ease: [0.22, 1, 0.36, 1],
duration: 0.5,
}
);
await animate(
"#bottom",
{
y: 0,
opacity: 1,
},
{
ease: [0.22, 1, 0.36, 1],
duration: 0.5,
}
);
setIsHumburgOpen(!isHumburgOpen);
}
setIsAnimating(false);
};
return (
<div ref={scope}>
<a
onClick={animateHumburger}
className="hover:focus-within:outline-none hover:focus:outline-none flex flex-col items-end justify-center h-24 gap-1 mt-50 cursor-pointer"
>
<span
id="top"
className="origin-center h-[3px] w-6 bg-black block rounded-full"
></span>
<span
id="middle"
className="h-[3px] w-6 bg-black rounded-full"
></span>
<span
id="bottom"
className="h-[3px] w-4 bg-black rounded-full"
></span>
</a>
</div>
);
}
);
export default Humburger;
the function animateHumburger is doing simply animating three vertical bar to a cross. am using framermotion to animate and am using useAnimate hook where i have to pass scope to the main container and id to sub element through which i will use in useanimatehook to animate them. after this humburger component is finished i have used it in navbar component but i get error .however the ref is being used in humburger component itself why i need to pass the useRef hook as suggested by chatgpt though it also didnot slove the problem . with function component cannot be given refs what does it actually mean ?? even in react doc they have used in functional component or am i getting it wrong. please correct me and help me to solve the issue ??
Also give me solution to the problem how to fix this error
The problem is that NavbarDropdown
is not wrapped in forwardRef
.
The docs state:
Custom component note: When using popLayout mode, any immediate child of AnimatePresence that's a custom component must be wrapped in React's forwardRef function, forwarding the provided ref to the DOM node you wish to pop out of the layout.
You are using a custom component (NavbarDropdown
) as the immediate child of AnimatePresence
and you are also using popLayout
mode:
<AnimatePresence mode="popLayout">
{isNavbarActive && <NavbarDropdown />}
</AnimatePresence>
So this constraint applies to you.
To fix, wrap NavbarDropdown
with forwardRef
and apply that ref on the element you want to "pop". I have assumed here it's the first element within NavbarDropdown
.
const NavbarDropdown = forwardRef<HTMLDivElement, {}>((props, ref) => {
const dropdownVariants = {
initial: {
opacity: 0,
height: 0,
},
enter: {
opacity: 1,
height: "14rem",
transition: {
ease: [0.83, 0, 0.17, 1],
duration: 0.5,
staggerChildren: 0.1,
staggerDirection: 1,
delayChildren: 0.2,
// Stagger the li children after the dropdown opens
},
},
exit: {
height: 0,
transition: {
ease: [0.83, 0, 0.17, 1],
staggerChildren: 0.1, // Stagger the li children on exit as well
staggerDirection: -1,
delay: 0.3,
// Reverse the direction (exit from bottom to top)
// Delay before shrinking the dropdown after the li items
},
},
};
const liVariants = {
initial: {
opacity: 0,
x: -50,
},
enter: {
opacity: 1,
x: 0,
transition: {
ease: [0.22, 1, 0.36, 1],
duration: 0.5,
},
},
exit: {
opacity: 0,
x: -50,
transition: {
ease: [0.22, 1, 0.36, 1],
},
},
};
return (
<motion.div
ref={ref}
variants={dropdownVariants}
initial="initial"
animate="enter"
exit="exit"
className="absolute origin-top top-[4.6rem] w-full bg-white drop-shadow-lg"
>
<motion.ul className="space-y-5 px-3">
{navlins.map((navlin, index) => (
<motion.li
custom={index}
key={index}
variants={liVariants} // Apply variants to each li item
className="text-gray-600 text-[1em] font-normal flex items-center gap-5"
>
{navlin}
{index === 0 || index === 1 ? (
<IoMdArrowDown size={24} color="gray" />
) : (
<></>
)}
</motion.li>
))}
</motion.ul>
</motion.div>
);
});