I am trying to create a slide-over navbar or slide menu with panels that open on top of eachother (I haven't found the best description yet as how to describe it).
Basically the idea is to have a slide menu that has sub-menu items that slide in (on top of eachother) as well. When a sub-menu item has been opened have the 'Back' button to go back to the main menu.
Here's a link with full code example: https://codesandbox.io/p/sandbox/over-lay-menu-n92xys
If you take a look at my SlidePanel.js
you can see how I am trying to pass props to SlidePanelLayer.js
hoping this would achieve that but in practice it does not work well:
"use client";
import { Fragment, useState } from "react";
import { Dialog, Transition } from "@headlessui/react";
import { XMarkIcon } from "@heroicons/react/24/outline";
import { MagnifyingGlassIcon } from "@heroicons/react/20/solid";
import { menuItems } from "../data/menu";
import SlidePanelLayer from "./SlidePanelLayer";
export default function SlidePanel({ slideOpen, setSlideOpen }) {
const [openLayer, setOpenLayer] = useState(false);
return (
<>
<SlidePanelLayer
openLayer={openLayer}
setOpenLayer={setOpenLayer}
setSlideOpen={setSlideOpen}
/>
<Transition.Root show={slideOpen} as={Fragment}>
<Dialog as="div" className="relative z-50" onClose={setSlideOpen}>
<Transition.Child
as={Fragment}
enter="ease-in-out duration-500"
enterFrom="opacity-0"
enterTo="opacity-100"
leave="ease-in-out duration-500"
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<div className="fixed inset-0 bg-gray-500 bg-opacity-75 transition-opacity" />
</Transition.Child>
<div className="fixed inset-0 overflow-hidden">
<div className="absolute inset-0 overflow-hidden">
<div className="pointer-events-none fixed inset-y-0 right-0 flex max-w-full pl-10">
<Transition.Child
as={Fragment}
enter="transform transition ease-in-out duration-500 sm:duration-700"
enterFrom="translate-x-full"
enterTo="translate-x-0"
leave="transform transition ease-in-out duration-500 sm:duration-700"
leaveFrom="translate-x-0"
leaveTo="translate-x-full"
>
<Dialog.Panel className="pointer-events-auto w-screen max-w-md">
<div className="flex h-full flex-col overflow-y-scroll bg-white py-6 shadow-xl rounded-l-2xl">
<div className="px-4 sm:px-6">
<div className="flex items-start justify-between">
<div className="flex h-7 items-center">
<button
type="button"
className="flex gap-2 items-center relative rounded-md bg-white text-gray-400 hover:text-gray-500"
onClick={() => setSlideOpen(false)}
>
<span className="sr-only">Close</span>
<XMarkIcon
className="h-5 w-5 text-secondary-90"
aria-hidden="true"
/>
<span className="text-gray-900 font-bold">
Close
</span>
</button>
</div>
</div>
</div>
<div className="relative mt-6 flex-1 px-4 sm:px-6">
{/* Start content */}
<div className="mt-6 flow-root">
<div className="-my-6">
<div className="space-y-2 pt-6 pb-4">
{menuItems.map((item) => (
<a
key={item.title}
href={item.href}
onClick={() => setOpenLayer(true)}
className="-mx-3 block rounded-lg px-3 py-2 text-base leading-7 text-secondary-90 hover:bg-gray-50 font-bold"
>
{item.title}
</a>
))}
</div>
</div>
</div>
{/* End content */}
</div>
</div>
</Dialog.Panel>
</Transition.Child>
</div>
</div>
</div>
</Dialog>
</Transition.Root>
</>
);
}
Header.js:
"use client";
import { useState } from "react";
import { Bars3Icon } from "@heroicons/react/24/outline";
import SlidePanel from "@/components/SlidePanel";
export default function HeaderComponent() {
const [activeNavbar, setActiveNavbar] = useState("");
return (
<header className="relative isolate z-40">
<div className="flex lg:flex-1 justify-between items-center mx-auto max-w-7xl px-6 lg:px-8 py-4 lg:py-8">
<a href="/" className="-m-1.5 p-1.5">
<span className="sr-only">Over-lay</span>
<span>Over-lay menu</span>
</a>
<div className="flex">
<button
type="button"
className="-m-2.5 inline-flex items-center justify-center rounded-md p-2.5 text-gray-700"
onClick={() => setActiveNavbar("mainMenu")}
>
<span className="sr-only">Open menu</span>
<Bars3Icon className="h-6 w-6" aria-hidden="true" />
</button>
</div>
</div>
<SlidePanel
activeNavbar={activeNavbar}
setActiveNavbar={setActiveNavbar}
/>
</header>
);
}
SlidePannel.js:
"use client";
import { Fragment, useState } from "react";
import { Dialog, Transition } from "@headlessui/react";
import { XMarkIcon } from "@heroicons/react/24/outline";
import { MagnifyingGlassIcon } from "@heroicons/react/20/solid";
import { menuItems } from "../data/menu";
import SlidePanelLayer from "./SlidePanelLayer";
export default function SlidePanel({ activeNavbar, setActiveNavbar }) {
// const [openLayer, setOpenLayer] = useState(false);
return (
<>
<SlidePanelLayer
activeNavbar={activeNavbar}
setActiveNavbar={setActiveNavbar}
/>
<Transition.Root show={!!activeNavbar} as={Fragment}>
<Dialog
as="div"
className="relative z-50"
onClose={() => setActiveNavbar("")}
>
<Transition.Child
as={Fragment}
enter="ease-in-out duration-500"
enterFrom="opacity-0"
enterTo="opacity-100"
leave="ease-in-out duration-500"
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<div className="fixed inset-0 bg-gray-500 bg-opacity-75 transition-opacity" />
</Transition.Child>
<div className="fixed inset-0 overflow-hidden">
<div className="absolute inset-0 overflow-hidden">
<div className="pointer-events-none fixed inset-y-0 right-0 flex max-w-full pl-10">
<Transition.Child
as={Fragment}
enter="transform transition ease-in-out duration-500 sm:duration-700"
enterFrom="translate-x-full"
enterTo="translate-x-0"
leave="transform transition ease-in-out duration-500 sm:duration-700"
leaveFrom="translate-x-0"
leaveTo="translate-x-full"
>
<Dialog.Panel className="pointer-events-auto w-screen max-w-md">
<div className="flex h-full flex-col overflow-y-scroll bg-white py-6 shadow-xl rounded-l-2xl">
<div className="px-4 sm:px-6">
<div className="flex items-start justify-between">
<div className="flex h-7 items-center">
<button
type="button"
className="flex gap-2 items-center relative rounded-md bg-white text-gray-400 hover:text-gray-500"
onClick={() => setActiveNavbar("")}
>
<span className="sr-only">Close</span>
<XMarkIcon
className="h-5 w-5 text-secondary-90"
aria-hidden="true"
/>
<span className="text-gray-900 font-bold">
Close
</span>
</button>
</div>
</div>
</div>
<div className="relative mt-6 flex-1 px-4 sm:px-6">
{/* Start content */}
<div className="mt-6 flow-root">
<div className="-my-6">
<div className="space-y-2 pt-6 pb-4">
{menuItems.map((item) => (
<a
key={item.title}
href={item.href}
onClick={() => setActiveNavbar(item.title)}
className="-mx-3 block rounded-lg px-3 py-2 text-base leading-7 text-secondary-90 hover:bg-gray-50 font-bold"
>
{item.title}
</a>
))}
</div>
</div>
</div>
{/* End content */}
</div>
</div>
</Dialog.Panel>
</Transition.Child>
</div>
</div>
</div>
</Dialog>
</Transition.Root>
</>
);
}
SlidePannelLayer.js:
import { Fragment, useState } from "react";
import { Dialog, Transition } from "@headlessui/react";
import { ChevronLeftIcon } from "@heroicons/react/20/solid";
import { about } from "../data/menu";
export default function SlidePanelLayer({ activeNavbar, setActiveNavbar }) {
// const [open, setOpen] = useState(true);
return (
<Transition.Root
show={!!activeNavbar && activeNavbar !== "mainMenu"}
as={Fragment}
>
<Dialog
as="div"
className="relative z-50"
onClose={() => {
setActiveNavbar("mainMenu");
}}
>
{/* <Transition.Child
as={Fragment}
enter="ease-in-out duration-500"
enterFrom="opacity-0"
enterTo="opacity-100"
leave="ease-in-out duration-500"
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<div className="fixed inset-0 bg-gray-500 bg-opacity-75 transition-opacity" />
</Transition.Child> */}
<div className="fixed inset-0 overflow-hidden">
<div className="absolute inset-0 overflow-hidden">
<div className="pointer-events-none fixed inset-y-0 right-0 flex max-w-full pl-10">
<Transition.Child
as={Fragment}
enter="transform transition ease-in-out duration-500 sm:duration-700"
enterFrom="translate-x-full"
enterTo="translate-x-0"
leave="transform transition ease-in-out duration-500 sm:duration-700"
leaveFrom="translate-x-0"
leaveTo="translate-x-full"
>
<Dialog.Panel className="pointer-events-auto w-screen max-w-md">
<div className="flex h-full flex-col overflow-y-scroll bg-white py-6 shadow-xl rounded-l-2xl">
<div className="px-4 sm:px-6">
<div className="flex items-start justify-between">
<div className="flex h-7 items-center">
<button
type="button"
className="flex gap-2 items-center relative rounded-md bg-white text-gray-400 hover:text-gray-500"
onClick={() => setActiveNavbar("mainMenu")}
>
<span className="sr-only">Back</span>
<ChevronLeftIcon
className="h-5 w-5 text-secondary-90"
aria-hidden="true"
/>
<span className="text-gray-900 font-bold">
Back
</span>
</button>
</div>
</div>
<div className="text-secondary-90 font-bold mt-6">
About
</div>
<hr className="my-6" />
</div>
<div className="relative flex-1 px-4 sm:px-6">
{about.map((item) => (
<div
key={item.title}
className="relative pb-12 leading-6"
>
<h2 className="mt-1 text-secondary-90 text-2xl font-bold">
{item.title}
</h2>
<p className="mt-1 mb-6 text-secondary-90">
{item.description}
</p>
{item.links.map((link) => (
<a
key={link.title}
href={link.href}
className="mt-4 block text-secondary-90 hover:text-secondary-100 group"
>
<span className="flex items-center">
{link.icon && (
<link.icon
className="flex-none w-5 h-5 text-gray-400"
aria-hidden="true"
/>
)}
<span className="ml-2 font-bold group-hover:underline">
{link.title}
</span>
</span>
<span className="block text-sm">
{link.description}
</span>
</a>
))}
</div>
))}
</div>
</div>
</Dialog.Panel>
</Transition.Child>
</div>
</div>
</div>
</Dialog>
</Transition.Root>
);
}