|  | @@ -5,6 +5,11 @@ import { Combobox, Listbox, Transition } from '@headlessui/react'
 | 
	
		
			
				|  |  |  import classNames from 'classnames'
 | 
	
		
			
				|  |  |  import { CheckIcon, ChevronDownIcon, ChevronUpIcon } from '@heroicons/react/20/solid'
 | 
	
		
			
				|  |  |  import { useTranslation } from 'react-i18next'
 | 
	
		
			
				|  |  | +import {
 | 
	
		
			
				|  |  | +  PortalToFollowElem,
 | 
	
		
			
				|  |  | +  PortalToFollowElemContent,
 | 
	
		
			
				|  |  | +  PortalToFollowElemTrigger,
 | 
	
		
			
				|  |  | +} from '@/app/components/base/portal-to-follow-elem'
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  const defaultItems = [
 | 
	
		
			
				|  |  |    { value: 1, name: 'option1' },
 | 
	
	
		
			
				|  | @@ -222,5 +227,83 @@ const SimpleSelect: FC<ISelectProps> = ({
 | 
	
		
			
				|  |  |      </Listbox>
 | 
	
		
			
				|  |  |    )
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  | -export { SimpleSelect }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +type PortalSelectProps = {
 | 
	
		
			
				|  |  | +  value: string | number
 | 
	
		
			
				|  |  | +  onSelect: (value: Item) => void
 | 
	
		
			
				|  |  | +  items: Item[]
 | 
	
		
			
				|  |  | +  placeholder?: string
 | 
	
		
			
				|  |  | +  popupClassName?: string
 | 
	
		
			
				|  |  | +}
 | 
	
		
			
				|  |  | +const PortalSelect: FC<PortalSelectProps> = ({
 | 
	
		
			
				|  |  | +  value,
 | 
	
		
			
				|  |  | +  onSelect,
 | 
	
		
			
				|  |  | +  items,
 | 
	
		
			
				|  |  | +  placeholder,
 | 
	
		
			
				|  |  | +  popupClassName,
 | 
	
		
			
				|  |  | +}) => {
 | 
	
		
			
				|  |  | +  const { t } = useTranslation()
 | 
	
		
			
				|  |  | +  const [open, setOpen] = useState(false)
 | 
	
		
			
				|  |  | +  const localPlaceholder = placeholder || t('common.placeholder.select')
 | 
	
		
			
				|  |  | +  const selectedItem = items.find(item => item.value === value)
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +  return (
 | 
	
		
			
				|  |  | +    <PortalToFollowElem
 | 
	
		
			
				|  |  | +      open={open}
 | 
	
		
			
				|  |  | +      onOpenChange={setOpen}
 | 
	
		
			
				|  |  | +      placement='bottom-start'
 | 
	
		
			
				|  |  | +      offset={4}
 | 
	
		
			
				|  |  | +    >
 | 
	
		
			
				|  |  | +      <PortalToFollowElemTrigger onClick={() => setOpen(v => !v)} className='w-full'>
 | 
	
		
			
				|  |  | +        <div
 | 
	
		
			
				|  |  | +          className={`
 | 
	
		
			
				|  |  | +            flex items-center justify-between px-2.5 h-9 rounded-lg border-0 bg-gray-100 text-sm cursor-pointer 
 | 
	
		
			
				|  |  | +          `}
 | 
	
		
			
				|  |  | +          title={selectedItem?.name}
 | 
	
		
			
				|  |  | +        >
 | 
	
		
			
				|  |  | +          <span
 | 
	
		
			
				|  |  | +            className={`
 | 
	
		
			
				|  |  | +              grow truncate
 | 
	
		
			
				|  |  | +              ${!selectedItem?.name && 'text-gray-400'}
 | 
	
		
			
				|  |  | +            `}
 | 
	
		
			
				|  |  | +          >
 | 
	
		
			
				|  |  | +            {selectedItem?.name ?? localPlaceholder}
 | 
	
		
			
				|  |  | +          </span>
 | 
	
		
			
				|  |  | +          <ChevronDownIcon className='shrink-0 h-4 w-4 text-gray-400' />
 | 
	
		
			
				|  |  | +        </div>
 | 
	
		
			
				|  |  | +      </PortalToFollowElemTrigger>
 | 
	
		
			
				|  |  | +      <PortalToFollowElemContent className={`z-20 ${popupClassName}`}>
 | 
	
		
			
				|  |  | +        <div
 | 
	
		
			
				|  |  | +          className='px-1 py-1 max-h-60 overflow-auto rounded-md bg-white text-base shadow-lg border-gray-200 border-[0.5px] focus:outline-none sm:text-sm'
 | 
	
		
			
				|  |  | +        >
 | 
	
		
			
				|  |  | +          {items.map((item: Item) => (
 | 
	
		
			
				|  |  | +            <div
 | 
	
		
			
				|  |  | +              key={item.value}
 | 
	
		
			
				|  |  | +              className={`
 | 
	
		
			
				|  |  | +                flex items-center justify-between px-2.5 h-9 cursor-pointer rounded-lg hover:bg-gray-100 text-gray-700
 | 
	
		
			
				|  |  | +                ${item.value === value && 'bg-gray-100'}
 | 
	
		
			
				|  |  | +              `}
 | 
	
		
			
				|  |  | +              title={item.name}
 | 
	
		
			
				|  |  | +              onClick={() => {
 | 
	
		
			
				|  |  | +                onSelect(item)
 | 
	
		
			
				|  |  | +                setOpen(false)
 | 
	
		
			
				|  |  | +              }}
 | 
	
		
			
				|  |  | +            >
 | 
	
		
			
				|  |  | +              <span
 | 
	
		
			
				|  |  | +                className='grow truncate'
 | 
	
		
			
				|  |  | +                title={item.name}
 | 
	
		
			
				|  |  | +              >
 | 
	
		
			
				|  |  | +                {item.name}
 | 
	
		
			
				|  |  | +              </span>
 | 
	
		
			
				|  |  | +              {item.value === value && (
 | 
	
		
			
				|  |  | +                <CheckIcon className='shrink-0 h-4 w-4' />
 | 
	
		
			
				|  |  | +              )}
 | 
	
		
			
				|  |  | +            </div>
 | 
	
		
			
				|  |  | +          ))}
 | 
	
		
			
				|  |  | +        </div>
 | 
	
		
			
				|  |  | +      </PortalToFollowElemContent>
 | 
	
		
			
				|  |  | +    </PortalToFollowElem>
 | 
	
		
			
				|  |  | +  )
 | 
	
		
			
				|  |  | +}
 | 
	
		
			
				|  |  | +export { SimpleSelect, PortalSelect }
 | 
	
		
			
				|  |  |  export default React.memo(Select)
 |