@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.
Files changed (168) hide show
  1. package/package.json +12 -11
  2. package/src/CheckboxFieldFF/CheckboxFieldFF.e2e.stories.js +52 -0
  3. package/src/CheckboxFieldFF/CheckboxFieldFF.js +52 -0
  4. package/src/CheckboxFieldFF/CheckboxFieldFF.prod.stories.js +142 -0
  5. package/src/CheckboxFieldFF/features/can_toggle_a_boolean/index.js +19 -0
  6. package/src/CheckboxFieldFF/features/can_toggle_a_boolean.feature +11 -0
  7. package/src/CheckboxFieldFF/features/can_toggle_a_value/index.js +21 -0
  8. package/src/CheckboxFieldFF/features/can_toggle_a_value.feature +11 -0
  9. package/src/CheckboxFieldFF/features/common/index.js +5 -0
  10. package/src/CheckboxFieldFF/features/displays_error/index.js +18 -0
  11. package/src/CheckboxFieldFF/features/displays_error.feature +6 -0
  12. package/src/FieldGroupFF/FieldGroupFF.js +42 -0
  13. package/src/FieldGroupFF/FieldGroupFF.prod.stories.js +49 -0
  14. package/src/FileInputFieldFF/FileInputFieldFF.e2e.stories.js +272 -0
  15. package/src/FileInputFieldFF/FileInputFieldFF.js +100 -0
  16. package/src/FileInputFieldFF/FileInputFieldFF.prod.stories.js +95 -0
  17. package/src/FileInputFieldFF/features/accepts_file/index.js +40 -0
  18. package/src/FileInputFieldFF/features/accepts_file.feature +13 -0
  19. package/src/FileInputFieldFF/features/common/index.js +9 -0
  20. package/src/FileInputFieldFF/features/displays_error/index.js +18 -0
  21. package/src/FileInputFieldFF/features/displays_error.feature +8 -0
  22. package/src/InputFieldFF/InputFieldFF.e2e.stories.js +19 -0
  23. package/src/InputFieldFF/InputFieldFF.js +58 -0
  24. package/src/InputFieldFF/InputFieldFF.prod.stories.js +102 -0
  25. package/src/InputFieldFF/features/can_set_a_value/index.js +14 -0
  26. package/src/InputFieldFF/features/can_set_a_value.feature +6 -0
  27. package/src/InputFieldFF/features/displays_error/index.js +15 -0
  28. package/src/InputFieldFF/features/displays_error.feature +6 -0
  29. package/src/MultiSelectFieldFF/MultiSelectFieldFF.e2e.stories.js +27 -0
  30. package/src/MultiSelectFieldFF/MultiSelectFieldFF.js +72 -0
  31. package/src/MultiSelectFieldFF/MultiSelectFieldFF.prod.stories.js +79 -0
  32. package/src/MultiSelectFieldFF/features/can_set_a_value/index.js +38 -0
  33. package/src/MultiSelectFieldFF/features/can_set_a_value.feature +14 -0
  34. package/src/MultiSelectFieldFF/features/common/index.js +7 -0
  35. package/src/MultiSelectFieldFF/features/displays_error/index.js +10 -0
  36. package/src/MultiSelectFieldFF/features/displays_error.feature +6 -0
  37. package/src/RadioFieldFF/RadioFieldFF.e2e.stories.js +39 -0
  38. package/src/RadioFieldFF/RadioFieldFF.js +52 -0
  39. package/src/RadioFieldFF/RadioFieldFF.prod.stories.js +104 -0
  40. package/src/RadioFieldFF/features/can_set_a_value/index.js +24 -0
  41. package/src/RadioFieldFF/features/can_set_a_value.feature +7 -0
  42. package/src/RadioFieldFF/features/common/index.js +9 -0
  43. package/src/RadioFieldFF/features/displays_error/index.js +13 -0
  44. package/src/RadioFieldFF/features/displays_error.feature +6 -0
  45. package/src/SimpleSingleSelectFieldFF/SimpleSingleSelectFieldFF.js +172 -0
  46. package/src/SimpleSingleSelectFieldFF/SimpleSingleSelectFieldFF.prod.stories.js +79 -0
  47. package/src/SimpleSingleSelectFieldFF/SimpleSingleSelectFieldFF.test.js +82 -0
  48. package/src/SingleSelectFieldFF/SingleSelectFieldFF.e2e.stories.js +23 -0
  49. package/src/SingleSelectFieldFF/SingleSelectFieldFF.js +70 -0
  50. package/src/SingleSelectFieldFF/SingleSelectFieldFF.prod.stories.js +77 -0
  51. package/src/SingleSelectFieldFF/features/can_set_a_value/index.js +22 -0
  52. package/src/SingleSelectFieldFF/features/can_set_a_value.feature +7 -0
  53. package/src/SingleSelectFieldFF/features/common/index.js +6 -0
  54. package/src/SingleSelectFieldFF/features/displays_error/index.js +10 -0
  55. package/src/SingleSelectFieldFF/features/displays_error.feature +6 -0
  56. package/src/SwitchFieldFF/SwitchFieldFF.e2e.stories.js +52 -0
  57. package/src/SwitchFieldFF/SwitchFieldFF.js +52 -0
  58. package/src/SwitchFieldFF/SwitchFieldFF.prod.stories.js +148 -0
  59. package/src/SwitchFieldFF/features/can_toggle_a_boolean/index.js +19 -0
  60. package/src/SwitchFieldFF/features/can_toggle_a_boolean.feature +11 -0
  61. package/src/SwitchFieldFF/features/can_toggle_a_value/index.js +21 -0
  62. package/src/SwitchFieldFF/features/can_toggle_a_value.feature +11 -0
  63. package/src/SwitchFieldFF/features/common/index.js +5 -0
  64. package/src/SwitchFieldFF/features/displays_error/index.js +18 -0
  65. package/src/SwitchFieldFF/features/displays_error.feature +6 -0
  66. package/src/TextAreaFieldFF/TextAreaFieldFF.e2e.stories.js +23 -0
  67. package/src/TextAreaFieldFF/TextAreaFieldFF.js +57 -0
  68. package/src/TextAreaFieldFF/TextAreaFieldFF.prod.stories.js +111 -0
  69. package/src/TextAreaFieldFF/features/can_set_a_value/index.js +14 -0
  70. package/src/TextAreaFieldFF/features/can_set_a_value.feature +6 -0
  71. package/src/TextAreaFieldFF/features/displays_error/index.js +15 -0
  72. package/src/TextAreaFieldFF/features/displays_error.feature +6 -0
  73. package/src/__tests__/__snapshots__/index.test.js.snap +65 -0
  74. package/src/__tests__/index.test.js +37 -0
  75. package/src/formDecorator.js +80 -0
  76. package/src/index.js +28 -0
  77. package/src/locales/ar/translations.json +30 -0
  78. package/src/locales/ar_IQ/translations.json +30 -0
  79. package/src/locales/ckb/translations.json +30 -0
  80. package/src/locales/cs/translations.json +30 -0
  81. package/src/locales/da/translations.json +30 -0
  82. package/src/locales/en/translations.json +30 -0
  83. package/src/locales/es/translations.json +30 -0
  84. package/src/locales/es_419/translations.json +30 -0
  85. package/src/locales/fr/translations.json +30 -0
  86. package/src/locales/hi_IN/translations.json +30 -0
  87. package/src/locales/id/translations.json +30 -0
  88. package/src/locales/index.js +84 -0
  89. package/src/locales/km/translations.json +30 -0
  90. package/src/locales/ko_KR/translations.json +30 -0
  91. package/src/locales/lo/translations.json +30 -0
  92. package/src/locales/my/translations.json +30 -0
  93. package/src/locales/nb/translations.json +30 -0
  94. package/src/locales/nl/translations.json +30 -0
  95. package/src/locales/prs/translations.json +30 -0
  96. package/src/locales/ps/translations.json +30 -0
  97. package/src/locales/pt/translations.json +30 -0
  98. package/src/locales/pt_BR/translations.json +30 -0
  99. package/src/locales/ro/translations.json +30 -0
  100. package/src/locales/ru/translations.json +30 -0
  101. package/src/locales/si/translations.json +30 -0
  102. package/src/locales/sv/translations.json +30 -0
  103. package/src/locales/tet/translations.json +30 -0
  104. package/src/locales/tg/translations.json +30 -0
  105. package/src/locales/uk/translations.json +30 -0
  106. package/src/locales/ur/translations.json +30 -0
  107. package/src/locales/uz_Latn/translations.json +30 -0
  108. package/src/locales/uz_UZ_Cyrl/translations.json +30 -0
  109. package/src/locales/uz_UZ_Latn/translations.json +30 -0
  110. package/src/locales/vi/translations.json +30 -0
  111. package/src/locales/zh/translations.json +30 -0
  112. package/src/locales/zh_CN/translations.json +30 -0
  113. package/src/shared/helpers/createBlurHandler.js +9 -0
  114. package/src/shared/helpers/createChangeHandler.js +21 -0
  115. package/src/shared/helpers/createFocusHandler.js +9 -0
  116. package/src/shared/helpers/createSelectChangeHandler.js +6 -0
  117. package/src/shared/helpers/createToggleChangeHandler.js +9 -0
  118. package/src/shared/helpers/getValidationText.js +21 -0
  119. package/src/shared/helpers/hasError.js +3 -0
  120. package/src/shared/helpers/isLoading.js +4 -0
  121. package/src/shared/helpers/isValid.js +4 -0
  122. package/src/shared/helpers.js +9 -0
  123. package/src/shared/propTypes.js +48 -0
  124. package/src/transformers/arrayWithIdObjects.js +8 -0
  125. package/src/transformers/index.js +1 -0
  126. package/src/validators/__tests__/alphaNumeric.test.js +29 -0
  127. package/src/validators/__tests__/boolean.test.js +23 -0
  128. package/src/validators/__tests__/composeValidators.test.js +23 -0
  129. package/src/validators/__tests__/createCharacterLengthRange.test.js +59 -0
  130. package/src/validators/__tests__/createEqualTo.test.js +43 -0
  131. package/src/validators/__tests__/createMaxCharacterLength.test.js +24 -0
  132. package/src/validators/__tests__/createMaxNumber.test.js +21 -0
  133. package/src/validators/__tests__/createMinCharacterLength.test.js +24 -0
  134. package/src/validators/__tests__/createMinNumber.test.js +21 -0
  135. package/src/validators/__tests__/createNumberRange.test.js +68 -0
  136. package/src/validators/__tests__/createPattern.test.js +40 -0
  137. package/src/validators/__tests__/dhis2Password.test.js +51 -0
  138. package/src/validators/__tests__/dhis2Username.test.js +75 -0
  139. package/src/validators/__tests__/email.test.js +83 -0
  140. package/src/validators/__tests__/hasValue.test.js +21 -0
  141. package/src/validators/__tests__/integer.test.js +48 -0
  142. package/src/validators/__tests__/internationalPhoneNumber.test.js +49 -0
  143. package/src/validators/__tests__/number.test.js +39 -0
  144. package/src/validators/__tests__/string.test.js +29 -0
  145. package/src/validators/__tests__/url.test.js +106 -0
  146. package/src/validators/alphaNumeric.js +15 -0
  147. package/src/validators/boolean.js +11 -0
  148. package/src/validators/composeValidators.js +8 -0
  149. package/src/validators/createCharacterLengthRange.js +27 -0
  150. package/src/validators/createEqualTo.js +16 -0
  151. package/src/validators/createMaxCharacterLength.js +13 -0
  152. package/src/validators/createMaxNumber.js +13 -0
  153. package/src/validators/createMinCharacterLength.js +13 -0
  154. package/src/validators/createMinNumber.js +13 -0
  155. package/src/validators/createNumberRange.js +28 -0
  156. package/src/validators/createPattern.js +22 -0
  157. package/src/validators/dhis2Password.js +81 -0
  158. package/src/validators/dhis2Username.js +16 -0
  159. package/src/validators/email.js +38 -0
  160. package/src/validators/hasValue.js +8 -0
  161. package/src/validators/helpers/index.js +23 -0
  162. package/src/validators/index.js +20 -0
  163. package/src/validators/integer.js +20 -0
  164. package/src/validators/internationalPhoneNumber.js +56 -0
  165. package/src/validators/number.js +9 -0
  166. package/src/validators/string.js +9 -0
  167. package/src/validators/test-helpers/index.js +21 -0
  168. 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,6 @@
