My navbar is setup such that on state change the hamburger menu opens and closes. While the enter animation works perfectly, the leave doesn't. My animation is a smooth slide in and slide out, but only the slide in works whereas on leave it just closes normally.
const NavbarMenu = ({ isOpen, menuClick }) => {
return (
<Transition appear={true} show={isOpen}>
<Transition.Child
class="flex flex-col bg-yellow-700 fixed top-0 right-0 p-5 z-20 w-1/2 h-full transition ease-in-out duration-300"
enter="transition-opacity ease-in-out duration-700"
enterFrom="translate-x-full"
enterTo="translate-x-0"
leave="transition-opacity ease-out duration-700"
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<Exit className="text-yellow-100 w-1/6" onClick={() => menuClick()} />
<div className="flex flex-col gap-y-4 mt-10 font-poppins font-bold text-xl text-yellow-100">
<span>About</span>
<span>About</span>
<span>About</span>
</div>
</Transition.Child>
</Transition>
);
};
Your leave transition is missing the translate-x classes.
You probably also want to add the opacity transition to the enter animation.
This should work:
const NavbarMenu = ({ isOpen, menuClick }) => {
return (
<Transition appear={true} show={isOpen}>
<Transition.Child
class="flex flex-col bg-yellow-700 fixed top-0 right-0 p-5 z-20 w-1/2 h-full transition duration-700"
enter="ease-in-out"
enterFrom="translate-x-full opacity-0"
enterTo="translate-x-0 opacity-100"
leave="ease-out"
leaveFrom="translate-x-0 opacity-100"
leaveTo="translate-x-full opacity-0"
>
<Exit className="text-yellow-100 w-1/6" onClick={() => menuClick()} />
<div className="flex flex-col gap-y-4 mt-10 font-poppins font-bold text-xl text-yellow-100">
<span>About</span>
<span>About</span>
<span>About</span>
</div>
</Transition.Child>
</Transition>
);
};
Most often when "leave transitions" doesn't work it's because it unmounts before the transition ends. To work around that you need a "between state" that is triggered when the Transition is in show mode. Then create a useEffect that listens to this state, and set a setTimeout that does the actual unmount of the element.
I found a solution, just delay the execution of set state to 0ms so the transition can trigger the leave property. I don't have a logical explanation but this worked smoothly.
const NavbarMenu = ({ isOpen, menuClick }) => {
return (
<Transition appear={true} show={isOpen}>
<Transition.Child
class="flex flex-col bg-yellow-700 fixed top-0 right-0 p-5 z-20 w-1/2 h-full transition ease-in-out duration-300"
enter="transition-opacity ease-in-out duration-700"
enterFrom="translate-x-full"
enterTo="translate-x-0"
leave="transition-opacity ease-out duration-700"
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<Exit className="text-yellow-100 w-1/6"
onClick={() =>
setTimeout(() => {
menuClick();
}, 0);
} />
<div className="flex flex-col gap-y-4 mt-10 font-poppins font-bold text-xl text-yellow-100">
<span>About</span>
<span>About</span>
<span>About</span>
</div>
</Transition.Child>
</Transition>
);
};
Related
I kept getting this error: Error: Hydration failed because the initial UI does not match what was rendered on the server.
Here is my code:
import { Fragment } from "react";
import { Menu, Transition } from "#headlessui/react";
import { ChevronDownIcon } from "#heroicons/react/20/solid";
import React from "react";
import { BsFillRecordCircleFill, BsFunnel } from "react-icons/bs";
import { FiCircle } from "react-icons/fi";
import Link from "next/link";
function classNames(...classes) {
return classes.filter(Boolean).join(" ");
}
const Filter = () => {
return (
<div className="">
{/* Menu */}
<Menu as="div" className="relative inline-block text-left">
<div>
<Menu.Button className="inline-flex w-full justify-center rounded-md border border-gray-300 bg-[#1F3C71] text-white px-4 py-3 text-sm font-medium shadow-sm focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 focus:ring-offset-gray-100 gap-1 items-center">
<BsFunnel />
Filter
<p className="bg-white text-[#1F3C71] rounded-full w-5 h-5">2</p>
</Menu.Button>
</div>
<Transition
as={Fragment}
enter="transition ease-out duration-100"
enterFrom="transform opacity-0 scale-95"
enterTo="transform opacity-100 scale-100"
leave="transition ease-in duration-75"
leaveFrom="transform opacity-100 scale-100"
leaveTo="transform opacity-0 scale-95"
>
<Menu.Items className="absolute z-10 mt-2 w-56 origin-top-right rounded-md bg-white shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none">
<div className="py-1">
<div className="flex items-center justify-between py-2 px-5">
<p className="font-bold">Filters</p>
<p className="font-semibold text-[#1F3C71]">Apply</p>
</div>
<hr />
<Menu.Item>
{({ active }) => (
<main className="flex items-center">
<BsFillRecordCircleFill className="ml-6 text-[#1F3C71]" />
<Link
href="#"
className={classNames(
active ? "bg-white text-gray-900" : "text-gray-700",
"block px-4 py-2 text-sm"
)}
>
Status
</Link>
</main>
)}
</Menu.Item>
<Menu.Item>
{({ active }) => (
<main className="flex items-center">
<FiCircle className="ml-6" />
<Link
href="#"
className={classNames(
active ? "bg-white text-gray-900" : "text-gray-700",
"block px-4 py-2 text-sm"
)}
>
Email
</Link>
</main>
)}
</Menu.Item>
<Menu.Item>
{({ active }) => (
<main className="flex items-center">
<BsFillRecordCircleFill className="ml-6 text-[#1F3C71]" />
<Link
href="#"
className={classNames(
active ? "bg-white text-gray-900" : "text-gray-700",
"block px-4 py-2 text-sm"
)}
>
Data Range
</Link>
</main>
)}
</Menu.Item>
</div>
</Menu.Items>
</Transition>
</Menu>
</div>
);
};
export default Filter;
I try to change the Link tag but still having the issue.
I also got this error as well: There was an error while hydrating. Because the error happened outside of a Suspense boundary, the entire root will switch to client rendering.
my div is overflow-y-auto. scrooltotop not working.
const scrollToTop = () => {
window.scrollTo({
top: 0,
behavior: "smooth",
});
};
<div className="sticky z-30 top-0 h-screen overflow-y-auto inset-0 w-full bg-slate-100">
<article className="prose px-5 py-2 my-4 bg-white rounded-md min-h-screen">
<MDXProvider>
<Post />
</MDXProvider>
</article>
<button type="button" className="text-sky-900 fixed right-10 bottom-10 p-1" onClick={()=>scrolltoTop}>
top
</button>
</div>
I also tried it as a divi reference, it still didn't work.
const scrollable = useRef<HTMLDivElement>(null);
<div className="sticky z-30 top-0 h-screen overflow-y-auto inset-0 w-full bg-slate-100" id="myElement" ref={scrollable}>
how can i get to the top in such a situation?
now working
const scrollable = React.useRef() as React.MutableRefObject;
const handleScrollTo = () => { scrollable.current.scrollTo({top:0, behavior: "smooth"}); };
In my Jest tests, I'd like to mock the animations of the Transition and Dialog components of #headlessui/react to speed up my tests. I currently just resort to await screen.findBy's to wait for the animations to end but test suite runtime is becoming a problem as I add more tests.
This is the mock I've tried so far:
jest.mock("#headlessui/react", () => ({
Transition: ({ children, show }) => <>{show ? children : ""}</>,
}));
This works for simpler components such as:
// SimpleTransition.js
function SimpleTransition() {
const [isShowing, setIsShowing] = useState(false);
return (
<div>
<button onClick={() => setIsShowing((isShowing) => !isShowing)}>
Toggle
</button>
<Transition
show={isShowing}
enter="transition-opacity duration-75"
enterFrom="opacity-0"
enterTo="opacity-100"
leave="transition-opacity duration-150"
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
I will fade in and out
</Transition>
</div>
);
}
But if I try it in Modal.js, it throws the ff error:
TypeError: Cannot read properties of undefined (reading 'Overlay')
at MyModal src/Modal.js:15:2
at Object.<anonymous> src/Modal.test.js:35:2
Modal.js uses both the Dialog and Transition components:
// Modal.js
import { Dialog, Transition } from "#headlessui/react";
import { Fragment, useState } from "react";
export function MyModal() {
let [isOpen, setIsOpen] = useState(false);
function closeModal() {
setIsOpen(false);
}
function openModal() {
setIsOpen(true);
}
return (
<>
<div className="fixed inset-0 flex items-center justify-center">
<button
type="button"
onClick={openModal}
className="px-4 py-2 text-sm font-medium text-white bg-black rounded-md bg-opacity-20 hover:bg-opacity-30 focus:outline-none focus-visible:ring-2 focus-visible:ring-white focus-visible:ring-opacity-75"
>
Open dialog
</button>
</div>
<Transition appear show={isOpen} as={Fragment}>
<Dialog
as="div"
className="fixed inset-0 z-10 overflow-y-auto"
onClose={closeModal}
>
<div className="min-h-screen px-4 text-center">
<Transition.Child
as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0"
enterTo="opacity-100"
leave="ease-in duration-200"
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<Dialog.Overlay className="fixed inset-0" />
</Transition.Child>
{/* This element is to trick the browser into centering the modal contents. */}
<span
className="inline-block h-screen align-middle"
aria-hidden="true"
>
</span>
<Transition.Child
as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0 scale-95"
enterTo="opacity-100 scale-100"
leave="ease-in duration-200"
leaveFrom="opacity-100 scale-100"
leaveTo="opacity-0 scale-95"
>
<div className="inline-block w-full max-w-md p-6 my-8 overflow-hidden text-left align-middle transition-all transform bg-white shadow-xl rounded-2xl">
<Dialog.Title
as="h3"
className="text-lg font-medium leading-6 text-gray-900"
>
Payment successful
</Dialog.Title>
<div className="mt-2">
<p className="text-sm text-gray-500">
Your payment has been successfully submitted. We’ve sent you
an email with all of the details of your order.
</p>
</div>
<div className="mt-4">
<button
type="button"
className="inline-flex justify-center px-4 py-2 text-sm font-medium text-blue-900 bg-blue-100 border border-transparent rounded-md hover:bg-blue-200 focus:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-blue-500"
onClick={closeModal}
>
cancel
</button>
</div>
</div>
</Transition.Child>
</div>
</Dialog>
</Transition>
</>
);
}
Here's the test:
//Modal.test.js
import { render, screen } from "#testing-library/react";
import userEvent from "#testing-library/user-event";
import "#testing-library/jest-dom/extend-expect";
import { MyModal } from "./Modal";
// Mock IntersectionObserver
class IntersectionObserver {
observe = jest.fn();
disconnect = jest.fn();
unobserve = jest.fn();
}
Object.defineProperty(window, "IntersectionObserver", {
writable: true,
configurable: true,
value: IntersectionObserver,
});
Object.defineProperty(global, "IntersectionObserver", {
writable: true,
configurable: true,
value: IntersectionObserver,
});
// This is what's causing the issue
jest.mock("#headlessui/react", () => ({
Transition: ({ children, show }) => <>{show ? children : ""}</>,
}));
test("Modal Test", () => {
render(<MyModal />);
userEvent.click(screen.getByRole("button", { name: /open dialog/i }));
expect(screen.getByRole("dialog")).toBeInTheDocument();
userEvent.click(screen.getByRole("button", { name: /cancel/i }));
expect(screen.queryByRole("dialog")).not.toBeInTheDocument();
});
I've tried searching their docs and repository, but there doesn't seem to be any docs on it.
I would like to implement a transition with tailwincss , on 'active' state change, I mean when the menu is shown/collapsed i would like to implement a smooth and pleasing transition
I tried adding it on the state change by adding transition delay-150 duration-300 ease-in-out but i couldn't make it work.
import Link from "next/link";
import { useState } from "react";
import HamburgerMenu from "react-hamburger-menu";
import dynamic from "next/dynamic";
const NavLink = dynamic(() => import("./NavLink"));
export const Navbar = () => {
const [active, setActive] = useState(false);
const [isOpen, setIsOpen] = useState(false);
const handleClick = () => {
setActive(!active);
setIsOpen(!isOpen);
};
const handleClose = () => {
setActive(false);
setIsOpen(false);
};
return (
<nav className="sticky top-0 z-10 flex flex-wrap items-center px-3 py-3 bg-white md:py-3 container bg-red-200">
<div className="flex flex-wrap items-center justify-between w-full">
<Link href="/">
<a className="inline-flex items-center">
<span className="text-xl font-bold tracking-wide text-black tahu">
Agoumi.
</span>
</a>
</Link>
<div className="inline-flex p-0 ml-auto text-xl rounded-full outline-none hover:shadow-sm hover:bg-gray-100 hover:text-black">
<HamburgerMenu
isOpen={isOpen}
menuClicked={handleClick}
width={20}
height={15}
strokeWidth={2}
rotate={0}
color="black"
// borderRadius={15}
animationDuration={1}
className="m-3"
/>
</div>
</div>
<div
className={`${
active ? "transition delay-150 duration-300 ease-in-out" : "hidden"
} w-full`}
>
<div className="flex flex-col items-start w-full align-center">
<NavLink close={handleClose} to="/" linkName="Home" isOpen={false} />
<NavLink
close={handleClose}
to="about"
linkName="About Me"
isOpen={false}
/>
<NavLink
close={handleClose}
to="value"
linkName="Values"
isOpen={false}
/>
<NavLink
close={handleClose}
to="projects"
linkName="Projects"
isOpen={false}
/>
<NavLink
close={handleClose}
to="contact"
linkName="Contact us"
isOpen={false}
/>
</div>
</div>
</nav>
);
};
export default Navbar;
The problem is that the display property (via the hidden class) cannot be animated, you can see the whole list of animatable properties here.
You can animate height, but it requires specifying the full menu height explicitly (auto will not animate), so h-32 in the code below is just an example, you may need to change it depending on your exact circumstances.
Here's an example:
<div
className={`${
active ? 'h-32' : 'h-0'
} transition-all delay-150 duration-300 overflow-hidden w-full`}
>
...
Note that transition-all is used, simple transition will not animate height, and ease-in-out is removed, as it is redundant, the same timing function is already included in transition-all.
how would it be possible to display a hidden menu through a hover. im getting confused as to how a hidden element can be connected to a visible element. this example is on the headlessui website. however instead of clicking on it, would it be possible to hover to show the popover?
return (
<div className='fixed w-full max-w-sm px-4 top-16'>
<Popover className='relative'>
{({ open }) => (
<>
<Popover.Button></Popover.Button>
<Transition>
<Popover.Panel className='absolute z-10 w-screen max-w-sm px-4 mt-3 transform -translate-x-1/2 left-1/2 sm:px-0 lg:max-w-3xl'></Popover.Panel>
</Transition>
</>
)}
</Popover>
</div>
);
Try using the onMouse in Events
return (
<div onMouseEnter={() => setOpen(true)}
onMouseLeave={() => setOpen(false)}
className='fixed w-full max-w-sm px-4 top-16'>
<Popover className='relative'>
{({ open }) => (
<>
<Popover.Button></Popover.Button>
<Transition>
<Popover.Panel className='absolute z-10 w-screen max-w-sm px-4 mt-3 transform -translate-x-1/2 left-1/2 sm:px-0 lg:max-w-3xl'></Popover.Panel>
</Transition>
</>
)}
</Popover>
</div>
);