Form.tsx 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178
  1. import { useEffect, useState } from 'react'
  2. import type { Dispatch, FC, SetStateAction } from 'react'
  3. import { useContext } from 'use-context-selector'
  4. import type { Field, FormValue, ProviderConfigModal } from '../declarations'
  5. import { useValidate } from '../../key-validator/hooks'
  6. import { ValidatingTip } from '../../key-validator/ValidateStatus'
  7. import { validateModelProviderFn } from '../utils'
  8. import Input from './Input'
  9. import I18n from '@/context/i18n'
  10. import { SimpleSelect } from '@/app/components/base/select'
  11. type FormProps = {
  12. modelModal?: ProviderConfigModal
  13. initValue?: FormValue
  14. fields: Field[]
  15. onChange: (v: FormValue) => void
  16. onValidatedError: (v: string) => void
  17. mode: string
  18. cleared: boolean
  19. onClearedChange: Dispatch<SetStateAction<boolean>>
  20. onValidating: (validating: boolean) => void
  21. }
  22. const nameClassName = `
  23. py-2 text-sm text-gray-900
  24. `
  25. const Form: FC<FormProps> = ({
  26. modelModal,
  27. initValue = {},
  28. fields,
  29. onChange,
  30. onValidatedError,
  31. mode,
  32. cleared,
  33. onClearedChange,
  34. onValidating,
  35. }) => {
  36. const { locale } = useContext(I18n)
  37. const [value, setValue] = useState(initValue)
  38. const [validate, validating, validatedStatusState] = useValidate(value)
  39. const [changeKey, setChangeKey] = useState('')
  40. useEffect(() => {
  41. onValidatedError(validatedStatusState.message || '')
  42. }, [validatedStatusState, onValidatedError])
  43. useEffect(() => {
  44. onValidating(validating)
  45. }, [validating, onValidating])
  46. const updateValue = (v: FormValue) => {
  47. setValue(v)
  48. onChange(v)
  49. }
  50. const handleMultiFormChange = (v: FormValue, newChangeKey: string) => {
  51. updateValue(v)
  52. setChangeKey(newChangeKey)
  53. const validateKeys = (typeof modelModal?.validateKeys === 'function' ? modelModal?.validateKeys(v) : modelModal?.validateKeys) || []
  54. if (validateKeys.length) {
  55. validate({
  56. before: () => {
  57. for (let i = 0; i < validateKeys.length; i++) {
  58. if (!v[validateKeys[i]])
  59. return false
  60. }
  61. return true
  62. },
  63. run: () => {
  64. return validateModelProviderFn(modelModal!.key, modelModal?.filterValue ? modelModal?.filterValue(v) : v)
  65. },
  66. })
  67. }
  68. }
  69. const handleClear = (saveValue?: FormValue) => {
  70. const needClearFields = modelModal?.fields.filter(field => field.type !== 'radio')
  71. const newValue: Record<string, string> = {}
  72. needClearFields?.forEach((field) => {
  73. newValue[field.key] = ''
  74. })
  75. updateValue({ ...value, ...newValue, ...saveValue })
  76. onClearedChange(true)
  77. }
  78. const handleFormChange = (k: string, v: string) => {
  79. if (mode === 'edit' && !cleared)
  80. handleClear({ [k]: v })
  81. else
  82. handleMultiFormChange({ ...value, [k]: v }, k)
  83. }
  84. const handleFocus = () => {
  85. if (mode === 'edit' && !cleared)
  86. handleClear()
  87. }
  88. const renderField = (field: Field) => {
  89. const hidden = typeof field.hidden === 'function' ? field.hidden(value) : field.hidden
  90. if (hidden)
  91. return null
  92. if (field.type === 'text') {
  93. return (
  94. <div key={field.key} className='py-3'>
  95. <div className={nameClassName}>{field.label[locale]}</div>
  96. <Input
  97. field={field}
  98. value={value}
  99. onChange={v => handleMultiFormChange(v, field.key)}
  100. onFocus={handleFocus}
  101. validatedStatusState={validatedStatusState}
  102. />
  103. {validating && changeKey === field.key && <ValidatingTip />}
  104. </div>
  105. )
  106. }
  107. if (field.type === 'radio') {
  108. const options = typeof field.options === 'function' ? field.options(value) : field.options
  109. return (
  110. <div key={field.key} className='py-3'>
  111. <div className={nameClassName}>{field.label[locale]}</div>
  112. <div className='grid grid-cols-2 gap-3'>
  113. {
  114. options?.map(option => (
  115. <div
  116. className={`
  117. flex items-center px-3 h-9 rounded-lg border border-gray-100 bg-gray-25 cursor-pointer
  118. ${value?.[field.key] === option.key && 'bg-white border-[1.5px] border-primary-400 shadow-sm'}
  119. `}
  120. onClick={() => handleFormChange(field.key, option.key)}
  121. key={`${field.key}-${option.key}`}
  122. >
  123. <div className={`
  124. flex justify-center items-center mr-2 w-4 h-4 border border-gray-300 rounded-full
  125. ${value?.[field.key] === option.key && 'border-[5px] border-primary-600'}
  126. `} />
  127. <div className='text-sm text-gray-900'>{option.label[locale]}</div>
  128. </div>
  129. ))
  130. }
  131. </div>
  132. {validating && changeKey === field.key && <ValidatingTip />}
  133. </div>
  134. )
  135. }
  136. if (field.type === 'select') {
  137. const options = typeof field.options === 'function' ? field.options(value) : field.options
  138. return (
  139. <div key={field.key} className='py-3'>
  140. <div className={nameClassName}>{field.label[locale]}</div>
  141. <SimpleSelect
  142. defaultValue={value[field.key]}
  143. items={options!.map(option => ({ value: option.key, name: option.label[locale] }))}
  144. onSelect={item => handleFormChange(field.key, item.value as string)}
  145. />
  146. {validating && changeKey === field.key && <ValidatingTip />}
  147. </div>
  148. )
  149. }
  150. }
  151. return (
  152. <div>
  153. {
  154. fields.map(field => renderField(field))
  155. }
  156. </div>
  157. )
  158. }
  159. export default Form