mirror of
https://github.com/langgenius/dify.git
synced 2026-04-15 01:38:19 +08:00
feat: Introduce new form field components and enhance existing ones with label options
This commit is contained in:
parent
b1fbaaed95
commit
d12e9b81e3
@ -2,52 +2,36 @@ import cn from '@/utils/classnames'
|
||||
import { useFieldContext } from '../..'
|
||||
import type { CustomSelectProps, Option } from '../../../select/custom'
|
||||
import CustomSelect from '../../../select/custom'
|
||||
import type { LabelProps } from '../label'
|
||||
import Label from '../label'
|
||||
import { useCallback } from 'react'
|
||||
|
||||
type CustomSelectFieldProps<T extends Option> = {
|
||||
label: string
|
||||
labelOptions?: Omit<LabelProps, 'htmlFor' | 'label'>
|
||||
options: T[]
|
||||
onChange?: (value: string) => void
|
||||
isRequired?: boolean
|
||||
showOptional?: boolean
|
||||
tooltip?: string
|
||||
className?: string
|
||||
labelClassName?: string
|
||||
} & Omit<CustomSelectProps<T>, 'options' | 'value' | 'onChange'>
|
||||
|
||||
const CustomSelectField = <T extends Option>({
|
||||
label,
|
||||
labelOptions,
|
||||
options,
|
||||
onChange,
|
||||
isRequired,
|
||||
showOptional,
|
||||
tooltip,
|
||||
className,
|
||||
labelClassName,
|
||||
...selectProps
|
||||
}: CustomSelectFieldProps<T>) => {
|
||||
const field = useFieldContext<string>()
|
||||
|
||||
const handleChange = useCallback((value: string) => {
|
||||
field.handleChange(value)
|
||||
onChange?.(value)
|
||||
}, [field, onChange])
|
||||
|
||||
return (
|
||||
<div className={cn('flex flex-col gap-y-0.5', className)}>
|
||||
<Label
|
||||
htmlFor={field.name}
|
||||
label={label}
|
||||
isRequired={isRequired}
|
||||
showOptional={showOptional}
|
||||
tooltip={tooltip}
|
||||
className={labelClassName}
|
||||
{...(labelOptions ?? {})}
|
||||
/>
|
||||
<CustomSelect<T>
|
||||
value={field.state.value}
|
||||
options={options}
|
||||
onChange={handleChange}
|
||||
onChange={value => field.handleChange(value)}
|
||||
{...selectProps}
|
||||
/>
|
||||
</div>
|
||||
|
||||
83
web/app/components/base/form/components/field/file-types.tsx
Normal file
83
web/app/components/base/form/components/field/file-types.tsx
Normal file
@ -0,0 +1,83 @@
|
||||
import cn from '@/utils/classnames'
|
||||
import type { LabelProps } from '../label'
|
||||
import { useFieldContext } from '../..'
|
||||
import Label from '../label'
|
||||
import { SupportUploadFileTypes } from '@/app/components/workflow/types'
|
||||
import FileTypeItem from '@/app/components/workflow/nodes/_base/components/file-type-item'
|
||||
import { useCallback } from 'react'
|
||||
|
||||
type FieldValue = {
|
||||
allowedFileTypes: string[],
|
||||
allowedFileExtensions: string[]
|
||||
}
|
||||
|
||||
type FileTypesFieldProps = {
|
||||
label: string
|
||||
labelOptions?: Omit<LabelProps, 'htmlFor' | 'label'>
|
||||
className?: string
|
||||
}
|
||||
|
||||
const FileTypesField = ({
|
||||
label,
|
||||
labelOptions,
|
||||
className,
|
||||
}: FileTypesFieldProps) => {
|
||||
const field = useFieldContext<FieldValue>()
|
||||
|
||||
const handleSupportFileTypeChange = useCallback((type: SupportUploadFileTypes) => {
|
||||
let newAllowFileTypes = [...field.state.value.allowedFileTypes]
|
||||
if (type === SupportUploadFileTypes.custom) {
|
||||
if (!newAllowFileTypes.includes(SupportUploadFileTypes.custom))
|
||||
newAllowFileTypes = [SupportUploadFileTypes.custom]
|
||||
else
|
||||
newAllowFileTypes = newAllowFileTypes.filter(v => v !== type)
|
||||
}
|
||||
else {
|
||||
newAllowFileTypes = newAllowFileTypes.filter(v => v !== SupportUploadFileTypes.custom)
|
||||
if (newAllowFileTypes.includes(type))
|
||||
newAllowFileTypes = newAllowFileTypes.filter(v => v !== type)
|
||||
else
|
||||
newAllowFileTypes.push(type)
|
||||
}
|
||||
field.handleChange({
|
||||
...field.state.value,
|
||||
allowedFileTypes: newAllowFileTypes,
|
||||
})
|
||||
}, [field])
|
||||
|
||||
const handleCustomFileTypesChange = useCallback((customFileTypes: string[]) => {
|
||||
field.handleChange({
|
||||
...field.state.value,
|
||||
allowedFileExtensions: customFileTypes,
|
||||
})
|
||||
}, [field])
|
||||
|
||||
return (
|
||||
<div className={cn('flex flex-col gap-y-0.5', className)}>
|
||||
<Label
|
||||
htmlFor={field.name}
|
||||
label={label}
|
||||
{...(labelOptions ?? {})}
|
||||
/>
|
||||
{
|
||||
[SupportUploadFileTypes.document, SupportUploadFileTypes.image, SupportUploadFileTypes.audio, SupportUploadFileTypes.video].map((type: SupportUploadFileTypes) => (
|
||||
<FileTypeItem
|
||||
key={type}
|
||||
type={type as SupportUploadFileTypes.image | SupportUploadFileTypes.document | SupportUploadFileTypes.audio | SupportUploadFileTypes.video}
|
||||
selected={field.state.value.allowedFileTypes.includes(type)}
|
||||
onToggle={handleSupportFileTypeChange}
|
||||
/>
|
||||
))
|
||||
}
|
||||
<FileTypeItem
|
||||
type={SupportUploadFileTypes.custom}
|
||||
selected={field.state.value.allowedFileTypes.includes(SupportUploadFileTypes.custom)}
|
||||
onToggle={handleSupportFileTypeChange}
|
||||
customFileTypes={field.state.value.allowedFileExtensions}
|
||||
onCustomFileTypesChange={handleCustomFileTypesChange}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default FileTypesField
|
||||
@ -0,0 +1,51 @@
|
||||
import { InputVarType } from '@/app/components/workflow/types'
|
||||
import { InputType } from './types'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import {
|
||||
RiAlignLeft,
|
||||
RiCheckboxLine,
|
||||
RiFileCopy2Line,
|
||||
RiFileTextLine,
|
||||
RiHashtag,
|
||||
RiListCheck3,
|
||||
RiTextSnippet,
|
||||
} from '@remixicon/react'
|
||||
|
||||
const i18nFileTypeMap: Record<string, string> = {
|
||||
'file': 'single-file',
|
||||
'file-list': 'multi-files',
|
||||
}
|
||||
|
||||
const INPUT_TYPE_ICON = {
|
||||
[InputVarType.textInput]: RiTextSnippet,
|
||||
[InputVarType.paragraph]: RiAlignLeft,
|
||||
[InputVarType.number]: RiHashtag,
|
||||
[InputVarType.select]: RiListCheck3,
|
||||
[InputVarType.checkbox]: RiCheckboxLine,
|
||||
[InputVarType.singleFile]: RiFileTextLine,
|
||||
[InputVarType.multiFiles]: RiFileCopy2Line,
|
||||
}
|
||||
|
||||
const DATA_TYPE = {
|
||||
[InputVarType.textInput]: 'string',
|
||||
[InputVarType.paragraph]: 'string',
|
||||
[InputVarType.number]: 'number',
|
||||
[InputVarType.select]: 'string',
|
||||
[InputVarType.checkbox]: 'boolean',
|
||||
[InputVarType.singleFile]: 'file',
|
||||
[InputVarType.multiFiles]: 'array[file]',
|
||||
}
|
||||
|
||||
export const useInputTypeOptions = (supportFile: boolean) => {
|
||||
const { t } = useTranslation()
|
||||
const options = supportFile ? InputType.options : InputType.exclude(['file', 'file-list']).options
|
||||
|
||||
return options.map((value) => {
|
||||
return {
|
||||
value,
|
||||
label: t(`appDebug.variableConfig.${i18nFileTypeMap[value] || value}`),
|
||||
Icon: INPUT_TYPE_ICON[value],
|
||||
type: DATA_TYPE[value],
|
||||
}
|
||||
})
|
||||
}
|
||||
@ -0,0 +1,64 @@
|
||||
import cn from '@/utils/classnames'
|
||||
import { useFieldContext } from '../../..'
|
||||
import type { CustomSelectProps } from '../../../../select/custom'
|
||||
import CustomSelect from '../../../../select/custom'
|
||||
import type { LabelProps } from '../../label'
|
||||
import Label from '../../label'
|
||||
import { useCallback } from 'react'
|
||||
import Trigger from './trigger'
|
||||
import type { FileTypeSelectOption } from './types'
|
||||
import { useInputTypeOptions } from './hooks'
|
||||
import Option from './option'
|
||||
|
||||
type InputTypeSelectFieldProps = {
|
||||
label: string
|
||||
labeOptions?: Omit<LabelProps, 'htmlFor' | 'label'>
|
||||
supportFile: boolean
|
||||
className?: string
|
||||
} & Omit<CustomSelectProps<FileTypeSelectOption>, 'options' | 'value' | 'onChange' | 'CustomTrigger' | 'CustomOption'>
|
||||
|
||||
const InputTypeSelectField = ({
|
||||
label,
|
||||
labeOptions,
|
||||
supportFile,
|
||||
className,
|
||||
...customSelectProps
|
||||
}: InputTypeSelectFieldProps) => {
|
||||
const field = useFieldContext<string>()
|
||||
const inputTypeOptions = useInputTypeOptions(supportFile)
|
||||
|
||||
const renderTrigger = useCallback((option: FileTypeSelectOption | undefined, open: boolean) => {
|
||||
return <Trigger option={option} open={open} />
|
||||
}, [])
|
||||
const renderOption = useCallback((option: FileTypeSelectOption) => {
|
||||
return <Option option={option} />
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<div className={cn('flex flex-col gap-y-0.5', className)}>
|
||||
<Label
|
||||
htmlFor={field.name}
|
||||
label={label}
|
||||
{...(labeOptions ?? {})}
|
||||
/>
|
||||
<CustomSelect<FileTypeSelectOption>
|
||||
value={field.state.value}
|
||||
options={inputTypeOptions}
|
||||
onChange={value => field.handleChange(value)}
|
||||
triggerProps={{
|
||||
className: 'gap-x-0.5',
|
||||
}}
|
||||
popupProps={{
|
||||
className: 'w-[368px]',
|
||||
wrapperClassName: 'z-40',
|
||||
itemClassName: 'gap-x-1',
|
||||
}}
|
||||
CustomTrigger={renderTrigger}
|
||||
CustomOption={renderOption}
|
||||
{...customSelectProps}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default InputTypeSelectField
|
||||
@ -0,0 +1,21 @@
|
||||
import React from 'react'
|
||||
import type { FileTypeSelectOption } from './types'
|
||||
import Badge from '@/app/components/base/badge'
|
||||
|
||||
type OptionProps = {
|
||||
option: FileTypeSelectOption
|
||||
}
|
||||
|
||||
const Option = ({
|
||||
option,
|
||||
}: OptionProps) => {
|
||||
return (
|
||||
<>
|
||||
<option.Icon className='h-4 w-4 shrink-0 text-text-tertiary' />
|
||||
<span className='grow px-1'>{option.label}</span>
|
||||
<Badge text={option.type} uppercase={false} />
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default React.memo(Option)
|
||||
@ -0,0 +1,42 @@
|
||||
import React from 'react'
|
||||
import Badge from '@/app/components/base/badge'
|
||||
import cn from '@/utils/classnames'
|
||||
import { RiArrowDownSLine } from '@remixicon/react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import type { FileTypeSelectOption } from './types'
|
||||
|
||||
type TriggerProps = {
|
||||
option: FileTypeSelectOption | undefined
|
||||
open: boolean
|
||||
}
|
||||
|
||||
const Trigger = ({
|
||||
option,
|
||||
open,
|
||||
}: TriggerProps) => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
return (
|
||||
<>
|
||||
{option ? (
|
||||
<>
|
||||
<option.Icon className='h-4 w-4 shrink-0 text-text-tertiary' />
|
||||
<span className='grow p-1'>{option.label}</span>
|
||||
<div className='pr-0.5'>
|
||||
<Badge text={option.type} uppercase={false} />
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<span className='grow p-1'>{t('common.placeholder.select')}</span>
|
||||
)}
|
||||
<RiArrowDownSLine
|
||||
className={cn(
|
||||
'h-4 w-4 shrink-0 text-text-quaternary group-hover:text-text-secondary',
|
||||
open && 'text-text-secondary',
|
||||
)}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default React.memo(Trigger)
|
||||
@ -0,0 +1,19 @@
|
||||
import type { RemixiconComponentType } from '@remixicon/react'
|
||||
import { z } from 'zod'
|
||||
|
||||
export const InputType = z.enum([
|
||||
'text-input',
|
||||
'paragraph',
|
||||
'number',
|
||||
'select',
|
||||
'checkbox',
|
||||
'file',
|
||||
'file-list',
|
||||
])
|
||||
|
||||
export type FileTypeSelectOption = {
|
||||
value: string
|
||||
label: string
|
||||
Icon: RemixiconComponentType
|
||||
type: string
|
||||
}
|
||||
@ -1,5 +1,6 @@
|
||||
import React from 'react'
|
||||
import { useFieldContext } from '../..'
|
||||
import type { LabelProps } from '../label'
|
||||
import Label from '../label'
|
||||
import cn from '@/utils/classnames'
|
||||
import type { InputNumberProps } from '../../../input-number'
|
||||
@ -7,20 +8,14 @@ import { InputNumber } from '../../../input-number'
|
||||
|
||||
type TextFieldProps = {
|
||||
label: string
|
||||
isRequired?: boolean
|
||||
showOptional?: boolean
|
||||
tooltip?: string
|
||||
labelOptions?: Omit<LabelProps, 'htmlFor' | 'label'>
|
||||
className?: string
|
||||
labelClassName?: string
|
||||
} & Omit<InputNumberProps, 'id' | 'value' | 'onChange' | 'onBlur'>
|
||||
|
||||
const NumberInputField = ({
|
||||
label,
|
||||
isRequired,
|
||||
showOptional,
|
||||
tooltip,
|
||||
labelOptions,
|
||||
className,
|
||||
labelClassName,
|
||||
...inputProps
|
||||
}: TextFieldProps) => {
|
||||
const field = useFieldContext<number | undefined>()
|
||||
@ -30,10 +25,7 @@ const NumberInputField = ({
|
||||
<Label
|
||||
htmlFor={field.name}
|
||||
label={label}
|
||||
isRequired={isRequired}
|
||||
showOptional={showOptional}
|
||||
tooltip={tooltip}
|
||||
className={labelClassName}
|
||||
{...(labelOptions ?? {})}
|
||||
/>
|
||||
<InputNumber
|
||||
id={field.name}
|
||||
|
||||
@ -0,0 +1,47 @@
|
||||
import cn from '@/utils/classnames'
|
||||
import type { LabelProps } from '../label'
|
||||
import { useFieldContext } from '../..'
|
||||
import Label from '../label'
|
||||
import type { InputNumberWithSliderProps } from '@/app/components/workflow/nodes/_base/components/input-number-with-slider'
|
||||
import InputNumberWithSlider from '@/app/components/workflow/nodes/_base/components/input-number-with-slider'
|
||||
|
||||
type NumberSliderFieldProps = {
|
||||
label: string
|
||||
labelOptions?: Omit<LabelProps, 'htmlFor' | 'label'>
|
||||
description?: string
|
||||
className?: string
|
||||
} & Omit<InputNumberWithSliderProps, 'value' | 'onChange'>
|
||||
|
||||
const NumberSliderField = ({
|
||||
label,
|
||||
labelOptions,
|
||||
description,
|
||||
className,
|
||||
...InputNumberWithSliderProps
|
||||
}: NumberSliderFieldProps) => {
|
||||
const field = useFieldContext<number>()
|
||||
|
||||
return (
|
||||
<div className={cn('flex flex-col gap-y-0.5', className)}>
|
||||
<div>
|
||||
<Label
|
||||
htmlFor={field.name}
|
||||
label={label}
|
||||
{...(labelOptions ?? {})}
|
||||
/>
|
||||
{description && (
|
||||
<div className='body-xs-regular pb-0.5 text-text-tertiary'>
|
||||
{description}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<InputNumberWithSlider
|
||||
value={field.state.value}
|
||||
onChange={value => field.handleChange(value)}
|
||||
{...InputNumberWithSliderProps}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default NumberSliderField
|
||||
@ -1,18 +1,19 @@
|
||||
import cn from '@/utils/classnames'
|
||||
import { useFieldContext } from '../..'
|
||||
import type { LabelProps } from '../label'
|
||||
import Label from '../label'
|
||||
import ConfigSelect from '@/app/components/app/configuration/config-var/config-select'
|
||||
|
||||
type OptionsFieldProps = {
|
||||
label: string;
|
||||
labelOptions?: Omit<LabelProps, 'htmlFor' | 'label'>
|
||||
className?: string;
|
||||
labelClassName?: string;
|
||||
}
|
||||
|
||||
const OptionsField = ({
|
||||
label,
|
||||
className,
|
||||
labelClassName,
|
||||
labelOptions,
|
||||
}: OptionsFieldProps) => {
|
||||
const field = useFieldContext<string[]>()
|
||||
|
||||
@ -21,7 +22,7 @@ const OptionsField = ({
|
||||
<Label
|
||||
htmlFor={field.name}
|
||||
label={label}
|
||||
className={labelClassName}
|
||||
{...(labelOptions ?? {})}
|
||||
/>
|
||||
<ConfigSelect
|
||||
options={field.state.value}
|
||||
|
||||
@ -2,52 +2,38 @@ import cn from '@/utils/classnames'
|
||||
import { useFieldContext } from '../..'
|
||||
import type { Option, PureSelectProps } from '../../../select/pure'
|
||||
import PureSelect from '../../../select/pure'
|
||||
import type { LabelProps } from '../label'
|
||||
import Label from '../label'
|
||||
import { useCallback } from 'react'
|
||||
|
||||
type SelectFieldProps = {
|
||||
label: string
|
||||
labelOptions?: Omit<LabelProps, 'htmlFor' | 'label'>
|
||||
options: Option[]
|
||||
onChange?: (value: string) => void
|
||||
isRequired?: boolean
|
||||
showOptional?: boolean
|
||||
tooltip?: string
|
||||
className?: string
|
||||
labelClassName?: string
|
||||
} & Omit<PureSelectProps, 'options' | 'value' | 'onChange'>
|
||||
|
||||
const SelectField = ({
|
||||
label,
|
||||
labelOptions,
|
||||
options,
|
||||
onChange,
|
||||
isRequired,
|
||||
showOptional,
|
||||
tooltip,
|
||||
className,
|
||||
labelClassName,
|
||||
...selectProps
|
||||
}: SelectFieldProps) => {
|
||||
const field = useFieldContext<string>()
|
||||
|
||||
const handleChange = useCallback((value: string) => {
|
||||
field.handleChange(value)
|
||||
onChange?.(value)
|
||||
}, [field, onChange])
|
||||
|
||||
return (
|
||||
<div className={cn('flex flex-col gap-y-0.5', className)}>
|
||||
<Label
|
||||
htmlFor={field.name}
|
||||
label={label}
|
||||
isRequired={isRequired}
|
||||
showOptional={showOptional}
|
||||
tooltip={tooltip}
|
||||
className={labelClassName}
|
||||
{...(labelOptions ?? {})}
|
||||
/>
|
||||
<PureSelect
|
||||
value={field.state.value}
|
||||
options={options}
|
||||
onChange={handleChange}
|
||||
onChange={value => field.handleChange(value)}
|
||||
{...selectProps}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@ -1,25 +1,20 @@
|
||||
import React from 'react'
|
||||
import { useFieldContext } from '../..'
|
||||
import Input, { type InputProps } from '../../../input'
|
||||
import type { LabelProps } from '../label'
|
||||
import Label from '../label'
|
||||
import cn from '@/utils/classnames'
|
||||
|
||||
type TextFieldProps = {
|
||||
label: string
|
||||
isRequired?: boolean
|
||||
showOptional?: boolean
|
||||
tooltip?: string
|
||||
labelOptions?: Omit<LabelProps, 'htmlFor' | 'label'>
|
||||
className?: string
|
||||
labelClassName?: string
|
||||
} & Omit<InputProps, 'className' | 'onChange' | 'onBlur' | 'value' | 'id'>
|
||||
|
||||
const TextField = ({
|
||||
label,
|
||||
isRequired,
|
||||
showOptional,
|
||||
tooltip,
|
||||
labelOptions,
|
||||
className,
|
||||
labelClassName,
|
||||
...inputProps
|
||||
}: TextFieldProps) => {
|
||||
const field = useFieldContext<string>()
|
||||
@ -29,10 +24,7 @@ const TextField = ({
|
||||
<Label
|
||||
htmlFor={field.name}
|
||||
label={label}
|
||||
isRequired={isRequired}
|
||||
showOptional={showOptional}
|
||||
tooltip={tooltip}
|
||||
className={labelClassName}
|
||||
{...(labelOptions ?? {})}
|
||||
/>
|
||||
<Input
|
||||
id={field.name}
|
||||
|
||||
@ -0,0 +1,58 @@
|
||||
import cn from '@/utils/classnames'
|
||||
import type { LabelProps } from '../label'
|
||||
import { useFieldContext } from '../..'
|
||||
import Label from '../label'
|
||||
import OptionCard from '@/app/components/workflow/nodes/_base/components/option-card'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { TransferMethod } from '@/types/app'
|
||||
import { useCallback } from 'react'
|
||||
|
||||
type UploadMethodFieldProps = {
|
||||
label: string
|
||||
labelOptions?: Omit<LabelProps, 'htmlFor' | 'label'>
|
||||
className?: string
|
||||
}
|
||||
|
||||
const UploadMethodField = ({
|
||||
label,
|
||||
labelOptions,
|
||||
className,
|
||||
}: UploadMethodFieldProps) => {
|
||||
const { t } = useTranslation()
|
||||
const field = useFieldContext<TransferMethod[]>()
|
||||
|
||||
const { value } = field.state
|
||||
|
||||
const handleUploadMethodChange = useCallback((method: TransferMethod) => {
|
||||
field.handleChange(method === TransferMethod.all ? [TransferMethod.local_file, TransferMethod.remote_url] : [method])
|
||||
}, [field])
|
||||
|
||||
return (
|
||||
<div className={cn('flex flex-col gap-y-0.5', className)}>
|
||||
<Label
|
||||
htmlFor={field.name}
|
||||
label={label}
|
||||
{...(labelOptions ?? {})}
|
||||
/>
|
||||
<div className='grid grid-cols-3 gap-2'>
|
||||
<OptionCard
|
||||
title={t('appDebug.variableConfig.localUpload')}
|
||||
selected={value.length === 1 && value.includes(TransferMethod.local_file)}
|
||||
onSelect={handleUploadMethodChange.bind(null, TransferMethod.local_file)}
|
||||
/>
|
||||
<OptionCard
|
||||
title="URL"
|
||||
selected={value.length === 1 && value.includes(TransferMethod.remote_url)}
|
||||
onSelect={handleUploadMethodChange.bind(null, TransferMethod.remote_url)}
|
||||
/>
|
||||
<OptionCard
|
||||
title={t('appDebug.variableConfig.both')}
|
||||
selected={value.includes(TransferMethod.local_file) && value.includes(TransferMethod.remote_url)}
|
||||
onSelect={handleUploadMethodChange.bind(null, TransferMethod.all)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default UploadMethodField
|
||||
@ -3,15 +3,15 @@ import { type BaseConfiguration, BaseVarType } from './types'
|
||||
import { withForm } from '../..'
|
||||
import { useStore } from '@tanstack/react-form'
|
||||
|
||||
type FieldProps<T> = {
|
||||
type BaseFieldProps<T> = {
|
||||
initialData?: T
|
||||
config: BaseConfiguration<T>
|
||||
}
|
||||
|
||||
const Field = <T,>({
|
||||
const BaseField = <T,>({
|
||||
initialData,
|
||||
config,
|
||||
}: FieldProps<T>) => withForm({
|
||||
}: BaseFieldProps<T>) => withForm({
|
||||
defaultValues: initialData,
|
||||
props: {
|
||||
config,
|
||||
@ -20,7 +20,7 @@ const Field = <T,>({
|
||||
form,
|
||||
config,
|
||||
}) {
|
||||
const { type, label, placeholder, variable, tooltip, showConditions, max, min, options } = config
|
||||
const { type, label, placeholder, variable, tooltip, showConditions, max, min, options, required, showOptional } = config
|
||||
|
||||
const fieldValues = useStore(form.store, state => state.values)
|
||||
|
||||
@ -43,7 +43,11 @@ const Field = <T,>({
|
||||
children={field => (
|
||||
<field.TextField
|
||||
label={label}
|
||||
tooltip={tooltip}
|
||||
labelOptions={{
|
||||
tooltip,
|
||||
isRequired: required,
|
||||
showOptional,
|
||||
}}
|
||||
placeholder={placeholder}
|
||||
/>
|
||||
)}
|
||||
@ -58,7 +62,11 @@ const Field = <T,>({
|
||||
children={field => (
|
||||
<field.NumberInputField
|
||||
label={label}
|
||||
tooltip={tooltip}
|
||||
labelOptions={{
|
||||
tooltip,
|
||||
isRequired: required,
|
||||
showOptional,
|
||||
}}
|
||||
placeholder={placeholder}
|
||||
max={max}
|
||||
min={min}
|
||||
@ -87,8 +95,13 @@ const Field = <T,>({
|
||||
name={variable}
|
||||
children={field => (
|
||||
<field.SelectField
|
||||
options={options!}
|
||||
label={label}
|
||||
labelOptions={{
|
||||
tooltip,
|
||||
isRequired: required,
|
||||
showOptional,
|
||||
}}
|
||||
options={options!}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
@ -99,4 +112,4 @@ const Field = <T,>({
|
||||
},
|
||||
})
|
||||
|
||||
export default Field
|
||||
export default BaseField
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import React from 'react'
|
||||
import { useAppForm } from '../..'
|
||||
import Field from './field'
|
||||
import BaseField from './field'
|
||||
import type { BaseFormProps } from './types'
|
||||
|
||||
const BaseForm = <T,>({
|
||||
@ -32,7 +32,7 @@ const BaseForm = <T,>({
|
||||
>
|
||||
<div className='flex flex-col gap-4 px-4 py-2'>
|
||||
{configurations.map((config, index) => {
|
||||
const FieldComponent = Field<T>({
|
||||
const FieldComponent = BaseField<T>({
|
||||
initialData,
|
||||
config,
|
||||
})
|
||||
|
||||
@ -29,6 +29,7 @@ export type BaseConfiguration<T> = {
|
||||
maxLength?: number // Max length for text input
|
||||
placeholder?: string
|
||||
required: boolean
|
||||
showOptional?: boolean // show optional label
|
||||
showConditions: ShowCondition<T>[] // Show this field only when all conditions are met
|
||||
type: BaseVarType
|
||||
tooltip?: string // Tooltip for this field
|
||||
|
||||
@ -39,8 +39,8 @@ export type CustomSelectProps<T extends Option> = {
|
||||
itemClassName?: string
|
||||
title?: string
|
||||
},
|
||||
CustomTrigger?: (option: T | undefined, open: boolean) => React.ReactNode
|
||||
CustomOption?: (option: T, selected: boolean) => React.ReactNode
|
||||
CustomTrigger?: (option: T | undefined, open: boolean) => React.JSX.Element
|
||||
CustomOption?: (option: T, selected: boolean) => React.JSX.Element
|
||||
}
|
||||
const CustomSelect = <T extends Option>({
|
||||
options,
|
||||
|
||||
@ -3,7 +3,7 @@ import type { FC } from 'react'
|
||||
import React, { useCallback } from 'react'
|
||||
import Slider from '@/app/components/base/slider'
|
||||
|
||||
type Props = {
|
||||
export type InputNumberWithSliderProps = {
|
||||
value: number
|
||||
defaultValue?: number
|
||||
min?: number
|
||||
@ -12,7 +12,7 @@ type Props = {
|
||||
onChange: (value: number) => void
|
||||
}
|
||||
|
||||
const InputNumberWithSlider: FC<Props> = ({
|
||||
const InputNumberWithSlider: FC<InputNumberWithSliderProps> = ({
|
||||
value,
|
||||
defaultValue = 0,
|
||||
min,
|
||||
|
||||
Loading…
Reference in New Issue
Block a user