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
Related
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 have table with inputs filters (2x select input, 1x search form).
I need keep this parameters in URL because if somebody will want refer on my page with specific filter like
www.example.com/?size=xxl&color=red&search=hats
So if will visitor visit the page i would like to automatic set the inputs from this query parameters.
Problem is in my code this not work, if i change select input or put some text in search label, i cant see any URL change. the url is static "www.example.com"
In search label work just if i remove the
event.preventDefault();
But i need this keep, because i dont want refresh page and loose state.
fetch method is GET. I wondering i can use some history.push or something from next router, but i thing its other way?
SearchLabel
import { DocumentSearchIcon } from '#heroicons/react/solid';
import { useState } from 'react';
const SearchLabel = (props) => {
const { setSearch } = props;
const [searchValue, setSearchValue] = useState('');
const changeHandler = (event: React.ChangeEvent<HTMLInputElement>) => {
setSearchValue(event.target.value);
};
const onSubmit = (event) => {
event.preventDefault();
setSearch(searchValue);
};
return (
<div>
<form onSubmit={onSubmit} onBlur={onSubmit}>
<label
htmlFor="email"
className="block text-sm font-medium text-gray-700"
>
Dle názvu
</label>
<div className="mt-1 flex rounded-md shadow-sm">
<div className="relative flex items-stretch flex-grow focus-within:z-10">
<input
onChange={(event) => changeHandler(event)}
type="text"
name="download"
id="download"
value={searchValue}
className="focus:ring-blue-500 focus:border-blue-500 block w-full rounded-none rounded-l-md sm:text-sm border-gray-300"
placeholder="Název dokumentu"
/>
</div>
<button
onClick={onSubmit}
type="button"
className="-ml-px relative inline-flex items-center space-x-2 px-4 py-2 border border-gray-300 text-sm font-medium rounded-r-md text-gray-700 bg-gray-50 hover:bg-gray-100 focus:outline-none focus:ring-1 focus:ring-blue-500 focus:border-blue-500"
>
<DocumentSearchIcon
className="h-5 w-5 text-gray-400"
aria-hidden="true"
/>
<span>Search</span>
</button>
</div>
</form>
</div>
);
};
export default SearchLabel;
Select filter
import { Fragment } from 'react';
import { Listbox, Transition } from '#headlessui/react';
import { CheckIcon, SelectorIcon } from '#heroicons/react/solid';
function classNames(...classes: string[]) {
return classes.filter(Boolean).join(' ');
}
const ListBox = (props: any) => {
const { filterData, name, filterValue, setFilter } = props;
return (
<Listbox value={filterValue} onChange={setFilter} >
{({ open }) => (
<>
<div className="flex-cols ">
<Listbox.Label className="block text-sm font-medium text-gray-700">
{name}
</Listbox.Label>
<div className="mt-1 relative flex-initial w-64 ">
<Listbox.Button className="bg-white relative w-full border border-gray-300 rounded-md shadow-sm pl-3 pr-10 py-2 text-left cursor-default focus:outline-none focus:ring-1 focus:ring-blue-500 focus:border-blue-500 sm:text-sm">
<span className="block truncate">
{filterValue === '' ? 'All' : filterValue}
</span>
<span className="absolute inset-y-0 right-0 flex items-center pr-2 pointer-events-none">
<SelectorIcon
className="h-5 w-5 text-gray-400"
aria-hidden="true"
/>
</span>
</Listbox.Button>
<Transition
show={open}
as={Fragment}
leave="transition ease-in duration-100"
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<Listbox.Options className="absolute z-10 mt-1 w-full bg-white shadow-lg max-h-60 rounded-md py-1 text-base ring-1 ring-black ring-opacity-5 overflow-auto focus:outline-none sm:text-sm">
{filterData.map((data, index) => (
<Listbox.Option
key={index}
className={({ active }) =>
classNames(
active ? 'text-white bg-blue-600' : 'text-gray-900',
'cursor-default select-none relative py-2 pl-3 pr-9'
)
}
value={data.value}
>
{({ selected, active }) => (
<>
<span
className={classNames(
selected ? 'font-semibold' : 'font-normal',
'block truncate'
)}
>
{data.name}
</span>
{selected ? (
<span
className={classNames(
active ? 'text-white' : 'text-blue-600',
'absolute inset-y-0 right-0 flex items-center pr-4'
)}
>
<CheckIcon
className="h-5 w-5"
aria-hidden="true"
/>
</span>
) : null}
</>
)}
</Listbox.Option>
))}
</Listbox.Options>
</Transition>
</div>
</div>
</>
)}
</Listbox>
);
};
export default ListBox;
i created responsive mobile menu with tailwind css in react js project. All code works fine. There's no error in my code. When i use in rendeaning ref it gives me a waring in console. "Unexpected ref object provided for div. Use either a ref-setter function or React.createRef()." How can i solve it?
https://i.ibb.co/PG0xFwn/Screenshot-2022-05-01-at-3-43-35-PM.png
<Transition
show={isOpen}
enter="transition ease-out duration-100 transform"
enterFrom="opacity-0 scale-95"
enterTo="opacity-100 scale-100"
leave="transition ease-in duration-75 transform"
leaveFrom="opacity-100 scale-100"
leaveTo="opacity-0 scale-95"
>
{(ref) => (
<div className="lg:hidden" id="mobile-menu">
<div ref={ref} className="px-2 pt-2 pb-3 space-y-1 sm:px-3">
<CustomLink
to="/"
className=" hover:bg-red-700 text-white block px-3 py-2 rounded-md text-base font-medium"
>
Home
</CustomLink>
<CustomLink
to="/manage"
className="text-gray-300 hover:bg-red-700 hover:text-white block px-3 py-2 rounded-md text-base font-medium "
>
Manage Inventories
</CustomLink>
{user && (
<>
<CustomLink
to="/addInventory"
className="text-gray-300 hover:bg-red-700 hover:text-white block px-3 py-2 rounded-md text-base font-medium"
>
Add Item
</CustomLink>
<CustomLink
to="/myItem"
className="text-gray-300 hover:bg-red-700 hover:text-white block px-3 py-2 rounded-md text-base font-medium"
>
My Items
</CustomLink>
</>
)}
<CustomLink
to="/blog"
className="text-gray-300 hover:bg-red-700 hover:text-white block px-3 py-2 rounded-md text-base font-medium"
>
Blog
</CustomLink>
{user ? (
<button
onClick={logOut}
className="border-2 bg-red-700 hover:bg-indigo-700 hover:border-white-700 px-4 py-1 text-white rounded-sm transition-all duration-300 ease-in-out focus:shadow-outline focus:outline-none"
>
Logout
</button>
) : (
<button
onClick={() => navigate("/login")}
className="border-2 hover:bg-red-700 hover:border-red-700 px-4 py-1 text-white rounded-sm transition-all duration-300 ease-in-out focus:shadow-outline focus:outline-none"
>
Login
</button>
)}
</div>
</div>
)}
</Transition>
you can solve this by creating the ref variable using useRef() hook or React.createRef() and then pass the variable directly to the ref prop
const divRef = useRef()
Then you can use the ref
<div ref={divRef} > </div>
or if you are using class component
this.divRef = React.createRef()
then
<div ref={this.divRef} > </div>
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>
);
}