From a430284aa21e3ae1f0d5654e55b2ad2852519cc2 Mon Sep 17 00:00:00 2001 From: wwf <yearningwang@iqtogether.com> Date: 星期三, 04 六月 2025 15:17:49 +0800 Subject: [PATCH] 初始化 --- app/components/base/select/index.tsx | 151 +++++++++++++++++++++++++------------------------- 1 files changed, 76 insertions(+), 75 deletions(-) diff --git a/app/components/base/select/index.tsx b/app/components/base/select/index.tsx index fa8730f..221d703 100644 --- a/app/components/base/select/index.tsx +++ b/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> ))} -- Gitblit v1.8.0