@dhis2/ui-forms 10.16.2 → 10.16.3
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,102 @@
|
|
|
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 { InputFieldFF } from './InputFieldFF.js'
|
|
7
|
+
|
|
8
|
+
const description = `
|
|
9
|
+
The \`InputFieldFF\` is a wrapper around a \`InputField\` 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={InputFieldFF} />\`. See the code samples below for examples.
|
|
16
|
+
|
|
17
|
+
#### Props
|
|
18
|
+
|
|
19
|
+
The props shown in the table below are generally provided to the \`InputFieldFF\` wrapper by the Final Form \`Field\`.
|
|
20
|
+
|
|
21
|
+
Note that any props beyond the API of the \`Field\` component will be spread to the \`InputFieldFF\`, which passes any extra props to the underlying \`InputField\` using \`{...rest}\`.
|
|
22
|
+
|
|
23
|
+
Therefore, to add any props to the \`InputFieldFF\` or \`InputField\`, add those props to the parent Final Form \`Field\` component.
|
|
24
|
+
|
|
25
|
+
Also see \`InputField\` for notes about props and implementation.
|
|
26
|
+
|
|
27
|
+
\`\`\`js
|
|
28
|
+
import { InputFieldFF } from '@dhis2/ui'
|
|
29
|
+
\`\`\`
|
|
30
|
+
|
|
31
|
+
Press **Submit** to see the form values logged to the console.
|
|
32
|
+
`
|
|
33
|
+
|
|
34
|
+
export default {
|
|
35
|
+
title: 'Final Form - Input Field ',
|
|
36
|
+
component: InputFieldFF,
|
|
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={InputFieldFF} name="agree" label="Do you agree?" />
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
export const Required = () => (
|
|
50
|
+
<Field
|
|
51
|
+
name="agree"
|
|
52
|
+
component={InputFieldFF}
|
|
53
|
+
required
|
|
54
|
+
validate={hasValue}
|
|
55
|
+
label="Do you agree?"
|
|
56
|
+
/>
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
export const Disabled = () => (
|
|
60
|
+
<Field
|
|
61
|
+
name="agree"
|
|
62
|
+
component={InputFieldFF}
|
|
63
|
+
disabled
|
|
64
|
+
label="Do you agree?"
|
|
65
|
+
/>
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
export const HelpText = () => (
|
|
69
|
+
<Field
|
|
70
|
+
name="agree"
|
|
71
|
+
component={InputFieldFF}
|
|
72
|
+
label="Do you agree?"
|
|
73
|
+
helpText="Click to agree"
|
|
74
|
+
/>
|
|
75
|
+
)
|
|
76
|
+
HelpText.storyName = 'Help text'
|
|
77
|
+
|
|
78
|
+
export const Statuses = () => (
|
|
79
|
+
<>
|
|
80
|
+
<Field
|
|
81
|
+
name="valid"
|
|
82
|
+
component={InputFieldFF}
|
|
83
|
+
label="Valid"
|
|
84
|
+
valid
|
|
85
|
+
validationText="Validation text"
|
|
86
|
+
/>
|
|
87
|
+
<Field
|
|
88
|
+
name="warning"
|
|
89
|
+
component={InputFieldFF}
|
|
90
|
+
label="Warning"
|
|
91
|
+
warning
|
|
92
|
+
validationText="Validation text"
|
|
93
|
+
/>
|
|
94
|
+
<Field
|
|
95
|
+
name="error"
|
|
96
|
+
component={InputFieldFF}
|
|
97
|
+
label="Error"
|
|
98
|
+
error
|
|
99
|
+
validationText="Validation text"
|
|
100
|
+
/>
|
|
101
|
+
</>
|
|
102
|
+
)
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { Given, When, Then } from '@badeball/cypress-cucumber-preprocessor'
|
|
2
|
+
|
|
3
|
+
Given('a Input with no text is rendered', () => {
|
|
4
|
+
cy.visitStory('Testing:InputFieldFF', 'Default')
|
|
5
|
+
cy.verifyFormValue('agree', undefined)
|
|
6
|
+
})
|
|
7
|
+
|
|
8
|
+
When('the user types something in the Input', () => {
|
|
9
|
+
cy.get('input').type('something')
|
|
10
|
+
})
|
|
11
|
+
|
|
12
|
+
Then("the form state's value equals the written text", () => {
|
|
13
|
+
cy.verifyFormValue('agree', '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 Input is rendered', () => {
|
|
5
|
+
cy.visitStory('Testing:InputFieldFF', '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,27 @@
|
|
|
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 { MultiSelectFieldFF } from './MultiSelectFieldFF.js'
|
|
6
|
+
|
|
7
|
+
const defaultOptions = [
|
|
8
|
+
{ value: 'first', label: 'First' },
|
|
9
|
+
{ value: 'second', label: 'Second' },
|
|
10
|
+
]
|
|
11
|
+
|
|
12
|
+
export default {
|
|
13
|
+
title: 'MultiSelectFieldFF',
|
|
14
|
+
component: MultiSelectFieldFF,
|
|
15
|
+
decorators: [formDecorator],
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export const Required = (_, { cypressProps }) => (
|
|
19
|
+
<Field
|
|
20
|
+
required
|
|
21
|
+
name="multiSelect"
|
|
22
|
+
label="Multi select"
|
|
23
|
+
component={MultiSelectFieldFF}
|
|
24
|
+
validate={hasValue}
|
|
25
|
+
options={cypressProps.options || defaultOptions}
|
|
26
|
+
/>
|
|
27
|
+
)
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { MultiSelectOption, MultiSelectField } from '@dhis2-ui/select'
|
|
2
|
+
import PropTypes from 'prop-types'
|
|
3
|
+
import React from 'react'
|
|
4
|
+
import {
|
|
5
|
+
createSelectChangeHandler,
|
|
6
|
+
createFocusHandler,
|
|
7
|
+
createBlurHandler,
|
|
8
|
+
hasError,
|
|
9
|
+
isLoading,
|
|
10
|
+
isValid,
|
|
11
|
+
getValidationText,
|
|
12
|
+
} from '../shared/helpers.js'
|
|
13
|
+
import { inputPropType, metaPropType } from '../shared/propTypes.js'
|
|
14
|
+
|
|
15
|
+
export const MultiSelectFieldFF = ({
|
|
16
|
+
error,
|
|
17
|
+
input,
|
|
18
|
+
loading,
|
|
19
|
+
meta,
|
|
20
|
+
onBlur,
|
|
21
|
+
onFocus,
|
|
22
|
+
options = [],
|
|
23
|
+
showLoadingStatus,
|
|
24
|
+
showValidStatus,
|
|
25
|
+
valid,
|
|
26
|
+
validationText,
|
|
27
|
+
...rest
|
|
28
|
+
}) => {
|
|
29
|
+
return (
|
|
30
|
+
<MultiSelectField
|
|
31
|
+
{...rest}
|
|
32
|
+
name={input.name}
|
|
33
|
+
error={hasError(meta, error)}
|
|
34
|
+
valid={isValid(meta, valid, showValidStatus)}
|
|
35
|
+
loading={isLoading(meta, loading, showLoadingStatus)}
|
|
36
|
+
validationText={getValidationText(meta, validationText, error)}
|
|
37
|
+
onFocus={createFocusHandler(input, onFocus)}
|
|
38
|
+
onChange={createSelectChangeHandler(input)}
|
|
39
|
+
onBlur={createBlurHandler(input, onBlur)}
|
|
40
|
+
selected={
|
|
41
|
+
input.value || []
|
|
42
|
+
} /* input.value is an empty string initially, so we're providing an empty array if falsey */
|
|
43
|
+
>
|
|
44
|
+
{options.map((option) => (
|
|
45
|
+
<MultiSelectOption key={option.value} {...option} />
|
|
46
|
+
))}
|
|
47
|
+
</MultiSelectField>
|
|
48
|
+
)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
MultiSelectFieldFF.propTypes = {
|
|
52
|
+
/** `input` props provided by Final Form `Field` */
|
|
53
|
+
input: inputPropType.isRequired,
|
|
54
|
+
/** `meta` props provided by Final Form `Field` */
|
|
55
|
+
meta: metaPropType.isRequired,
|
|
56
|
+
|
|
57
|
+
error: PropTypes.bool,
|
|
58
|
+
loading: PropTypes.bool,
|
|
59
|
+
options: PropTypes.arrayOf(
|
|
60
|
+
PropTypes.shape({
|
|
61
|
+
label: PropTypes.string,
|
|
62
|
+
value: PropTypes.string,
|
|
63
|
+
})
|
|
64
|
+
),
|
|
65
|
+
showLoadingStatus: PropTypes.bool,
|
|
66
|
+
showValidStatus: PropTypes.bool,
|
|
67
|
+
valid: PropTypes.bool,
|
|
68
|
+
validationText: PropTypes.string,
|
|
69
|
+
|
|
70
|
+
onBlur: PropTypes.func,
|
|
71
|
+
onFocus: PropTypes.func,
|
|
72
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
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 { MultiSelectFieldFF } from './MultiSelectFieldFF.js'
|
|
6
|
+
|
|
7
|
+
const description = `
|
|
8
|
+
The \`MultiSelectFieldFF\` is a wrapper around a \`MultiSelectField\` that enables it to work with Final Form, the preferred library for form validation and utilities in DHIS 2 apps.
|
|
9
|
+
|
|
10
|
+
#### Final Form
|
|
11
|
+
|
|
12
|
+
See how to use Final Form at [Final Form - Getting Started](https://final-form.org/docs/react-final-form/getting-started).
|
|
13
|
+
|
|
14
|
+
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={MultiSelectFieldFF} />\`. See the code samples below for examples.
|
|
15
|
+
|
|
16
|
+
#### Props
|
|
17
|
+
|
|
18
|
+
The props shown in the table below are generally provided to the \`MultiSelectFieldFF\` wrapper by the Final Form \`Field\`.
|
|
19
|
+
|
|
20
|
+
Note that any props beyond the API of the \`Field\` component will be spread to the \`MultiSelectFieldFF\`, which passes any extra props to the underlying \`MultiSelectField\` using \`{...rest}\`.
|
|
21
|
+
|
|
22
|
+
Therefore, to add any props to the \`MultiSelectFieldFF\` or \`MultiSelectField\`, add those props to the parent Final Form \`Field\` component.
|
|
23
|
+
|
|
24
|
+
Also see \`MultiSelect\` and \`MultiSelectField\` for notes about props and implementation.
|
|
25
|
+
|
|
26
|
+
\`\`\`js
|
|
27
|
+
import { MultiSelectFieldFF } from '@dhis2/ui'
|
|
28
|
+
\`\`\`
|
|
29
|
+
|
|
30
|
+
Press **Submit** to see the form values logged to the console.
|
|
31
|
+
|
|
32
|
+
_**Note:** Dropdowns may not appear correctly on this page. See the affected demos in the 'Canvas' tab for propper dropdown placement._
|
|
33
|
+
`
|
|
34
|
+
|
|
35
|
+
const options = [
|
|
36
|
+
{ value: '1', label: 'one' },
|
|
37
|
+
{ value: '2', label: 'two' },
|
|
38
|
+
{ value: '3', label: 'three' },
|
|
39
|
+
{ value: '4', label: 'four' },
|
|
40
|
+
{ value: '5', label: 'five' },
|
|
41
|
+
{ value: '6', label: 'six' },
|
|
42
|
+
{ value: '7', label: 'seven' },
|
|
43
|
+
{ value: '8', label: 'eight' },
|
|
44
|
+
{ value: '9', label: 'nine' },
|
|
45
|
+
{ value: '10', label: 'ten' },
|
|
46
|
+
]
|
|
47
|
+
|
|
48
|
+
const initialValue = ['3', '4', '9', '10']
|
|
49
|
+
|
|
50
|
+
export default {
|
|
51
|
+
title: 'Multi Select Field (Final Form)',
|
|
52
|
+
component: MultiSelectFieldFF,
|
|
53
|
+
decorators: [formDecorator],
|
|
54
|
+
parameters: { docs: { description: { component: description } } },
|
|
55
|
+
argTypes: {
|
|
56
|
+
input: { ...inputArgType },
|
|
57
|
+
meta: { ...metaArgType },
|
|
58
|
+
},
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export const Default = () => (
|
|
62
|
+
<Field
|
|
63
|
+
component={MultiSelectFieldFF}
|
|
64
|
+
name="agree"
|
|
65
|
+
label="Do you agree?"
|
|
66
|
+
options={options}
|
|
67
|
+
/>
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
export const InitialValue = () => (
|
|
71
|
+
<Field
|
|
72
|
+
component={MultiSelectFieldFF}
|
|
73
|
+
name="agree"
|
|
74
|
+
label="Do you agree?"
|
|
75
|
+
options={options}
|
|
76
|
+
initialValue={initialValue}
|
|
77
|
+
/>
|
|
78
|
+
)
|
|
79
|
+
InitialValue.storyName = 'InitialValue'
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { Given, When, Then } from '@badeball/cypress-cucumber-preprocessor'
|
|
2
|
+
|
|
3
|
+
Given('the MultiSelect has two options', () => {
|
|
4
|
+
const options = [
|
|
5
|
+
{ value: 'value1', label: 'Label 1' },
|
|
6
|
+
{ value: 'value2', label: 'Label 2' },
|
|
7
|
+
]
|
|
8
|
+
|
|
9
|
+
cy.wrap(options).as('options')
|
|
10
|
+
cy.window().then((win) => {
|
|
11
|
+
win.updateCypressProps({ options })
|
|
12
|
+
})
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
When('the user selects the first option', () => {
|
|
16
|
+
cy.get('[data-test="dhis2-uicore-multiselect"]').click()
|
|
17
|
+
cy.get('[data-test="dhis2-uicore-checkbox"]').first().click()
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
When('the user selects the second option', () => {
|
|
21
|
+
cy.get('[data-test="dhis2-uicore-checkbox"]').last().click()
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
Then("the form state's value equals the first option's value", () => {
|
|
25
|
+
cy.getFormValue('multiSelect').then((selected) => {
|
|
26
|
+
expect(selected).to.have.lengthOf(1)
|
|
27
|
+
expect(selected).to.deep.equal(['value1'])
|
|
28
|
+
})
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
Then("the form state's value contains both options", () => {
|
|
32
|
+
cy.get('@options').then((options) => {
|
|
33
|
+
cy.getFormValue('multiSelect').then((selected) => {
|
|
34
|
+
expect(selected).to.have.lengthOf(options.length)
|
|
35
|
+
expect(selected).to.deep.equal(['value1', 'value2'])
|
|
36
|
+
})
|
|
37
|
+
})
|
|
38
|
+
})
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
Feature: The MultiSelect can set a value
|
|
2
|
+
|
|
3
|
+
Scenario: The user selects one option
|
|
4
|
+
Given a required MultiSelect with no selected value
|
|
5
|
+
And the MultiSelect has two options
|
|
6
|
+
When the user selects the first option
|
|
7
|
+
Then the form state's value equals the first option's value
|
|
8
|
+
|
|
9
|
+
Scenario: The user selects two options
|
|
10
|
+
Given a required MultiSelect with no selected value
|
|
11
|
+
And the MultiSelect has two options
|
|
12
|
+
When the user selects the first option
|
|
13
|
+
And the user selects the second option
|
|
14
|
+
Then the form state's value contains both options
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { When, Then } from '@badeball/cypress-cucumber-preprocessor'
|
|
2
|
+
import { hasValueMessage } from '../../../validators/hasValue.js'
|
|
3
|
+
|
|
4
|
+
When('the user submits the form', () => {
|
|
5
|
+
cy.get('button[type="submit"]').click()
|
|
6
|
+
})
|
|
7
|
+
|
|
8
|
+
Then('an error message is shown', () => {
|
|
9
|
+
cy.get('.error').should('contain', hasValueMessage)
|
|
10
|
+
})
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import { Field } from 'react-final-form'
|
|
3
|
+
import { FieldGroupFF } from '../FieldGroupFF/FieldGroupFF.js'
|
|
4
|
+
import { formDecorator } from '../formDecorator.js'
|
|
5
|
+
import { hasValue } from '../validators/index.js'
|
|
6
|
+
import { RadioFieldFF } from './RadioFieldFF.js'
|
|
7
|
+
|
|
8
|
+
export default { title: 'Testing:RadioFieldFF', decorators: [formDecorator] }
|
|
9
|
+
export const RequiredAndNoSelectedValue = () => (
|
|
10
|
+
<FieldGroupFF name="choice">
|
|
11
|
+
<Field
|
|
12
|
+
type="radio"
|
|
13
|
+
component={RadioFieldFF}
|
|
14
|
+
name="choice"
|
|
15
|
+
label="One"
|
|
16
|
+
value="one"
|
|
17
|
+
validate={hasValue}
|
|
18
|
+
required
|
|
19
|
+
/>
|
|
20
|
+
<Field
|
|
21
|
+
type="radio"
|
|
22
|
+
component={RadioFieldFF}
|
|
23
|
+
name="choice"
|
|
24
|
+
label="Two"
|
|
25
|
+
value="two"
|
|
26
|
+
validate={hasValue}
|
|
27
|
+
required
|
|
28
|
+
/>
|
|
29
|
+
<Field
|
|
30
|
+
type="radio"
|
|
31
|
+
component={RadioFieldFF}
|
|
32
|
+
name="choice"
|
|
33
|
+
label="Three"
|
|
34
|
+
value="three"
|
|
35
|
+
validate={hasValue}
|
|
36
|
+
required
|
|
37
|
+
/>
|
|
38
|
+
</FieldGroupFF>
|
|
39
|
+
)
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { Radio } from '@dhis2-ui/radio'
|
|
2
|
+
import PropTypes from 'prop-types'
|
|
3
|
+
import React from 'react'
|
|
4
|
+
import {
|
|
5
|
+
createToggleChangeHandler,
|
|
6
|
+
createFocusHandler,
|
|
7
|
+
createBlurHandler,
|
|
8
|
+
hasError,
|
|
9
|
+
isValid,
|
|
10
|
+
getValidationText,
|
|
11
|
+
} from '../shared/helpers.js'
|
|
12
|
+
import { metaPropType, inputPropType } from '../shared/propTypes.js'
|
|
13
|
+
|
|
14
|
+
export const RadioFieldFF = ({
|
|
15
|
+
error,
|
|
16
|
+
input,
|
|
17
|
+
meta,
|
|
18
|
+
showValidStatus,
|
|
19
|
+
valid,
|
|
20
|
+
validationText,
|
|
21
|
+
onBlur,
|
|
22
|
+
onFocus,
|
|
23
|
+
...rest
|
|
24
|
+
}) => (
|
|
25
|
+
<Radio
|
|
26
|
+
{...rest}
|
|
27
|
+
name={input.name}
|
|
28
|
+
checked={input.checked}
|
|
29
|
+
value={input.value}
|
|
30
|
+
error={hasError(meta, error)}
|
|
31
|
+
valid={isValid(meta, valid, showValidStatus)}
|
|
32
|
+
validationText={getValidationText(meta, validationText, error)}
|
|
33
|
+
onFocus={createFocusHandler(input, onFocus)}
|
|
34
|
+
onChange={createToggleChangeHandler(input)}
|
|
35
|
+
onBlur={createBlurHandler(input, onBlur)}
|
|
36
|
+
/>
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
RadioFieldFF.propTypes = {
|
|
40
|
+
/** `input` props received from Final Form `Field` */
|
|
41
|
+
input: inputPropType.isRequired,
|
|
42
|
+
/** `meta` props received from Final Form `Field` */
|
|
43
|
+
meta: metaPropType.isRequired,
|
|
44
|
+
|
|
45
|
+
error: PropTypes.bool,
|
|
46
|
+
showValidStatus: PropTypes.bool,
|
|
47
|
+
valid: PropTypes.bool,
|
|
48
|
+
validationText: PropTypes.string,
|
|
49
|
+
|
|
50
|
+
onBlur: PropTypes.func,
|
|
51
|
+
onFocus: PropTypes.func,
|
|
52
|
+
}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { FieldGroup } from '@dhis2-ui/field'
|
|
2
|
+
import React from 'react'
|
|
3
|
+
import { Field } from 'react-final-form'
|
|
4
|
+
import { formDecorator } from '../formDecorator.js'
|
|
5
|
+
import { inputArgType, metaArgType } from '../shared/propTypes.js'
|
|
6
|
+
import { RadioFieldFF } from './RadioFieldFF.js'
|
|
7
|
+
|
|
8
|
+
const description = `
|
|
9
|
+
The \`RadioFieldFF\` is a wrapper around a \`Radio\` 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={RadioFieldFF} />\`. See the code samples below for examples.
|
|
16
|
+
|
|
17
|
+
#### Props
|
|
18
|
+
|
|
19
|
+
The props shown in the table below are generally provided to the \`RadioFieldFF\` wrapper by the Final Form \`Field\`.
|
|
20
|
+
|
|
21
|
+
Note that any props beyond the API of the \`Field\` component will be spread to the \`RadioFieldFF\`, which passes any extra props to the underlying \`Radio\` using \`{...rest}\`.
|
|
22
|
+
|
|
23
|
+
Therefore, to add any props to the \`RadioFieldFF\` or \`Radio\`, add those props to the parent Final Form \`Field\` component.
|
|
24
|
+
|
|
25
|
+
Also see \`Radio\` for notes about props and implementation.
|
|
26
|
+
|
|
27
|
+
\`\`\`js
|
|
28
|
+
import { RadioFieldFF } from '@dhis2/ui'
|
|
29
|
+
\`\`\`
|
|
30
|
+
|
|
31
|
+
Press **Submit** to see the form values logged to the console.
|
|
32
|
+
`
|
|
33
|
+
|
|
34
|
+
export default {
|
|
35
|
+
title: 'Radio Field (Final Form)',
|
|
36
|
+
component: RadioFieldFF,
|
|
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
|
+
<FieldGroup>
|
|
47
|
+
<Field
|
|
48
|
+
type="radio"
|
|
49
|
+
component={RadioFieldFF}
|
|
50
|
+
name="choice"
|
|
51
|
+
label="One"
|
|
52
|
+
value="one"
|
|
53
|
+
/>
|
|
54
|
+
<Field
|
|
55
|
+
type="radio"
|
|
56
|
+
component={RadioFieldFF}
|
|
57
|
+
name="choice"
|
|
58
|
+
label="Two"
|
|
59
|
+
value="two"
|
|
60
|
+
/>
|
|
61
|
+
<Field
|
|
62
|
+
type="radio"
|
|
63
|
+
component={RadioFieldFF}
|
|
64
|
+
name="choice"
|
|
65
|
+
label="Three"
|
|
66
|
+
value="three"
|
|
67
|
+
/>
|
|
68
|
+
</FieldGroup>
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
export const Statuses = () => (
|
|
72
|
+
<>
|
|
73
|
+
<FieldGroup label="Valid">
|
|
74
|
+
<Field
|
|
75
|
+
type="radio"
|
|
76
|
+
name="valid"
|
|
77
|
+
component={RadioFieldFF}
|
|
78
|
+
label="Valid"
|
|
79
|
+
value="valid"
|
|
80
|
+
valid
|
|
81
|
+
/>
|
|
82
|
+
</FieldGroup>
|
|
83
|
+
<FieldGroup label="Warning">
|
|
84
|
+
<Field
|
|
85
|
+
type="radio"
|
|
86
|
+
name="warning"
|
|
87
|
+
component={RadioFieldFF}
|
|
88
|
+
label="Warning"
|
|
89
|
+
value="warning"
|
|
90
|
+
warning
|
|
91
|
+
/>
|
|
92
|
+
</FieldGroup>
|
|
93
|
+
<FieldGroup label="Error">
|
|
94
|
+
<Field
|
|
95
|
+
type="radio"
|
|
96
|
+
name="error"
|
|
97
|
+
component={RadioFieldFF}
|
|
98
|
+
label="Error"
|
|
99
|
+
value="error"
|
|
100
|
+
error
|
|
101
|
+
/>
|
|
102
|
+
</FieldGroup>
|
|
103
|
+
</>
|
|
104
|
+
)
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { Given, When, Then } from '@badeball/cypress-cucumber-preprocessor'
|
|
2
|
+
|
|
3
|
+
Given('there are three options', () => {
|
|
4
|
+
const options = [
|
|
5
|
+
{ value: 'one', label: 'One' },
|
|
6
|
+
{ value: 'two', label: 'Two' },
|
|
7
|
+
{ value: 'three', label: 'Three' },
|
|
8
|
+
]
|
|
9
|
+
|
|
10
|
+
cy.wrap(options).as('options')
|
|
11
|
+
cy.window().then((win) => {
|
|
12
|
+
win.updateCypressProps({ options })
|
|
13
|
+
})
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
When('the user selects the last option', () => {
|
|
17
|
+
cy.get('label:last').click()
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
Then("the form state's value equals the last option's value", () => {
|
|
21
|
+
cy.get('@options').then((options) => {
|
|
22
|
+
cy.verifyFormValue('choice', options[2].value)
|
|
23
|
+
})
|
|
24
|
+
})
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
Feature: The RadioFieldFF can set a value
|
|
2
|
+
|
|
3
|
+
Scenario: The user clicks the first option
|
|
4
|
+
Given a FieldGroupFF with required RadioFieldFFs and no selected value
|
|
5
|
+
And there are three options
|
|
6
|
+
When the user selects the last option
|
|
7
|
+
Then the form state's value equals the last option's value
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { Given } from '@badeball/cypress-cucumber-preprocessor'
|
|
2
|
+
|
|
3
|
+
Given(
|
|
4
|
+
'a FieldGroupFF with required RadioFieldFFs and no selected value',
|
|
5
|
+
() => {
|
|
6
|
+
cy.visitStory('Testing:RadioFieldFF', 'Required and no selected value')
|
|
7
|
+
cy.verifyFormValue('choice', undefined)
|
|
8
|
+
}
|
|
9
|
+
)
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { When, Then } from '@badeball/cypress-cucumber-preprocessor'
|
|
2
|
+
import { hasValueMessage } from '../../../validators/hasValue.js'
|
|
3
|
+
|
|
4
|
+
When('the user submits the form', () => {
|
|
5
|
+
cy.get('button[type="submit"]').click()
|
|
6
|
+
})
|
|
7
|
+
|
|
8
|
+
Then('an error message is shown', () => {
|
|
9
|
+
cy.get('[data-test="dhis2-uicore-field-validation"]').should(
|
|
10
|
+
'contain',
|
|
11
|
+
hasValueMessage
|
|
12
|
+
)
|
|
13
|
+
})
|