1
+ Feature: The Input can set a value
2
+
3
+ Scenario: The user types some text
4
+ Given a Input with no text is rendered
5
+ When the user types something in the Input
6
+ Then the form state's value equals the written text
@@ -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,6 @@
1
+ Feature: The Input field displays an error when invalid
2
+
3
+ Scenario: Form is submitted with empty input
4
+ Given an empty, required Input is rendered
5
+ When the user submits the form
6
+ Then an error message is shown
@@ -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,7 @@
1
+ import { Given } from '@badeball/cypress-cucumber-preprocessor'
2
+
3
+ Given('a required MultiSelect with no selected value', () => {
4
+ cy.visitStory('MultiSelectFieldFF', 'Required')
5
+ cy.getFormValue('multiSelect')
6
+ cy.verifyFormValue('multiSelect', undefined)
7
+ })
@@ -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,6 @@
1
+ Feature: The MultiSelect field displays an error when invalid
2
+
3
+ Scenario: Form is submitted with none of the options selected
4
+ Given a required MultiSelect with no selected value
5
+ When the user submits the form
6
+ Then an error message is shown
@@ -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
+ })
@@ -0,0 +1,6 @@
1
+ Feature: The RadioFieldFF field displays an error when invalid
2
+
3
+ Scenario: Form is submitted with none of the options selected
4
+ Given a FieldGroupFF with required RadioFieldFFs and no selected value
5
+ When the user submits the form
6
+ Then an error message is shown