refactor: migrate PresetsParameter and ParameterItem to base/ui overlay primitives

Replace deprecated Dropdown, SimpleSelect, and Tooltip with DropdownMenu,
Select, and Tooltip compound components from base/ui. Hoist TONE_ICONS to
module level, remove FC in favor of function declarations, and prune
obsolete ESLint suppressions.
This commit is contained in:
yyh
2026-03-11 16:54:14 +08:00
parent 6cb68b6de5
commit d72fbce31c
5 changed files with 68 additions and 85 deletions

View File

@ -109,7 +109,7 @@ describe('ParameterItem', () => {
it('should render select for string with options', () => {
render(<ParameterItem parameterRule={createRule({ type: 'string', options: ['a', 'b'] })} value="a" />)
// SimpleSelect renders an element with text 'a'
// Select renders the selected value in the trigger
expect(screen.getByText('a')).toBeInTheDocument()
})

View File

@ -1,12 +1,11 @@
import type { FC } from 'react'
import type { ModelParameterRule } from '../declarations'
import { useEffect, useRef, useState } from 'react'
import Radio from '@/app/components/base/radio'
import { SimpleSelect } from '@/app/components/base/select'
import Slider from '@/app/components/base/slider'
import Switch from '@/app/components/base/switch'
import TagInput from '@/app/components/base/tag-input'
import Tooltip from '@/app/components/base/tooltip'
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/app/components/base/ui/select'
import { Tooltip, TooltipContent, TooltipTrigger } from '@/app/components/base/ui/tooltip'
import { cn } from '@/utils/classnames'
import { useLanguage } from '../hooks'
import { isNullOrUndefined } from '../utils'
@ -20,13 +19,13 @@ type ParameterItemProps = {
onSwitch?: (checked: boolean, assignValue: ParameterValue) => void
isInWorkflow?: boolean
}
const ParameterItem: FC<ParameterItemProps> = ({
function ParameterItem({
parameterRule,
value,
onChange,
onSwitch,
isInWorkflow,
}) => {
}: ParameterItemProps) {
const language = useLanguage()
const [localValue, setLocalValue] = useState(value)
const numberInputRef = useRef<HTMLInputElement>(null)
@ -99,10 +98,6 @@ const ParameterItem: FC<ParameterItemProps> = ({
handleInputChange(e.target.value)
}
const handleSelect = (option: { value: string | number, name: string }) => {
handleInputChange(option.value)
}
const handleTagChange = (newSequences: string[]) => {
handleInputChange(newSequences)
}
@ -222,13 +217,19 @@ const ParameterItem: FC<ParameterItemProps> = ({
if (parameterRule.type === 'string' && !!parameterRule?.options?.length) {
return (
<SimpleSelect
className="!py-0"
wrapperClassName={cn('!h-8 w-full')}
defaultValue={renderValue as string}
onSelect={handleSelect}
items={parameterRule.options.map(option => ({ value: option, name: option }))}
/>
<Select
value={renderValue as string}
onValueChange={v => handleInputChange(v)}
>
<SelectTrigger className="w-full">
<SelectValue />
</SelectTrigger>
<SelectContent>
{parameterRule.options!.map(option => (
<SelectItem key={option} value={option}>{option}</SelectItem>
))}
</SelectContent>
</Select>
)
}
@ -272,13 +273,18 @@ const ParameterItem: FC<ParameterItemProps> = ({
</div>
{
parameterRule.help && (
<Tooltip
popupContent={(
<Tooltip>
<TooltipTrigger
render={(
<span className="mr-1 flex h-4 w-4 shrink-0 items-center justify-center">
<span aria-hidden className="i-ri-question-line h-3.5 w-3.5 text-text-quaternary" />
</span>
)}
/>
<TooltipContent popupClassName="mr-1">
<div className="w-[150px] whitespace-pre-wrap">{parameterRule.help[language] || parameterRule.help.en_US}</div>
)}
popupClassName="mr-1"
triggerClassName="mr-1 w-4 h-4 shrink-0"
/>
</TooltipContent>
</Tooltip>
)
}
</div>

View File

@ -18,13 +18,12 @@ describe('PresetsParameter', () => {
expect(onSelect).toHaveBeenCalledWith(1)
})
// open=true: trigger has bg-state-base-hover class
it('should apply hover background class when open is true', () => {
it('should mark trigger as open when dropdown is expanded', () => {
render(<PresetsParameter onSelect={vi.fn()} />)
fireEvent.click(screen.getByRole('button', { name: /common\.modelProvider\.loadPresets/i }))
const button = screen.getByRole('button', { name: /common\.modelProvider\.loadPresets/i })
expect(button).toHaveClass('bg-state-base-hover')
expect(button).toHaveAttribute('data-popup-open')
})
// Tone map branch 2: Balanced → Scales02 icon

View File

@ -1,14 +1,16 @@
import type { FC } from 'react'
import { RiArrowDownSLine } from '@remixicon/react'
import { useCallback } from 'react'
import type { ReactNode } from 'react'
import { useTranslation } from 'react-i18next'
import Button from '@/app/components/base/button'
import Dropdown from '@/app/components/base/dropdown'
import { Brush01 } from '@/app/components/base/icons/src/vender/solid/editor'
import { Scales02 } from '@/app/components/base/icons/src/vender/solid/FinanceAndECommerce'
import { Target04 } from '@/app/components/base/icons/src/vender/solid/general'
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from '@/app/components/base/ui/dropdown-menu'
import { TONE_LIST } from '@/config'
import { cn } from '@/utils/classnames'
const toneI18nKeyMap = {
Creative: 'model.tone.Creative',
@ -17,53 +19,42 @@ const toneI18nKeyMap = {
Custom: 'model.tone.Custom',
} as const
const TONE_ICONS: Record<number, ReactNode> = {
1: <Brush01 className="mr-2 h-[14px] w-[14px] text-[#6938EF]" />,
2: <Scales02 className="mr-2 h-[14px] w-[14px] text-indigo-600" />,
3: <Target04 className="mr-2 h-[14px] w-[14px] text-[#107569]" />,
}
type PresetsParameterProps = {
onSelect: (toneId: number) => void
}
const PresetsParameter: FC<PresetsParameterProps> = ({
onSelect,
}) => {
function PresetsParameter({ onSelect }: PresetsParameterProps) {
const { t } = useTranslation()
const renderTrigger = useCallback((open: boolean) => {
return (
<Button
size="small"
variant="secondary"
className={cn(open && 'bg-state-base-hover')}
>
{t('modelProvider.loadPresets', { ns: 'common' })}
<RiArrowDownSLine className="ml-0.5 h-3.5 w-3.5" />
</Button>
)
}, [t])
const getToneIcon = (toneId: number) => {
const className = 'mr-2 w-[14px] h-[14px]'
const res = ({
1: <Brush01 className={`${className} text-[#6938EF]`} />,
2: <Scales02 className={`${className} text-indigo-600`} />,
3: <Target04 className={`${className} text-[#107569]`} />,
})[toneId]
return res
}
const options = TONE_LIST.slice(0, 3).map((tone) => {
return {
value: tone.id,
text: (
<div className="flex h-full items-center">
{getToneIcon(tone.id)}
{t(toneI18nKeyMap[tone.name], { ns: 'common' })}
</div>
),
}
})
return (
<Dropdown
renderTrigger={renderTrigger}
items={options}
onSelect={item => onSelect(item.value as number)}
popupClassName="z-[1003]"
/>
<DropdownMenu>
<DropdownMenuTrigger
render={(
<Button
size="small"
variant="secondary"
className="data-[popup-open]:bg-state-base-hover"
/>
)}
>
{t('modelProvider.loadPresets', { ns: 'common' })}
<span className="i-ri-arrow-down-s-line ml-0.5 h-3.5 w-3.5" />
</DropdownMenuTrigger>
<DropdownMenuContent>
{TONE_LIST.slice(0, 3).map(tone => (
<DropdownMenuItem key={tone.id} onClick={() => onSelect(tone.id)}>
{TONE_ICONS[tone.id]}
{t(toneI18nKeyMap[tone.name], { ns: 'common' })}
</DropdownMenuItem>
))}
</DropdownMenuContent>
</DropdownMenu>
)
}