@toptal/picasso-forms 6.0.4 → 6.0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +657 -0
- package/{Autocomplete → dist-package/Autocomplete}/Autocomplete.d.ts +0 -0
- package/{Autocomplete → dist-package/Autocomplete}/Autocomplete.js +0 -0
- package/{Autocomplete → dist-package/Autocomplete}/Autocomplete.js.map +0 -0
- package/{Autocomplete → dist-package/Autocomplete}/index.d.ts +0 -0
- package/{Autocomplete → dist-package/Autocomplete}/index.js +0 -0
- package/{Autocomplete → dist-package/Autocomplete}/index.js.map +0 -0
- package/{ButtonCheckbox → dist-package/ButtonCheckbox}/ButtonCheckbox.d.ts +0 -0
- package/{ButtonCheckbox → dist-package/ButtonCheckbox}/ButtonCheckbox.js +0 -0
- package/{ButtonCheckbox → dist-package/ButtonCheckbox}/ButtonCheckbox.js.map +0 -0
- package/{ButtonCheckbox → dist-package/ButtonCheckbox}/index.d.ts +0 -0
- package/{ButtonCheckbox → dist-package/ButtonCheckbox}/index.js +0 -0
- package/{ButtonCheckbox → dist-package/ButtonCheckbox}/index.js.map +0 -0
- package/{ButtonRadio → dist-package/ButtonRadio}/ButtonRadio.d.ts +0 -0
- package/{ButtonRadio → dist-package/ButtonRadio}/ButtonRadio.js +0 -0
- package/{ButtonRadio → dist-package/ButtonRadio}/ButtonRadio.js.map +0 -0
- package/{ButtonRadio → dist-package/ButtonRadio}/index.d.ts +0 -0
- package/{ButtonRadio → dist-package/ButtonRadio}/index.js +0 -0
- package/{ButtonRadio → dist-package/ButtonRadio}/index.js.map +0 -0
- package/{Checkbox → dist-package/Checkbox}/Checkbox.d.ts +0 -0
- package/{Checkbox → dist-package/Checkbox}/Checkbox.js +0 -0
- package/{Checkbox → dist-package/Checkbox}/Checkbox.js.map +0 -0
- package/{Checkbox → dist-package/Checkbox}/index.d.ts +0 -0
- package/{Checkbox → dist-package/Checkbox}/index.js +0 -0
- package/{Checkbox → dist-package/Checkbox}/index.js.map +0 -0
- package/{CheckboxGroup → dist-package/CheckboxGroup}/CheckboxGroup.d.ts +0 -0
- package/{CheckboxGroup → dist-package/CheckboxGroup}/CheckboxGroup.js +0 -0
- package/{CheckboxGroup → dist-package/CheckboxGroup}/CheckboxGroup.js.map +0 -0
- package/{CheckboxGroup → dist-package/CheckboxGroup}/CheckboxGroupContext.d.ts +0 -0
- package/{CheckboxGroup → dist-package/CheckboxGroup}/CheckboxGroupContext.js +0 -0
- package/{CheckboxGroup → dist-package/CheckboxGroup}/CheckboxGroupContext.js.map +0 -0
- package/{CheckboxGroup → dist-package/CheckboxGroup}/index.d.ts +0 -0
- package/{CheckboxGroup → dist-package/CheckboxGroup}/index.js +0 -0
- package/{CheckboxGroup → dist-package/CheckboxGroup}/index.js.map +0 -0
- package/{DatePicker → dist-package/DatePicker}/DatePicker.d.ts +0 -0
- package/{DatePicker → dist-package/DatePicker}/DatePicker.js +0 -0
- package/{DatePicker → dist-package/DatePicker}/DatePicker.js.map +0 -0
- package/{DatePicker → dist-package/DatePicker}/index.d.ts +0 -0
- package/{DatePicker → dist-package/DatePicker}/index.js +0 -0
- package/{DatePicker → dist-package/DatePicker}/index.js.map +0 -0
- package/{FieldWrapper → dist-package/FieldWrapper}/FieldWrapper.d.ts +0 -0
- package/{FieldWrapper → dist-package/FieldWrapper}/FieldWrapper.js +0 -0
- package/{FieldWrapper → dist-package/FieldWrapper}/FieldWrapper.js.map +0 -0
- package/{FieldWrapper → dist-package/FieldWrapper}/index.d.ts +0 -0
- package/{FieldWrapper → dist-package/FieldWrapper}/index.js +0 -0
- package/{FieldWrapper → dist-package/FieldWrapper}/index.js.map +0 -0
- package/{FileInput → dist-package/FileInput}/FileInput.d.ts +0 -0
- package/{FileInput → dist-package/FileInput}/FileInput.js +0 -0
- package/{FileInput → dist-package/FileInput}/FileInput.js.map +0 -0
- package/{FileInput → dist-package/FileInput}/index.d.ts +0 -0
- package/{FileInput → dist-package/FileInput}/index.js +0 -0
- package/{FileInput → dist-package/FileInput}/index.js.map +0 -0
- package/{Form → dist-package/Form}/Form.d.ts +0 -0
- package/{Form → dist-package/Form}/Form.js +0 -0
- package/{Form → dist-package/Form}/Form.js.map +0 -0
- package/{Form → dist-package/Form}/FormContext.d.ts +0 -0
- package/{Form → dist-package/Form}/FormContext.js +0 -0
- package/{Form → dist-package/Form}/FormContext.js.map +0 -0
- package/{Form → dist-package/Form}/index.d.ts +0 -0
- package/{Form → dist-package/Form}/index.js +0 -0
- package/{Form → dist-package/Form}/index.js.map +0 -0
- package/{FormConfig → dist-package/FormConfig}/FormConfig.d.ts +0 -0
- package/{FormConfig → dist-package/FormConfig}/FormConfig.js +0 -0
- package/{FormConfig → dist-package/FormConfig}/FormConfig.js.map +0 -0
- package/{FormConfig → dist-package/FormConfig}/index.d.ts +0 -0
- package/{FormConfig → dist-package/FormConfig}/index.js +0 -0
- package/{FormConfig → dist-package/FormConfig}/index.js.map +0 -0
- package/{Input → dist-package/Input}/Input.d.ts +0 -0
- package/{Input → dist-package/Input}/Input.js +0 -0
- package/{Input → dist-package/Input}/Input.js.map +0 -0
- package/{Input → dist-package/Input}/index.d.ts +0 -0
- package/{Input → dist-package/Input}/index.js +0 -0
- package/{Input → dist-package/Input}/index.js.map +0 -0
- package/{Input → dist-package/Input}/utils/get-input-name.d.ts +0 -0
- package/{Input → dist-package/Input}/utils/get-input-name.js +0 -0
- package/{Input → dist-package/Input}/utils/get-input-name.js.map +0 -0
- package/{Input → dist-package/Input}/utils/get-input-name.test.d.ts +0 -0
- package/{Input → dist-package/Input}/utils/get-input-name.test.js +0 -0
- package/{Input → dist-package/Input}/utils/get-input-name.test.js.map +0 -0
- package/{NumberInput → dist-package/NumberInput}/NumberInput.d.ts +0 -0
- package/{NumberInput → dist-package/NumberInput}/NumberInput.js +0 -0
- package/{NumberInput → dist-package/NumberInput}/NumberInput.js.map +0 -0
- package/{NumberInput → dist-package/NumberInput}/index.d.ts +0 -0
- package/{NumberInput → dist-package/NumberInput}/index.js +0 -0
- package/{NumberInput → dist-package/NumberInput}/index.js.map +0 -0
- package/dist-package/README.md +29 -0
- package/{Radio → dist-package/Radio}/Radio.d.ts +0 -0
- package/{Radio → dist-package/Radio}/Radio.js +0 -0
- package/{Radio → dist-package/Radio}/Radio.js.map +0 -0
- package/{Radio → dist-package/Radio}/index.d.ts +0 -0
- package/{Radio → dist-package/Radio}/index.js +0 -0
- package/{Radio → dist-package/Radio}/index.js.map +0 -0
- package/{RadioGroup → dist-package/RadioGroup}/RadioGroup.d.ts +0 -0
- package/{RadioGroup → dist-package/RadioGroup}/RadioGroup.js +0 -0
- package/{RadioGroup → dist-package/RadioGroup}/RadioGroup.js.map +0 -0
- package/{RadioGroup → dist-package/RadioGroup}/RadioGroupContext.d.ts +0 -0
- package/{RadioGroup → dist-package/RadioGroup}/RadioGroupContext.js +0 -0
- package/{RadioGroup → dist-package/RadioGroup}/RadioGroupContext.js.map +0 -0
- package/{RadioGroup → dist-package/RadioGroup}/index.d.ts +0 -0
- package/{RadioGroup → dist-package/RadioGroup}/index.js +0 -0
- package/{RadioGroup → dist-package/RadioGroup}/index.js.map +0 -0
- package/{Rating → dist-package/Rating}/Rating.d.ts +0 -0
- package/{Rating → dist-package/Rating}/Rating.js +0 -0
- package/{Rating → dist-package/Rating}/Rating.js.map +0 -0
- package/{Rating → dist-package/Rating}/index.d.ts +0 -0
- package/{Rating → dist-package/Rating}/index.js +0 -0
- package/{Rating → dist-package/Rating}/index.js.map +0 -0
- package/{Select → dist-package/Select}/Select.d.ts +0 -0
- package/{Select → dist-package/Select}/Select.js +0 -0
- package/{Select → dist-package/Select}/Select.js.map +0 -0
- package/{Select → dist-package/Select}/index.d.ts +0 -0
- package/{Select → dist-package/Select}/index.js +0 -0
- package/{Select → dist-package/Select}/index.js.map +0 -0
- package/{SubmitButton → dist-package/SubmitButton}/SubmitButton.d.ts +0 -0
- package/{SubmitButton → dist-package/SubmitButton}/SubmitButton.js +0 -0
- package/{SubmitButton → dist-package/SubmitButton}/SubmitButton.js.map +0 -0
- package/{SubmitButton → dist-package/SubmitButton}/index.d.ts +0 -0
- package/{SubmitButton → dist-package/SubmitButton}/index.js +0 -0
- package/{SubmitButton → dist-package/SubmitButton}/index.js.map +0 -0
- package/{Switch → dist-package/Switch}/Switch.d.ts +0 -0
- package/{Switch → dist-package/Switch}/Switch.js +0 -0
- package/{Switch → dist-package/Switch}/Switch.js.map +0 -0
- package/{Switch → dist-package/Switch}/index.d.ts +0 -0
- package/{Switch → dist-package/Switch}/index.js +0 -0
- package/{Switch → dist-package/Switch}/index.js.map +0 -0
- package/{TagSelector → dist-package/TagSelector}/TagSelector.d.ts +0 -0
- package/{TagSelector → dist-package/TagSelector}/TagSelector.js +0 -0
- package/{TagSelector → dist-package/TagSelector}/TagSelector.js.map +0 -0
- package/{TagSelector → dist-package/TagSelector}/index.d.ts +0 -0
- package/{TagSelector → dist-package/TagSelector}/index.js +0 -0
- package/{TagSelector → dist-package/TagSelector}/index.js.map +0 -0
- package/{TimePicker → dist-package/TimePicker}/TimePicker.d.ts +0 -0
- package/{TimePicker → dist-package/TimePicker}/TimePicker.js +0 -0
- package/{TimePicker → dist-package/TimePicker}/TimePicker.js.map +0 -0
- package/{TimePicker → dist-package/TimePicker}/index.d.ts +0 -0
- package/{TimePicker → dist-package/TimePicker}/index.js +0 -0
- package/{TimePicker → dist-package/TimePicker}/index.js.map +0 -0
- package/{index.d.ts → dist-package/index.d.ts} +2 -1
- package/{index.js → dist-package/index.js} +0 -1
- package/dist-package/index.js.map +1 -0
- package/dist-package/package.json +44 -0
- package/{utils → dist-package/utils}/flat-map.d.ts +0 -0
- package/{utils → dist-package/utils}/flat-map.js +0 -0
- package/{utils → dist-package/utils}/flat-map.js.map +0 -0
- package/{utils → dist-package/utils}/index.d.ts +0 -0
- package/{utils → dist-package/utils}/index.js +0 -0
- package/{utils → dist-package/utils}/index.js.map +0 -0
- package/{utils → dist-package/utils}/scroll-to-error-decorator.d.ts +0 -0
- package/{utils → dist-package/utils}/scroll-to-error-decorator.js +0 -0
- package/{utils → dist-package/utils}/scroll-to-error-decorator.js.map +0 -0
- package/{utils → dist-package/utils}/validators.d.ts +0 -0
- package/{utils → dist-package/utils}/validators.js +0 -0
- package/{utils → dist-package/utils}/validators.js.map +0 -0
- package/package.json +4 -5
- package/src/Autocomplete/Autocomplete.tsx +21 -0
- package/src/Autocomplete/index.ts +1 -0
- package/src/ButtonCheckbox/ButtonCheckbox.tsx +57 -0
- package/src/ButtonCheckbox/index.ts +1 -0
- package/src/ButtonRadio/ButtonRadio.tsx +24 -0
- package/src/ButtonRadio/index.ts +1 -0
- package/src/Checkbox/Checkbox.tsx +73 -0
- package/src/Checkbox/__snapshots__/test.tsx.snap +254 -0
- package/src/Checkbox/index.ts +1 -0
- package/src/Checkbox/test.tsx +91 -0
- package/src/CheckboxGroup/CheckboxGroup.tsx +30 -0
- package/src/CheckboxGroup/CheckboxGroupContext.ts +3 -0
- package/src/CheckboxGroup/index.ts +3 -0
- package/src/CheckboxGroup/test.tsx +35 -0
- package/src/DatePicker/DatePicker.tsx +26 -0
- package/src/DatePicker/index.ts +1 -0
- package/src/FieldWrapper/FieldWrapper.tsx +287 -0
- package/src/FieldWrapper/index.ts +2 -0
- package/src/FieldWrapper/story/index.jsx +137 -0
- package/src/FileInput/FileInput.tsx +66 -0
- package/src/FileInput/index.ts +1 -0
- package/src/Form/Form.tsx +181 -0
- package/src/Form/FormContext.ts +38 -0
- package/src/Form/__image_snapshots__/form-default-snap.png +0 -0
- package/src/Form/__image_snapshots__/form-form-level-configurations-snap.png +0 -0
- package/src/Form/__snapshots__/test.tsx.snap +61 -0
- package/src/Form/index.ts +1 -0
- package/src/Form/story/BackendCommunication.example.tsx +139 -0
- package/src/Form/story/CustomFormLevelConfiguration.example.tsx +26 -0
- package/src/Form/story/CustomValidator.example.tsx +45 -0
- package/src/Form/story/Default.example.tsx +177 -0
- package/src/Form/story/FileInput.example.tsx +42 -0
- package/src/Form/story/ParseInput.example.tsx +28 -0
- package/src/Form/story/TitleCase.example.tsx +167 -0
- package/src/Form/story/ValidateOnSubmit.example.tsx +85 -0
- package/src/Form/story/index.jsx +203 -0
- package/src/Form/test.tsx +27 -0
- package/src/FormConfig/FormConfig.ts +12 -0
- package/src/FormConfig/index.ts +1 -0
- package/src/FormConfig/test.tsx +44 -0
- package/src/Input/Input.tsx +27 -0
- package/src/Input/index.ts +1 -0
- package/src/Input/test.tsx +34 -0
- package/src/Input/utils/get-input-name.test.ts +16 -0
- package/src/Input/utils/get-input-name.ts +11 -0
- package/src/NumberInput/NumberInput.tsx +45 -0
- package/src/NumberInput/index.ts +1 -0
- package/src/Radio/Radio.tsx +24 -0
- package/src/Radio/__snapshots__/test.tsx.snap +231 -0
- package/src/Radio/index.ts +1 -0
- package/src/Radio/test.tsx +49 -0
- package/src/RadioGroup/RadioGroup.tsx +39 -0
- package/src/RadioGroup/RadioGroupContext.ts +3 -0
- package/src/RadioGroup/index.ts +3 -0
- package/src/RadioGroup/test.tsx +35 -0
- package/src/Rating/Rating.tsx +22 -0
- package/src/Rating/index.ts +1 -0
- package/src/Select/Select.tsx +47 -0
- package/src/Select/index.ts +1 -0
- package/src/SubmitButton/SubmitButton.tsx +70 -0
- package/src/SubmitButton/__image_snapshots__/submitbutton-button-types-snap.png +0 -0
- package/src/SubmitButton/__image_snapshots__/submitbutton-default-snap.png +0 -0
- package/src/SubmitButton/index.ts +6 -0
- package/src/SubmitButton/story/ButtonTypes.example.tsx +46 -0
- package/src/SubmitButton/story/Default.example.tsx +15 -0
- package/src/SubmitButton/story/index.jsx +32 -0
- package/src/Switch/Switch.tsx +23 -0
- package/src/Switch/index.ts +1 -0
- package/src/TagSelector/TagSelector.tsx +25 -0
- package/src/TagSelector/index.ts +1 -0
- package/src/TimePicker/TimePicker.tsx +24 -0
- package/src/TimePicker/index.ts +1 -0
- package/src/index.ts +16 -0
- package/src/story/Deserialization.example.tsx +34 -0
- package/src/story/FormSpy.example.tsx +42 -0
- package/src/story/index.jsx +37 -0
- package/src/utils/flat-map.ts +4 -0
- package/src/utils/index.ts +3 -0
- package/src/utils/scroll-to-error-decorator.ts +78 -0
- package/src/utils/validators.ts +18 -0
- package/tsconfig.build.json +7 -0
- package/index.js.map +0 -1
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
import React, { ChangeEvent, FocusEvent, useCallback, useEffect } from 'react'
|
|
2
|
+
import {
|
|
3
|
+
useField,
|
|
4
|
+
FieldProps as FinalFieldProps,
|
|
5
|
+
FieldMetaState,
|
|
6
|
+
FieldRenderProps
|
|
7
|
+
} from 'react-final-form'
|
|
8
|
+
import { Form as PicassoForm, RequiredDecoration } from '@toptal/picasso'
|
|
9
|
+
import { Item } from '@toptal/picasso/Autocomplete'
|
|
10
|
+
import { FileUpload } from '@toptal/picasso/FileInput'
|
|
11
|
+
import { DateOrDateRangeType } from '@toptal/picasso-lab'
|
|
12
|
+
import { TextLabelProps } from '@toptal/picasso-shared'
|
|
13
|
+
|
|
14
|
+
import { useFormContext } from '../Form/FormContext'
|
|
15
|
+
import { useFormConfig, FormConfigProps, RequiredVariant } from '../FormConfig'
|
|
16
|
+
import { validators } from '../utils'
|
|
17
|
+
|
|
18
|
+
const { composeValidators, required: requiredValidator } = validators
|
|
19
|
+
|
|
20
|
+
type ValueType =
|
|
21
|
+
| string
|
|
22
|
+
| string[]
|
|
23
|
+
| number
|
|
24
|
+
| boolean
|
|
25
|
+
| null
|
|
26
|
+
| undefined
|
|
27
|
+
| FileUpload[]
|
|
28
|
+
| DateOrDateRangeType
|
|
29
|
+
| Item
|
|
30
|
+
| Item[]
|
|
31
|
+
|
|
32
|
+
export type FieldProps<TInputValue> = FinalFieldProps<
|
|
33
|
+
TInputValue,
|
|
34
|
+
FieldRenderProps<TInputValue, HTMLInputElement>,
|
|
35
|
+
HTMLInputElement
|
|
36
|
+
> &
|
|
37
|
+
TextLabelProps
|
|
38
|
+
|
|
39
|
+
export type Props<
|
|
40
|
+
TInputValue,
|
|
41
|
+
TWrappedComponentProps
|
|
42
|
+
> = TWrappedComponentProps &
|
|
43
|
+
FieldProps<TInputValue> &
|
|
44
|
+
TextLabelProps & {
|
|
45
|
+
name: string
|
|
46
|
+
type?: string
|
|
47
|
+
hideFieldLabel?: boolean
|
|
48
|
+
hideLabelRequiredDecoration?: boolean
|
|
49
|
+
fieldType?: string
|
|
50
|
+
children: (props: any) => React.ReactNode
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
type FieldMeta<T> = FieldMetaState<T> & {
|
|
54
|
+
dirtyAfterBlur?: boolean
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const getInputError = <T extends ValueType>(
|
|
58
|
+
meta: FieldMeta<T>,
|
|
59
|
+
formConfig: FormConfigProps
|
|
60
|
+
) => {
|
|
61
|
+
if (formConfig.validateOnSubmit && meta.modifiedSinceLastSubmit) {
|
|
62
|
+
return null
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (!meta.error && !meta.submitError) {
|
|
66
|
+
return null
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (!meta.touched) {
|
|
70
|
+
return null
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (meta.error) {
|
|
74
|
+
return meta.error
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (meta.dirtySinceLastSubmit) {
|
|
78
|
+
return null
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return meta.submitError
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const getValidators = (required: boolean, validate?: any) => {
|
|
85
|
+
if (required && validate) {
|
|
86
|
+
return composeValidators([requiredValidator, validate])
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (required && !validate) {
|
|
90
|
+
return requiredValidator
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return validate
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const getProps = ({
|
|
97
|
+
hideFieldLabel,
|
|
98
|
+
error,
|
|
99
|
+
label
|
|
100
|
+
}: {
|
|
101
|
+
hideFieldLabel?: boolean
|
|
102
|
+
error: string
|
|
103
|
+
label: string
|
|
104
|
+
}) => {
|
|
105
|
+
if (hideFieldLabel) {
|
|
106
|
+
return {
|
|
107
|
+
label
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return {
|
|
112
|
+
error: Boolean(error)
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const getRequiredDecoration = (
|
|
117
|
+
hideLabelRequiredDecoration?: boolean,
|
|
118
|
+
required?: boolean,
|
|
119
|
+
requiredVariant?: RequiredVariant
|
|
120
|
+
): RequiredDecoration | undefined => {
|
|
121
|
+
if (hideLabelRequiredDecoration) {
|
|
122
|
+
return
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const showAsterisk = required && requiredVariant === 'asterisk'
|
|
126
|
+
|
|
127
|
+
if (showAsterisk) {
|
|
128
|
+
return 'asterisk'
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const showOptional =
|
|
132
|
+
!required && (!requiredVariant || requiredVariant === 'default')
|
|
133
|
+
|
|
134
|
+
if (showOptional) {
|
|
135
|
+
return 'optional'
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const FieldWrapper = <
|
|
140
|
+
TWrappedComponentProps extends { value?: ValueType },
|
|
141
|
+
TInputValue extends ValueType = TWrappedComponentProps['value']
|
|
142
|
+
>(
|
|
143
|
+
props: Props<TInputValue, TWrappedComponentProps>
|
|
144
|
+
) => {
|
|
145
|
+
const {
|
|
146
|
+
type,
|
|
147
|
+
hideFieldLabel,
|
|
148
|
+
hideLabelRequiredDecoration,
|
|
149
|
+
hint,
|
|
150
|
+
label,
|
|
151
|
+
required,
|
|
152
|
+
enableReset,
|
|
153
|
+
onResetClick,
|
|
154
|
+
'data-testid': dataTestId,
|
|
155
|
+
// FieldProps - https://final-form.org/docs/react-final-form/types/FieldProps
|
|
156
|
+
afterSubmit,
|
|
157
|
+
allowNull,
|
|
158
|
+
beforeSubmit,
|
|
159
|
+
children,
|
|
160
|
+
data,
|
|
161
|
+
defaultValue,
|
|
162
|
+
format,
|
|
163
|
+
formatOnBlur,
|
|
164
|
+
initialValue,
|
|
165
|
+
isEqual,
|
|
166
|
+
name,
|
|
167
|
+
id = name,
|
|
168
|
+
parse,
|
|
169
|
+
subscription,
|
|
170
|
+
validate,
|
|
171
|
+
validateFields,
|
|
172
|
+
value,
|
|
173
|
+
titleCase,
|
|
174
|
+
//
|
|
175
|
+
...rest
|
|
176
|
+
} = props
|
|
177
|
+
|
|
178
|
+
const formConfig = useFormConfig()
|
|
179
|
+
const { setValidators, clearValidators } = useFormContext()
|
|
180
|
+
const validators = getValidators(required, validate)
|
|
181
|
+
|
|
182
|
+
if (formConfig.validateOnSubmit) {
|
|
183
|
+
setValidators(name, validators)
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
useEffect(() => {
|
|
187
|
+
return () => {
|
|
188
|
+
if (formConfig.validateOnSubmit) {
|
|
189
|
+
clearValidators(name)
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}, [clearValidators, formConfig.validateOnSubmit, name])
|
|
193
|
+
|
|
194
|
+
const { meta, input } = useField<TInputValue>(name, {
|
|
195
|
+
validate: formConfig.validateOnSubmit ? undefined : validators,
|
|
196
|
+
type,
|
|
197
|
+
afterSubmit,
|
|
198
|
+
allowNull,
|
|
199
|
+
beforeSubmit,
|
|
200
|
+
data,
|
|
201
|
+
defaultValue,
|
|
202
|
+
format,
|
|
203
|
+
formatOnBlur,
|
|
204
|
+
initialValue,
|
|
205
|
+
isEqual,
|
|
206
|
+
parse,
|
|
207
|
+
subscription,
|
|
208
|
+
validateFields,
|
|
209
|
+
value
|
|
210
|
+
})
|
|
211
|
+
|
|
212
|
+
const defaultResetClickHandler = useCallback(() => {
|
|
213
|
+
input.onChange('')
|
|
214
|
+
}, [input])
|
|
215
|
+
|
|
216
|
+
const resetClickHandler = useCallback(() => {
|
|
217
|
+
onResetClick((resetValue: string) => {
|
|
218
|
+
input.onChange(resetValue)
|
|
219
|
+
})
|
|
220
|
+
}, [input, onResetClick])
|
|
221
|
+
|
|
222
|
+
const error = getInputError<TInputValue>(meta, formConfig)
|
|
223
|
+
|
|
224
|
+
const childProps: Record<string, unknown> = {
|
|
225
|
+
id,
|
|
226
|
+
...rest,
|
|
227
|
+
...input,
|
|
228
|
+
...getProps({ hideFieldLabel, error, label }),
|
|
229
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
230
|
+
onChange: (event: ChangeEvent<HTMLElement> | any) => {
|
|
231
|
+
input.onChange(event)
|
|
232
|
+
|
|
233
|
+
if (rest.onChange) {
|
|
234
|
+
rest.onChange(event)
|
|
235
|
+
}
|
|
236
|
+
},
|
|
237
|
+
onBlur: (event: FocusEvent<HTMLElement>) => {
|
|
238
|
+
input.onBlur(event)
|
|
239
|
+
|
|
240
|
+
if (rest.onBlur) {
|
|
241
|
+
rest.onBlur(event)
|
|
242
|
+
}
|
|
243
|
+
},
|
|
244
|
+
onFocus: (event: FocusEvent<HTMLElement>) => {
|
|
245
|
+
input.onFocus(event)
|
|
246
|
+
|
|
247
|
+
if (rest.onFocus) {
|
|
248
|
+
rest.onFocus(event)
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
if (enableReset) {
|
|
254
|
+
childProps.onResetClick = onResetClick
|
|
255
|
+
? resetClickHandler
|
|
256
|
+
: defaultResetClickHandler
|
|
257
|
+
childProps.enableReset = enableReset
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
const requiredDecoration = getRequiredDecoration(
|
|
261
|
+
hideLabelRequiredDecoration,
|
|
262
|
+
required,
|
|
263
|
+
formConfig.requiredVariant
|
|
264
|
+
)
|
|
265
|
+
|
|
266
|
+
return (
|
|
267
|
+
<PicassoForm.Field error={error} hint={hint} data-testid={dataTestId}>
|
|
268
|
+
{!hideFieldLabel && label && (
|
|
269
|
+
<PicassoForm.Label
|
|
270
|
+
requiredDecoration={requiredDecoration}
|
|
271
|
+
htmlFor={id}
|
|
272
|
+
disabled={rest.disabled}
|
|
273
|
+
titleCase={titleCase}
|
|
274
|
+
>
|
|
275
|
+
{label}
|
|
276
|
+
</PicassoForm.Label>
|
|
277
|
+
)}
|
|
278
|
+
{children(childProps)}
|
|
279
|
+
</PicassoForm.Field>
|
|
280
|
+
)
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
FieldWrapper.defaultProps = {}
|
|
284
|
+
|
|
285
|
+
FieldWrapper.displayName = 'FieldWrapper'
|
|
286
|
+
|
|
287
|
+
export default FieldWrapper
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import FieldWrapper from '../FieldWrapper'
|
|
2
|
+
import PicassoBook from '~/.storybook/components/PicassoBook'
|
|
3
|
+
|
|
4
|
+
const componentDocs = PicassoBook.createComponentDocs(
|
|
5
|
+
FieldWrapper,
|
|
6
|
+
'Form.Field',
|
|
7
|
+
undefined,
|
|
8
|
+
{
|
|
9
|
+
name: {
|
|
10
|
+
name: 'name',
|
|
11
|
+
type: 'string',
|
|
12
|
+
description: 'The field name',
|
|
13
|
+
required: true
|
|
14
|
+
},
|
|
15
|
+
label: {
|
|
16
|
+
name: 'label',
|
|
17
|
+
type: 'string',
|
|
18
|
+
description: 'The field label text'
|
|
19
|
+
},
|
|
20
|
+
hint: {
|
|
21
|
+
name: 'hint',
|
|
22
|
+
type: 'string',
|
|
23
|
+
description: 'The hint of the field with some additional information'
|
|
24
|
+
},
|
|
25
|
+
required: {
|
|
26
|
+
name: 'required',
|
|
27
|
+
type: 'boolean',
|
|
28
|
+
description: 'Makes field to be required in the form'
|
|
29
|
+
},
|
|
30
|
+
// FieldProps - https://final-form.org/docs/react-final-form/types/FieldProps
|
|
31
|
+
afterSubmit: {
|
|
32
|
+
name: 'afterSubmit',
|
|
33
|
+
type: {
|
|
34
|
+
name: 'function',
|
|
35
|
+
description: '() => void'
|
|
36
|
+
},
|
|
37
|
+
description:
|
|
38
|
+
'A callback to notify fields after submission has completed successfully'
|
|
39
|
+
},
|
|
40
|
+
allowNull: {
|
|
41
|
+
name: 'allowNull',
|
|
42
|
+
type: 'boolean',
|
|
43
|
+
description: 'By default null value is converted to empty string',
|
|
44
|
+
defaultValue: false
|
|
45
|
+
},
|
|
46
|
+
beforeSubmit: {
|
|
47
|
+
name: 'beforeSubmit',
|
|
48
|
+
type: {
|
|
49
|
+
name: 'function',
|
|
50
|
+
description: '() => void | false'
|
|
51
|
+
},
|
|
52
|
+
description: 'A function to call just before calling onSubmit'
|
|
53
|
+
},
|
|
54
|
+
data: {
|
|
55
|
+
name: 'data',
|
|
56
|
+
type: 'object',
|
|
57
|
+
description: 'Initial state for arbitrary values to be placed by mutators'
|
|
58
|
+
},
|
|
59
|
+
defaultValue: {
|
|
60
|
+
name: 'defaultValue',
|
|
61
|
+
type: 'any',
|
|
62
|
+
description: 'Default value of the field upon creation'
|
|
63
|
+
},
|
|
64
|
+
format: {
|
|
65
|
+
name: 'format',
|
|
66
|
+
type: {
|
|
67
|
+
name: 'function',
|
|
68
|
+
description: '(value: any, name: string) => any'
|
|
69
|
+
},
|
|
70
|
+
description:
|
|
71
|
+
'A function that takes the value from the form values and the name of the field and formats the value to give to the input'
|
|
72
|
+
},
|
|
73
|
+
formatOnBlur: {
|
|
74
|
+
name: 'formatOnBlur',
|
|
75
|
+
type: 'boolean',
|
|
76
|
+
description:
|
|
77
|
+
'If true, the format function will only be called when the field is blurred. If false, format will be called on every render'
|
|
78
|
+
},
|
|
79
|
+
initialValue: {
|
|
80
|
+
name: 'initialValue',
|
|
81
|
+
type: 'any',
|
|
82
|
+
description: 'The initial value for the field'
|
|
83
|
+
},
|
|
84
|
+
isEqual: {
|
|
85
|
+
name: 'isEqual',
|
|
86
|
+
type: {
|
|
87
|
+
name: 'function',
|
|
88
|
+
description: '(a: any, b: any) => boolean'
|
|
89
|
+
},
|
|
90
|
+
description: 'A function to determine if two values are equal'
|
|
91
|
+
},
|
|
92
|
+
parse: {
|
|
93
|
+
name: 'parse',
|
|
94
|
+
type: {
|
|
95
|
+
name: 'function',
|
|
96
|
+
description: '(value: any, name: string) => any'
|
|
97
|
+
},
|
|
98
|
+
description:
|
|
99
|
+
"A function that takes the value from the input and name of the field and converts the value into the value you want stored as this field's value in the form"
|
|
100
|
+
},
|
|
101
|
+
subscription: {
|
|
102
|
+
name: 'subscription',
|
|
103
|
+
type: {
|
|
104
|
+
name: 'object',
|
|
105
|
+
description: '{ [string]: boolean }'
|
|
106
|
+
},
|
|
107
|
+
description:
|
|
108
|
+
'An object of the parts of FieldState (final-form) to subscribe to'
|
|
109
|
+
},
|
|
110
|
+
validate: {
|
|
111
|
+
name: 'validate',
|
|
112
|
+
type: {
|
|
113
|
+
name: 'function',
|
|
114
|
+
description:
|
|
115
|
+
'(value: ?any, allValues: Object, meta: ?FieldState) => ?any'
|
|
116
|
+
},
|
|
117
|
+
description:
|
|
118
|
+
'A function that takes the field value, all the values of the form and the meta data about the field and returns an error if the value is invalid, or undefined if the value is valid'
|
|
119
|
+
},
|
|
120
|
+
validateFields: {
|
|
121
|
+
name: 'validateFields',
|
|
122
|
+
type: 'string[]',
|
|
123
|
+
description: 'An array of field names to validate when this field changes'
|
|
124
|
+
},
|
|
125
|
+
// //
|
|
126
|
+
allFieldProps: {
|
|
127
|
+
name: '<all field props>',
|
|
128
|
+
type: 'any',
|
|
129
|
+
description:
|
|
130
|
+
'This component also accepts all the native props from the corresponding form component, ex. Form.Input accepts all the Picasso Input props'
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
export default {
|
|
136
|
+
componentDocs
|
|
137
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import { FileInput as PicassoFileInput, FileInputProps } from '@toptal/picasso'
|
|
3
|
+
import { FieldInputProps as FinalFieldInputProps } from 'react-final-form'
|
|
4
|
+
import { FileUpload } from '@toptal/picasso/FileInput'
|
|
5
|
+
|
|
6
|
+
import FieldWrapper, { FieldProps } from '../FieldWrapper'
|
|
7
|
+
|
|
8
|
+
type FinalFormOnChangeType = FinalFieldInputProps<
|
|
9
|
+
FileInputProps['value']
|
|
10
|
+
>['onChange']
|
|
11
|
+
|
|
12
|
+
export type Props = FileInputProps & FieldProps<FileInputProps['value']>
|
|
13
|
+
|
|
14
|
+
export const FileInput = (props: Props) => {
|
|
15
|
+
const handleChange = (
|
|
16
|
+
event: React.ChangeEvent<HTMLInputElement>,
|
|
17
|
+
value: FileUpload[] | undefined = [],
|
|
18
|
+
finalFormOnChange: FinalFormOnChangeType
|
|
19
|
+
) => {
|
|
20
|
+
if (!event.target || !event.target.files || !event.target.files.length) {
|
|
21
|
+
return null
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const newFiles = Array.from(event.target.files).map(file => ({
|
|
25
|
+
file,
|
|
26
|
+
uploading: false
|
|
27
|
+
}))
|
|
28
|
+
|
|
29
|
+
finalFormOnChange([...value, ...newFiles])
|
|
30
|
+
|
|
31
|
+
// reset input
|
|
32
|
+
event.target.value = ''
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const handleRemove = (
|
|
36
|
+
fileIndex: number,
|
|
37
|
+
value: FileUpload[] | undefined = [],
|
|
38
|
+
finalFormOnChange: FinalFormOnChangeType
|
|
39
|
+
) => {
|
|
40
|
+
const updatedFiles = value.filter((_, index) => index !== fileIndex)
|
|
41
|
+
|
|
42
|
+
finalFormOnChange(updatedFiles)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return (
|
|
46
|
+
<FieldWrapper<FileInputProps, FileUpload[] | undefined> {...props}>
|
|
47
|
+
{inputProps => (
|
|
48
|
+
<PicassoFileInput
|
|
49
|
+
{...inputProps}
|
|
50
|
+
onChange={(event: React.ChangeEvent<HTMLInputElement>) => {
|
|
51
|
+
handleChange(event, inputProps.value, inputProps.onChange)
|
|
52
|
+
}}
|
|
53
|
+
onRemove={(fileName: string, index: number) => {
|
|
54
|
+
handleRemove(index, inputProps.value, inputProps.onChange)
|
|
55
|
+
}}
|
|
56
|
+
/>
|
|
57
|
+
)}
|
|
58
|
+
</FieldWrapper>
|
|
59
|
+
)
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
FileInput.defaultProps = {}
|
|
63
|
+
|
|
64
|
+
FileInput.displayName = 'FileInput'
|
|
65
|
+
|
|
66
|
+
export default FileInput
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default } from './FileInput'
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
import React, { useMemo, ReactNode, useRef } from 'react'
|
|
2
|
+
import {
|
|
3
|
+
Form as FinalForm,
|
|
4
|
+
FormProps as FinalFormProps
|
|
5
|
+
} from 'react-final-form'
|
|
6
|
+
import { FormApi, SubmissionErrors, getIn, setIn, AnyObject } from 'final-form'
|
|
7
|
+
import { Form as PicassoForm, Container } from '@toptal/picasso'
|
|
8
|
+
import { useNotifications } from '@toptal/picasso/utils'
|
|
9
|
+
|
|
10
|
+
import Autocomplete from '../Autocomplete'
|
|
11
|
+
import Input from '../Input'
|
|
12
|
+
import Select from '../Select'
|
|
13
|
+
import Radio from '../Radio'
|
|
14
|
+
import ButtonRadio from '../ButtonRadio'
|
|
15
|
+
import RadioGroup from '../RadioGroup'
|
|
16
|
+
import Checkbox from '../Checkbox'
|
|
17
|
+
import ButtonCheckbox from '../ButtonCheckbox'
|
|
18
|
+
import CheckboxGroup from '../CheckboxGroup'
|
|
19
|
+
import NumberInput from '../NumberInput'
|
|
20
|
+
import FileInput from '../FileInput'
|
|
21
|
+
import DatePicker from '../DatePicker'
|
|
22
|
+
import TimePicker from '../TimePicker'
|
|
23
|
+
import TagSelector from '../TagSelector'
|
|
24
|
+
import SubmitButton from '../SubmitButton'
|
|
25
|
+
import Switch from '../Switch'
|
|
26
|
+
import Rating from '../Rating'
|
|
27
|
+
import { FormConfigContext } from '../FormConfig'
|
|
28
|
+
import { createScrollToErrorDecorator } from '../utils'
|
|
29
|
+
import {
|
|
30
|
+
FormContext,
|
|
31
|
+
Validators,
|
|
32
|
+
FormContextProps,
|
|
33
|
+
createFormContext
|
|
34
|
+
} from './FormContext'
|
|
35
|
+
|
|
36
|
+
export type Props<T = AnyObject> = FinalFormProps<T> & {
|
|
37
|
+
autoComplete?: HTMLFormElement['autocomplete']
|
|
38
|
+
successSubmitMessage?: ReactNode
|
|
39
|
+
failedSubmitMessage?: ReactNode
|
|
40
|
+
scrollOffsetTop?: number
|
|
41
|
+
'data-testid'?: string
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const getValidationErrors = (
|
|
45
|
+
validators: Validators,
|
|
46
|
+
formValues: any,
|
|
47
|
+
form: FormApi<any>
|
|
48
|
+
): SubmissionErrors | void => {
|
|
49
|
+
let errors: SubmissionErrors
|
|
50
|
+
|
|
51
|
+
Object.entries(validators).forEach(([key, validator]) => {
|
|
52
|
+
const fieldValue = getIn(formValues, key)
|
|
53
|
+
const fieldMetaState = form.getFieldState(key)
|
|
54
|
+
|
|
55
|
+
if (!validator) {
|
|
56
|
+
return
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const error = validator(fieldValue, formValues, fieldMetaState)
|
|
60
|
+
|
|
61
|
+
if (error) {
|
|
62
|
+
errors = setIn(errors || {}, key, error)
|
|
63
|
+
}
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
return errors
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export const Form = <T extends any = AnyObject>(props: Props<T>) => {
|
|
70
|
+
const {
|
|
71
|
+
children,
|
|
72
|
+
autoComplete,
|
|
73
|
+
onSubmit,
|
|
74
|
+
successSubmitMessage,
|
|
75
|
+
failedSubmitMessage,
|
|
76
|
+
decorators = [],
|
|
77
|
+
'data-testid': dataTestId,
|
|
78
|
+
...rest
|
|
79
|
+
} = props
|
|
80
|
+
const { showSuccess, showError } = useNotifications()
|
|
81
|
+
const scrollToErrorDecorator = useMemo(
|
|
82
|
+
() => createScrollToErrorDecorator(),
|
|
83
|
+
[]
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
const validationObject = useRef<FormContextProps>(createFormContext())
|
|
87
|
+
|
|
88
|
+
const showSuccessNotification = () => {
|
|
89
|
+
if (!successSubmitMessage) {
|
|
90
|
+
return
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
showSuccess(successSubmitMessage)
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const showErrorNotification = (errors: SubmissionErrors) => {
|
|
97
|
+
if (typeof errors === 'string') {
|
|
98
|
+
showError(errors, undefined, { persist: true })
|
|
99
|
+
|
|
100
|
+
return
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if (!failedSubmitMessage) {
|
|
104
|
+
return
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
showError(failedSubmitMessage, undefined, { persist: true })
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const handleSubmit = async (
|
|
111
|
+
values: T,
|
|
112
|
+
form: FormApi<T>,
|
|
113
|
+
callback?: (errors?: SubmissionErrors) => void
|
|
114
|
+
) => {
|
|
115
|
+
const validationErrors = getValidationErrors(
|
|
116
|
+
validationObject.current.getValidators(),
|
|
117
|
+
values,
|
|
118
|
+
form
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
if (validationErrors) {
|
|
122
|
+
return validationErrors
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const submissionErrors = await onSubmit(values, form, callback)
|
|
126
|
+
|
|
127
|
+
if (!submissionErrors) {
|
|
128
|
+
showSuccessNotification()
|
|
129
|
+
} else {
|
|
130
|
+
showErrorNotification(submissionErrors)
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
return submissionErrors
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return (
|
|
137
|
+
<FormContext.Provider value={validationObject}>
|
|
138
|
+
<FinalForm
|
|
139
|
+
render={({ handleSubmit }) => (
|
|
140
|
+
<Container>
|
|
141
|
+
<PicassoForm
|
|
142
|
+
autoComplete={autoComplete}
|
|
143
|
+
onSubmit={handleSubmit}
|
|
144
|
+
data-testid={dataTestId}
|
|
145
|
+
>
|
|
146
|
+
{children}
|
|
147
|
+
</PicassoForm>
|
|
148
|
+
</Container>
|
|
149
|
+
)}
|
|
150
|
+
onSubmit={handleSubmit}
|
|
151
|
+
decorators={[...decorators, scrollToErrorDecorator]}
|
|
152
|
+
{...rest}
|
|
153
|
+
/>
|
|
154
|
+
</FormContext.Provider>
|
|
155
|
+
)
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
Form.defaultProps = {}
|
|
159
|
+
|
|
160
|
+
Form.displayName = 'Form'
|
|
161
|
+
|
|
162
|
+
Form.Autocomplete = Autocomplete
|
|
163
|
+
Form.Input = Input
|
|
164
|
+
Form.Select = Select
|
|
165
|
+
Form.Radio = Radio
|
|
166
|
+
Form.ButtonRadio = ButtonRadio
|
|
167
|
+
Form.RadioGroup = RadioGroup
|
|
168
|
+
Form.Checkbox = Checkbox
|
|
169
|
+
Form.ButtonCheckbox = ButtonCheckbox
|
|
170
|
+
Form.CheckboxGroup = CheckboxGroup
|
|
171
|
+
Form.NumberInput = NumberInput
|
|
172
|
+
Form.FileInput = FileInput
|
|
173
|
+
Form.DatePicker = DatePicker
|
|
174
|
+
Form.TimePicker = TimePicker
|
|
175
|
+
Form.TagSelector = TagSelector
|
|
176
|
+
Form.SubmitButton = SubmitButton
|
|
177
|
+
Form.ConfigProvider = FormConfigContext.Provider
|
|
178
|
+
Form.Switch = Switch
|
|
179
|
+
Form.Rating = Rating
|
|
180
|
+
|
|
181
|
+
export default Form
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { createContext, useContext, MutableRefObject } from 'react'
|
|
2
|
+
import { FieldValidator } from 'final-form'
|
|
3
|
+
|
|
4
|
+
export type Validators = Record<string, FieldValidator<unknown>>
|
|
5
|
+
|
|
6
|
+
export type FormContextProps = {
|
|
7
|
+
getValidators: () => Validators
|
|
8
|
+
setValidators: (fieldName: string, validator: FieldValidator<unknown>) => void
|
|
9
|
+
clearValidators: (fieldName: string) => void
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export const createFormContext = (): FormContextProps => {
|
|
13
|
+
const validators: Validators = {}
|
|
14
|
+
|
|
15
|
+
return {
|
|
16
|
+
getValidators: () => validators,
|
|
17
|
+
setValidators: (fieldName, validator) => {
|
|
18
|
+
validators[fieldName] = validator
|
|
19
|
+
},
|
|
20
|
+
clearValidators: fieldName => {
|
|
21
|
+
delete validators[fieldName]
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export const FormContext = createContext<MutableRefObject<
|
|
27
|
+
FormContextProps
|
|
28
|
+
> | null>(null)
|
|
29
|
+
|
|
30
|
+
export const useFormContext = () => {
|
|
31
|
+
const context = useContext(FormContext)
|
|
32
|
+
|
|
33
|
+
if (!context) {
|
|
34
|
+
throw new Error('Form Field cannot be rendered outside Form component')
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return context.current
|
|
38
|
+
}
|
|
Binary file
|