mirror of
				https://github.com/langgenius/dify.git
				synced 2025-11-04 12:53:38 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			225 lines
		
	
	
		
			7.7 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
			
		
		
	
	
			225 lines
		
	
	
		
			7.7 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
'use client'
 | 
						|
import type { FC } from 'react'
 | 
						|
import React, { Fragment, useEffect, useState } from 'react'
 | 
						|
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'
 | 
						|
 | 
						|
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
 | 
						|
}
 | 
						|
 | 
						|
export type ISelectProps = {
 | 
						|
  className?: string
 | 
						|
  wrapperClassName?: string
 | 
						|
  items?: Item[]
 | 
						|
  defaultValue?: number | string
 | 
						|
  disabled?: boolean
 | 
						|
  onSelect: (value: Item) => void
 | 
						|
  allowSearch?: boolean
 | 
						|
  bgClassName?: string
 | 
						|
  placeholder?: string
 | 
						|
}
 | 
						|
const Select: FC<ISelectProps> = ({
 | 
						|
  className,
 | 
						|
  items = defaultItems,
 | 
						|
  defaultValue = 1,
 | 
						|
  disabled = false,
 | 
						|
  onSelect,
 | 
						|
  allowSearch = true,
 | 
						|
  bgClassName = 'bg-gray-100',
 | 
						|
}) => {
 | 
						|
  const [query, setQuery] = useState('')
 | 
						|
  const [open, setOpen] = useState(false)
 | 
						|
 | 
						|
  const [selectedItem, setSelectedItem] = useState<Item | null>(null)
 | 
						|
  useEffect(() => {
 | 
						|
    let defaultSelect = null
 | 
						|
    const existed = items.find((item: Item) => item.value === defaultValue)
 | 
						|
    if (existed) {
 | 
						|
      defaultSelect = existed
 | 
						|
    }
 | 
						|
    setSelectedItem(defaultSelect)
 | 
						|
  }, [defaultValue])
 | 
						|
 | 
						|
  const filteredItems: Item[]
 | 
						|
    = query === ''
 | 
						|
      ? items
 | 
						|
      : items.filter((item) => {
 | 
						|
        return item.name.toLowerCase().includes(query.toLowerCase())
 | 
						|
      })
 | 
						|
 | 
						|
  return (
 | 
						|
    <Combobox
 | 
						|
      as="div"
 | 
						|
      disabled={disabled}
 | 
						|
      value={selectedItem}
 | 
						|
      className={className}
 | 
						|
      onChange={(value: Item) => {
 | 
						|
        if (!disabled) {
 | 
						|
          setSelectedItem(value)
 | 
						|
          setOpen(false)
 | 
						|
          onSelect(value)
 | 
						|
        }
 | 
						|
      }}>
 | 
						|
      <div className={classNames('relative')}>
 | 
						|
        <div className='group text-gray-800'>
 | 
						|
          {allowSearch
 | 
						|
            ? <Combobox.Input
 | 
						|
              className={`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 cursor-not-allowed`}
 | 
						|
              onChange={(event) => {
 | 
						|
                if (!disabled)
 | 
						|
                  setQuery(event.target.value)
 | 
						|
              }}
 | 
						|
              displayValue={(item: Item) => item?.name}
 | 
						|
            />
 | 
						|
            : <Combobox.Button onClick={
 | 
						|
              () => {
 | 
						|
                if (!disabled)
 | 
						|
                  setOpen(!open)
 | 
						|
              }
 | 
						|
            } className={`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`}>
 | 
						|
              {selectedItem?.name}
 | 
						|
            </Combobox.Button>}
 | 
						|
          <Combobox.Button className="absolute inset-y-0 right-0 flex items-center rounded-r-md px-2 focus:outline-none group-hover:bg-gray-200" onClick={
 | 
						|
            () => {
 | 
						|
              if (!disabled)
 | 
						|
                setOpen(!open)
 | 
						|
            }
 | 
						|
          }>
 | 
						|
            {open ? <ChevronUpIcon className="h-5 w-5" /> : <ChevronDownIcon className="h-5 w-5" />}
 | 
						|
          </Combobox.Button>
 | 
						|
        </div>
 | 
						|
 | 
						|
        {filteredItems.length > 0 && (
 | 
						|
          <Combobox.Options className="absolute z-10 mt-1 px-1 max-h-60 w-full overflow-auto rounded-md bg-white py-1 text-base shadow-lg border-gray-200 border-[0.5px] focus:outline-none sm:text-sm">
 | 
						|
            {filteredItems.map((item: Item) => (
 | 
						|
              <Combobox.Option
 | 
						|
                key={item.value}
 | 
						|
                value={item}
 | 
						|
                className={({ active }: { active: boolean }) =>
 | 
						|
                  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' : '',
 | 
						|
                  )
 | 
						|
                }
 | 
						|
              >
 | 
						|
                {({ /* active, */ selected }) => (
 | 
						|
                  <>
 | 
						|
                    <span className={classNames('block truncate', selected && 'font-normal')}>{item.name}</span>
 | 
						|
                    {selected && (
 | 
						|
                      <span
 | 
						|
                        className={classNames(
 | 
						|
                          'absolute inset-y-0 right-0 flex items-center pr-4 text-gray-700',
 | 
						|
                        )}
 | 
						|
                      >
 | 
						|
                        <CheckIcon className="h-5 w-5" aria-hidden="true" />
 | 
						|
                      </span>
 | 
						|
                    )}
 | 
						|
                  </>
 | 
						|
                )}
 | 
						|
              </Combobox.Option>
 | 
						|
            ))}
 | 
						|
          </Combobox.Options>
 | 
						|
        )}
 | 
						|
      </div>
 | 
						|
    </Combobox >
 | 
						|
  )
 | 
						|
}
 | 
						|
 | 
						|
