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

pull/21398/head
twwu 1 year ago
parent 839fe12087
commit d1d83f8a2a

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

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

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

@ -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

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

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

@ -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

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

Loading…
Cancel
Save