import React from 'react'; import { Listbox, ListboxButton, Label, ListboxOptions, ListboxOption, Transition, } from '@headlessui/react'; import type { Option, OptionWithIcon } from '~/common'; import CheckMark from '../svg/CheckMark'; import { useLocalize } from '~/hooks'; import { cn } from '~/utils/'; import { useMultiSearch } from './MultiSearch'; type SelectDropDownProps = { id?: string; title?: string; value: string | null | Option | OptionWithIcon; disabled?: boolean; setValue: (value: string) => void; tabIndex?: number; availableValues: string[] | Option[] | OptionWithIcon[]; emptyTitle?: boolean; showAbove?: boolean; showLabel?: boolean; iconSide?: 'left' | 'right'; renderOption?: () => React.ReactNode; containerClassName?: string; currentValueClass?: string; optionsListClass?: string; optionsClass?: string; subContainerClassName?: string; className?: string; searchClassName?: string; searchPlaceholder?: string; showOptionIcon?: boolean; }; function SelectDropDown({ title: _title, value, disabled, setValue, tabIndex, availableValues, showAbove = false, showLabel = true, emptyTitle = false, iconSide = 'right', containerClassName, optionsListClass, optionsClass, currentValueClass, subContainerClassName, className, renderOption, searchClassName, searchPlaceholder, showOptionIcon, }: SelectDropDownProps) { const localize = useLocalize(); const transitionProps = { className: 'top-full mt-3' }; if (showAbove) { transitionProps.className = 'bottom-full mb-3'; } let title = _title; if (emptyTitle) { title = ''; } else if (!title) { title = localize('com_ui_model'); } // Detemine if we should to convert this component into a searchable select. If we have enough elements, a search // input will appear near the top of the menu, allowing correct filtering of different model menu items. This will // reset once the component is unmounted (as per a normal search) const [filteredValues, searchRender] = useMultiSearch({ availableOptions: availableValues, placeholder: searchPlaceholder, getTextKeyOverride: (option) => ((option as Option)?.label || '').toUpperCase(), className: searchClassName, }); const hasSearchRender = Boolean(searchRender); const options = hasSearchRender ? filteredValues : availableValues; return (
{({ open }) => ( <> {' '} {showLabel && ( )} {!showLabel && !emptyTitle && ( {title}: )} {showOptionIcon && value && (value as OptionWithIcon)?.icon && ( {(value as OptionWithIcon).icon} )} {typeof value !== 'string' && value ? value?.label ?? '' : value ?? ''} {renderOption && ( {renderOption()} )} {searchRender} {options.map((option: string | Option, i: number) => { if (!option) { return null; } const currentLabel = typeof option === 'string' ? option : option?.label ?? ''; const currentValue = typeof option === 'string' ? option : option?.value ?? ''; const currentIcon = typeof option === 'string' ? null : (option?.icon as React.ReactNode) ?? null; let activeValue: string | number | null | Option = value; if (typeof activeValue !== 'string') { activeValue = activeValue?.value ?? ''; } return ( cn( 'group relative flex h-[42px] cursor-pointer select-none items-center overflow-hidden border-b border-black/10 pl-3 pr-9 text-gray-800 last:border-0 hover:bg-gray-20 dark:border-white/20 dark:text-white dark:hover:bg-gray-700', active ? 'bg-surface-tertiary' : '', optionsClass ?? '', ) } > {currentIcon && {currentIcon}} {currentLabel} {currentValue === activeValue && ( )} ); })} )}
); } export default SelectDropDown;