const SimpleSelect: FC<ISelectProps> = ({
 | 
						|
  className,
 | 
						|
  wrapperClassName,
 | 
						|
  items = defaultItems,
 | 
						|
  defaultValue = 1,
 | 
						|
  disabled = false,
 | 
						|
  onSelect,
 | 
						|
  placeholder,
 | 
						|
}) => {
 | 
						|
  const { t } = useTranslation()
 | 
						|
  const localPlaceholder = placeholder || t('common.placeholder.select')
 | 
						|
 | 
						|
  const [selectedItem, setSelectedItem] = useState<Item | null>(null)
 | 
						|
  useEffect(() => {
 | 
						|
    let defaultSelect = null
 | 
						|
    const existed = items.find((item: Item) => item.value === defaultValue)
 | 
						|
    if (existed) {
 | 
						|
      defaultSelect = existed
 | 
						|
    }
 | 
						|
    setSelectedItem(defaultSelect)
 | 
						|
  }, [defaultValue])
 | 
						|
 | 
						|
  return (
 | 
						|
    <Listbox
 | 
						|
      value={selectedItem}
 | 
						|
      onChange={(value: Item) => {
 | 
						|
        if (!disabled) {
 | 
						|
          setSelectedItem(value)
 | 
						|
          onSelect(value)
 | 
						|
        }
 | 
						|
      }}
 | 
						|
    >
 | 
						|
      <div className={`relative h-9 ${wrapperClassName}`}>
 | 
						|
        <Listbox.Button className={`w-full h-full rounded-lg border-0 bg-gray-100 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 cursor-pointer ${className}`}>
 | 
						|
          <span className={classNames("block truncate text-left", !selectedItem?.name && 'text-gray-400')}>{selectedItem?.name ?? localPlaceholder}</span>
 | 
						|
          <span className="pointer-events-none absolute inset-y-0 right-0 flex items-center pr-2">
 | 
						|
            <ChevronDownIcon
 | 
						|
              className="h-5 w-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 z-10 mt-1 px-1 max-h-60 w-full overflow-auto rounded-md bg-white py-1 text-base shadow-lg border-gray-200 border-[0.5px] focus:outline-none sm:text-sm">
 | 
						|
            {items.map((item: Item) => (
 | 
						|
              <Listbox.Option
 | 
						|
                key={item.value}
 | 
						|
                className={({ active }) =>
 | 
						|
                  `relative cursor-pointer select-none py-2 pl-3 pr-9 rounded-lg hover:bg-gray-100 text-gray-700 ${active ? 'bg-gray-100' : ''
 | 
						|
                  }`
 | 
						|
                }
 | 
						|
                value={item}
 | 
						|
                disabled={disabled}
 | 
						|
              >
 | 
						|
                {({ /* active, */ selected }) => (
 | 
						|
                  <>
 | 
						|
                    <span className={classNames('block truncate', selected && 'font-normal')}>{item.name}</span>
 | 
						|
                    {selected && (
 | 
						|
                      <span
 | 
						|
                        className={classNames(
 | 
						|
                          'absolute inset-y-0 right-0 flex items-center pr-4 text-gray-700',
 | 
						|
                        )}
 | 
						|
                      >
 | 
						|
                        <CheckIcon className="h-5 w-5" aria-hidden="true" />
 | 
						|
                      </span>
 | 
						|
                    )}
 | 
						|
                  </>
 | 
						|
                )}
 | 
						|
              </Listbox.Option>
 | 
						|
            ))}
 | 
						|
          </Listbox.Options>
 | 
						|
        </Transition>
 | 
						|
      </div>
 | 
						|
    </Listbox>
 | 
						|
  )
 | 
						|
}
 | 
						|
export { SimpleSelect }
 | 
						|
export default React.memo(Select)
 |