@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,85 @@
|
|
|
1
|
+
import React, { useCallback } from 'react'
|
|
2
|
+
import { useField } from 'react-final-form'
|
|
3
|
+
import { Container } from '@toptal/picasso'
|
|
4
|
+
import { Form } from '@toptal/picasso-forms'
|
|
5
|
+
|
|
6
|
+
type FormType = {
|
|
7
|
+
hide: boolean
|
|
8
|
+
name: {
|
|
9
|
+
first: string
|
|
10
|
+
last: string
|
|
11
|
+
}
|
|
12
|
+
dob: string
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const FormContent = () => {
|
|
16
|
+
const {
|
|
17
|
+
input: { value: hide }
|
|
18
|
+
} = useField('hide')
|
|
19
|
+
|
|
20
|
+
return (
|
|
21
|
+
<>
|
|
22
|
+
<Form.Checkbox name='hide' label='Check to hide fields below' />
|
|
23
|
+
|
|
24
|
+
{!hide && (
|
|
25
|
+
<>
|
|
26
|
+
<Form.Input
|
|
27
|
+
enableReset
|
|
28
|
+
required
|
|
29
|
+
name='name.first'
|
|
30
|
+
label='Your first name'
|
|
31
|
+
placeholder='e.g. Bruce'
|
|
32
|
+
/>
|
|
33
|
+
<Form.Input
|
|
34
|
+
enableReset
|
|
35
|
+
required
|
|
36
|
+
name='name.last'
|
|
37
|
+
label='Your last name'
|
|
38
|
+
placeholder='e.g. Wayne'
|
|
39
|
+
/>
|
|
40
|
+
<Form.DatePicker required name='dob' label='DOB' />
|
|
41
|
+
</>
|
|
42
|
+
)}
|
|
43
|
+
</>
|
|
44
|
+
)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const Example = () => {
|
|
48
|
+
const handleSubmit = useCallback((values: FormType) => api.submit(values), [])
|
|
49
|
+
|
|
50
|
+
return (
|
|
51
|
+
<Form.ConfigProvider value={{ validateOnSubmit: true }}>
|
|
52
|
+
<Form<FormType>
|
|
53
|
+
onSubmit={handleSubmit}
|
|
54
|
+
successSubmitMessage='Success!'
|
|
55
|
+
failedSubmitMessage='Failure!'
|
|
56
|
+
>
|
|
57
|
+
<FormContent />
|
|
58
|
+
|
|
59
|
+
<Container top='small'>
|
|
60
|
+
<Form.SubmitButton>Submit</Form.SubmitButton>
|
|
61
|
+
</Container>
|
|
62
|
+
</Form>
|
|
63
|
+
</Form.ConfigProvider>
|
|
64
|
+
)
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// the emulation of the api call
|
|
68
|
+
const responseWithDelay = async (response: any) =>
|
|
69
|
+
new Promise(resolve => setTimeout(() => resolve(response), 2000))
|
|
70
|
+
|
|
71
|
+
const api = {
|
|
72
|
+
submit: async (values: FormType) => {
|
|
73
|
+
if (values.hide || values.name?.first.toLowerCase() === 'bruce') {
|
|
74
|
+
return responseWithDelay(undefined)
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return responseWithDelay({
|
|
78
|
+
name: {
|
|
79
|
+
first: 'Unknown first name'
|
|
80
|
+
}
|
|
81
|
+
})
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export default Example
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
import Form from '../Form'
|
|
2
|
+
import formFieldStory from '../../FieldWrapper/story'
|
|
3
|
+
import PicassoBook from '~/.storybook/components/PicassoBook'
|
|
4
|
+
|
|
5
|
+
const page = PicassoBook.section('Picasso Forms').createPage('Form', 'Form')
|
|
6
|
+
|
|
7
|
+
page
|
|
8
|
+
.createTabChapter('Props')
|
|
9
|
+
.addComponentDocs({
|
|
10
|
+
component: Form,
|
|
11
|
+
name: 'Form',
|
|
12
|
+
additionalDocs: {
|
|
13
|
+
autoComplete: {
|
|
14
|
+
name: 'autoComplete',
|
|
15
|
+
type: {
|
|
16
|
+
name: 'string',
|
|
17
|
+
enums: ['on', 'off']
|
|
18
|
+
},
|
|
19
|
+
description: `HTML Form autocomplete attribute.\n
|
|
20
|
+
The autocomplete attribute specifies whether a form should have autocomplete 'on' or 'off'.
|
|
21
|
+
When autocomplete is 'on', the browser automatically complete values based on values that the user has entered before.\n
|
|
22
|
+
Tip: It is possible to have autocomplete 'on' for the form, and 'off' for specific input fields, or vice versa.`
|
|
23
|
+
},
|
|
24
|
+
debug: {
|
|
25
|
+
name: 'debug',
|
|
26
|
+
type: {
|
|
27
|
+
name: 'function',
|
|
28
|
+
description:
|
|
29
|
+
'(state: FormState, fieldStates: { [string]: FieldState }) => void'
|
|
30
|
+
},
|
|
31
|
+
description:
|
|
32
|
+
'A callback for debugging that receives the form state and the states of all the fields'
|
|
33
|
+
},
|
|
34
|
+
decorators: {
|
|
35
|
+
name: 'decorators',
|
|
36
|
+
type: 'Decorator[]',
|
|
37
|
+
description: 'An array of decorators to apply to the form'
|
|
38
|
+
},
|
|
39
|
+
initialValues: {
|
|
40
|
+
name: 'initialValues',
|
|
41
|
+
type: 'FormValues | Object',
|
|
42
|
+
description: 'The initial values of the form'
|
|
43
|
+
},
|
|
44
|
+
initialValuesEqual: {
|
|
45
|
+
name: 'initialValuesEqual',
|
|
46
|
+
type: {
|
|
47
|
+
name: 'function',
|
|
48
|
+
description: '(Object | undefined, Object | undefined) => boolean'
|
|
49
|
+
},
|
|
50
|
+
description:
|
|
51
|
+
'A predicate to determine whether or not the initialValues prop has changed'
|
|
52
|
+
},
|
|
53
|
+
keepDirtyOnReinitialize: {
|
|
54
|
+
name: 'keepDirtyOnReinitialize',
|
|
55
|
+
type: 'boolean',
|
|
56
|
+
description:
|
|
57
|
+
'If true, only pristine values will be overwritten when initialize(newValues) is called'
|
|
58
|
+
},
|
|
59
|
+
mutators: {
|
|
60
|
+
name: 'mutators',
|
|
61
|
+
type: {
|
|
62
|
+
name: 'object',
|
|
63
|
+
description: '{ [string]: Mutator }'
|
|
64
|
+
},
|
|
65
|
+
description: 'Named mutator functions'
|
|
66
|
+
},
|
|
67
|
+
onSubmit: {
|
|
68
|
+
name: 'onSubmit',
|
|
69
|
+
type: {
|
|
70
|
+
name: 'function',
|
|
71
|
+
description:
|
|
72
|
+
'(values: FormValues, form: FormApi, callback: ?(errors: ?Object) => void) => ?Object | Promise<?Object> | void'
|
|
73
|
+
},
|
|
74
|
+
description: 'Function to call when the form is submitted',
|
|
75
|
+
required: true
|
|
76
|
+
},
|
|
77
|
+
subscription: {
|
|
78
|
+
name: 'subscription',
|
|
79
|
+
type: {
|
|
80
|
+
name: 'object',
|
|
81
|
+
description: '{ [string]: boolean }'
|
|
82
|
+
},
|
|
83
|
+
description:
|
|
84
|
+
'An object of the parts of FormState (final-form) to subscribe to'
|
|
85
|
+
},
|
|
86
|
+
validate: {
|
|
87
|
+
name: 'validate',
|
|
88
|
+
type: {
|
|
89
|
+
name: 'function',
|
|
90
|
+
description: '(values: FormValues) => Object | Promise<Object>'
|
|
91
|
+
},
|
|
92
|
+
description:
|
|
93
|
+
'A whole-record validation function that takes all the values of the form and returns any validation errors'
|
|
94
|
+
},
|
|
95
|
+
validateOnBlur: {
|
|
96
|
+
name: 'validateOnBlur',
|
|
97
|
+
type: 'boolean',
|
|
98
|
+
description:
|
|
99
|
+
'If true, validation will happen on blur. If false, validation will happen on change',
|
|
100
|
+
defaultValue: false
|
|
101
|
+
},
|
|
102
|
+
successSubmitMessage: {
|
|
103
|
+
name: 'successSubmitMessage',
|
|
104
|
+
type: 'ReactNode',
|
|
105
|
+
description:
|
|
106
|
+
'Message to display in a tooltip when form submitted successfully'
|
|
107
|
+
},
|
|
108
|
+
failedSubmitMessage: {
|
|
109
|
+
name: 'failedSubmitMessage',
|
|
110
|
+
type: 'ReactNode',
|
|
111
|
+
description:
|
|
112
|
+
'Message to display in a tooltip when form submission failed'
|
|
113
|
+
},
|
|
114
|
+
scrollOffsetTop: {
|
|
115
|
+
name: 'scrollOffsetTop',
|
|
116
|
+
type: 'number',
|
|
117
|
+
description:
|
|
118
|
+
'Offset from the viewport for inputs to focus on, defaults to the center of the window (deprecated, will not have any effect)'
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
})
|
|
122
|
+
.addComponentDocs(formFieldStory.componentDocs)
|
|
123
|
+
|
|
124
|
+
page
|
|
125
|
+
.createChapter()
|
|
126
|
+
.addTextSection(
|
|
127
|
+
`
|
|
128
|
+
Form is a wrapper for 'react-final-form' Form component. It also
|
|
129
|
+
provides inside all the necessary input components types.
|
|
130
|
+
`
|
|
131
|
+
)
|
|
132
|
+
.addExample(
|
|
133
|
+
'Form/story/Default.example.tsx',
|
|
134
|
+
{
|
|
135
|
+
title: 'Default',
|
|
136
|
+
description: `
|
|
137
|
+
A general look of the form includes the examples of all the input
|
|
138
|
+
types supported by picasso-forms.
|
|
139
|
+
`
|
|
140
|
+
},
|
|
141
|
+
'picasso-form'
|
|
142
|
+
)
|
|
143
|
+
.addExample(
|
|
144
|
+
'Form/story/CustomValidator.example.tsx',
|
|
145
|
+
{
|
|
146
|
+
title: 'Custom validator',
|
|
147
|
+
description: `
|
|
148
|
+
We have a 'required' validator included by default to each input type,
|
|
149
|
+
however, you may need custom validators for more complex types of fields.
|
|
150
|
+
`
|
|
151
|
+
},
|
|
152
|
+
'picasso-form'
|
|
153
|
+
) // picasso-skip-visuals
|
|
154
|
+
.addExample(
|
|
155
|
+
'Form/story/ParseInput.example.tsx',
|
|
156
|
+
{
|
|
157
|
+
title: 'Change form input value',
|
|
158
|
+
description: `
|
|
159
|
+
When you use picasso-forms your form input components are no longer
|
|
160
|
+
completely controlled and they are controlled by final-form, which
|
|
161
|
+
gives you the opportunity to rely on it with displaying errors,
|
|
162
|
+
validations, etc.
|
|
163
|
+
|
|
164
|
+
However, sometimes you may need to be able to modify the form input
|
|
165
|
+
value.
|
|
166
|
+
`
|
|
167
|
+
},
|
|
168
|
+
'picasso-form'
|
|
169
|
+
) // picasso-skip-visuals
|
|
170
|
+
.addExample(
|
|
171
|
+
'Form/story/BackendCommunication.example.tsx',
|
|
172
|
+
{
|
|
173
|
+
title: 'Backend communication',
|
|
174
|
+
description: `
|
|
175
|
+
The form usually need to send data to backend, so we need to have
|
|
176
|
+
backend communication and display the process of submission and
|
|
177
|
+
the results. The form-level results are represented by notifications.
|
|
178
|
+
`
|
|
179
|
+
},
|
|
180
|
+
'picasso-form'
|
|
181
|
+
) // picasso-skip-visuals
|
|
182
|
+
.addExample(
|
|
183
|
+
'Form/story/CustomFormLevelConfiguration.example.tsx',
|
|
184
|
+
'Form Level Configurations'
|
|
185
|
+
)
|
|
186
|
+
.addExample(
|
|
187
|
+
'Form/story/ValidateOnSubmit.example.tsx',
|
|
188
|
+
{
|
|
189
|
+
title: 'Validate only on submit',
|
|
190
|
+
description: `
|
|
191
|
+
All fields should not show any validation error messages until submission is made.
|
|
192
|
+
`
|
|
193
|
+
},
|
|
194
|
+
'picasso-form'
|
|
195
|
+
) // picasso-skip-visuals
|
|
196
|
+
.addExample('Form/story/FileInput.example.tsx', {
|
|
197
|
+
title: 'File input on a Form',
|
|
198
|
+
description: 'Showcase how to upload files on the form submission'
|
|
199
|
+
}) // picasso-skip-visuals
|
|
200
|
+
.addExample('Form/story/TitleCase.example.tsx', {
|
|
201
|
+
title: 'Title case',
|
|
202
|
+
description: "Display the field's label in title case."
|
|
203
|
+
}) // picasso-skip-visuals
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import { render } from '@toptal/picasso/test-utils'
|
|
3
|
+
import { OmitInternalProps } from '@toptal/picasso-shared'
|
|
4
|
+
import { Button } from '@toptal/picasso'
|
|
5
|
+
|
|
6
|
+
import Form, { Props } from './Form'
|
|
7
|
+
|
|
8
|
+
const renderForm = (props: OmitInternalProps<Props>) => {
|
|
9
|
+
const { onSubmit } = props
|
|
10
|
+
|
|
11
|
+
return render(
|
|
12
|
+
<Form onSubmit={onSubmit}>
|
|
13
|
+
<Form.Input name='test' placeholder='test input' />
|
|
14
|
+
<Button type='submit'>Submit</Button>
|
|
15
|
+
</Form>
|
|
16
|
+
)
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
describe('Form', () => {
|
|
20
|
+
it('renders', () => {
|
|
21
|
+
const { container } = renderForm({
|
|
22
|
+
onSubmit: () => {}
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
expect(container).toMatchSnapshot()
|
|
26
|
+
})
|
|
27
|
+
})
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { createContext, useContext } from 'react'
|
|
2
|
+
|
|
3
|
+
export type RequiredVariant = 'default' | 'asterisk'
|
|
4
|
+
|
|
5
|
+
export interface FormConfigProps {
|
|
6
|
+
validateOnSubmit?: boolean
|
|
7
|
+
requiredVariant?: RequiredVariant
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export const FormConfigContext = createContext<FormConfigProps>({})
|
|
11
|
+
|
|
12
|
+
export const useFormConfig = () => useContext(FormConfigContext)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './FormConfig'
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import { screen, render, fireEvent } from '@toptal/picasso/test-utils'
|
|
3
|
+
|
|
4
|
+
import Form from '../Form'
|
|
5
|
+
import { FormConfigProps } from './FormConfig'
|
|
6
|
+
|
|
7
|
+
const TEXT_INPUT_LABEL = 'Test text field'
|
|
8
|
+
|
|
9
|
+
const renderForm = (configProps: FormConfigProps) => {
|
|
10
|
+
return render(
|
|
11
|
+
<Form.ConfigProvider value={configProps}>
|
|
12
|
+
<Form onSubmit={() => {}}>
|
|
13
|
+
<Form.Input label={TEXT_INPUT_LABEL} required name='test' />
|
|
14
|
+
<Form.SubmitButton>Submit</Form.SubmitButton>
|
|
15
|
+
</Form>
|
|
16
|
+
</Form.ConfigProvider>
|
|
17
|
+
)
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
describe('Form.ConfigProvider', () => {
|
|
21
|
+
it('validate only on submit', async () => {
|
|
22
|
+
renderForm({ validateOnSubmit: true })
|
|
23
|
+
|
|
24
|
+
fireEvent.blur(screen.getByLabelText(TEXT_INPUT_LABEL))
|
|
25
|
+
|
|
26
|
+
expect(
|
|
27
|
+
screen.queryByText('Please complete this field.')
|
|
28
|
+
).not.toBeInTheDocument()
|
|
29
|
+
|
|
30
|
+
fireEvent.click(screen.getByRole('button', { name: 'Submit' }))
|
|
31
|
+
|
|
32
|
+
expect(
|
|
33
|
+
await screen.findByText('Please complete this field.')
|
|
34
|
+
).toBeInTheDocument()
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
it('validate normally on blur / change', async () => {
|
|
38
|
+
renderForm({ validateOnSubmit: false })
|
|
39
|
+
|
|
40
|
+
fireEvent.blur(screen.getByLabelText(TEXT_INPUT_LABEL))
|
|
41
|
+
|
|
42
|
+
expect(screen.getByText('Please complete this field.')).toBeInTheDocument()
|
|
43
|
+
})
|
|
44
|
+
})
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import { Input as PicassoInput, InputProps } from '@toptal/picasso'
|
|
3
|
+
|
|
4
|
+
import FieldWrapper, { FieldProps } from '../FieldWrapper'
|
|
5
|
+
import getInputName from './utils/get-input-name'
|
|
6
|
+
|
|
7
|
+
export type FormInputProps = Omit<InputProps, 'onResetClick'> & {
|
|
8
|
+
/** Callback invoked when reset button was clicked */
|
|
9
|
+
onResetClick?: (set: (value: string) => void) => void
|
|
10
|
+
}
|
|
11
|
+
export type Props = FormInputProps & FieldProps<InputProps['value']>
|
|
12
|
+
|
|
13
|
+
export const Input = React.forwardRef<HTMLInputElement, Props>((props, ref) => (
|
|
14
|
+
<FieldWrapper<FormInputProps> {...props}>
|
|
15
|
+
{({ name, ...inputProps }: InputProps) => (
|
|
16
|
+
// TODO: remove getInputName completely when Chrome fixes autocomplete issue
|
|
17
|
+
// Link to the issue: https://bugs.chromium.org/p/chromium/issues/detail?id=1255609
|
|
18
|
+
<PicassoInput name={getInputName(name)} {...inputProps} ref={ref} />
|
|
19
|
+
)}
|
|
20
|
+
</FieldWrapper>
|
|
21
|
+
))
|
|
22
|
+
|
|
23
|
+
Input.defaultProps = {}
|
|
24
|
+
|
|
25
|
+
Input.displayName = 'Input'
|
|
26
|
+
|
|
27
|
+
export default Input
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default } from './Input'
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import { render, fireEvent } from '@toptal/picasso/test-utils'
|
|
3
|
+
import { Button } from '@toptal/picasso'
|
|
4
|
+
|
|
5
|
+
import Form, { Props as FormProps } from '../Form/Form'
|
|
6
|
+
import { Props as InputProps } from './Input'
|
|
7
|
+
|
|
8
|
+
type TestFormProps = Pick<FormProps, 'onSubmit'> & Pick<InputProps, 'onFocus'>
|
|
9
|
+
|
|
10
|
+
const renderForm = (props: TestFormProps) => {
|
|
11
|
+
const { onFocus, onSubmit } = props
|
|
12
|
+
|
|
13
|
+
return render(
|
|
14
|
+
<Form onSubmit={onSubmit}>
|
|
15
|
+
<Form.Input onFocus={onFocus} name='test' placeholder='test input' />
|
|
16
|
+
<Button type='submit'>Submit</Button>
|
|
17
|
+
</Form>
|
|
18
|
+
)
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
describe('Input', () => {
|
|
22
|
+
it('fires the onFocus callback after focusing the input', () => {
|
|
23
|
+
const handleFocus = jest.fn()
|
|
24
|
+
|
|
25
|
+
const { getByPlaceholderText } = renderForm({
|
|
26
|
+
onSubmit: () => {},
|
|
27
|
+
onFocus: handleFocus
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
fireEvent.focus(getByPlaceholderText('test input'))
|
|
31
|
+
|
|
32
|
+
expect(handleFocus).toHaveBeenCalled()
|
|
33
|
+
})
|
|
34
|
+
})
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import getInputName from './get-input-name'
|
|
2
|
+
|
|
3
|
+
describe('#getInputName', () => {
|
|
4
|
+
it.each(['title', 'field', 'input'] as const)(
|
|
5
|
+
'adds postfix to "%s"',
|
|
6
|
+
name => {
|
|
7
|
+
expect(getInputName(name)).toBe(`${name}-picasso`)
|
|
8
|
+
}
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
it('returns all other names', () => {
|
|
12
|
+
expect(getInputName('name')).toBe('name')
|
|
13
|
+
expect(getInputName('email')).toBe('email')
|
|
14
|
+
expect(getInputName(undefined)).toBeUndefined()
|
|
15
|
+
})
|
|
16
|
+
})
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import {
|
|
3
|
+
NumberInput as PicassoNumberInput,
|
|
4
|
+
NumberInputProps
|
|
5
|
+
} from '@toptal/picasso'
|
|
6
|
+
import { FieldValidator } from 'final-form'
|
|
7
|
+
|
|
8
|
+
import { validators } from '../utils'
|
|
9
|
+
import FieldWrapper, { FieldProps } from '../FieldWrapper'
|
|
10
|
+
|
|
11
|
+
export type Props = NumberInputProps & FieldProps<NumberInputProps['value']>
|
|
12
|
+
|
|
13
|
+
const MIN = -2147483648
|
|
14
|
+
const MAX = 2147483647
|
|
15
|
+
|
|
16
|
+
const { composeValidators } = validators
|
|
17
|
+
|
|
18
|
+
export const NumberInput = (props: Props) => {
|
|
19
|
+
const { min = MIN, max = MAX, validate } = props
|
|
20
|
+
const validateNumberLimits: FieldValidator<NumberInputProps['value']> = value => {
|
|
21
|
+
if (Number(value) > max) {
|
|
22
|
+
return `Must be less than or equal to ${max}.`
|
|
23
|
+
}
|
|
24
|
+
if (Number(value) < min) {
|
|
25
|
+
return `Must be greater than or equal to ${min}.`
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return (
|
|
30
|
+
<FieldWrapper<NumberInputProps>
|
|
31
|
+
{...props}
|
|
32
|
+
validate={composeValidators([validateNumberLimits, validate])}
|
|
33
|
+
>
|
|
34
|
+
{(inputProps: NumberInputProps) => {
|
|
35
|
+
return <PicassoNumberInput {...inputProps} />
|
|
36
|
+
}}
|
|
37
|
+
</FieldWrapper>
|
|
38
|
+
)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
NumberInput.defaultProps = {}
|
|
42
|
+
|
|
43
|
+
NumberInput.displayName = 'NumberInput'
|
|
44
|
+
|
|
45
|
+
export default NumberInput
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default } from './NumberInput'
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import React, { useContext } from 'react'
|
|
2
|
+
import { Radio as PicassoRadio, RadioProps } from '@toptal/picasso'
|
|
3
|
+
import { Field } from 'react-final-form'
|
|
4
|
+
|
|
5
|
+
import { RadioGroupContext } from '../RadioGroup'
|
|
6
|
+
|
|
7
|
+
// Intersection with the type { name?: string } is needed here because of
|
|
8
|
+
// TS compiler issue https://github.com/microsoft/TypeScript/issues/34793
|
|
9
|
+
export type Props = RadioProps & {
|
|
10
|
+
name?: string
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const Radio = ({ name, ...rest }: Props) => {
|
|
14
|
+
const groupName = useContext(RadioGroupContext)
|
|
15
|
+
|
|
16
|
+
return (
|
|
17
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
18
|
+
<Field name={name || groupName!} type='radio' value={rest.value}>
|
|
19
|
+
{({ input }) => <PicassoRadio checked={input.checked} {...rest} />}
|
|
20
|
+
</Field>
|
|
21
|
+
)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export default Radio
|