@dhis2/ui-forms 10.16.1 → 10.16.3-alpha.1
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/package.json +12 -11
- package/src/CheckboxFieldFF/CheckboxFieldFF.e2e.stories.js +52 -0
- package/src/CheckboxFieldFF/CheckboxFieldFF.js +52 -0
- package/src/CheckboxFieldFF/CheckboxFieldFF.prod.stories.js +142 -0
- package/src/CheckboxFieldFF/features/can_toggle_a_boolean/index.js +19 -0
- package/src/CheckboxFieldFF/features/can_toggle_a_boolean.feature +11 -0
- package/src/CheckboxFieldFF/features/can_toggle_a_value/index.js +21 -0
- package/src/CheckboxFieldFF/features/can_toggle_a_value.feature +11 -0
- package/src/CheckboxFieldFF/features/common/index.js +5 -0
- package/src/CheckboxFieldFF/features/displays_error/index.js +18 -0
- package/src/CheckboxFieldFF/features/displays_error.feature +6 -0
- package/src/FieldGroupFF/FieldGroupFF.js +42 -0
- package/src/FieldGroupFF/FieldGroupFF.prod.stories.js +49 -0
- package/src/FileInputFieldFF/FileInputFieldFF.e2e.stories.js +272 -0
- package/src/FileInputFieldFF/FileInputFieldFF.js +100 -0
- package/src/FileInputFieldFF/FileInputFieldFF.prod.stories.js +95 -0
- package/src/FileInputFieldFF/features/accepts_file/index.js +40 -0
- package/src/FileInputFieldFF/features/accepts_file.feature +13 -0
- package/src/FileInputFieldFF/features/common/index.js +9 -0
- package/src/FileInputFieldFF/features/displays_error/index.js +18 -0
- package/src/FileInputFieldFF/features/displays_error.feature +8 -0
- package/src/InputFieldFF/InputFieldFF.e2e.stories.js +19 -0
- package/src/InputFieldFF/InputFieldFF.js +58 -0
- package/src/InputFieldFF/InputFieldFF.prod.stories.js +102 -0
- package/src/InputFieldFF/features/can_set_a_value/index.js +14 -0
- package/src/InputFieldFF/features/can_set_a_value.feature +6 -0
- package/src/InputFieldFF/features/displays_error/index.js +15 -0
- package/src/InputFieldFF/features/displays_error.feature +6 -0
- package/src/MultiSelectFieldFF/MultiSelectFieldFF.e2e.stories.js +27 -0
- package/src/MultiSelectFieldFF/MultiSelectFieldFF.js +72 -0
- package/src/MultiSelectFieldFF/MultiSelectFieldFF.prod.stories.js +79 -0
- package/src/MultiSelectFieldFF/features/can_set_a_value/index.js +38 -0
- package/src/MultiSelectFieldFF/features/can_set_a_value.feature +14 -0
- package/src/MultiSelectFieldFF/features/common/index.js +7 -0
- package/src/MultiSelectFieldFF/features/displays_error/index.js +10 -0
- package/src/MultiSelectFieldFF/features/displays_error.feature +6 -0
- package/src/RadioFieldFF/RadioFieldFF.e2e.stories.js +39 -0
- package/src/RadioFieldFF/RadioFieldFF.js +52 -0
- package/src/RadioFieldFF/RadioFieldFF.prod.stories.js +104 -0
- package/src/RadioFieldFF/features/can_set_a_value/index.js +24 -0
- package/src/RadioFieldFF/features/can_set_a_value.feature +7 -0
- package/src/RadioFieldFF/features/common/index.js +9 -0
- package/src/RadioFieldFF/features/displays_error/index.js +13 -0
- package/src/RadioFieldFF/features/displays_error.feature +6 -0
- package/src/SimpleSingleSelectFieldFF/SimpleSingleSelectFieldFF.js +172 -0
- package/src/SimpleSingleSelectFieldFF/SimpleSingleSelectFieldFF.prod.stories.js +79 -0
- package/src/SimpleSingleSelectFieldFF/SimpleSingleSelectFieldFF.test.js +82 -0
- package/src/SingleSelectFieldFF/SingleSelectFieldFF.e2e.stories.js +23 -0
- package/src/SingleSelectFieldFF/SingleSelectFieldFF.js +70 -0
- package/src/SingleSelectFieldFF/SingleSelectFieldFF.prod.stories.js +77 -0
- package/src/SingleSelectFieldFF/features/can_set_a_value/index.js +22 -0
- package/src/SingleSelectFieldFF/features/can_set_a_value.feature +7 -0
- package/src/SingleSelectFieldFF/features/common/index.js +6 -0
- package/src/SingleSelectFieldFF/features/displays_error/index.js +10 -0
- package/src/SingleSelectFieldFF/features/displays_error.feature +6 -0
- package/src/SwitchFieldFF/SwitchFieldFF.e2e.stories.js +52 -0
- package/src/SwitchFieldFF/SwitchFieldFF.js +52 -0
- package/src/SwitchFieldFF/SwitchFieldFF.prod.stories.js +148 -0
- package/src/SwitchFieldFF/features/can_toggle_a_boolean/index.js +19 -0
- package/src/SwitchFieldFF/features/can_toggle_a_boolean.feature +11 -0
- package/src/SwitchFieldFF/features/can_toggle_a_value/index.js +21 -0
- package/src/SwitchFieldFF/features/can_toggle_a_value.feature +11 -0
- package/src/SwitchFieldFF/features/common/index.js +5 -0
- package/src/SwitchFieldFF/features/displays_error/index.js +18 -0
- package/src/SwitchFieldFF/features/displays_error.feature +6 -0
- package/src/TextAreaFieldFF/TextAreaFieldFF.e2e.stories.js +23 -0
- package/src/TextAreaFieldFF/TextAreaFieldFF.js +57 -0
- package/src/TextAreaFieldFF/TextAreaFieldFF.prod.stories.js +111 -0
- package/src/TextAreaFieldFF/features/can_set_a_value/index.js +14 -0
- package/src/TextAreaFieldFF/features/can_set_a_value.feature +6 -0
- package/src/TextAreaFieldFF/features/displays_error/index.js +15 -0
- package/src/TextAreaFieldFF/features/displays_error.feature +6 -0
- package/src/__tests__/__snapshots__/index.test.js.snap +65 -0
- package/src/__tests__/index.test.js +37 -0
- package/src/formDecorator.js +80 -0
- package/src/index.js +28 -0
- package/src/locales/ar/translations.json +30 -0
- package/src/locales/ar_IQ/translations.json +30 -0
- package/src/locales/ckb/translations.json +30 -0
- package/src/locales/cs/translations.json +30 -0
- package/src/locales/da/translations.json +30 -0
- package/src/locales/en/translations.json +30 -0
- package/src/locales/es/translations.json +30 -0
- package/src/locales/es_419/translations.json +30 -0
- package/src/locales/fr/translations.json +30 -0
- package/src/locales/hi_IN/translations.json +30 -0
- package/src/locales/id/translations.json +30 -0
- package/src/locales/index.js +84 -0
- package/src/locales/km/translations.json +30 -0
- package/src/locales/ko_KR/translations.json +30 -0
- package/src/locales/lo/translations.json +30 -0
- package/src/locales/my/translations.json +30 -0
- package/src/locales/nb/translations.json +30 -0
- package/src/locales/nl/translations.json +30 -0
- package/src/locales/prs/translations.json +30 -0
- package/src/locales/ps/translations.json +30 -0
- package/src/locales/pt/translations.json +30 -0
- package/src/locales/pt_BR/translations.json +30 -0
- package/src/locales/ro/translations.json +30 -0
- package/src/locales/ru/translations.json +30 -0
- package/src/locales/si/translations.json +30 -0
- package/src/locales/sv/translations.json +30 -0
- package/src/locales/tet/translations.json +30 -0
- package/src/locales/tg/translations.json +30 -0
- package/src/locales/uk/translations.json +30 -0
- package/src/locales/ur/translations.json +30 -0
- package/src/locales/uz_Latn/translations.json +30 -0
- package/src/locales/uz_UZ_Cyrl/translations.json +30 -0
- package/src/locales/uz_UZ_Latn/translations.json +30 -0
- package/src/locales/vi/translations.json +30 -0
- package/src/locales/zh/translations.json +30 -0
- package/src/locales/zh_CN/translations.json +30 -0
- package/src/shared/helpers/createBlurHandler.js +9 -0
- package/src/shared/helpers/createChangeHandler.js +21 -0
- package/src/shared/helpers/createFocusHandler.js +9 -0
- package/src/shared/helpers/createSelectChangeHandler.js +6 -0
- package/src/shared/helpers/createToggleChangeHandler.js +9 -0
- package/src/shared/helpers/getValidationText.js +21 -0
- package/src/shared/helpers/hasError.js +3 -0
- package/src/shared/helpers/isLoading.js +4 -0
- package/src/shared/helpers/isValid.js +4 -0
- package/src/shared/helpers.js +9 -0
- package/src/shared/propTypes.js +48 -0
- package/src/transformers/arrayWithIdObjects.js +8 -0
- package/src/transformers/index.js +1 -0
- package/src/validators/__tests__/alphaNumeric.test.js +29 -0
- package/src/validators/__tests__/boolean.test.js +23 -0
- package/src/validators/__tests__/composeValidators.test.js +23 -0
- package/src/validators/__tests__/createCharacterLengthRange.test.js +59 -0
- package/src/validators/__tests__/createEqualTo.test.js +43 -0
- package/src/validators/__tests__/createMaxCharacterLength.test.js +24 -0
- package/src/validators/__tests__/createMaxNumber.test.js +21 -0
- package/src/validators/__tests__/createMinCharacterLength.test.js +24 -0
- package/src/validators/__tests__/createMinNumber.test.js +21 -0
- package/src/validators/__tests__/createNumberRange.test.js +68 -0
- package/src/validators/__tests__/createPattern.test.js +40 -0
- package/src/validators/__tests__/dhis2Password.test.js +51 -0
- package/src/validators/__tests__/dhis2Username.test.js +75 -0
- package/src/validators/__tests__/email.test.js +83 -0
- package/src/validators/__tests__/hasValue.test.js +21 -0
- package/src/validators/__tests__/integer.test.js +48 -0
- package/src/validators/__tests__/internationalPhoneNumber.test.js +49 -0
- package/src/validators/__tests__/number.test.js +39 -0
- package/src/validators/__tests__/string.test.js +29 -0
- package/src/validators/__tests__/url.test.js +106 -0
- package/src/validators/alphaNumeric.js +15 -0
- package/src/validators/boolean.js +11 -0
- package/src/validators/composeValidators.js +8 -0
- package/src/validators/createCharacterLengthRange.js +27 -0
- package/src/validators/createEqualTo.js +16 -0
- package/src/validators/createMaxCharacterLength.js +13 -0
- package/src/validators/createMaxNumber.js +13 -0
- package/src/validators/createMinCharacterLength.js +13 -0
- package/src/validators/createMinNumber.js +13 -0
- package/src/validators/createNumberRange.js +28 -0
- package/src/validators/createPattern.js +22 -0
- package/src/validators/dhis2Password.js +81 -0
- package/src/validators/dhis2Username.js +16 -0
- package/src/validators/email.js +38 -0
- package/src/validators/hasValue.js +8 -0
- package/src/validators/helpers/index.js +23 -0
- package/src/validators/index.js +20 -0
- package/src/validators/integer.js +20 -0
- package/src/validators/internationalPhoneNumber.js +56 -0
- package/src/validators/number.js +9 -0
- package/src/validators/string.js +9 -0
- package/src/validators/test-helpers/index.js +21 -0
- package/src/validators/url.js +15 -0
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import { Field } from 'react-final-form'
|
|
3
|
+
import { formDecorator } from '../formDecorator.js'
|
|
4
|
+
import { inputArgType, metaArgType } from '../shared/propTypes.js'
|
|
5
|
+
import { hasValue } from '../validators/index.js'
|
|
6
|
+
import { SwitchFieldFF } from './SwitchFieldFF.js'
|
|
7
|
+
|
|
8
|
+
const description = `
|
|
9
|
+
The \`SwitchFieldFF\` is a wrapper around a \`SwitchField\` that enables it to work with Final Form, the preferred library for form validation and utilities in DHIS 2 apps.
|
|
10
|
+
|
|
11
|
+
#### Final Form
|
|
12
|
+
|
|
13
|
+
See how to use Final Form at [Final Form - Getting Started](https://final-form.org/docs/react-final-form/getting-started).
|
|
14
|
+
|
|
15
|
+
Inside a Final Form \`<Form>\` component, these 'FF' UI components are intended to be used in the \`component\` prop of the [Final Form \`<Field>\` components](https://final-form.org/docs/react-final-form/api/Field) where they will receive some props from the Field, e.g. \`<Field component={SwitchFieldFF} />\`. See the code samples below for examples.
|
|
16
|
+
|
|
17
|
+
#### Props
|
|
18
|
+
|
|
19
|
+
The props shown in the table below are generally provided to the \`SwitchFieldFF\` wrapper by the Final Form \`Field\`.
|
|
20
|
+
|
|
21
|
+
Note that any props beyond the API of the \`Field\` component will be spread to the \`SwitchFieldFF\`, which passes any extra props to the underlying \`SwitchField\` using \`{...rest}\`.
|
|
22
|
+
|
|
23
|
+
Therefore, to add any props to the \`SwitchFieldFF\` or \`SwitchField\`, add those props to the parent Final Form \`Field\` component.
|
|
24
|
+
|
|
25
|
+
Also see \`Switch\` and \`SwitchField\` for notes about props and implementation.
|
|
26
|
+
|
|
27
|
+
\`\`\`js
|
|
28
|
+
import { SwitchFieldFF } from '@dhis2/ui'
|
|
29
|
+
\`\`\`
|
|
30
|
+
|
|
31
|
+
Press **Submit** to see the form values logged to the console.
|
|
32
|
+
`
|
|
33
|
+
|
|
34
|
+
export default {
|
|
35
|
+
title: 'Switch Field (Final Form)',
|
|
36
|
+
component: SwitchFieldFF,
|
|
37
|
+
decorators: [formDecorator],
|
|
38
|
+
parameters: { docs: { description: { component: description } } },
|
|
39
|
+
argTypes: {
|
|
40
|
+
input: { ...inputArgType },
|
|
41
|
+
meta: { ...metaArgType },
|
|
42
|
+
},
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export const Default = () => (
|
|
46
|
+
<Field
|
|
47
|
+
type="checkbox"
|
|
48
|
+
component={SwitchFieldFF}
|
|
49
|
+
name="agree"
|
|
50
|
+
label="Do you agree?"
|
|
51
|
+
/>
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
export const Required = () => (
|
|
55
|
+
<Field
|
|
56
|
+
type="checkbox"
|
|
57
|
+
name="agree"
|
|
58
|
+
component={SwitchFieldFF}
|
|
59
|
+
required
|
|
60
|
+
validate={hasValue}
|
|
61
|
+
label="Do you agree?"
|
|
62
|
+
/>
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
export const Disabled = () => (
|
|
66
|
+
<Field
|
|
67
|
+
type="checkbox"
|
|
68
|
+
name="agree"
|
|
69
|
+
component={SwitchFieldFF}
|
|
70
|
+
disabled
|
|
71
|
+
label="Do you agree?"
|
|
72
|
+
/>
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
export const HelpText = () => (
|
|
76
|
+
<Field
|
|
77
|
+
type="checkbox"
|
|
78
|
+
name="agree"
|
|
79
|
+
component={SwitchFieldFF}
|
|
80
|
+
label="Do you agree?"
|
|
81
|
+
helpText="Click to agree"
|
|
82
|
+
/>
|
|
83
|
+
)
|
|
84
|
+
HelpText.storyName = 'Help text'
|
|
85
|
+
|
|
86
|
+
export const Statuses = () => (
|
|
87
|
+
<>
|
|
88
|
+
<Field
|
|
89
|
+
type="checkbox"
|
|
90
|
+
name="valid"
|
|
91
|
+
component={SwitchFieldFF}
|
|
92
|
+
label="Valid"
|
|
93
|
+
valid
|
|
94
|
+
validationText="Validation text"
|
|
95
|
+
/>
|
|
96
|
+
<Field
|
|
97
|
+
type="checkbox"
|
|
98
|
+
name="warning"
|
|
99
|
+
component={SwitchFieldFF}
|
|
100
|
+
label="Warning"
|
|
101
|
+
warning
|
|
102
|
+
validationText="Validation text"
|
|
103
|
+
/>
|
|
104
|
+
<Field
|
|
105
|
+
type="checkbox"
|
|
106
|
+
name="error"
|
|
107
|
+
component={SwitchFieldFF}
|
|
108
|
+
label="Error"
|
|
109
|
+
error
|
|
110
|
+
validationText="Validation text"
|
|
111
|
+
/>
|
|
112
|
+
</>
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
export const ValueWhenChecked = () => (
|
|
116
|
+
<>
|
|
117
|
+
<Field
|
|
118
|
+
type="checkbox"
|
|
119
|
+
name="bool"
|
|
120
|
+
component={SwitchFieldFF}
|
|
121
|
+
label="I produce boolean form values"
|
|
122
|
+
helpText="Click submit and check the console"
|
|
123
|
+
/>
|
|
124
|
+
<Field
|
|
125
|
+
type="checkbox"
|
|
126
|
+
name="string"
|
|
127
|
+
component={SwitchFieldFF}
|
|
128
|
+
label="I produce string form values because the 'value' prop is set"
|
|
129
|
+
value="value_when_checked"
|
|
130
|
+
helpText="Click submit and check the console"
|
|
131
|
+
/>
|
|
132
|
+
<Field
|
|
133
|
+
type="checkbox"
|
|
134
|
+
name="string"
|
|
135
|
+
component={SwitchFieldFF}
|
|
136
|
+
label="I also produce string form values"
|
|
137
|
+
value="another_value_when_checked"
|
|
138
|
+
helpText="Click submit and check the console"
|
|
139
|
+
/>
|
|
140
|
+
</>
|
|
141
|
+
)
|
|
142
|
+
ValueWhenChecked.parameters = {
|
|
143
|
+
docs: {
|
|
144
|
+
description: {
|
|
145
|
+
story: 'See the details about using the `value` prop at the [Final Form docs](https://final-form.org/docs/react-final-form/types/FieldProps#value)',
|
|
146
|
+
},
|
|
147
|
+
},
|
|
148
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { Given, Then } from '@badeball/cypress-cucumber-preprocessor'
|
|
2
|
+
|
|
3
|
+
Given('an unchecked Switch without value is rendered', () => {
|
|
4
|
+
cy.visitStory('Testing:SwitchFieldFF', 'Unchecked')
|
|
5
|
+
cy.verifyFormValue('switch', undefined)
|
|
6
|
+
})
|
|
7
|
+
|
|
8
|
+
Then('the form value that corresponds to the switch will be true', () => {
|
|
9
|
+
cy.verifyFormValue('switch', true)
|
|
10
|
+
})
|
|
11
|
+
|
|
12
|
+
Given('a checked Switch without value is rendered', () => {
|
|
13
|
+
cy.visitStory('Testing:SwitchFieldFF', 'Checked')
|
|
14
|
+
cy.verifyFormValue('switch', true)
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
Then('the form value that corresponds to the switch will be false', () => {
|
|
18
|
+
cy.verifyFormValue('switch', false)
|
|
19
|
+
})
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
Feature: The Switch can toggle a value
|
|
2
|
+
|
|
3
|
+
Scenario: The user enables the Switch
|
|
4
|
+
Given an unchecked Switch without value is rendered
|
|
5
|
+
When the user clicks on the Switch
|
|
6
|
+
Then the form value that corresponds to the switch will be true
|
|
7
|
+
|
|
8
|
+
Scenario: The user disables the Switch
|
|
9
|
+
Given a checked Switch without value is rendered
|
|
10
|
+
When the user clicks on the Switch
|
|
11
|
+
Then the form value that corresponds to the switch will be false
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { Given, Then } from '@badeball/cypress-cucumber-preprocessor'
|
|
2
|
+
|
|
3
|
+
Given('an unchecked Switch with a value is rendered', () => {
|
|
4
|
+
cy.visitStory('Testing:SwitchFieldFF', 'Unchecked with value')
|
|
5
|
+
cy.verifyFormValue('switch', undefined)
|
|
6
|
+
})
|
|
7
|
+
|
|
8
|
+
Then('the form value that corresponds to the switch will be yes', () => {
|
|
9
|
+
cy.verifyFormArrayValue('switch', 'yes')
|
|
10
|
+
})
|
|
11
|
+
|
|
12
|
+
Given('a checked Switch with a value is rendered', () => {
|
|
13
|
+
cy.visitStory('Testing:SwitchFieldFF', 'Checked with value')
|
|
14
|
+
cy.verifyFormArrayValue('switch', 'yes')
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
Then('the form value that corresponds to the switch will be correct', () => {
|
|
18
|
+
cy.window().then((win) => {
|
|
19
|
+
expect(win.formValues.switch).to.deep.equal([])
|
|
20
|
+
})
|
|
21
|
+
})
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
Feature: The Switch can toggle a value
|
|
2
|
+
|
|
3
|
+
Scenario: The user enables the Switch
|
|
4
|
+
Given an unchecked Switch with a value is rendered
|
|
5
|
+
When the user clicks on the Switch
|
|
6
|
+
Then the form value that corresponds to the switch will be yes
|
|
7
|
+
|
|
8
|
+
Scenario: The user disables the Switch
|
|
9
|
+
Given a checked Switch with a value is rendered
|
|
10
|
+
When the user clicks on the Switch
|
|
11
|
+
Then the form value that corresponds to the switch will be correct
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { Given, When, Then } from '@badeball/cypress-cucumber-preprocessor'
|
|
2
|
+
import { hasValueMessage } from '../../../validators/hasValue.js'
|
|
3
|
+
|
|
4
|
+
Given('an unchecked Switch is rendered', () => {
|
|
5
|
+
cy.visitStory('Testing:SwitchFieldFF', 'Unchecked')
|
|
6
|
+
cy.verifyFormValue('switch', undefined)
|
|
7
|
+
})
|
|
8
|
+
|
|
9
|
+
When('the user submits the form', () => {
|
|
10
|
+
cy.get('button[type="submit"]').click()
|
|
11
|
+
})
|
|
12
|
+
|
|
13
|
+
Then('an error message is shown', () => {
|
|
14
|
+
cy.get('[data-test="dhis2-uiwidgets-switchfield-validation"]').should(
|
|
15
|
+
'contain',
|
|
16
|
+
hasValueMessage
|
|
17
|
+
)
|
|
18
|
+
})
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import { Field } from 'react-final-form'
|
|
3
|
+
import { formDecorator } from '../formDecorator.js'
|
|
4
|
+
import { hasValue } from '../validators/index.js'
|
|
5
|
+
import { TextAreaFieldFF } from './TextAreaFieldFF.js'
|
|
6
|
+
|
|
7
|
+
export default { title: 'TextArea', decorators: [formDecorator] }
|
|
8
|
+
export const Default = () => (
|
|
9
|
+
<Field
|
|
10
|
+
component={TextAreaFieldFF}
|
|
11
|
+
name="comment"
|
|
12
|
+
label="Do you have any comments?"
|
|
13
|
+
/>
|
|
14
|
+
)
|
|
15
|
+
export const Required = () => (
|
|
16
|
+
<Field
|
|
17
|
+
required
|
|
18
|
+
name="comment"
|
|
19
|
+
component={TextAreaFieldFF}
|
|
20
|
+
validate={hasValue}
|
|
21
|
+
label="Do you have any comments?"
|
|
22
|
+
/>
|
|
23
|
+
)
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { TextAreaField } from '@dhis2-ui/text-area'
|
|
2
|
+
import PropTypes from 'prop-types'
|
|
3
|
+
import React from 'react'
|
|
4
|
+
import {
|
|
5
|
+
createChangeHandler,
|
|
6
|
+
createFocusHandler,
|
|
7
|
+
createBlurHandler,
|
|
8
|
+
hasError,
|
|
9
|
+
isLoading,
|
|
10
|
+
isValid,
|
|
11
|
+
getValidationText,
|
|
12
|
+
} from '../shared/helpers.js'
|
|
13
|
+
import { metaPropType, inputPropType } from '../shared/propTypes.js'
|
|
14
|
+
|
|
15
|
+
export const TextAreaFieldFF = ({
|
|
16
|
+
input,
|
|
17
|
+
meta,
|
|
18
|
+
error,
|
|
19
|
+
showValidStatus,
|
|
20
|
+
valid,
|
|
21
|
+
validationText,
|
|
22
|
+
onBlur,
|
|
23
|
+
onFocus,
|
|
24
|
+
loading,
|
|
25
|
+
showLoadingStatus,
|
|
26
|
+
...rest
|
|
27
|
+
}) => (
|
|
28
|
+
<TextAreaField
|
|
29
|
+
{...rest}
|
|
30
|
+
name={input.name}
|
|
31
|
+
error={hasError(meta, error)}
|
|
32
|
+
valid={isValid(meta, valid, showValidStatus)}
|
|
33
|
+
loading={isLoading(meta, loading, showLoadingStatus)}
|
|
34
|
+
validationText={getValidationText(meta, validationText, error)}
|
|
35
|
+
onFocus={createFocusHandler(input, onFocus)}
|
|
36
|
+
onChange={createChangeHandler(input)}
|
|
37
|
+
onBlur={createBlurHandler(input, onBlur)}
|
|
38
|
+
value={input.value}
|
|
39
|
+
/>
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
TextAreaFieldFF.propTypes = {
|
|
43
|
+
/** `input` props received from Final Form `Field` */
|
|
44
|
+
input: inputPropType.isRequired,
|
|
45
|
+
/** `meta` props received from Final Form `Field` */
|
|
46
|
+
meta: metaPropType.isRequired,
|
|
47
|
+
|
|
48
|
+
error: PropTypes.bool,
|
|
49
|
+
loading: PropTypes.bool,
|
|
50
|
+
showLoadingStatus: PropTypes.bool,
|
|
51
|
+
showValidStatus: PropTypes.bool,
|
|
52
|
+
valid: PropTypes.bool,
|
|
53
|
+
validationText: PropTypes.string,
|
|
54
|
+
|
|
55
|
+
onBlur: PropTypes.func,
|
|
56
|
+
onFocus: PropTypes.func,
|
|
57
|
+
}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import { Field } from 'react-final-form'
|
|
3
|
+
import { formDecorator } from '../formDecorator.js'
|
|
4
|
+
import { inputArgType, metaArgType } from '../shared/propTypes.js'
|
|
5
|
+
import { hasValue } from '../validators/index.js'
|
|
6
|
+
import { TextAreaFieldFF } from './TextAreaFieldFF.js'
|
|
7
|
+
|
|
8
|
+
const description = `
|
|
9
|
+
The \`TextAreaFieldFF\` is a wrapper around a \`TextAreaField\` that enables it to work with Final Form, the preferred library for form validation and utilities in DHIS 2 apps.
|
|
10
|
+
|
|
11
|
+
#### Final Form
|
|
12
|
+
|
|
13
|
+
See how to use Final Form at [Final Form - Getting Started](https://final-form.org/docs/react-final-form/getting-started).
|
|
14
|
+
|
|
15
|
+
Inside a Final Form \`<Form>\` component, these 'FF' UI components are intended to be used in the \`component\` prop of the [Final Form \`<Field>\` components](https://final-form.org/docs/react-final-form/api/Field) where they will receive some props from the Field, e.g. \`<Field component={TextAreaFieldFF} />\`. See the code samples below for examples.
|
|
16
|
+
|
|
17
|
+
#### Props
|
|
18
|
+
|
|
19
|
+
The props shown in the table below are generally provided to the \`TextAreaFieldFF\` wrapper by the Final Form \`Field\`.
|
|
20
|
+
|
|
21
|
+
Note that any props beyond the API of the \`Field\` component will be spread to the \`TextAreaFieldFF\`, which passes any extra props to the underlying \`TextAreaField\` using \`{...rest}\`.
|
|
22
|
+
|
|
23
|
+
Therefore, to add any props to the \`TextAreaFieldFF\` or \`TextAreaField\`, add those props to the parent Final Form \`Field\` component.
|
|
24
|
+
|
|
25
|
+
Also see \`TextArea\` and \`TextAreaField\` for notes about props and implementation.
|
|
26
|
+
|
|
27
|
+
\`\`\`js
|
|
28
|
+
import { TextAreaFieldFF } from '@dhis2/ui'
|
|
29
|
+
\`\`\`
|
|
30
|
+
|
|
31
|
+
Press **Submit** to see the form values logged to the console.
|
|
32
|
+
`
|
|
33
|
+
|
|
34
|
+
export default {
|
|
35
|
+
title: 'Text Area Field (Final Form)',
|
|
36
|
+
component: TextAreaFieldFF,
|
|
37
|
+
decorators: [formDecorator],
|
|
38
|
+
parameters: { docs: { description: { component: description } } },
|
|
39
|
+
argTypes: {
|
|
40
|
+
input: { ...inputArgType },
|
|
41
|
+
meta: { ...metaArgType },
|
|
42
|
+
},
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export const Default = () => (
|
|
46
|
+
<Field component={TextAreaFieldFF} name="agree" label="Do you agree?" />
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
export const Autogrow = () => (
|
|
50
|
+
<Field
|
|
51
|
+
component={TextAreaFieldFF}
|
|
52
|
+
name="agree"
|
|
53
|
+
label="Do you agree?"
|
|
54
|
+
autoGrow
|
|
55
|
+
/>
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
export const Required = () => (
|
|
59
|
+
<Field
|
|
60
|
+
name="agree"
|
|
61
|
+
component={TextAreaFieldFF}
|
|
62
|
+
required
|
|
63
|
+
validate={hasValue}
|
|
64
|
+
label="Do you agree?"
|
|
65
|
+
/>
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
export const Disabled = () => (
|
|
69
|
+
<Field
|
|
70
|
+
name="agree"
|
|
71
|
+
component={TextAreaFieldFF}
|
|
72
|
+
disabled
|
|
73
|
+
label="Do you agree?"
|
|
74
|
+
/>
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
export const HelpText = () => (
|
|
78
|
+
<Field
|
|
79
|
+
name="agree"
|
|
80
|
+
component={TextAreaFieldFF}
|
|
81
|
+
label="Do you agree?"
|
|
82
|
+
helpText="Click to agree"
|
|
83
|
+
/>
|
|
84
|
+
)
|
|
85
|
+
HelpText.storyName = 'Help text'
|
|
86
|
+
|
|
87
|
+
export const Statuses = () => (
|
|
88
|
+
<>
|
|
89
|
+
<Field
|
|
90
|
+
name="valid"
|
|
91
|
+
component={TextAreaFieldFF}
|
|
92
|
+
label="Valid"
|
|
93
|
+
valid
|
|
94
|
+
validationText="Validation text"
|
|
95
|
+
/>
|
|
96
|
+
<Field
|
|
97
|
+
name="warning"
|
|
98
|
+
component={TextAreaFieldFF}
|
|
99
|
+
label="Warning"
|
|
100
|
+
warning
|
|
101
|
+
validationText="Validation text"
|
|
102
|
+
/>
|
|
103
|
+
<Field
|
|
104
|
+
name="error"
|
|
105
|
+
component={TextAreaFieldFF}
|
|
106
|
+
label="Error"
|
|
107
|
+
error
|
|
108
|
+
validationText="Validation text"
|
|
109
|
+
/>
|
|
110
|
+
</>
|
|
111
|
+
)
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { Given, When, Then } from '@badeball/cypress-cucumber-preprocessor'
|
|
2
|
+
|
|
3
|
+
Given('a TextArea with no text is rendered', () => {
|
|
4
|
+
cy.visitStory('TextArea', 'Default')
|
|
5
|
+
cy.verifyFormValue('comment', undefined)
|
|
6
|
+
})
|
|
7
|
+
|
|
8
|
+
When('the user types something in the TextArea', () => {
|
|
9
|
+
cy.get('textarea').type('something')
|
|
10
|
+
})
|
|
11
|
+
|
|
12
|
+
Then("the form state's value equals the written text", () => {
|
|
13
|
+
cy.verifyFormValue('comment', 'something')
|
|
14
|
+
})
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { Given, When, Then } from '@badeball/cypress-cucumber-preprocessor'
|
|
2
|
+
import { hasValueMessage } from '../../../validators/hasValue.js'
|
|
3
|
+
|
|
4
|
+
Given('an empty, required TextArea is rendered', () => {
|
|
5
|
+
cy.visitStory('TextArea', 'Required')
|
|
6
|
+
cy.verifyFormValue('agree', undefined)
|
|
7
|
+
})
|
|
8
|
+
|
|
9
|
+
When('the user submits the form', () => {
|
|
10
|
+
cy.get('button[type="submit"]').click()
|
|
11
|
+
})
|
|
12
|
+
|
|
13
|
+
Then('an error message is shown', () => {
|
|
14
|
+
cy.get('.error').should('contain', hasValueMessage)
|
|
15
|
+
})
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
|
2
|
+
|
|
3
|
+
exports[`final-form named export should re-export the expected exports 1`] = `
|
|
4
|
+
Array [
|
|
5
|
+
"ARRAY_ERROR",
|
|
6
|
+
"FORM_ERROR",
|
|
7
|
+
"configOptions",
|
|
8
|
+
"createForm",
|
|
9
|
+
"fieldSubscriptionItems",
|
|
10
|
+
"formSubscriptionItems",
|
|
11
|
+
"getIn",
|
|
12
|
+
"setIn",
|
|
13
|
+
"version",
|
|
14
|
+
"default",
|
|
15
|
+
]
|
|
16
|
+
`;
|
|
17
|
+
|
|
18
|
+
exports[`final-form named export should re-export the expected type for ARRAY_ERROR 1`] = `"string"`;
|
|
19
|
+
|
|
20
|
+
exports[`final-form named export should re-export the expected type for FORM_ERROR 1`] = `"string"`;
|
|
21
|
+
|
|
22
|
+
exports[`final-form named export should re-export the expected type for configOptions 1`] = `"object"`;
|
|
23
|
+
|
|
24
|
+
exports[`final-form named export should re-export the expected type for createForm 1`] = `"function"`;
|
|
25
|
+
|
|
26
|
+
exports[`final-form named export should re-export the expected type for default 1`] = `"object"`;
|
|
27
|
+
|
|
28
|
+
exports[`final-form named export should re-export the expected type for fieldSubscriptionItems 1`] = `"object"`;
|
|
29
|
+
|
|
30
|
+
exports[`final-form named export should re-export the expected type for formSubscriptionItems 1`] = `"object"`;
|
|
31
|
+
|
|
32
|
+
exports[`final-form named export should re-export the expected type for getIn 1`] = `"function"`;
|
|
33
|
+
|
|
34
|
+
exports[`final-form named export should re-export the expected type for setIn 1`] = `"function"`;
|
|
35
|
+
|
|
36
|
+
exports[`final-form named export should re-export the expected type for version 1`] = `"string"`;
|
|
37
|
+
|
|
38
|
+
exports[`react-final-form named export should re-export the expected exports 1`] = `
|
|
39
|
+
Array [
|
|
40
|
+
"Field",
|
|
41
|
+
"Form",
|
|
42
|
+
"FormSpy",
|
|
43
|
+
"useField",
|
|
44
|
+
"useForm",
|
|
45
|
+
"useFormState",
|
|
46
|
+
"version",
|
|
47
|
+
"withTypes",
|
|
48
|
+
]
|
|
49
|
+
`;
|
|
50
|
+
|
|
51
|
+
exports[`react-final-form named export should re-export the expected type for Field 1`] = `"object"`;
|
|
52
|
+
|
|
53
|
+
exports[`react-final-form named export should re-export the expected type for Form 1`] = `"function"`;
|
|
54
|
+
|
|
55
|
+
exports[`react-final-form named export should re-export the expected type for FormSpy 1`] = `"function"`;
|
|
56
|
+
|
|
57
|
+
exports[`react-final-form named export should re-export the expected type for useField 1`] = `"function"`;
|
|
58
|
+
|
|
59
|
+
exports[`react-final-form named export should re-export the expected type for useForm 1`] = `"function"`;
|
|
60
|
+
|
|
61
|
+
exports[`react-final-form named export should re-export the expected type for useFormState 1`] = `"function"`;
|
|
62
|
+
|
|
63
|
+
exports[`react-final-form named export should re-export the expected type for version 1`] = `"string"`;
|
|
64
|
+
|
|
65
|
+
exports[`react-final-form named export should re-export the expected type for withTypes 1`] = `"function"`;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/* eslint-disable import/namespace */
|
|
2
|
+
|
|
3
|
+
import { FinalForm, ReactFinalForm } from '../index.js'
|
|
4
|
+
|
|
5
|
+
describe('final-form named export', () => {
|
|
6
|
+
it('should re-export the expected exports', () => {
|
|
7
|
+
const exports = Object.keys(FinalForm)
|
|
8
|
+
|
|
9
|
+
expect(exports).toMatchSnapshot()
|
|
10
|
+
})
|
|
11
|
+
|
|
12
|
+
it.each(Object.keys(FinalForm))(
|
|
13
|
+
'should re-export the expected type for %s',
|
|
14
|
+
(exportName) => {
|
|
15
|
+
const type = typeof FinalForm[exportName]
|
|
16
|
+
|
|
17
|
+
expect(type).toMatchSnapshot()
|
|
18
|
+
}
|
|
19
|
+
)
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
describe('react-final-form named export', () => {
|
|
23
|
+
it('should re-export the expected exports', () => {
|
|
24
|
+
const exports = Object.keys(ReactFinalForm)
|
|
25
|
+
|
|
26
|
+
expect(exports).toMatchSnapshot()
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
it.each(Object.keys(ReactFinalForm))(
|
|
30
|
+
'should re-export the expected type for %s',
|
|
31
|
+
(exportName) => {
|
|
32
|
+
const type = typeof ReactFinalForm[exportName]
|
|
33
|
+
|
|
34
|
+
expect(type).toMatchSnapshot()
|
|
35
|
+
}
|
|
36
|
+
)
|
|
37
|
+
})
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { Button } from '@dhis2-ui/button'
|
|
2
|
+
import PropTypes from 'prop-types'
|
|
3
|
+
import React from 'react'
|
|
4
|
+
import { FormSpy, Form } from 'react-final-form'
|
|
5
|
+
|
|
6
|
+
const formProps = {
|
|
7
|
+
onSubmit: (values) => {
|
|
8
|
+
console.log(
|
|
9
|
+
'++++++++++++++++\n',
|
|
10
|
+
'Form was submitted with values:\n',
|
|
11
|
+
values,
|
|
12
|
+
'\n----------------'
|
|
13
|
+
)
|
|
14
|
+
},
|
|
15
|
+
mutators: {},
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
class FormWithSpyAndSubmit extends React.Component {
|
|
19
|
+
state = {
|
|
20
|
+
cypressProps: {},
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
componentDidMount() {
|
|
24
|
+
window.updateCypressProps = this.updateCypressProps
|
|
25
|
+
window.clearCypressProps = this.clearCypressProps
|
|
26
|
+
this.forceUpdate()
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
componentWillUnmount() {
|
|
30
|
+
delete window.updateCypressProps
|
|
31
|
+
delete window.clearCypressProps
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
updateCypressProps = (updateObj) => {
|
|
35
|
+
const cypressProps = {
|
|
36
|
+
...this.state.cypressProps,
|
|
37
|
+
...updateObj,
|
|
38
|
+
}
|
|
39
|
+
this.setState({ cypressProps })
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
clearCypressProps = () => {
|
|
43
|
+
this.setState({ cypressProps: {} })
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
render() {
|
|
47
|
+
return (
|
|
48
|
+
<Form {...formProps}>
|
|
49
|
+
{(formRenderProps) => (
|
|
50
|
+
<form onSubmit={formRenderProps.handleSubmit}>
|
|
51
|
+
{this.props.renderChildren({
|
|
52
|
+
formRenderProps,
|
|
53
|
+
cypressProps: this.state.cypressProps,
|
|
54
|
+
})}
|
|
55
|
+
|
|
56
|
+
<Button primary type="submit">
|
|
57
|
+
Submit
|
|
58
|
+
</Button>
|
|
59
|
+
|
|
60
|
+
{/* render after components to ensure capturing "initialValue"s */}
|
|
61
|
+
<FormSpy>
|
|
62
|
+
{({ values }) => {
|
|
63
|
+
window.formValues = values
|
|
64
|
+
return <span className="form-spy-internal" />
|
|
65
|
+
}}
|
|
66
|
+
</FormSpy>
|
|
67
|
+
</form>
|
|
68
|
+
)}
|
|
69
|
+
</Form>
|
|
70
|
+
)
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
FormWithSpyAndSubmit.propTypes = {
|
|
75
|
+
renderChildren: PropTypes.func.isRequired,
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export const formDecorator = (fn) => (
|
|
79
|
+
<FormWithSpyAndSubmit renderChildren={fn} />
|
|
80
|
+
)
|