Created
May 13, 2023 04:32
-
-
Save travishorn/91ac5c6de66abdd02caef69f2b1cc65d to your computer and use it in GitHub Desktop.
Next.js Headless UI Listbox as Selection Filter
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { useState } from 'react' | |
import { useRouter } from 'next/router'; | |
import { Listbox } from '@headlessui/react' | |
import { ChevronUpDownIcon } from '@heroicons/react/20/solid' | |
import queryString from "query-string"; | |
export interface FilterOption { | |
value: string; | |
label: string; | |
} | |
export interface FilterProps { | |
id: string; | |
label: string; | |
options: FilterOption[]; | |
selected: FilterOption; | |
} | |
export function Filter({ | |
id, | |
label, | |
options, | |
selected: initialSelected | |
}: FilterProps) { | |
const router = useRouter(); | |
const [selected, setSelected] = useState(initialSelected); | |
const filterChange = (newSelected: FilterOption) => { | |
setSelected(newSelected); | |
const newQuery = router.query; | |
newQuery[id] = newSelected.value; | |
const newQueryString = queryString.stringify(newQuery); | |
router.push(`${router.pathname}?${newQueryString}`); | |
}; | |
return ( | |
<div> | |
<Listbox value={selected} onChange={filterChange}> | |
<Listbox.Label | |
className="uppercase text-sm font-semibold text-gray-500" | |
>{label}</Listbox.Label> | |
<div className="relative"> | |
<Listbox.Button className="relative mt-1 w-full cursor-default rounded-sm bg-white py-2 pl-3 pr-10 text-left shadow border border-gray-200"> | |
<span className="block truncate">{selected.label}</span> | |
<span className="pointer-events-none absolute inset-y-0 right-0 flex items-center pr-2"> | |
<ChevronUpDownIcon | |
className="h-5 w-5 text-gray-400" | |
aria-hidden="true" | |
/> | |
</span> | |
</Listbox.Button> | |
<Listbox.Options className="z-10 absolute max-h-60 w-full overflow-auto bg-white py-1 shadow"> | |
{options.map((option) => ( | |
<Listbox.Option | |
key={option.value} | |
className={({ active }) => | |
`relative cursor-default select-none py-2 px-4 ${active ? 'bg-blue-100 text-blue-900' : 'text-gray-800' | |
}` | |
} | |
value={option} | |
> | |
<span className="block truncate">{option.label}</span> | |
</Listbox.Option> | |
))} | |
</Listbox.Options> | |
</div> | |
</Listbox> | |
</div> | |
) | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { Filter, FilterOption } from "../components/Filter"; | |
import { GetServerSidePropsContext } from "next"; | |
export async function getServerSideProps({ query }: GetServerSidePropsContext) { | |
const people = [ | |
{ value: "0", label: "Wade Cooper" }, | |
{ value: "1", label: "Arlene Mccoy" }, | |
{ value: "2", label: "Devon Webb" }, | |
{ value: "3", label: "Tom Cook" }, | |
{ value: "4", label: "Tanya Fox" }, | |
{ value: "5", label: "Hellen Schmidt" }, | |
]; | |
const selectedPerson = people | |
.find((person) => person.value === query.personId) | |
|| people[0]; | |
const locations = [ | |
{ value: "0", label: "New York" }, | |
{ value: "1", label: "Los Angeles" }, | |
{ value: "2", label: "Chicago" }, | |
{ value: "3", label: "Houston" }, | |
{ value: "4", label: "Phoenix" }, | |
{ value: "5", label: "Philadelphia" }, | |
]; | |
const selectedLocation = locations | |
.find((location) => location.value === query.locationId) | |
|| locations[0]; | |
return { | |
props: { people, selectedPerson, locations, selectedLocation }, | |
}; | |
}; | |
interface PageProps { | |
people: FilterOption[], | |
selectedPerson: FilterOption, | |
locations: FilterOption[], | |
selectedLocation: FilterOption, | |
} | |
export default function Page({ | |
people, | |
selectedPerson, | |
locations, | |
selectedLocation | |
}: PageProps) { | |
return ( | |
<main className="p-10 flex flex-col gap-10"> | |
<Filter | |
id="personId" | |
label="Person" | |
options={people} | |
selected={selectedPerson} | |
/> | |
<Filter | |
id="locationId" | |
label="Location" | |
options={locations} | |
selected={selectedLocation} | |
/> | |
</main> | |
) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment