mirror of https://github.com/langgenius/dify.git
feat(trigger): add formitem desc
This commit is contained in:
parent
06d1a2e2fd
commit
949ac9d930
|
|
@ -1,5 +1,5 @@
|
|||
import CheckboxList from '@/app/components/base/checkbox-list'
|
||||
import type { FieldState, FormSchema } from '@/app/components/base/form/types'
|
||||
import type { FieldState, FormSchema, TypeWithI18N } from '@/app/components/base/form/types'
|
||||
import { FormItemValidateStatusEnum, FormTypeEnum } from '@/app/components/base/form/types'
|
||||
import Input from '@/app/components/base/input'
|
||||
import Radio from '@/app/components/base/radio'
|
||||
|
|
@ -31,6 +31,19 @@ const getExtraProps = (type: FormTypeEnum) => {
|
|||
}
|
||||
}
|
||||
|
||||
const getTranslatedContent = ({ content, render }: {
|
||||
content: React.ReactNode | string | null | undefined | TypeWithI18N<string> | Record<string, string>
|
||||
render: (content: TypeWithI18N<string> | Record<string, string>) => string
|
||||
}): string => {
|
||||
if (isValidElement(content) || typeof content === 'string')
|
||||
return content as string
|
||||
|
||||
if (typeof content === 'object' && content !== null)
|
||||
return render(content as TypeWithI18N<string>)
|
||||
|
||||
return ''
|
||||
}
|
||||
|
||||
const VALIDATE_STATUS_STYLE_MAP: Record<FormItemValidateStatusEnum, { componentClassName: string, textClassName: string, infoFieldName: string }> = {
|
||||
[FormItemValidateStatusEnum.Error]: {
|
||||
componentClassName: 'border-components-input-border-destructive focus:border-components-input-border-destructive',
|
||||
|
|
@ -91,24 +104,21 @@ const BaseField = ({
|
|||
multiple = false,
|
||||
tooltip,
|
||||
showCopy,
|
||||
description,
|
||||
url,
|
||||
help,
|
||||
} = formSchema
|
||||
const disabled = propsDisabled || formSchemaDisabled
|
||||
|
||||
const memorizedLabel = useMemo(() => {
|
||||
if (isValidElement(label) || typeof label === 'string')
|
||||
return label
|
||||
|
||||
if (typeof label === 'object' && label !== null)
|
||||
return renderI18nObject(label as Record<string, string>)
|
||||
}, [label, renderI18nObject])
|
||||
|
||||
const memorizedPlaceholder = useMemo(() => {
|
||||
if (typeof placeholder === 'string')
|
||||
return placeholder
|
||||
|
||||
if (typeof placeholder === 'object' && placeholder !== null)
|
||||
return renderI18nObject(placeholder as Record<string, string>)
|
||||
}, [placeholder, renderI18nObject])
|
||||
const [translatedLabel, translatedPlaceholder, translatedTooltip, translatedDescription, translatedHelp] = useMemo(() => {
|
||||
return [
|
||||
label,
|
||||
placeholder,
|
||||
tooltip,
|
||||
description,
|
||||
help,
|
||||
].map(v => getTranslatedContent({ content: v, render: renderI18nObject }))
|
||||
}, [label, placeholder, tooltip, description, help, renderI18nObject])
|
||||
|
||||
const watchedVariables = useMemo(() => {
|
||||
const variables = new Set<string>()
|
||||
|
|
@ -139,7 +149,7 @@ const BaseField = ({
|
|||
})
|
||||
}).map((option) => {
|
||||
return {
|
||||
label: typeof option.label === 'string' ? option.label : renderI18nObject(option.label),
|
||||
label: getTranslatedContent({ content: option.label, render: renderI18nObject }),
|
||||
value: option.value,
|
||||
}
|
||||
}) || []
|
||||
|
|
@ -162,7 +172,7 @@ const BaseField = ({
|
|||
if (!dynamicOptionsData?.options)
|
||||
return []
|
||||
return dynamicOptionsData.options.map(option => ({
|
||||
name: typeof option.label === 'string' ? option.label : renderI18nObject(option.label),
|
||||
name: getTranslatedContent({ content: option.label, render: renderI18nObject }),
|
||||
value: option.value,
|
||||
}))
|
||||
}, [dynamicOptionsData, renderI18nObject])
|
||||
|
|
@ -173,150 +183,158 @@ const BaseField = ({
|
|||
}, [field, onChange])
|
||||
|
||||
return (
|
||||
<div className={cn(fieldClassName)}>
|
||||
<div className={cn(labelClassName, formLabelClassName)}>
|
||||
{memorizedLabel}
|
||||
{
|
||||
required && !isValidElement(label) && (
|
||||
<span className='ml-1 text-text-destructive-secondary'>*</span>
|
||||
)
|
||||
}
|
||||
{tooltip && (
|
||||
<Tooltip
|
||||
popupContent={<div className='w-[200px]'>{typeof tooltip === 'string' ? tooltip : renderI18nObject(tooltip as Record<string, string>)}</div>}
|
||||
triggerClassName='ml-0.5 w-4 h-4'
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<div className={cn(inputContainerClassName)}>
|
||||
{
|
||||
[FormTypeEnum.textInput, FormTypeEnum.secretInput, FormTypeEnum.textNumber].includes(formItemType) && (
|
||||
<Input
|
||||
id={field.name}
|
||||
name={field.name}
|
||||
className={cn(inputClassName, VALIDATE_STATUS_STYLE_MAP[fieldState?.validateStatus as FormItemValidateStatusEnum]?.componentClassName)}
|
||||
value={value || ''}
|
||||
onChange={(e) => {
|
||||
handleChange(e.target.value)
|
||||
}}
|
||||
onBlur={field.handleBlur}
|
||||
disabled={disabled}
|
||||
placeholder={memorizedPlaceholder}
|
||||
{...getExtraProps(formItemType)}
|
||||
showCopyIcon={showCopy}
|
||||
<>
|
||||
<div className={cn(fieldClassName)}>
|
||||
<div className={cn(labelClassName, formLabelClassName)}>
|
||||
{translatedLabel}
|
||||
{
|
||||
required && !isValidElement(label) && (
|
||||
<span className='ml-1 text-text-destructive-secondary'>*</span>
|
||||
)
|
||||
}
|
||||
{tooltip && (
|
||||
<Tooltip
|
||||
popupContent={<div className='w-[200px]'>{translatedTooltip}</div>}
|
||||
triggerClassName='ml-0.5 w-4 h-4'
|
||||
/>
|
||||
)
|
||||
}
|
||||
{
|
||||
formItemType === FormTypeEnum.select && !multiple && (
|
||||
<PureSelect
|
||||
value={value}
|
||||
onChange={v => handleChange(v)}
|
||||
disabled={disabled}
|
||||
placeholder={memorizedPlaceholder}
|
||||
options={memorizedOptions}
|
||||
triggerPopupSameWidth
|
||||
popupProps={{
|
||||
className: 'max-h-[320px] overflow-y-auto',
|
||||
}}
|
||||
/>
|
||||
)
|
||||
}
|
||||
{
|
||||
formItemType === FormTypeEnum.checkbox /* && multiple */ && (
|
||||
<CheckboxList
|
||||
title={name}
|
||||
value={value}
|
||||
onChange={v => field.handleChange(v)}
|
||||
options={memorizedOptions}
|
||||
maxHeight='200px'
|
||||
/>
|
||||
)
|
||||
}
|
||||
{
|
||||
formItemType === FormTypeEnum.dynamicSelect && (
|
||||
<PortalSelect
|
||||
value={value}
|
||||
onSelect={(item: any) => field.handleChange(item.value)}
|
||||
readonly={disabled || isDynamicOptionsLoading}
|
||||
placeholder={
|
||||
isDynamicOptionsLoading
|
||||
? 'Loading options...'
|
||||
: memorizedPlaceholder || 'Select an option'
|
||||
}
|
||||
items={dynamicOptions}
|
||||
popupClassName="z-[9999]"
|
||||
/>
|
||||
)
|
||||
}
|
||||
{
|
||||
formItemType === FormTypeEnum.radio && (
|
||||
)}
|
||||
</div>
|
||||
<div className={cn(inputContainerClassName)}>
|
||||
{
|
||||
[FormTypeEnum.textInput, FormTypeEnum.secretInput, FormTypeEnum.textNumber].includes(formItemType) && (
|
||||
<Input
|
||||
id={field.name}
|
||||
name={field.name}
|
||||
className={cn(inputClassName, VALIDATE_STATUS_STYLE_MAP[fieldState?.validateStatus as FormItemValidateStatusEnum]?.componentClassName)}
|
||||
value={value || ''}
|
||||
onChange={(e) => {
|
||||
handleChange(e.target.value)
|
||||
}}
|
||||
onBlur={field.handleBlur}
|
||||
disabled={disabled}
|
||||
placeholder={translatedPlaceholder}
|
||||
{...getExtraProps(formItemType)}
|
||||
showCopyIcon={showCopy}
|
||||
/>
|
||||
)
|
||||
}
|
||||
{
|
||||
formItemType === FormTypeEnum.select && !multiple && (
|
||||
<PureSelect
|
||||
value={value}
|
||||
onChange={v => handleChange(v)}
|
||||
disabled={disabled}
|
||||
placeholder={translatedPlaceholder}
|
||||
options={memorizedOptions}
|
||||
triggerPopupSameWidth
|
||||
popupProps={{
|
||||
className: 'max-h-[320px] overflow-y-auto',
|
||||
}}
|
||||
/>
|
||||
)
|
||||
}
|
||||
{
|
||||
formItemType === FormTypeEnum.checkbox /* && multiple */ && (
|
||||
<CheckboxList
|
||||
title={name}
|
||||
value={value}
|
||||
onChange={v => field.handleChange(v)}
|
||||
options={memorizedOptions}
|
||||
maxHeight='200px'
|
||||
/>
|
||||
)
|
||||
}
|
||||
{
|
||||
formItemType === FormTypeEnum.dynamicSelect && (
|
||||
<PortalSelect
|
||||
value={value}
|
||||
onSelect={(item: any) => field.handleChange(item.value)}
|
||||
readonly={disabled || isDynamicOptionsLoading}
|
||||
placeholder={
|
||||
isDynamicOptionsLoading
|
||||
? 'Loading options...'
|
||||
: translatedPlaceholder || 'Select an option'
|
||||
}
|
||||
items={dynamicOptions}
|
||||
popupClassName="z-[9999]"
|
||||
/>
|
||||
)
|
||||
}
|
||||
{
|
||||
formItemType === FormTypeEnum.radio && (
|
||||
<div className={cn(
|
||||
memorizedOptions.length < 3 ? 'flex items-center space-x-2' : 'space-y-2',
|
||||
)}>
|
||||
{
|
||||
memorizedOptions.map(option => (
|
||||
<div
|
||||
key={option.value}
|
||||
className={cn(
|
||||
'system-sm-regular hover:bg-components-option-card-option-hover-bg hover:border-components-option-card-option-hover-border flex h-8 flex-[1] grow cursor-pointer items-center justify-center gap-2 rounded-lg border border-components-option-card-option-border bg-components-option-card-option-bg p-2 text-text-secondary',
|
||||
value === option.value && 'border-components-option-card-option-selected-border bg-components-option-card-option-selected-bg text-text-primary shadow-xs',
|
||||
disabled && 'cursor-not-allowed opacity-50',
|
||||
inputClassName,
|
||||
)}
|
||||
onClick={() => !disabled && handleChange(option.value)}
|
||||
>
|
||||
{
|
||||
formSchema.showRadioUI && (
|
||||
<RadioE
|
||||
className='mr-2'
|
||||
isChecked={value === option.value}
|
||||
/>
|
||||
)
|
||||
}
|
||||
{option.label}
|
||||
</div>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
{
|
||||
formItemType === FormTypeEnum.boolean && (
|
||||
<Radio.Group
|
||||
className='flex w-fit items-center'
|
||||
value={value}
|
||||
onChange={v => field.handleChange(v)}
|
||||
>
|
||||
<Radio value={true} className='!mr-1'>True</Radio>
|
||||
<Radio value={false}>False</Radio>
|
||||
</Radio.Group>
|
||||
)
|
||||
}
|
||||
{fieldState?.validateStatus && [FormItemValidateStatusEnum.Error, FormItemValidateStatusEnum.Warning].includes(fieldState?.validateStatus) && (
|
||||
<div className={cn(
|
||||
memorizedOptions.length < 3 ? 'flex items-center space-x-2' : 'space-y-2',
|
||||
'system-xs-regular mt-1 px-0 py-[2px]',
|
||||
VALIDATE_STATUS_STYLE_MAP[fieldState?.validateStatus].textClassName,
|
||||
)}>
|
||||
{
|
||||
memorizedOptions.map(option => (
|
||||
<div
|
||||
key={option.value}
|
||||
className={cn(
|
||||
'system-sm-regular hover:bg-components-option-card-option-hover-bg hover:border-components-option-card-option-hover-border flex h-8 flex-[1] grow cursor-pointer items-center justify-center gap-2 rounded-lg border border-components-option-card-option-border bg-components-option-card-option-bg p-2 text-text-secondary',
|
||||
value === option.value && 'border-components-option-card-option-selected-border bg-components-option-card-option-selected-bg text-text-primary shadow-xs',
|
||||
disabled && 'cursor-not-allowed opacity-50',
|
||||
inputClassName,
|
||||
)}
|
||||
onClick={() => !disabled && handleChange(option.value)}
|
||||
>
|
||||
{
|
||||
formSchema.showRadioUI && (
|
||||
<RadioE
|
||||
className='mr-2'
|
||||
isChecked={value === option.value}
|
||||
/>
|
||||
)
|
||||
}
|
||||
{option.label}
|
||||
</div>
|
||||
))
|
||||
}
|
||||
{fieldState?.[VALIDATE_STATUS_STYLE_MAP[fieldState?.validateStatus].infoFieldName as keyof FieldState]}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
{
|
||||
formItemType === FormTypeEnum.boolean && (
|
||||
<Radio.Group
|
||||
className='flex w-fit items-center'
|
||||
value={value}
|
||||
onChange={v => field.handleChange(v)}
|
||||
>
|
||||
<Radio value={true} className='!mr-1'>True</Radio>
|
||||
<Radio value={false}>False</Radio>
|
||||
</Radio.Group>
|
||||
)
|
||||
}
|
||||
{fieldState?.validateStatus && [FormItemValidateStatusEnum.Error, FormItemValidateStatusEnum.Warning].includes(fieldState?.validateStatus) && (
|
||||
<div className={cn(
|
||||
'system-xs-regular mt-1 px-0 py-[2px]',
|
||||
VALIDATE_STATUS_STYLE_MAP[fieldState?.validateStatus].textClassName,
|
||||
)}>
|
||||
{fieldState?.[VALIDATE_STATUS_STYLE_MAP[fieldState?.validateStatus].infoFieldName as keyof FieldState]}
|
||||
</div>
|
||||
)}
|
||||
{
|
||||
formSchema.url && (
|
||||
<a
|
||||
className='system-xs-regular mt-4 flex items-center text-text-accent'
|
||||
href={formSchema?.url}
|
||||
target='_blank'
|
||||
>
|
||||
<span className='break-all'>
|
||||
{renderI18nObject(formSchema?.help as any)}
|
||||
</span>
|
||||
<RiExternalLinkLine className='ml-1 h-3 w-3 shrink-0' />
|
||||
</a>
|
||||
)
|
||||
}
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{description && (
|
||||
<div className='system-xs-regular mt-4 text-text-tertiary'>
|
||||
{translatedDescription}
|
||||
</div>
|
||||
)}
|
||||
{
|
||||
url && (
|
||||
<a
|
||||
className='system-xs-regular mt-4 flex items-center text-text-accent'
|
||||
href={url}
|
||||
target='_blank'
|
||||
>
|
||||
<span className='break-all'>
|
||||
{translatedHelp}
|
||||
</span>
|
||||
<RiExternalLinkLine className='ml-1 h-3 w-3 shrink-0' />
|
||||
</a>
|
||||
)
|
||||
}
|
||||
</>
|
||||
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ type ModalProps = {
|
|||
footerSlot?: React.ReactNode
|
||||
bottomSlot?: React.ReactNode
|
||||
disabled?: boolean
|
||||
containerClassName?: string
|
||||
}
|
||||
const Modal = ({
|
||||
onClose,
|
||||
|
|
@ -44,6 +45,7 @@ const Modal = ({
|
|||
footerSlot,
|
||||
bottomSlot,
|
||||
disabled,
|
||||
containerClassName,
|
||||
}: ModalProps) => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
|
|
@ -55,9 +57,10 @@ const Modal = ({
|
|||
>
|
||||
<div
|
||||
className={cn(
|
||||
'flex max-h-[80%] min-h-[360px] flex-col rounded-2xl border-[0.5px] border-components-panel-border bg-components-panel-bg shadow-xs',
|
||||
'flex max-h-[80%] flex-col rounded-2xl border-[0.5px] border-components-panel-border bg-components-panel-bg shadow-xs',
|
||||
size === 'sm' && 'w-[480px]',
|
||||
size === 'md' && 'w-[640px]',
|
||||
containerClassName,
|
||||
)}
|
||||
onClick={e => e.stopPropagation()}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -370,6 +370,7 @@ export const CommonCreateModal = ({ onClose, createType, builder }: Props) => {
|
|||
disabled={isVerifyingCredentials || isBuilding}
|
||||
bottomSlot={currentStep === ApiKeyStep.Verify ? <EncryptedBottom /> : null}
|
||||
size={createType === SupportedCreationMethods.MANUAL ? 'md' : 'sm'}
|
||||
containerClassName='min-h-[360px]'
|
||||
>
|
||||
{createType === SupportedCreationMethods.APIKEY && <MultiSteps currentStep={currentStep} />}
|
||||
{currentStep === ApiKeyStep.Verify && (
|
||||
|
|
@ -432,6 +433,7 @@ export const CommonCreateModal = ({ onClose, createType, builder }: Props) => {
|
|||
credential_id: subscriptionBuilder?.id || '',
|
||||
} : undefined,
|
||||
fieldClassName: schema.type === FormTypeEnum.boolean ? 'flex items-center justify-between' : undefined,
|
||||
labelClassName: schema.type === FormTypeEnum.boolean ? 'mb-0' : undefined,
|
||||
}
|
||||
})}
|
||||
ref={autoCommonParametersFormRef}
|
||||
|
|
|
|||
|
|
@ -189,7 +189,7 @@ export const OAuthClientSettingsModal = ({ oauthConfig, onClose, showOAuthCreate
|
|||
onCancel={() => handleSave(false)}
|
||||
onConfirm={() => handleSave(true)}
|
||||
footerSlot={
|
||||
oauthConfig?.custom_enabled && oauthConfig?.params && (
|
||||
oauthConfig?.custom_enabled && oauthConfig?.params && clientType === ClientTypeEnum.Custom && (
|
||||
<div className='grow'>
|
||||
<Button
|
||||
variant='secondary'
|
||||
|
|
|
|||
Loading…
Reference in New Issue