Skip to content

Instantly share code, notes, and snippets.

@Shahaed
Created October 1, 2021 04:36
Show Gist options
  • Save Shahaed/6094a3a971fc3afd7f82c8b1ddcc71cc to your computer and use it in GitHub Desktop.
Save Shahaed/6094a3a971fc3afd7f82c8b1ddcc71cc to your computer and use it in GitHub Desktop.
How to use headless ui listbox with formik
import { Fragment, useState } from 'react'
import { Listbox, Transition } from '@headlessui/react'
import { CheckIcon, SelectorIcon } from '@heroicons/react/solid'
export interface ListboxProps<T> {
/**
* The options to display in the dropdown.
*/
options: T[];
/**
* Callback for when the value changes.
*/
onChange?: (value?: T) => void;
/**
* The selected value.
*/
value?: T;
/**
* Will call on each onBlur event
*/
setTouched?: (value: boolean) => void;
/**
* Placeholder text to display when no value is selected.
*/
placeholder?: string;
/**
* Use this to disable the entire Listbox component & related children.
*/
disabled?: boolean;
/**
* When true, the orientation of the Listbox.Options will be horizontal, otherwise it will be
* vertical.
*/
horizontal?: boolean;
}
export default function Listbox<T>(props: ListboxProps) {
const [selected, setSelected] = useState(
placeholder ? undefined : props.options?.[0]
);
return (
<div className="w-72 fixed top-16">
<Listboxvalue={props.value ?? selected}
onChange={(value) => {
setSelected(value);
props.onChange?.(value);
}}
>
<div className="relative mt-1">
<Listbox.Button
onBlur={() => props.setTouched?.(true)}
className="relative w-full py-2 pl-3 pr-10 text-left bg-white rounded-lg shadow-md cursor-default focus:outline-none focus-visible:ring-2 focus-visible:ring-opacity-75 focus-visible:ring-white focus-visible:ring-offset-orange-300 focus-visible:ring-offset-2 focus-visible:border-indigo-500 sm:text-sm"
>
<span className="block truncate">{selected.name}</span>
<span className="absolute inset-y-0 right-0 flex items-center pr-2 pointer-events-none">
<SelectorIcon
className="w-5 h-5 text-gray-400"
aria-hidden="true"
/>
</span>
</Listbox.Button>
<Transition
as={Fragment}
leave="transition ease-in duration-100"
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<Listbox.Options className="absolute w-full py-1 mt-1 overflow-auto text-base bg-white rounded-md shadow-lg max-h-60 ring-1 ring-black ring-opacity-5 focus:outline-none sm:text-sm">
{options.map((option, i) => (
<Listbox.Option
key={i}
className={({ active }) =>
`${active ? 'text-amber-900 bg-amber-100' : 'text-gray-900'}
cursor-default select-none relative py-2 pl-10 pr-4`
}
value={option}
>
{({ selected, active }) => (
<>
<span
className={`${
selected ? 'font-medium' : 'font-normal'
} block truncate`}
>
{option}
</span>
{selected ? (
<span
className={`${
active ? 'text-amber-600' : 'text-amber-600'
}
absolute inset-y-0 left-0 flex items-center pl-3`}
>
<CheckIcon className="w-5 h-5" aria-hidden="true" />
</span>
) : null}
</>
)}
</Listbox.Option>
))}
</Listbox.Options>
</Transition>
</div>
</Listbox>
</div>
)
}
import { Field, Form, Formik, FormikProps, useField } from "formik";
import Listbox from "./listbox";
export default function ListboxWithFormik() {
const [, meta, helpers] = useField("mySelect");
const { value, error } = meta;
const { setValue, setTouched } = helpers;
return (
<Formik>
<Form>
<Listbox
options={["Jan", "Feb", "Mar", "Apr"]}
value={value}
onChange={setValue}
setTouched={setTouched}
/>
</Form>
</Formik>
);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment