import { Fragment, useRef } from "react"; import { Listbox, Transition } from "@headlessui/react"; import { useTranslations } from "next-intl"; import { Controller, useFormContext } from "react-hook-form"; import { IoChevronDown, IoCloseOutline, IoSearchOutline, } from "react-icons/io5"; import { twMerge } from "tailwind-merge"; import { Field, FieldProps } from "./Field"; export type SelectOption = { value: string; label: React.ReactNode; selectedLabel?: React.ReactNode; disabled?: boolean; }; type SelectFieldProps = FieldProps & { required?: boolean; placeholder?: string; noOptionsMessage?: string; options: SelectOption[]; searchable?: boolean; listboxClassName?: string; dropdownClassName?: string; searchValue?: string; onChangeSearchValue?: (value: string) => void; onOptionSelected?: (option: SelectOption) => void; }; export const SelectField = ({ name, label, className, labelClassName, childrenWrapperClassName, hideErrorMessage = false, required, placeholder, noOptionsMessage, options, searchable, searchValue = "", onChangeSearchValue, onOptionSelected, listboxClassName, dropdownClassName, }: SelectFieldProps) => { const at = useTranslations("App"); const form = useFormContext(); // We need to keep track of the selected option to be able to continue to // display it when the user searches for something else const curOption = useRef(null); return (
{ const selectedOption = curOption.current?.value === value ? curOption.current : options.find((option) => option.value === value); return ( { curOption.current = options.find((option) => option.value === value) ?? null; onChangeSearchValue?.(""); onChange(value); onOptionSelected?.(curOption.current!); }} > {({ open }) => (
{selectedOption?.selectedLabel ?? selectedOption?.label ?? placeholder ?? at("selectPlaceholder")}
ul]:outline-none", "border-gray-300 bg-gray-50 text-gray-900", "dark:border-gray-600 dark:bg-gray-700 dark:text-white", dropdownClassName, )} > {searchable && (
onChangeSearchValue(e.target.value) : undefined } placeholder={at("searchPlaceholder")} type="search" /> {searchValue.trim() !== "" && ( )}
)} {options.length === 0 ? (
{noOptionsMessage ?? at("noOptionsMessage")}
) : (
{options.map((option) => ( {option.label} ))}
)}
)}
); }} />
); };