wwf
2 天以前 a430284aa21e3ae1f0d5654e55b2ad2852519cc2
app/components/base/select/index.tsx
@@ -1,9 +1,8 @@
'use client'
import type { FC } from 'react'
import React, { useEffect, useState } from 'react'
import { Combobox, ComboboxButton, ComboboxInput, ComboboxOption, ComboboxOptions, Listbox, ListboxButton, ListboxOption, ListboxOptions } from '@headlessui/react'
import React, { Fragment, useEffect, useState } from 'react'
import { Combobox, Listbox, Transition } from '@headlessui/react'
import { ChevronDownIcon, ChevronUpIcon, XMarkIcon } from '@heroicons/react/20/solid'
import Badge from '../badge/index'
import { RiCheckLine } from '@remixicon/react'
import { useTranslation } from 'react-i18next'
import classNames from '@/utils/classnames'
@@ -31,7 +30,7 @@
export type ISelectProps = {
  className?: string
  wrapperClassName?: string
  renderTrigger?: (value: Item | null) => React.JSX.Element | null
  renderTrigger?: (value: Item | null) => JSX.Element | null
  items?: Item[]
  defaultValue?: number | string
  disabled?: boolean
@@ -59,7 +58,7 @@
  disabled = false,
  onSelect,
  allowSearch = true,
  bgClassName = 'bg-components-input-bg-normal',
  bgClassName = 'bg-gray-100',
  overlayClassName,
  optionClassName,
  renderOption,
@@ -99,44 +98,44 @@
        }
      }}>
      <div className={classNames('relative')}>
        <div className='group text-text-secondary'>
        <div className='group text-gray-800'>
          {allowSearch
            ? <ComboboxInput
              className={`w-full rounded-lg border-0 ${bgClassName} py-1.5 pl-3 pr-10 shadow-sm focus-visible:bg-state-base-hover focus-visible:outline-none group-hover:bg-state-base-hover sm:text-sm sm:leading-6 ${disabled ? 'cursor-not-allowed' : 'cursor-pointer'}`}
            ? <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 ${disabled ? 'cursor-not-allowed' : 'cursor-pointer'}`}
              onChange={(event) => {
                if (!disabled)
                  setQuery(event.target.value)
              }}
              displayValue={(item: Item) => item?.name}
            />
            : <ComboboxButton onClick={
            : <Combobox.Button onClick={
              () => {
                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-state-base-hover group-hover:bg-state-base-hover`, optionClassName)}>
              <div className='w-0 grow truncate text-left' title={selectedItem?.name}>{selectedItem?.name}</div>
            </ComboboxButton>}
          <ComboboxButton className="absolute inset-y-0 right-0 flex items-center rounded-r-md px-2 focus:outline-none" onClick={
            } 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)}>
              <div className='w-0 grow text-left truncate' title={selectedItem?.name}>{selectedItem?.name}</div>
            </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" />}
          </ComboboxButton>
          </Combobox.Button>
        </div>
        {(filteredItems.length > 0 && open) && (
          <ComboboxOptions className={`absolute z-10 mt-1 max-h-60 w-full overflow-auto rounded-md border-[0.5px] border-components-panel-border bg-components-panel-bg-blur px-1 py-1 text-base shadow-lg backdrop-blur-sm focus:outline-none sm:text-sm ${overlayClassName}`}>
          <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 ${overlayClassName}`}>
            {filteredItems.map((item: Item) => (
              <ComboboxOption
              <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-state-base-hover text-text-secondary',
                    active ? 'bg-state-base-hover' : '',
                    '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,
                  )
                }
@@ -151,7 +150,7 @@
                          {selected && (
                            <span
                              className={classNames(
                                'absolute inset-y-0 right-0 flex items-center pr-4 text-text-secondary',
                                'absolute inset-y-0 right-0 flex items-center pr-4 text-gray-700',
                              )}
                            >
                              <RiCheckLine className="h-4 w-4" aria-hidden="true" />
@@ -161,9 +160,9 @@
                      )}
                  </>
                )}
              </ComboboxOption>
              </Combobox.Option>
            ))}
          </ComboboxOptions>
          </Combobox.Options>
        )}
      </div>
    </Combobox >
@@ -210,9 +209,9 @@
      }}
    >
      <div className={classNames('group/simple-select relative h-9', wrapperClassName)}>
        {renderTrigger && <ListboxButton className='w-full'>{renderTrigger(selectedItem)}</ListboxButton>}
        {renderTrigger && <Listbox.Button className='w-full'>{renderTrigger(selectedItem)}</Listbox.Button>}
        {!renderTrigger && (
          <ListboxButton className={classNames(`flex items-center w-full h-full rounded-lg border-0 bg-components-input-bg-normal pl-3 pr-10 sm:text-sm sm:leading-6 focus-visible:outline-none focus-visible:bg-state-base-hover-alt group-hover/simple-select:bg-state-base-hover-alt ${disabled ? 'cursor-not-allowed' : 'cursor-pointer'}`, className)}>
          <Listbox.Button className={classNames(`flex items-center w-full h-full rounded-lg border-0 bg-components-input-bg-normal pl-3 pr-10 sm:text-sm sm:leading-6 focus-visible:outline-none focus-visible:bg-state-base-hover-alt group-hover/simple-select:bg-state-base-hover-alt ${disabled ? 'cursor-not-allowed' : 'cursor-pointer'}`, className)}>
            <span className={classNames('block truncate text-left system-sm-regular text-components-input-text-filled', !selectedItem?.name && 'text-components-input-text-placeholder')}>{selectedItem?.name ?? localPlaceholder}</span>
            <span className="absolute inset-y-0 right-0 flex items-center pr-2">
              {(selectedItem && !notClearable)
@@ -223,7 +222,7 @@
                      setSelectedItem(null)
                      onSelect({ name: '', value: '' })
                    }}
                    className="h-4 w-4 cursor-pointer text-text-quaternary"
                    className="h-4 w-4 text-text-quaternary cursor-pointer"
                    aria-hidden="false"
                  />
                )
@@ -234,44 +233,52 @@
                  />
                )}
            </span>
          </ListboxButton>
          </Listbox.Button>
        )}
        {!disabled && (
          <ListboxOptions className={classNames('absolute z-10 mt-1 px-1 max-h-60 w-full overflow-auto rounded-xl bg-components-panel-bg-blur backdrop-blur-sm py-1 text-base shadow-lg border-components-panel-border border-[0.5px] focus:outline-none sm:text-sm', optionWrapClassName)}>
            {items.map((item: Item) => (
              <ListboxOption
                key={item.value}
                className={
                  classNames(
                    'relative cursor-pointer select-none py-2 pl-3 pr-9 rounded-lg hover:bg-state-base-hover text-text-secondary',
                    optionClassName,
                  )
                }
                value={item}
                disabled={disabled}
              >
                {({ /* active, */ selected }) => (
                  <>
                    {renderOption
                      ? renderOption({ item, selected })
                      : (<>
                        <span className={classNames('block', selected && 'font-normal')}>{item.name}</span>
                        {selected && !hideChecked && (
                          <span
                            className={classNames(
                              'absolute inset-y-0 right-0 flex items-center pr-4 text-text-accent',
                            )}
                          >
                            <RiCheckLine className="h-4 w-4" aria-hidden="true" />
                          </span>
                        )}
                      </>)}
                  </>
                )}
              </ListboxOption>
            ))}
          </ListboxOptions>
          <Transition
            as={Fragment}
            leave="transition ease-in duration-100"
            leaveFrom="opacity-100"
            leaveTo="opacity-0"
          >
            <Listbox.Options className={classNames('absolute z-10 mt-1 px-1 max-h-60 w-full overflow-auto rounded-md bg-components-panel-bg-blur py-1 text-base shadow-lg border-components-panel-border border-[0.5px] focus:outline-none sm:text-sm', optionWrapClassName)}>
              {items.map((item: Item) => (
                <Listbox.Option
                  key={item.value}
                  className={({ active }) =>
                    classNames(
                      'relative cursor-pointer select-none py-2 pl-3 pr-9 rounded-lg hover:bg-state-base-hover text-text-secondary',
                      optionClassName,
                    )
                  }
                  value={item}
                  disabled={disabled}
                >
                  {({ /* active, */ selected }) => (
                    <>
                      {renderOption
                        ? renderOption({ item, selected })
                        : (<>
                          <span className={classNames('block', selected && 'font-normal')}>{item.name}</span>
                          {selected && !hideChecked && (
                            <span
                              className={classNames(
                                'absolute inset-y-0 right-0 flex items-center pr-4 text-text-accent',
                              )}
                            >
                              <RiCheckLine className="h-4 w-4" aria-hidden="true" />
                            </span>
                          )}
                        </>)}
                    </>
                  )}
                </Listbox.Option>
              ))}
            </Listbox.Options>
          </Transition>
        )}
      </div>
    </Listbox>
@@ -283,8 +290,7 @@
  onSelect: (value: Item) => void
  items: Item[]
  placeholder?: string
  installedValue?: string | number
  renderTrigger?: (value?: Item) => React.JSX.Element | null
  renderTrigger?: (value?: Item) => JSX.Element | null
  triggerClassName?: string
  triggerClassNameFn?: (open: boolean) => string
  popupClassName?: string
@@ -297,7 +303,6 @@
  onSelect,
  items,
  placeholder,
  installedValue,
  renderTrigger,
  triggerClassName,
  triggerClassNameFn,
@@ -309,7 +314,7 @@
  const { t } = useTranslation()
  const [open, setOpen] = useState(false)
  const localPlaceholder = placeholder || t('common.placeholder.select')
  const selectedItem = value ? items.find(item => item.value === value) : undefined
  const selectedItem = items.find(item => item.value === value)
  return (
    <PortalToFollowElem
@@ -324,34 +329,33 @@
          : (
            <div
              className={classNames(`
            group flex items-center justify-between px-2.5 h-9 rounded-lg border-0 bg-components-input-bg-normal hover:bg-state-base-hover-alt text-sm ${readonly ? 'cursor-not-allowed' : 'cursor-pointer'}
            flex items-center justify-between px-2.5 h-9 rounded-lg border-0 bg-gray-100 text-sm ${readonly ? 'cursor-not-allowed' : 'cursor-pointer'}
          `, triggerClassName, triggerClassNameFn?.(open))}
              title={selectedItem?.name}
            >
              <span
                className={`
              grow truncate
              ${!selectedItem?.name && 'text-components-input-text-placeholder'}
              ${!selectedItem?.name && 'text-gray-400'}
            `}
              >
                {selectedItem?.name ?? localPlaceholder}
              </span>
              <div className='mx-0.5'>{installedValue && selectedItem && selectedItem.value !== installedValue && <Badge>{installedValue} {'->'} {selectedItem.value} </Badge>}</div>
              <ChevronDownIcon className='h-4 w-4 shrink-0 text-text-quaternary group-hover:text-text-secondary' />
              <ChevronDownIcon className='shrink-0 h-4 w-4 text-gray-400' />
            </div>
          )}
      </PortalToFollowElemTrigger>
      <PortalToFollowElemContent className={`z-20 ${popupClassName}`}>
        <div
          className={classNames('px-1 py-1 max-h-60 overflow-auto rounded-md text-base shadow-lg border-components-panel-border bg-components-panel-bg border-[0.5px] focus:outline-none sm:text-sm', popupInnerClassName)}
          className={classNames('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', popupInnerClassName)}
        >
          {items.map((item: Item) => (
            <div
              key={item.value}
              className={`
                flex h-9 cursor-pointer items-center justify-between rounded-lg px-2.5 text-text-secondary hover:bg-state-base-hover
                ${item.value === value && 'bg-state-base-hover'}
                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={() => {
@@ -363,13 +367,10 @@
                className='w-0 grow truncate'
                title={item.name}
              >
                <span className='truncate'>{item.name}</span>
                {item.value === installedValue && (
                  <Badge uppercase={true} className='ml-1 shrink-0'>INSTALLED</Badge>
                )}
                {item.name}
              </span>
              {!hideChecked && item.value === value && (
                <RiCheckLine className='h-4 w-4 shrink-0 text-text-accent' />
                <RiCheckLine className='shrink-0 h-4 w-4 text-text-accent' />
              )}
            </div>
          ))}