feat(trigger): dynamic options opt

This commit is contained in:
yessenia 2025-10-28 14:42:55 +08:00
parent 7d56ca5294
commit bf7b18d442
5 changed files with 82 additions and 29 deletions

View File

@ -4,7 +4,6 @@ import { FormItemValidateStatusEnum, FormTypeEnum } from '@/app/components/base/
import Input from '@/app/components/base/input'
import Radio from '@/app/components/base/radio'
import RadioE from '@/app/components/base/radio/ui'
import { PortalSelect } from '@/app/components/base/select'
import PureSelect from '@/app/components/base/select/pure'
import Tooltip from '@/app/components/base/tooltip'
import { useRenderI18nObject } from '@/hooks/use-i18n'
@ -161,7 +160,7 @@ const BaseField = ({
const value = useStore(field.form.store, s => s.values[field.name])
const { data: dynamicOptionsData, isLoading: isDynamicOptionsLoading } = useTriggerPluginDynamicOptions(
const { data: dynamicOptionsData, isLoading: isDynamicOptionsLoading, error: dynamicOptionsError } = useTriggerPluginDynamicOptions(
dynamicSelectParams || {
plugin_id: '',
provider: '',
@ -176,7 +175,7 @@ const BaseField = ({
if (!dynamicOptionsData?.options)
return []
return dynamicOptionsData.options.map(option => ({
name: getTranslatedContent({ content: option.label, render: renderI18nObject }),
label: getTranslatedContent({ content: option.label, render: renderI18nObject }),
value: option.value,
}))
}, [dynamicOptionsData, renderI18nObject])
@ -250,17 +249,20 @@ const BaseField = ({
}
{
formItemType === FormTypeEnum.dynamicSelect && (
<PortalSelect
<PureSelect
options={dynamicOptions}
value={value}
onSelect={(item: any) => field.handleChange(item.value)}
readonly={disabled || isDynamicOptionsLoading}
onChange={field.handleChange}
disabled={disabled || isDynamicOptionsLoading}
placeholder={
isDynamicOptionsLoading
? 'Loading options...'
: translatedPlaceholder || 'Select an option'
? t('common.dynamicSelect.loading')
: translatedPlaceholder
}
items={dynamicOptions}
popupClassName="z-[9999]"
{...(dynamicOptionsError ? { popupProps: { title: t('common.dynamicSelect.error'), titleClassName: 'text-text-destructive-secondary' } }
: (!dynamicOptions.length ? { popupProps: { title: t('common.dynamicSelect.noData') } } : {}))}
triggerPopupSameWidth
multiple={multiple}
/>
)
}

View File

@ -1,5 +1,6 @@
import {
useCallback,
useMemo,
useState,
} from 'react'
import { useTranslation } from 'react-i18next'
@ -22,10 +23,8 @@ export type Option = {
value: string
}
export type PureSelectProps = {
type SharedPureSelectProps = {
options: Option[]
value?: string
onChange?: (value: string) => void
containerProps?: PortalToFollowElemOptions & {
open?: boolean
onOpenChange?: (open: boolean) => void
@ -38,22 +37,39 @@ export type PureSelectProps = {
className?: string
itemClassName?: string
title?: string
titleClassName?: string
},
placeholder?: string
disabled?: boolean
triggerPopupSameWidth?: boolean
}
const PureSelect = ({
options,
value,
onChange,
containerProps,
triggerProps,
popupProps,
placeholder,
disabled,
triggerPopupSameWidth,
}: PureSelectProps) => {
type SingleSelectProps = {
multiple?: false
value?: string
onChange?: (value: string) => void
}
type MultiSelectProps = {
multiple: true
value?: string[]
onChange?: (value: string[]) => void
}
export type PureSelectProps = SharedPureSelectProps & (SingleSelectProps | MultiSelectProps)
const PureSelect = (props: PureSelectProps) => {
const {
options,
containerProps,
triggerProps,
popupProps,
placeholder,
disabled,
triggerPopupSameWidth,
multiple,
value,
onChange,
} = props
const { t } = useTranslation()
const {
open,
@ -69,6 +85,7 @@ const PureSelect = ({
className: popupClassName,
itemClassName: popupItemClassName,
title: popupTitle,
titleClassName: popupTitleClassName,
} = popupProps || {}
const [localOpen, setLocalOpen] = useState(false)
@ -79,8 +96,13 @@ const PureSelect = ({
setLocalOpen(openValue)
}, [onOpenChange])
const selectedOption = options.find(option => option.value === value)
const triggerText = selectedOption?.label || placeholder || t('common.placeholder.select')
const triggerText = useMemo(() => {
const placeholderText = placeholder || t('common.placeholder.select')
if (multiple)
return value?.length ? t('common.dynamicSelect.selected', { count: value.length }) : placeholderText
return options.find(option => option.value === value)?.label || placeholderText
}, [multiple, value, options, placeholder])
return (
<PortalToFollowElem
@ -122,13 +144,16 @@ const PureSelect = ({
)}>
<div
className={cn(
'rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-bg-blur p-1 shadow-lg',
'max-h-80 overflow-auto rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-bg-blur p-1 shadow-lg',
popupClassName,
)}
>
{
popupTitle && (
<div className='system-xs-medium-uppercase flex h-[22px] items-center px-3 text-text-tertiary'>
<div className={cn(
'system-xs-medium-uppercase flex h-[22px] items-center px-3 text-text-tertiary',
popupTitleClassName,
)}>
{popupTitle}
</div>
)
@ -144,6 +169,14 @@ const PureSelect = ({
title={option.label}
onClick={() => {
if (disabled) return
if (multiple) {
const currentValues = value ?? []
const nextValues = currentValues.includes(option.value)
? currentValues.filter(valueItem => valueItem !== option.value)
: [...currentValues, option.value]
onChange?.(nextValues)
return
}
onChange?.(option.value)
handleOpenChange(false)
}}
@ -152,7 +185,11 @@ const PureSelect = ({
{option.label}
</div>
{
value === option.value && <RiCheckLine className='h-4 w-4 shrink-0 text-text-accent' />
(
multiple
? (value ?? []).includes(option.value)
: value === option.value
) && <RiCheckLine className='h-4 w-4 shrink-0 text-text-accent' />
}
</div>
))

View File

@ -776,6 +776,12 @@ const translation = {
supportedFormats: 'Supports PNG, JPG, JPEG, WEBP and GIF',
},
you: 'You',
dynamicSelect: {
error: 'Loading options failed',
noData: 'No options available',
loading: 'Loading options...',
selected: '{{count}} selected',
},
}
export default translation

View File

@ -776,6 +776,12 @@ const translation = {
title: '提供反馈',
placeholder: '请描述发生了什么问题或我们可以如何改进...',
},
dynamicSelect: {
error: '加载选项失败',
noData: '没有可用的选项',
loading: '加载选项...',
selected: '已选择 {{count}} 项',
},
}
export default translation

View File

@ -299,8 +299,10 @@ export const useTriggerPluginDynamicOptions = (payload: {
provider_type: 'trigger', // Add required provider_type parameter
},
},
{ silent: true },
),
enabled: enabled && !!payload.plugin_id && !!payload.provider && !!payload.action && !!payload.parameter && !!payload.credential_id,
retry: 0,
})
}