I'm making a react app with tailwindcss, and I want to make a hidden mobile navbar and when the user click on the icon it appears.
So I want to make a transition while the menu appears.
I use:
React
Tailwindcss
Headlessui
My Code:
MobileMenu.js:
function MobileMenu() {
return (
<div className="block md:hidden px-4 py-3 text-white w-full bg-gray-800 border-t border-opacity-70 border-slate-700">
<div className="flex items-center mb-3 pb-3 border-b border-slate-700">
<img
src="https://africaprime.com/wp-content/uploads/2020/04/ElonMusk.jpg"
className="rounded-full w-8 h-8 cursor-pointer"
/>
<h6 className="ml-5 cursor-pointer">Elon Musk</h6>
</div>
<div className="mobile-nav-icon">
<ImHome size={20} />
<h4 className="ml-5">Home</h4>
</div>
<div className="mobile-nav-icon">
<HiUsers size={20} />
<h4 className="ml-5">Friends</h4>
</div>
<div className="mobile-nav-icon">
<CgProfile size={20} />
<h4 className="ml-5">My Profile</h4>
</div>
</div>
);
}
export default MobileMenu;
How I show it in Navbar.js:
function Navbar() {
const [mobileMenuOpen, setMobileMenuOpen] = useState(false);
return (
<>
<nav className="flex justify-between items-center px-4 lg:px-8 py-3 bg-gray-900 text-white">
{/* Mobile Menu Icon */}
<div
className="block md:hidden p-2 cursor-pointer rounded-full hover:bg-gray-700 transition-2"
onClick={() => setMobileMenuOpen(!mobileMenuOpen)}
>
<FiMenu size={20} />
</div>
</nav>
{/* Mobile Menu */}
{mobileMenuOpen && <MobileMenu />}
</>
);
}
export default Navbar;
Thanks in advance!
You can use #framer/motion package that allows you easily animate elements.
const menuVariants = {
open: {
opacity: 1,
x: 0,
},
closed: {
opacity: 0,
x: '-100%',
},
}
Animations can be changed however you want according to #framer/motion docs.
And attach variants to your <MobileMenu /> component.
function MobileMenu({isMenuOpen}) {
return (
<motion.div animate={isMenuOpen ? 'open' : 'closed'}
variants={menuVariants}> className="block md:hidden px-4 py-3 text-white w-full bg-gray-800 border-t border-opacity-70 border-slate-700">
<div className="flex items-center mb-3 pb-3 border-b border-slate-700">
<img
src="https://africaprime.com/wp-content/uploads/2020/04/ElonMusk.jpg"
className="rounded-full w-8 h-8 cursor-pointer"
/>
<h6 className="ml-5 cursor-pointer">Elon Musk</h6>
</div>
<div className="mobile-nav-icon">
<ImHome size={20} />
<h4 className="ml-5">Home</h4>
</div>
<div className="mobile-nav-icon">
<HiUsers size={20} />
<h4 className="ml-5">Friends</h4>
</div>
<div className="mobile-nav-icon">
<CgProfile size={20} />
<h4 className="ml-5">My Profile</h4>
</div>
</div>
);
}
export default MobileMenu;
And you can pass isMenuOpen variable as a prop.
function Navbar() {
const [mobileMenuOpen, setMobileMenuOpen] = useState(false);
return (
<>
<nav className="flex justify-between items-center px-4 lg:px-8 py-3 bg-gray-900 text-white">
{/* Mobile Menu Icon */}
<div
className="block md:hidden p-2 cursor-pointer rounded-full hover:bg-gray-700 transition-2"
onClick={() => setMobileMenuOpen(!mobileMenuOpen)}
>
<FiMenu size={20} />
</div>
</nav>
{/* Mobile Menu */}
{mobileMenuOpen && <MobileMenu isMenuOpen={mobileMenuOpen}/>}
</>
);
}
export default Navbar;
One potential issue with the provided answer is that state is not being set correctly. See the React Docs about updating state based on prior state value.
<div className="block md:hidden p-2 cursor-pointer rounded-full hover:bg-gray-700 transition-2" onClick={() => setMobileMenuOpen(prevState => !prevState)}>
Related
enter image description here
I want to implement the above screenshot. When I click on the country dropdown, the default country name above plus the flag changes .
So far I have I have been using headless UI. The problem is that I dont know how to implement the logic in menu-item to change the default country and flag when dropdown is clicked, also the selectedactive country should be checked .Below is my code ,someone help`
import { Fragment, useState, useMemo, useEffect } from "react";
import Sidebar from "./sidebar";
import Image from "next/image";
import Link from "next/link";
import { Menu, Transition ,RadioGroup} from "#headlessui/react";
import { ChevronDownIcon } from "#heroicons/react/20/solid";
import { signOut, useSession } from "next-auth/react";
function classNames(...classes: any[]) {
return classes.filter(Boolean).join(" ");
}
const logout = () => {
signOut();
};
const LayoutContent = ({
children,
sidebar,
}: {
children: any;
sidebar: any;
}) => {
const { data: Session } = useSession();
const [active,setActive] = useState(' ')
return (
<>
<main
className={`max-w-screen-3xl mx-auto flex flex-col ${
sidebar ? "lg:pl-20" : "lg:pl-80"
}`}
>
<div className="pl-0 lg:mx-1">
<nav className="mx-1 lg:mx-1 relative w-full flex flex-wrap items-center justify-end py-1 text-gray-800 ">
<Menu as="div" className="relative inline-block text-left mr-5">
<div>
<Menu.Button className="inline-flex w-full items-center bg-white px-4 py-2 text-sm font-medium text-gray-700 ">
<Image
width={35}
height={35}
className="px-8 py-2.5"
src="/KE.svg"
alt="actions"
/>
<span className="ml-4 text-gray-700 text-[13px]">Kenya</span>
<ChevronDownIcon
className="-mr-1 ml-2 h-7 w-7"
aria-hidden="true"
/>
</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 right-0 z-10 mt-2 w-56 h-48 bg-white origin-top-right rounded-lg shadow-[0px_0px_28px_rgba(0,0,0,0.08)] ring-opacity-5 focus:outline-none">
<div className="py-1">
<span className="text-gray-500 ml-4 ">
Select Country
</span>
<Menu.Item>
{({ active}) => (
<a
href="#"
className={classNames(
active
? " flex items-center text-gray bg-[#FDE6D4] rounded mb-3 ml-4 mr-4 mt-2"
: "flex items-center text-gray-500 bg-gray-100 rounded mb-3 ml-4 mr-4 mt-2 ",
"block px-4 py-2 text-sm"
)}
>
<Image
width={25}
height={25}
className="px-8 py-2.5"
src="/KE.svg"
alt="actions"
/>
<span className="ml-4 text-[13px] ">Kenya</span>
</a>
)}
</Menu.Item>
<Menu.Item>
{({ active }) => (
<a
href="#"
className={classNames(
active
? " flex items-center text-gray bg-[#FDE6D4] rounded mb-3 ml-4 mr-4"
: "flex items-center text-gray-500 bg-gray-100 rounded mb-3 ml-4 mr-4 ",
"block px-4 py-2 text-sm"
)}
>
<Image
width={25}
height={25}
className="px-8 py-2.5"
src="/TZ (2).svg"
alt="actions"
/>
<span className="ml-4 text-[13px]">Tanzania</span>
</a>
)}
</Menu.Item>
<Menu.Item>
{({ active}) => (
<a
href="#"
className={classNames(
active
? " flex items-center text-gray bg-[#FDE6D4] rounded ml-4 mr-4"
: " flex items-center text-gray-500 bg-gray-100 rounded ml-4 mr-4",
"block px-4 py-2 text-sm"
)}
>
<Image
width={25}
height={25}
className="px-8 py-2.5"
src="/MZ.svg"
alt="actions"
/>
<span className="ml-4 text-[13px]">Mozambique</span>
</a>
)}
</Menu.Item>
</div>
</Menu.Items>
</Transition>
</Menu>
<p>
<span className="px-0 py-2 text-gray-700 font-medium text-xs focus:outline-none focus:ring-0 active:shadow-lg transition duration-150 ease-in-out flex items-center">
<Image
width={25}
height={25}
className="p-2 rounded-full"
src="/rectangle.svg"
alt="Profile"
/>
</span>
</p>
<p>
<span className="px-1 py-2 text-gray-700 font-medium text-xs focus:outline-none focus:ring-0 active:shadow-lg transition duration-150 ease-in-out flex items-center">
{Session ? <p>{Session!.user!.name}</p> : "..."}
</span>
</p>
<p>
<span className="px-0 py-2 text-gray-700 font-medium text-xs focus:outline-none focus:ring-0 active:shadow-lg transition duration-150 ease-in-out flex items-center mr-12">
<Image
width={13}
height={13}
className="p-2 rounded-full"
src="/dropdown.svg"
alt="Profile"
/>
</span>
</p>
</nav>
{children}
</div>
</main>
</>
);
};
export default LayoutContent;
`
I just implement headless UI without logic for updating the country and flag.What other options do I have ?
Are you looking to show the selected country at top right, you can try something like this
//data
const countries = [
{
id: 1,
src: "/KE.svg",
label: "Kenya",
},
{
id: 1,
src: "/TZ (2).svg",
label: "Tanzania",
}
{
id: 1,
src: "/MZ.svg",
label: "Mozambique",
}
]
Inside component
// keep selected state
const [selected, setSelected] = useState();
// show selected country
{
selected ?
(
<div>
<Menu.Button className="inline-flex w-full items-center bg-white px-4 py-2 text-sm font-medium text-gray-700 ">
<Image
width={35}
height={35}
className="px-8 py-2.5"
src={selected.src}
alt="actions"
/>
<span className="ml-4 text-gray-700 text-[13px]">{selected.label}</span>
<ChevronDownIcon
className="-mr-1 ml-2 h-7 w-7"
aria-hidden="true"
/>
</Menu.Button>
</div>
) : null
}
// display list of countries and on click select the country
<Menu.Items className="absolute right-0 z-10 mt-2 w-56 h-48 bg-white origin-top-right rounded-lg shadow-[0px_0px_28px_rgba(0,0,0,0.08)] ring-opacity-5 focus:outline-none">
<div className="py-1">
<span className="text-gray-500 ml-4 ">
Select Country
</span>
{
data.map(country => {
return (
<Menu.Item key={country.id}
// if on click does not work here move to <a>
onClick={e => setSelected(country)}
>
{({ active}) => (
<a
href="#"
className={classNames(
active
? " flex items-center text-gray bg-[#FDE6D4] rounded mb-3 ml-4 mr-4 mt-2"
: "flex items-center text-gray-500 bg-gray-100 rounded mb-3 ml-4 mr-4 mt-2 ",
"block px-4 py-2 text-sm"
)}
>
<Image
width={25}
height={25}
className="px-8 py-2.5"
src={country.src}
alt="actions"
/>
<span className="ml-4 text-[13px] ">{country.label}</span>
</a>
)}
</Menu.Item>
)
});
}
</div>
</Menu.Items>
Hope it helps you to find a better solution
So i try to open modal using props from react js..., i try to console log, i found that the props is passed but the modal component is not open.. here is my try:
const [open, setOpen] = useState(false);
const openModal = () => {
setOpen(!open);
};
const handleLogin = (e) => {
e.preventDefault();
openModal();
};
<div>
{/* container */}
<div className="bg-gradient-to-r from-yellow-500 block h-screen items-center justify-center p-4 pt-32 md:flex">
{<WarnModal props={open} />}
{/* login.card */}
<div className=" bg-no-repeat bg-left bg-image flex flex-col items-center max-w-screen-lg overflow-hidden rounded-lg shadow-lg text-white w-full md:flex-row">
{/* logo */}
<div className="backdrop-blur-sm backdrop-filter flex flex-col items-center justify-center p-4 text-dark w-full md:w-1/2"> </div></div>
here is whats looks like on the modal component:
const WarnModal = ({ props }) => {
console.log("props", props);
const [open, setOpen] = useState(false);
const showModal = () => {
setOpen(props);
};
const cancelButtonRef = useRef(null);
return (
<Transition appear={true} show={open} as={Fragment}>
<Dialog
as="div"
className="relative z-10"
initialFocus={cancelButtonRef}
onClose={setOpen}
>
<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"
>
<div className="fixed inset-0 bg-gray-500 bg-opacity-75 transition-opacity" />
</Transition.Child>
<div className="fixed inset-0 z-10 overflow-y-auto">
<div className="flex min-h-full items-end justify-center p-4 text-center sm:items-center sm:p-0">
<Transition.Child
as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
enterTo="opacity-100 translate-y-0 sm:scale-100"
leave="ease-in duration-200"
leaveFrom="opacity-100 translate-y-0 sm:scale-100"
leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
>
<Dialog.Panel className="relative transform overflow-hidden rounded-lg bg-white text-left shadow-xl transition-all sm:my-8 sm:w-full sm:max-w-lg">
<div className="bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4">
<div className="sm:flex sm:items-start">
<div className="mx-auto flex h-12 w-12 flex-shrink-0 items-center justify-center rounded-full bg-red-100 sm:mx-0 sm:h-10 sm:w-10">
<ExclamationTriangleIcon
className="h-6 w-6 text-red-600"
aria-hidden="true"
/>
</div>
<div className="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left">
<Dialog.Title
as="h3"
className="text-lg font-medium leading-6 text-gray-900"
>
Account Not Found
</Dialog.Title>
<div className="mt-2">
<p className="text-sm text-gray-500">
Please Make Sure You Have Entered The Correct Username
or Password that you used to register. Otherwise
change your password if you have forgotten it.
</p>
</div>
</div>
</div>
</div>
<div className="bg-gray-50 px-4 py-3 sm:flex sm:flex-row-reverse sm:px-6">
<button
type="button"
className="inline-flex w-full justify-center rounded-md border border-transparent bg-red-600 px-4 py-2 text-base font-medium text-white shadow-sm hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-red-500 focus:ring-offset-2 sm:ml-3 sm:w-auto sm:text-sm"
onClick={() => setOpen(false)}
>
Okay
</button>
</div>
</Dialog.Panel>
</Transition.Child>
</div>
</div>
</Dialog>
</Transition>
);
};
export default WarnModal;
can someone explained to me where did i do wrong here, i thinks the props has been pass to the component
I've a map that creates edit and delete buttons.
The delete button needs to be confirmed.
So I created a modal.
console.log(additive._id) at the first delete button gives me the correct ._id
the confirm button inside the modal gives me always the last ._id of the array i map through, so it'll always delete the last item.
How can i get the correct ._id?
{additives.sort(byId).map((additive) => (
<div key={additive._id}>
<div>
<div </div>
<div>{additive.additive[0]?.label}</div>
</div>
<div className='flex justify-between items-center'>
<button
onClick={() =>
editAdditive(
additive._id,
additive.additive[0]?.value,
additive.additive[0]?.label
)
}>
Edit
</button>
<button
onClick={() => {
setShowModal(!showModal);
console.log(additive._id);
}}
>
Delete
</button>
{showModal && (
<>
<div
className='backdrop'
onClick={(e) => {
if (e.target.className === 'backdrop') {
setShowModal(false);
}
}}
>
<div className='relative w-auto my-6 mx-auto max-w-3xl'>
<div className=' rounded-lg shadow-2xl relative flex flex-col w-full bg-white outline-none focus:outline-none'>
<div className='flex items-start justify-between p-5 border-b border-solid border-slate-200 rounded-t bg-gray-600'>
<h3 className='text-3xl font-semibold text-red-500'>
Delete Additive
</h3>
</div>
<div className='relative p-6 flex-auto bg-gray-600'>
<p className='my-4 text-white text-lg leading-relaxed'>
Are you sure?
</p>
</div>
<div className='flex items-center justify-end p-6 border-t border-solid border-slate-200 rounded-b bg-gray-600'>
<button
className='text-red-500 background-transparent font-bold uppercase px-6 py-2 text-sm outline-none focus:outline-none mr-1 mb-1 ease-linear transition-all duration-150'
type='button'
onClick={() => setShowModal(!showModal)}
>
Cancel
</button>
<button
className='bg-red-500 text-white active:bg-red-600 font-bold uppercase text-sm px-6 py-3 rounded shadow hover:shadow-lg outline-none focus:outline-none mr-1 mb-1 ease-linear transition-all duration-150'
type='button'
onClick={() => {
deleteAdditive(additive._id);
setShowModal(!showModal);
console.log(additive._id);
}}
>
Confirm
</button>
</div>
</div>
</div>
</div>
</>
)}
</div>
</div>
))}
store the additive id in a state and use it in the modal
const [additiveId, setAdditiveId] = useState()
<button
onClick={() => {
setShowModal(!showModal);
setAdditiveId(additive._id)
console.log(additive._id);
}}
>
Delete
</button>
<button
className='bg-red-500 text-white active:bg-red-600 font-bold uppercase text-sm px-6 py-3 rounded shadow hover:shadow-lg outline-none focus:outline-none mr-1 mb-1 ease-linear transition-all duration-150'
type='button'
onClick={() => {
deleteAdditive(additiveId);
setShowModal(!showModal);
console.log(additiveId);
}}
>
Confirm
</button>
I am using heroicons, tailwindCSS, nextjs/react.
I have tried creating a useState for the index and using useEffect to update it every time. selectedIMG has a state but that didnt work as well. I cannot seem to understand why it is not looping the array when clicking on the arrow icons.
const [selectedIMG, setSelectedIMG] = useState();
{/* Popup Image */}
{selectedIMG && (
<div className="fixed top-0 z-50 w-[100vw] h-[100vh] flex justify-center items-center backdrop-blur-sm select-none">
<div className="absolute w-[100vw] h-[100vh] bg-custom-black bg-opacity-60 " />
<XIcon
className="w-12 h-12 absolute top-10 right-10 cursor-pointer z-10 p-2 border rounded-full xl:scale-150 bg-opacity-10 bg-white"
onClick={() => setSelectedIMG()}
/>
<ArrowLeftIcon
className="absolute text-white z-50 w-12 h-12 p-2 border rounded-full left-5 lg:left-[10vw] xl:scale-150 cursor-pointer shadow-2xl bg-white bg-opacity-10"
onClick={() => {
const selectedIndex = galleryImages.findIndex(
(item) => item == selectedIMG
);
console.log(selectedIndex);
if (selectedIndex <= 0) {
setSelectedIMG(galleryImages[galleryImages.length]);
} else {
setSelectedIMG(galleryImages[selectedIndex - 1]);
}
}}
/>
<ArrowRightIcon
className="absolute text-white z-50 w-12 h-12 p-2 border rounded-full right-5 lg:right-[10vw] xl:scale-150 cursor-pointer shadow-2xl bg-white bg-opacity-10"
onClick={() => {
const selectedIndex = galleryImages.findIndex(
(item) => item.src == selectedIMG.src
);
if (selectedIndex == galleryImages.length) {
setSelectedIMG(galleryImages[0]);
} else {
setSelectedIMG(galleryImages[selectedIndex + 1]);
}
}}
/>
<div className="relative w-full h-3/4">
<Image
priority
layout="fill"
objectFit="contain"
src={selectedIMG.src}
alt=""
/>
</div>
</div>
)}
{/* */}
<section>
<PageHeader title={"Gallery"} info={"Showcase of work"} />
<div className="grid grid-cols-2 sm:grid-cols-3 gap-y-1 gap-x-1 justify-center items-center mt-5">
{galleryImages.map((img) => (
<div
className="relative rounded-sm min-w-[100px] w-full h-full overflow-hidden mx-auto p-3 cursor-pointer"
key={img.src}
whileHover={{
scale: 1.1,
transition: {
ease: [0.6, 0.01, -0.05, 0.95],
},
}}
onClick={() => setSelectedIMG(img)}
>
<div>
<Image
width={"100%"}
height={"100%"}
layout="responsive"
objectFit="cover"
src={img.src}
alt={img.caption}
/>
</div>
</div>
))}
</div>
</section>
I am creating a search page. You can have a search query, e.g. "Nike" and then you can click different categories or conditions.
My file structure in pages is just:
search > index.tsx
When a user changes a category or condition, the search works correctly and finds the correct results. The search query is also correct. Here is a sample query:
/search?searchQuery=nike&category=clothes
However, the page reloads, which causes UI elements to move around, expanding menu's close etc.
I need the page to research without reloading, I read that using shallow: true should avoid this behaviour but it isn't working at the moment. I am only updating the query params.
Here is my (search) index.tsx:
export default function Search() {
const [listings, setListings] = useState<null | Array<ListingObject>>(null);
const [noResultsListings, setNoResultsListings] = useState<
Array<ListingObject>
>([]);
const [loading, setLoading] = useState("idle");
const [mobileFiltersOpen, setMobileFiltersOpen] = useState(false);
const [sortBy, setSortBy] = useState("listings");
const [currentPage, setCurrentPage] = useState(0);
const [paginationData, setPaginationData] = useState<PaginationData>({
totalHits: 0,
totalPages: 0,
});
const router = useRouter();
const { query, isReady } = useRouter();
let searchClient;
if (process.env.NODE_ENV === "development") {
searchClient = algoliasearch(
process.env.NEXT_PUBLIC_ALGOLIA_TEST_APP!,
process.env.NEXT_PUBLIC_ALGOLIA_TEST_API!
);
}
useEffect(() => {
if (query && sortBy && loading === "idle" && isReady) {
console.log("Search use effect ran");
search(query, 0);
}
}, [isReady, query]);
//============================================
//When a search filter checkbox is checked/unchecked, this is called
//============================================
function handleCheck(checked: Boolean, id: string, option) {
let tempQuery = query;
if (checked) {
if (tempQuery[id]) {
tempQuery[id] += `,${option.value}`;
} else {
tempQuery[id] = `${option.value}`;
}
}
if (!checked) {
if (tempQuery[id] && tempQuery[id].toString().split(",").length > 1) {
let param = tempQuery[id].toString();
let array = param.split(",");
tempQuery[id] = array.filter((param) => param !== option.value);
} else {
delete query[id];
}
}
router.push(
{
pathname: `/search`,
query: tempQuery,
},
undefined,
{ shallow: true }
);
}
//============================================
//Where the "search" actually happens.
//============================================
async function search(query, page: number) {
if (loading === "idle") {
setListings(null);
setLoading("loading");
let listingIndex = searchClient.initIndex(sortBy);
const readyQuery = await handleSearchQuery(query);
const response = await listingIndex.search(readyQuery.searchQuery, {
numericFilters: readyQuery.filters.numericFilters,
facetFilters: readyQuery.filters.facetFilters,
page: page,
});
if (response) {
const hits = response.hits;
setPaginationData({
totalPages: response.nbPages,
totalHits: response.nbHits,
});
console.log(hits);
setListings(hits);
setLoading("idle");
}
}
}
//=========================================================
//When a user changes page, uses npm package react-paginate
//=========================================================
const handlePageClick = (data) => {
let selected = data.selected;
setCurrentPage(data.selected);
search(query, selected);
window.scrollTo(0, 0);
};
return (
<div className="flex flex-col items-center bg-white min-h-screen">
<Navbar page={"Search"} />
<div className="container">
{/* Mobile filter dialog */}
<Transition.Root show={mobileFiltersOpen} as={Fragment}>
<Dialog
as="div"
className="fixed inset-0 flex z-40 lg:hidden"
onClose={setMobileFiltersOpen}
>
<Transition.Child
as={Fragment}
enter="transition-opacity ease-linear duration-300"
enterFrom="opacity-0"
enterTo="opacity-100"
leave="transition-opacity ease-linear duration-300"
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<Dialog.Overlay className="fixed inset-0 bg-black bg-opacity-25" />
</Transition.Child>
<Transition.Child
as={Fragment}
enter="transition ease-in-out duration-300 transform"
enterFrom="translate-x-full"
enterTo="translate-x-0"
leave="transition ease-in-out duration-300 transform"
leaveFrom="translate-x-0"
leaveTo="translate-x-full"
>
<div className="ml-auto relative max-w-xs w-full h-full bg-white shadow-xl py-4 pb-12 flex flex-col overflow-y-auto">
<div className="px-4 flex items-center justify-between">
<h2 className="text-lg font-medium text-gray-900">Filters</h2>
<button
type="button"
className="-mr-2 w-10 h-10 bg-white p-2 rounded-md flex items-center justify-center text-gray-400"
onClick={() => setMobileFiltersOpen(false)}
>
<span className="sr-only">Close menu</span>
<XIcon className="h-6 w-6" aria-hidden="true" />
</button>
</div>
{/* Filters */}
<form className="mt-4 border-t border-gray-200">
{filters.map((section) => (
<Disclosure
as="div"
key={section.id}
className="border-t border-gray-200 px-4 py-6"
>
{({ open }) => (
<>
<h3 className="-my-3 flow-root">
<Disclosure.Button className="px-2 py-3 bg-white w-full flex items-center justify-between text-gray-400 hover:text-gray-500">
<span className="font-medium text-gray-900">
{section.name}
</span>
<span className="ml-6 flex items-center">
{open ? (
<MinusSmIcon
className="h-5 w-5"
aria-hidden="true"
/>
) : (
<PlusSmIcon
className="h-5 w-5"
aria-hidden="true"
/>
)}
</span>
</Disclosure.Button>
</h3>
<Disclosure.Panel className="pt-6">
<div className="space-y-6">
{section.options.map((option, optionIdx) => (
<div
key={option.value}
className="flex items-center"
>
<input
checked={router?.query[
section.id
]?.includes(option.value)}
id={`filter-mobile-${section.id}-${optionIdx}`}
name={`${section.id}[]`}
defaultValue={option.value}
type="checkbox"
className="h-4 w-4 border-gray-300 rounded text-indigo-600 focus:ring-indigo-500"
/>
<label
htmlFor={`filter-mobile-${section.id}-${optionIdx}`}
className="ml-3 min-w-0 flex-1 text-gray-500"
>
{option.label}
</label>
</div>
))}
</div>
</Disclosure.Panel>
</>
)}
</Disclosure>
))}
</form>
</div>
</Transition.Child>
</Dialog>
</Transition.Root>
<main className="w-full mx-auto px-4 md:px-0">
{listings?.length > 0 && (
<div className="flex flex-col relative z-30 md:items-center justify-between pb-6 md:flex-row">
<div className="flex items-center mt-8">
<Menu as="div" className="relative inline-block text-left">
<Menu.Button className="group inline-flex justify-center text-sm font-medium text-gray-700 hover:text-gray-900">
{" "}
{sortOptions.find((item) => item.value === sortBy).label}
<ChevronDownIcon
className="flex-shrink-0 -mr-1 ml-1 h-5 w-5 text-gray-400 group-hover:text-gray-500"
aria-hidden="true"
/>
</Menu.Button>
<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="origin-top-right absolute left-0 md:right-0 mt-2 w-48 p-0 shadow-2xl bg-white ring-1 ring-black ring-opacity-5 focus:outline-none z-30">
<div>
{sortOptions.map((option) => (
<Menu.Item key={option.value}>
{({ active }) => (
<button
className={classNames(
sortBy === option.value
? "font-medium text-gray-900"
: "text-gray-500",
active ? "bg-gray-100" : "",
"block px-4 py-2 text-sm w-full text-left"
)}
onClick={() => {
setSortBy(option.value);
}}
>
{option.label}
</button>
)}
</Menu.Item>
))}
</div>
</Menu.Items>
</Transition>
</Menu>
<button
type="button"
className="p-2 -m-2 ml-4 sm:ml-6 text-gray-400 hover:text-gray-500 lg:hidden"
onClick={() => setMobileFiltersOpen(true)}
>
<span className="sr-only">Filters</span>
<FilterIcon className="w-5 h-5" aria-hidden="true" />
</button>
</div>
</div>
)}
<section className="flex w-full">
<div className="hidden md:grid grid-cols-1 lg:grid-cols-1 min-h-screen w-1/5 lg:pr-4">
{/* Filters */}
<form className="hidden h-full overflow-y-auto md:inline-block">
{listings?.length > 0 &&
filters.map((section) => (
<Disclosure
as="div"
key={section.id}
className="border-gray-200 py-6"
>
{({ open }) => (
<>
<h3 className="-my-3 flow-root">
<Disclosure.Button className="py-3 bg-white w-full flex items-center justify-between text-sm text-gray-400 hover:text-gray-500">
<span className="font-medium text-gray-900">
{section.name}
</span>
<span className="ml-6 flex items-center">
{open ? (
<MinusSmIcon
className="h-5 w-5"
aria-hidden="true"
/>
) : (
<PlusSmIcon
className="h-5 w-5"
aria-hidden="true"
/>
)}
</span>
</Disclosure.Button>
</h3>
<Disclosure.Panel className="pt-6">
<div className="space-y-4 px-1">
{section.options.map((option, optionIdx) => (
<div
key={option.value}
className="flex items-center"
>
<input
checked={
query[section.id] &&
query[section.id]
?.toString()
.split(",")
.includes(option.value)
? true
: false
}
id={`filter-${section.id}-${optionIdx}`}
name={`${section.id}[]`}
type="checkbox"
className="checkbox"
onChange={(e) => {
handleCheck(
e.target.checked,
section.id,
option
);
}}
/>
<label
htmlFor={`filter-${section.id}-${optionIdx}`}
className="ml-3 text-sm text-gray-600"
>
{option.label}
</label>
</div>
))}
</div>
</Disclosure.Panel>
</>
)}
</Disclosure>
))}
</form>
</div>
{/* Product grid */}
<ul
role="list"
className="h-full w-full grid grid-cols-1 py-4 gap-8 md:grid-cols-4 md:w-4/5 md:p-4 xl:gap-x-8"
>
{!listings && <SearchGridSkeleton />}
{listings?.length > 0 &&
listings.map((listing) => (
<ListingCard
key={listing.docID}
listing={listing}
type={"normal"}
/>
))}
{listings?.length === 0 && (
<div className="w-full h-full flex flex-col items-center justify-center col-span-4 relative py-8">
<h2 className="text-lg font-semibold text-center">
Oops, we found no results for "
<span className="underline text-blue-600">
{query.searchQuery}
</span>
"
</h2>
<div className="mt-16 flex flex-col justify-center items-center w-full md:w-3/4">
<h3 className="text-base font-semibold">
You may also like:
</h3>
<div className="w-full grid gap-8 mt-8 border-t pt-8 grid-cols-1 md:grid-cols-3">
{noResultsListings?.map((listing: ListingObject) => (
<ListingCard
className="col-span-1"
key={listing.docID}
type="normal"
listing={listing}
/>
))}
</div>
</div>
</div>
)}
</ul>
</section>
{listings?.length > 0 && (
<ReactPaginate
previousLabel={
<span className="flex items-center">
<ArrowNarrowLeftIcon
className="mr-3 h-5 w-5 text-gray-400"
aria-hidden="true"
/>{" "}
Previous
</span>
}
nextLabel={
<span className="flex items-center">
Next{" "}
<ArrowNarrowRightIcon
className="ml-3 h-5 w-5 text-gray-400"
aria-hidden="true"
/>
</span>
}
breakLabel={"..."}
breakClassName={"break-me"}
pageCount={paginationData.totalPages}
marginPagesDisplayed={2}
pageRangeDisplayed={10}
onPageChange={handlePageClick}
disabledClassName={"opacity-20"}
pageClassName={
"border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300 border-t-2 pt-4 px-4 inline-flex items-center text-sm font-medium"
}
containerClassName={
"border-t border-gray-200 px-4 flex items-center justify-center sm:px-0 pb-8"
}
activeClassName={"border-indigo-500 text-indigo-600"}
nextClassName={
"border-t-2 border-transparent pt-4 pr-1 inline-flex items-center text-sm font-medium text-gray-500 hover:text-gray-700 hover:border-gray-300 ml-auto"
}
previousClassName={
"border-t-2 border-transparent pt-4 pr-1 inline-flex items-center text-sm font-medium text-gray-500 hover:text-gray-700 hover:border-gray-300 mr-auto"
}
initialPage={0}
/>
)}
</main>
</div>
<Footer />
</div>
);
}