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