Skip to content

Instantly share code, notes, and snippets.

@severinlandolt
Created November 21, 2024 16:53
Show Gist options
  • Save severinlandolt/a0fbbbb7ad7b2d87290b995986f3594d to your computer and use it in GitHub Desktop.
Save severinlandolt/a0fbbbb7ad7b2d87290b995986f3594d to your computer and use it in GitHub Desktop.
Headless UI Combobox
"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