fix(select): isolate trigger states and unavailable package selection (#41843)

This commit is contained in:
yyh 2026-09-05 03:49:39 +00:00 committed by GitHub
parent ad90cb9111
commit 00e5786067
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 28 additions and 43 deletions

View File

@ -62,7 +62,7 @@ type SelectGroupProps = BaseSelect.Group.Props
const selectTriggerVariants = cva(
[
'group flex w-full items-center border-0 bg-components-input-bg-normal text-start text-components-input-text-filled outline-hidden',
'group/select-trigger flex w-full items-center border-0 bg-components-input-bg-normal text-start text-components-input-text-filled outline-hidden',
'hover:bg-state-base-hover-alt focus-visible:bg-state-base-hover-alt data-popup-open:bg-state-base-hover-alt',
'focus-visible:ring-2 focus-visible:ring-state-accent-solid',
'data-placeholder:text-components-input-text-placeholder',
@ -94,7 +94,7 @@ function SelectTrigger({ className, children, size, ...props }: SelectTriggerPro
return (
<BaseSelect.Trigger className={cn(selectTriggerVariants({ size, className }))} {...props}>
<span className="min-w-0 grow truncate">{children}</span>
<BaseSelect.Icon className="shrink-0 text-text-quaternary transition-colors group-hover:text-text-secondary group-data-readonly:hidden data-popup-open:text-text-secondary">
<BaseSelect.Icon className="shrink-0 text-text-quaternary transition-colors group-data-readonly/select-trigger:hidden group-[:hover:not([data-disabled])]/select-trigger:text-text-secondary data-popup-open:text-text-secondary">
<span className="i-ri-arrow-down-s-line h-4 w-4" aria-hidden="true" />
</BaseSelect.Icon>
</BaseSelect.Trigger>

View File

@ -12,8 +12,8 @@ import {
SelectPortal,
SelectPositioner,
SelectTrigger,
SelectValue,
} from '@langgenius/dify-ui/select'
import { RiArrowDownSLine } from '@remixicon/react'
import dayjs from 'dayjs'
import * as React from 'react'
import { useCallback, useMemo, useState } from 'react'
@ -35,7 +35,6 @@ type Props = Readonly<{
const RangeSelector: FC<Props> = ({ isCustomRange, ranges, onSelect }) => {
const { t } = useTranslation()
const [open, setOpen] = useState(false)
const items = useMemo<TimePeriodOption[]>(() => {
return ranges.map((range) => ({
...range,
@ -69,8 +68,6 @@ const RangeSelector: FC<Props> = ({ isCustomRange, ranges, onSelect }) => {
return (
<Select<number>
value={selectedItem?.value ?? null}
open={open}
onOpenChange={setOpen}
onValueChange={(nextValue) => {
if (nextValue == null) return
const nextItem = items.find((item) => item.value === nextValue)
@ -79,15 +76,12 @@ const RangeSelector: FC<Props> = ({ isCustomRange, ranges, onSelect }) => {
handleSelectRange(nextItem)
}}
>
<SelectTrigger className="h-auto w-fit max-w-none border-0 bg-transparent p-0 hover:bg-transparent focus-visible:bg-transparent [&>*:last-child]:hidden">
<div className="flex h-8 cursor-pointer items-center space-x-1.5 rounded-lg bg-components-input-bg-normal pr-2 pl-3 group-data-popup-open:bg-state-base-hover-alt">
<div className="system-sm-regular text-components-input-text-filled">
{isCustomRange
? t(($) => $['filter.period.custom'], { ns: 'appLog' })
: selectedItem?.name}
</div>
<RiArrowDownSLine className="size-4 text-text-quaternary group-data-popup-open:text-text-secondary" />
</div>
<SelectTrigger className="w-fit">
<SelectValue>
{isCustomRange
? t(($) => $['filter.period.custom'], { ns: 'appLog' })
: selectedItem?.name}
</SelectValue>
</SelectTrigger>
<SelectPortal>
<SelectPositioner className="-translate-x-6">

View File

@ -262,16 +262,28 @@ describe('SelectPackage', () => {
expect(onSelectVersion).toHaveBeenCalledWith({ value: 'v0.9.0', name: 'v0.9.0' })
})
it('should select a valid package option', async () => {
it('should only allow package selection after a version is selected', async () => {
const user = userEvent.setup()
const onSelectPackage = vi.fn()
renderSelectPackage({
selectedVersion: 'v1.0.0',
const props = {
...createDefaultProps(),
updatePayload: createUpdatePayload(),
onSelectPackage,
})
}
const { rerender } = render(<SelectPackage {...props} packages={[]} />)
const section = getSection('plugin.installFromGitHub.selectPackage')
await user.click(within(section).getByRole('combobox'))
const packageTrigger = screen.getByRole('combobox', {
name: 'plugin.installFromGitHub.selectPackage',
})
expect(packageTrigger).toBeDisabled()
await user.click(packageTrigger)
expect(screen.queryByRole('listbox')).not.toBeInTheDocument()
expect(onSelectPackage).not.toHaveBeenCalled()
rerender(<SelectPackage {...props} selectedVersion="v1.0.0" />)
expect(packageTrigger).toBeEnabled()
await user.click(packageTrigger)
await user.click(await screen.findByRole('option', { name: 'plugin.tar.gz' }))
expect(onSelectPackage).toHaveBeenCalledWith({
@ -472,27 +484,6 @@ describe('SelectPackage', () => {
})
})
// ================================
// PortalSelect Readonly State Tests
// ================================
describe('PortalSelect Readonly State', () => {
it('should make package select readonly when no version selected', () => {
renderSelectPackage({ selectedVersion: '' })
// When no version is selected, package select should be readonly
const trigger = screen.getAllByRole('combobox')[1]
expect(trigger).toHaveAttribute('aria-readonly', 'true')
})
it('should make package select active when version is selected', () => {
renderSelectPackage({ selectedVersion: 'v1.0.0' })
// When version is selected, package select should be active
const trigger = screen.getAllByRole('combobox')[1]
expect(trigger).not.toHaveAttribute('aria-readonly', 'true')
})
})
// ================================
// installedValue Props Tests
// ================================

View File

@ -128,7 +128,7 @@ const SelectPackage: React.FC<SelectPackageProps> = ({
<Field name="package" className="gap-4 self-stretch">
<Select
value={selectedPackageOption?.value ?? null}
readOnly={!selectedVersion}
disabled={!selectedVersion}
onValueChange={(value) => {
if (value == null) return
const selectedItem = packages.find((item) => item.value === value)