feat: Enhance form components with hidden fields and popup properties for improved configuration

This commit is contained in:
twwu 2025-04-27 20:17:50 +08:00
parent 839fe12087
commit d1d83f8a2a
8 changed files with 200 additions and 99 deletions

View File

@ -1,4 +1,4 @@
import React, { useMemo } from 'react'
import React from 'react'
import { type BaseConfiguration, BaseFieldType } from './types'
import { withForm } from '../..'
import { useStore } from '@tanstack/react-form'
@ -16,18 +16,30 @@ const BaseField = <T,>({
render: function Render({
form,
}) {
const { type, label, placeholder, variable, tooltip, showConditions, max, min, options, required, showOptional } = config
const {
type,
label,
placeholder,
variable,
tooltip,
showConditions,
max,
min,
options,
required,
showOptional,
popupProps,
} = config
const fieldValues = useStore(form.store, state => state.values)
const isAllConditionsMet = useMemo(() => {
const isAllConditionsMet = useStore(form.store, (state) => {
const fieldValues = state.values
if (!showConditions.length) return true
return showConditions.every((condition) => {
const { variable, value } = condition
const fieldValue = fieldValues[variable as keyof typeof fieldValues]
return fieldValue === value
})
}, [fieldValues, showConditions])
})
if (!isAllConditionsMet)
return <></>
@ -98,6 +110,7 @@ const BaseField = <T,>({
showOptional,
}}
options={options!}
popupProps={popupProps}
/>
)}
/>

View File

@ -21,6 +21,12 @@ export type NumberConfiguration = {
export type SelectConfiguration = {
options: Option[] // Options for select field
popupProps?: {
wrapperClassName?: string
className?: string
itemClassName?: string
title?: string
}
}
export type BaseConfiguration<T> = {

View File

@ -1,4 +1,4 @@
import React, { useMemo } from 'react'
import React from 'react'
import { type InputFieldConfiguration, InputFieldType } from './types'
import { withForm } from '../..'
import { useStore } from '@tanstack/react-form'
@ -31,18 +31,17 @@ const InputField = <T,>({
description,
options,
listeners,
popupProps,
} = config
const fieldValues = useStore(form.store, state => state.values)
const isAllConditionsMet = useMemo(() => {
if (!showConditions.length) return true
const isAllConditionsMet = useStore(form.store, (state) => {
const fieldValues = state.values
return showConditions.every((condition) => {
const { variable, value } = condition
const fieldValue = fieldValues[variable as keyof typeof fieldValues]
return fieldValue === value
})
}, [fieldValues, showConditions])
})
if (!isAllConditionsMet)
return <></>
@ -134,6 +133,7 @@ const InputField = <T,>({
showOptional,
}}
options={options!}
popupProps={popupProps}
/>
)}
/>

View File

@ -0,0 +1,39 @@
import React from 'react'
import { withForm } from '@/app/components/base/form'
import type { FormData } from './types'
import InputField from '@/app/components/base/form/form-scenarios/input-field/field'
import { useStore } from '@tanstack/react-form'
import { useHiddenConfigurations } from './hooks'
type HiddenFieldsProps = {
initialData?: FormData
}
const HiddenFields = ({
initialData,
}: HiddenFieldsProps) => withForm({
defaultValues: initialData,
render: function Render({
form,
}) {
const options = useStore(form.store, state => state.values.options)
const hiddenConfigurations = useHiddenConfigurations({
options,
})
return (
<>
{hiddenConfigurations.map((config, index) => {
const FieldComponent = InputField<FormData>({
initialData,
config,
})
return <FieldComponent key={index} form={form} />
})}
</>
)
},
})
export default HiddenFields

View File

@ -64,40 +64,11 @@ export const useHiddenFieldNames = (type: InputVarType) => {
}
export const useConfigurations = (props: {
type: string,
options: string[] | undefined,
setFieldValue: (fieldName: DeepKeys<FormData>, value: any) => void,
supportFile: boolean
}) => {
const { t } = useTranslation()
const { type, options, setFieldValue, supportFile } = props
const { data: fileUploadConfigResponse } = useFileUploadConfig()
const {
imgSizeLimit,
docSizeLimit,
audioSizeLimit,
videoSizeLimit,
} = useFileSizeLimit(fileUploadConfigResponse)
const isSelectInput = type === InputVarType.select
const defaultSelectOptions = useMemo(() => {
if (isSelectInput && options) {
const defaultOptions = [
{
value: '',
label: t('appDebug.variableConfig.noDefaultSelected'),
},
]
const otherOptions = options.map((option: string) => ({
value: option,
label: option,
}))
return [...defaultOptions, ...otherOptions]
}
return []
}, [isSelectInput, options, t])
const { setFieldValue, supportFile } = props
const handleTypeChange = useCallback((type: string) => {
if ([InputVarType.singleFile, InputVarType.multiFiles].includes(type as InputVarType)) {
@ -186,6 +157,41 @@ export const useConfigurations = (props: {
}]
}, [handleTypeChange, supportFile, t])
return initialConfigurations
}
export const useHiddenConfigurations = (props: {
options: string[] | undefined,
}) => {
const { t } = useTranslation()
const { options } = props
const { data: fileUploadConfigResponse } = useFileUploadConfig()
const {
imgSizeLimit,
docSizeLimit,
audioSizeLimit,
videoSizeLimit,
} = useFileSizeLimit(fileUploadConfigResponse)
const defaultSelectOptions = useMemo(() => {
if (options) {
const defaultOptions = [
{
value: '',
label: t('appDebug.variableConfig.noDefaultSelected'),
},
]
const otherOptions = options.map((option: string) => ({
value: option,
label: option,
}))
return [...defaultOptions, ...otherOptions]
}
return []
}, [options, t])
const hiddenConfigurations = useMemo((): InputFieldConfiguration<FormData>[] => {
return [{
type: InputFieldType.textInput,
@ -220,6 +226,9 @@ export const useConfigurations = (props: {
}],
showOptional: true,
options: defaultSelectOptions,
popupProps: {
wrapperClassName: 'z-40',
},
}, {
type: InputFieldType.textInput,
label: t('appDebug.variableConfig.placeholder'),
@ -296,8 +305,5 @@ export const useConfigurations = (props: {
}]
}, [defaultSelectOptions, imgSizeLimit, docSizeLimit, audioSizeLimit, videoSizeLimit, t])
return {
initialConfigurations,
hiddenConfigurations,
}
return hiddenConfigurations
}

View File

@ -1,19 +1,17 @@
import { useTranslation } from 'react-i18next'
import { useCallback, useState } from 'react'
import type { DeepKeys } from '@tanstack/react-form'
import { useStore } from '@tanstack/react-form'
import { ChangeType } from '@/app/components/workflow/types'
import { useFileUploadConfig } from '@/service/use-common'
import type { FormData, InputFieldFormProps } from './types'
import type { InputFieldFormProps } from './types'
import { useAppForm } from '@/app/components/base/form'
import { createInputFieldSchema } from './schema'
import Toast from '@/app/components/base/toast'
import { useFileSizeLimit } from '@/app/components/base/file-uploader/hooks'
import { useConfigurations, useHiddenFieldNames } from './hooks'
import Divider from '@/app/components/base/divider'
import ShowAllSettings from './show-all-settings'
import Button from '@/app/components/base/button'
import InputField from '@/app/components/base/form/form-scenarios/input-field/field'
import InitialFields from './initial-fields'
import HiddenFields from './hidden-fields'
const InputFieldForm = ({
initialData,
@ -59,25 +57,24 @@ const InputFieldForm = ({
})
const [showAllSettings, setShowAllSettings] = useState(false)
const type = useStore(inputFieldForm.store, state => state.values.type)
const options = useStore(inputFieldForm.store, state => state.values.options)
const setFieldValue = useCallback((fieldName: DeepKeys<FormData>, value: any) => {
inputFieldForm.setFieldValue(fieldName, value)
}, [inputFieldForm])
const hiddenFieldNames = useHiddenFieldNames(type)
const { initialConfigurations, hiddenConfigurations } = useConfigurations({
type,
options,
setFieldValue,
const InitialFieldsComp = InitialFields({
initialData,
supportFile,
})
const HiddenFieldsComp = HiddenFields({
initialData,
})
const handleShowAllSettings = useCallback(() => {
setShowAllSettings(true)
}, [])
const ShowAllSettingComp = ShowAllSettings({
initialData,
handleShowAllSettings,
})
return (
<form
className='w-full'
@ -88,30 +85,13 @@ const InputFieldForm = ({
}}
>
<div className='flex flex-col gap-4 px-4 py-2'>
{initialConfigurations.map((config, index) => {
const FieldComponent = InputField<FormData>({
initialData,
config,
})
return <FieldComponent key={`${config.type}-${index}`} form={inputFieldForm} />
})}
<InitialFieldsComp form={inputFieldForm} />
<Divider type='horizontal' />
{!showAllSettings && (
<ShowAllSettings
handleShowAllSettings={handleShowAllSettings}
description={hiddenFieldNames}
/>
<ShowAllSettingComp form={inputFieldForm} />
)}
{showAllSettings && (
<>
{hiddenConfigurations.map((config, index) => {
const FieldComponent = InputField<FormData>({
initialData,
config,
})
return <FieldComponent key={`hidden-${config.type}-${index}`} form={inputFieldForm} />
})}
</>
<HiddenFieldsComp form={inputFieldForm} />
)}
</div>
<div className='flex items-center justify-end gap-x-2 p-4 pt-2'>

View File

@ -0,0 +1,44 @@
import React, { useCallback } from 'react'
import { withForm } from '@/app/components/base/form'
import type { FormData } from './types'
import InputField from '@/app/components/base/form/form-scenarios/input-field/field'
import type { DeepKeys } from '@tanstack/react-form'
import { useConfigurations } from './hooks'
type InitialFieldsProps = {
initialData?: FormData
supportFile: boolean
}
const InitialFields = ({
initialData,
supportFile,
}: InitialFieldsProps) => withForm({
defaultValues: initialData,
render: function Render({
form,
}) {
const setFieldValue = useCallback((fieldName: DeepKeys<FormData>, value: any) => {
form.setFieldValue(fieldName, value)
}, [form])
const initialConfigurations = useConfigurations({
setFieldValue,
supportFile,
})
return (
<>
{initialConfigurations.map((config, index) => {
const FieldComponent = InputField<FormData>({
initialData,
config,
})
return <FieldComponent key={index} form={form} />
})}
</>
)
},
})
export default InitialFields

View File

@ -1,30 +1,43 @@
import React from 'react'
import { withForm } from '@/app/components/base/form'
import type { FormData } from './types'
import { useStore } from '@tanstack/react-form'
import { useHiddenFieldNames } from './hooks'
import { useTranslation } from 'react-i18next'
import { RiArrowRightSLine } from '@remixicon/react'
type ShowAllSettingsProps = {
description: string
initialData?: FormData
handleShowAllSettings: () => void
}
const ShowAllSettings = ({
description,
initialData,
handleShowAllSettings,
}: ShowAllSettingsProps) => {
const { t } = useTranslation()
}: ShowAllSettingsProps) => withForm({
defaultValues: initialData,
render: function Render({
form,
}) {
const { t } = useTranslation()
const type = useStore(form.store, state => state.values.type)
return (
<div className='flex cursor-pointer items-center gap-x-4' onClick={handleShowAllSettings}>
<div className='flex grow flex-col'>
<span className='system-sm-medium flex min-h-6 items-center text-text-secondary'>
{t('appDebug.variableConfig.showAllSettings')}
</span>
<span className='body-xs-regular pb-0.5 text-text-tertiary first-letter:capitalize'>
{description}
</span>
const hiddenFieldNames = useHiddenFieldNames(type)
return (
<div className='flex cursor-pointer items-center gap-x-4' onClick={handleShowAllSettings}>
<div className='flex grow flex-col'>
<span className='system-sm-medium flex min-h-6 items-center text-text-secondary'>
{t('appDebug.variableConfig.showAllSettings')}
</span>
<span className='body-xs-regular pb-0.5 text-text-tertiary first-letter:capitalize'>
{hiddenFieldNames}
</span>
</div>
<RiArrowRightSLine className='h-4 w-4 shrink-0 text-text-secondary' />
</div>
<RiArrowRightSLine className='h-4 w-4 shrink-0 text-text-secondary' />
</div>
)
}
)
},
})
export default ShowAllSettings