Created
November 21, 2024 16:53
-
-
Save severinlandolt/a0fbbbb7ad7b2d87290b995986f3594d to your computer and use it in GitHub Desktop.
Headless UI Combobox
This file contains hidden or 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
"use client"; | |
import { | |
Combobox, | |
ComboboxButton, | |
ComboboxInput, | |
ComboboxOption, | |
ComboboxOptions, | |
Field, | |
Label, | |
} from "@headlessui/react"; | |
import { ChevronDownIcon } from "@heroicons/react/20/solid"; | |
import clsx from "clsx"; | |
import { Dispatch, useState } from "react"; | |
import { CheckIcon } from "./check"; | |
import { Product, products } from "./products"; | |
export default function ProductSelection({ | |
selected, | |
setSelected, | |
}: { | |
selected: Product | null; | |
setSelected: Dispatch<React.SetStateAction<Product | null>>; | |
}) { | |
const [query, setQuery] = useState(""); | |
const filteredProducts = | |
query === "" | |
? products | |
: products.filter((product) => { | |
return product.produkttyp.toLowerCase().includes(query.toLowerCase()); | |
}); | |
return ( | |
<Field> | |
<Label className="sr-only">Select product</Label> | |
<Combobox | |
value={selected} | |
onChange={(value: Product) => setSelected(value)} | |
onClose={() => setQuery("")} | |
virtual={{ options: filteredProducts }} | |
> | |
<div className="relative"> | |
<ComboboxInput | |
placeholder="Produktkategorie..." | |
className={clsx( | |
"w-full rounded-lg border-none bg-white py-3 px-4 text-gray-700", | |
"focus:outline-none data-[focus]:outline-2 data-[focus]:outline-offset-1 data-[focus]:outline-orange-500 ring-1 ring-inset ring-gray-300" | |
)} | |
displayValue={(product: Product) => | |
product?.produkttyp.replace(/\b\w/g, (char) => | |
char.toUpperCase() | |
) || "" | |
} | |
onChange={(event) => setQuery(event.target.value)} | |
/> | |
<ComboboxButton className="group absolute inset-y-0 right-0 px-2.5"> | |
<ChevronDownIcon className="size-6 fill-orange-500 group-data-[hover]:fill-orange-700" /> | |
</ComboboxButton> | |
</div> | |
{filteredProducts.length > 0 && ( | |
<ComboboxOptions | |
anchor="bottom" | |
transition | |
className={clsx( | |
"min-w-[var(--input-width)] [--anchor-gap:8px] rounded-xl border border-gray-200 bg-white p-1 empty:invisible [--anchor-max-height:35rem]", | |
"transition duration-100 ease-in data-[leave]:data-[closed]:opacity-0" | |
)} | |
> | |
{({ option: product }) => ( | |
<ComboboxOption | |
key={product.produkttyp} | |
value={product} | |
className="group flex cursor-default items-center gap-2 rounded-lg py-1.5 px-3 select-none data-[focus]:bg-orange-500 w-full" | |
> | |
<CheckIcon className="group-data-[focus]:stroke-white invisible size-4 stroke-orange-500 group-data-[selected]:visible" /> | |
<div className="flex justify-between items-center w-full"> | |
<div className="text-gray-700 group-data-[focus]:text-white capitalize whitespace-nowrap"> | |
{product.produkttyp} | |
</div> | |
<div className="text-gray-400 font-mono text-sm group-data-[focus]:text-orange-200"> | |
{product.erfolgsprovision * 100 + "%"} | |
</div> | |
</div> | |
</ComboboxOption> | |
)} | |
</ComboboxOptions> | |
)} | |
</Combobox> | |
</Field> | |
); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment