'use client' import type { FC } from 'react' import React, { Fragment, useEffect, useState } from 'react' import { Combobox, Listbox, Transition } from '@headlessui/react' import { CheckIcon, ChevronDownIcon, ChevronUpIcon, XMarkIcon } from '@heroicons/react/20/solid' import { useTranslation } from 'react-i18next' import classNames from '@/utils/classnames' import { PortalToFollowElem, PortalToFollowElemContent, PortalToFollowElemTrigger, } from '@/app/components/base/portal-to-follow-elem' const defaultItems = [ { value: 1, name: 'option1' }, { value: 2, name: 'option2' }, { value: 3, name: 'option3' }, { value: 4, name: 'option4' }, { value: 5, name: 'option5' }, { value: 6, name: 'option6' }, { value: 7, name: 'option7' }, ] export type Item = { value: number | string name: string } & Record export type ISelectProps = { className?: string wrapperClassName?: string renderTrigger?: (value: Item | null) => JSX.Element | null items?: Item[] defaultValue?: number | string disabled?: boolean onSelect: (value: Item) => void allowSearch?: boolean bgClassName?: string placeholder?: string overlayClassName?: string optionWrapClassName?: string optionClassName?: string hideChecked?: boolean notClearable?: boolean renderOption?: ({ item, selected, }: { item: Item selected: boolean }) => React.ReactNode } const Select: FC = ({ className, items = defaultItems, defaultValue = 1, disabled = false, onSelect, allowSearch = true, bgClassName = 'bg-gray-100', overlayClassName, optionClassName, renderOption, }) => { const [query, setQuery] = useState('') const [open, setOpen] = useState(false) const [selectedItem, setSelectedItem] = useState(null) useEffect(() => { let defaultSelect = null const existed = items.find((item: Item) => item.value === defaultValue) if (existed) defaultSelect = existed setSelectedItem(defaultSelect) // eslint-disable-next-line react-hooks/exhaustive-deps }, [defaultValue]) const filteredItems: Item[] = query === '' ? items : items.filter((item) => { return item.name.toLowerCase().includes(query.toLowerCase()) }) return ( { if (!disabled) { setSelectedItem(value) setOpen(false) onSelect(value) } }}>
{allowSearch ? { if (!disabled) setQuery(event.target.value) }} displayValue={(item: Item) => item?.name} /> : { if (!disabled) setOpen(!open) } } className={classNames(`flex items-center h-9 w-full rounded-lg border-0 ${bgClassName} py-1.5 pl-3 pr-10 shadow-sm sm:text-sm sm:leading-6 focus-visible:outline-none focus-visible:bg-gray-200 group-hover:bg-gray-200`, optionClassName)}>
{selectedItem?.name}
} { if (!disabled) setOpen(!open) } }> {open ? : }
{(filteredItems.length > 0 && open) && ( {filteredItems.map((item: Item) => ( classNames( 'relative cursor-default select-none py-2 pl-3 pr-9 rounded-lg hover:bg-gray-100 text-gray-700', active ? 'bg-gray-100' : '', optionClassName, ) } > {({ /* active, */ selected }) => ( <> {renderOption ? renderOption({ item, selected }) : ( <> {item.name} {selected && ( )} )} )} ))} )}
) } const SimpleSelect: FC = ({ className, wrapperClassName = '', renderTrigger, items = defaultItems, defaultValue = 1, disabled = false, onSelect, placeholder, optionWrapClassName, optionClassName, hideChecked, notClearable, renderOption, }) => { const { t } = useTranslation() const localPlaceholder = placeholder || t('common.placeholder.select') const [selectedItem, setSelectedItem] = useState(null) useEffect(() => { let defaultSelect = null const existed = items.find((item: Item) => item.value === defaultValue) if (existed) defaultSelect = existed setSelectedItem(defaultSelect) // eslint-disable-next-line react-hooks/exhaustive-deps }, [defaultValue]) return ( { if (!disabled) { setSelectedItem(value) onSelect(value) } }} >
{renderTrigger && {renderTrigger(selectedItem)}} {!renderTrigger && ( {selectedItem?.name ?? localPlaceholder} {(selectedItem && !notClearable) ? ( { e.stopPropagation() setSelectedItem(null) onSelect({ name: '', value: '' }) }} className="h-4 w-4 text-text-quaternary cursor-pointer" aria-hidden="false" /> ) : ( )} {!disabled && ( {items.map((item: Item) => ( classNames( `relative cursor-pointer select-none py-2 pl-3 pr-9 rounded-lg hover:bg-gray-100 text-gray-700 ${active ? 'bg-gray-100' : ''}`, optionClassName, ) } value={item} disabled={disabled} > {({ /* active, */ selected }) => ( <> {renderOption ? renderOption({ item, selected }) : (<> {item.name} {selected && !hideChecked && ( )} )} )} ))} )}
) } type PortalSelectProps = { value: string | number onSelect: (value: Item) => void items: Item[] placeholder?: string renderTrigger?: (value?: Item) => JSX.Element | null triggerClassName?: string triggerClassNameFn?: (open: boolean) => string popupClassName?: string popupInnerClassName?: string readonly?: boolean hideChecked?: boolean } const PortalSelect: FC = ({ value, onSelect, items, placeholder, renderTrigger, triggerClassName, triggerClassNameFn, popupClassName, popupInnerClassName, readonly, hideChecked, }) => { const { t } = useTranslation() const [open, setOpen] = useState(false) const localPlaceholder = placeholder || t('common.placeholder.select') const selectedItem = items.find(item => item.value === value) return ( !readonly && setOpen(v => !v)} className='w-full'> {renderTrigger ? renderTrigger(selectedItem) : (
{selectedItem?.name ?? localPlaceholder}
)}
{items.map((item: Item) => (
{ onSelect(item) setOpen(false) }} > {item.name} {!hideChecked && item.value === value && ( )}
))}
) } export { SimpleSelect, PortalSelect } export default React.memo(Select)