@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.
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,172 @@
1
+ import { requiredIf } from '@dhis2/prop-types'
2
+ import { SimpleSingleSelectField } from '@dhis2-ui/select'
3
+ import PropTypes from 'prop-types'
4
+ import React from 'react'
5
+ import {
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 SimpleSingleSelectFieldFF = ({
16
+ error,
17
+ input,
18
+ loading,
19
+ meta,
20
+ showLoadingStatus,
21
+ showValidStatus,
22
+ valid,
23
+ validationText,
24
+ onBlur,
25
+ onFocus,
26
+ ...rest
27
+ }) => {
28
+ return (
29
+ <SimpleSingleSelectField
30
+ {...rest}
31
+ name={input.name}
32
+ error={hasError(meta, error)}
33
+ valid={isValid(meta, valid, showValidStatus)}
34
+ loading={isLoading(meta, loading, showLoadingStatus)}
35
+ validationText={getValidationText(meta, validationText, error)}
36
+ onFocus={createFocusHandler(input, onFocus)}
37
+ onChange={(value) => input.onChange(value)}
38
+ onBlur={createBlurHandler(input, onBlur)}
39
+ value={input.value || ''}
40
+ />
41
+ )
42
+ }
43
+
44
+ SimpleSingleSelectFieldFF.propTypes = {
45
+ /** `input` props received from Final Form `Field` */
46
+ input: inputPropType.isRequired,
47
+
48
+ /** Label displayed above the input **/
49
+ label: PropTypes.string.isRequired,
50
+
51
+ /** `meta` props received from Final Form `Field` */
52
+ meta: metaPropType.isRequired,
53
+
54
+ options: PropTypes.arrayOf(
55
+ PropTypes.shape({
56
+ label: PropTypes.string.isRequired,
57
+ value: PropTypes.string.isRequired,
58
+ })
59
+ ).isRequired,
60
+
61
+ /** Will focus the select initially **/
62
+ autoFocus: PropTypes.bool,
63
+
64
+ /** Additional class names that will be applied to the root element **/
65
+ className: PropTypes.string,
66
+
67
+ /** This will allow us to put an aria-label on the clear button **/
68
+ clearText: requiredIf((props) => props.clearable, PropTypes.string),
69
+
70
+ /** Whether a clear button should be displayed or not **/
71
+ clearable: PropTypes.bool,
72
+
73
+ /** A value for a `data-test` attribute on the root element **/
74
+ dataTest: PropTypes.string,
75
+
76
+ /** Renders a select with lower height **/
77
+ dense: PropTypes.bool,
78
+
79
+ /** Disables all interactions with the select (except focussing) **/
80
+ disabled: PropTypes.bool,
81
+
82
+ /** Text or component to display when there are no options **/
83
+ empty: PropTypes.node,
84
+
85
+ /** Applies 'error' appearance for validation feedback. Mutually exclusive with `warning` and `valid` props **/
86
+ error: PropTypes.bool,
87
+
88
+ /** Help text that will be displayed below the input **/
89
+ filterHelpText: PropTypes.string,
90
+
91
+ /** Value will be used as aria-label attribute on the filter input **/
92
+ filterLabel: PropTypes.string,
93
+
94
+ /** Placeholder for the filter input **/
95
+ filterPlaceholder: PropTypes.string,
96
+
97
+ /** Value of the filter input **/
98
+ filterValue: PropTypes.string,
99
+
100
+ /** Whether the select should display a filter input **/
101
+ filterable: PropTypes.bool,
102
+
103
+ /** Help text, displayed below the input **/
104
+ helpText: PropTypes.string,
105
+
106
+ /** Will show a loading indicator at the end of the options-list **/
107
+ loading: PropTypes.bool,
108
+
109
+ /** Text that will be displayed next to the loading indicator **/
110
+ menuLoadingText: PropTypes.string,
111
+
112
+ /** Allows to modify the max height of the menu **/
113
+ menuMaxHeight: PropTypes.string,
114
+
115
+ /** String that will be displayed when the select is being filtered but the options array is empty **/
116
+ noMatchText: requiredIf((props) => props.filterable, PropTypes.string),
117
+
118
+ /** Allows to override what's rendered inside the `button[role="option"]`.
119
+ * Can be overriden on an individual option basis **/
120
+ optionComponent: PropTypes.elementType,
121
+
122
+ /** For a11y: How aggressively the user should be updated about changes in options **/
123
+ optionUpdateStrategy: PropTypes.oneOf(['off', 'polite', 'assertive']),
124
+
125
+ /** String to show when there's no value and no valueLabel **/
126
+ placeholder: PropTypes.string,
127
+
128
+ /** String that will be displayed before the label of the selected option **/
129
+ prefix: PropTypes.string,
130
+
131
+ /** Whether a value is required or not **/
132
+ required: PropTypes.bool,
133
+
134
+ showLoadingStatus: PropTypes.bool,
135
+
136
+ showValidStatus: PropTypes.bool,
137
+
138
+ /** Standard HTML tab-index attribute that will be put on the combobox's root element **/
139
+ tabIndex: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
140
+
141
+ valid: PropTypes.bool,
142
+
143
+ validationText: PropTypes.string,
144
+
145
+ /**
146
+ * When the option is not in the options list (e.g. not loaded or list is
147
+ * filtered), but a selected value needs to be displayed, then this prop can
148
+ * be used to supply the text to be shown.
149
+ **/
150
+ valueLabel: requiredIf((props) => {
151
+ if (props.options.find(({ value }) => props.value === value)) {
152
+ return false
153
+ }
154
+
155
+ return props.value
156
+ }, PropTypes.string),
157
+
158
+ /** Applies 'warning' appearance for validation feedback. Mutually exclusive with `warning` and `valid` props **/
159
+ warning: PropTypes.bool,
160
+
161
+ /** Will be called when the combobox is loses focus **/
162
+ onBlur: PropTypes.func,
163
+
164
+ /** Will be called when the last option is scrolled into the visible area **/
165
+ onEndReached: PropTypes.func,
166
+
167
+ /** Will be called when the filter value changes **/
168
+ onFilterChange: PropTypes.func,
169
+
170
+ /** Will be called when the combobox is being focused **/
171
+ onFocus: PropTypes.func,
172
+ }
@@ -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 { hasValue } from '../validators/index.js'
6
+ import { SimpleSingleSelectFieldFF } from './SimpleSingleSelectFieldFF.js'
7
+
8
+ const description = `
9
+ The \`SimpleSingleSelectFieldFF\` is a wrapper around a \`SimpleSingleSelectField\` 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={SimpleSingleSelectFieldFF} />\`. See the code samples below for examples.
16
+
17
+ #### Props
18
+
19
+ The props shown in the table below are generally provided to the \`SimpleSingleSelectFieldFF\` wrapper by the Final Form \`Field\`.
20
+
21
+ Note that any props beyond the API of the \`Field\` component will be spread to the \`SimpleSingleSelectFieldFF\`, which passes any extra props to the underlying \`SimpleSingleSelectField\` using \`{...rest}\`.
22
+
23
+ Therefore, to add any props to the \`SimpleSingleSelectFieldFF\` or \`SimpleSingleSelectField\`, add those props to the parent Final Form \`Field\` component.
24
+
25
+ Also see \`SingleSelect\` and \`SimpleSingleSelectField\` for notes about props and implementation.
26
+
27
+ \`\`\`js
28
+ import { SimpleSingleSelectFieldFF } from '@dhis2/ui'
29
+ \`\`\`
30
+
31
+ Press **Submit** to see the form values logged to the console.
32
+
33
+ _**Note:** Dropdowns may not appear correctly on this page. See the affected demos in the 'Canvas' tab for propper dropdown placement._
34
+ `
35
+
36
+ const options = [
37
+ { value: '1', label: 'one' },
38
+ { value: '2', label: 'two' },
39
+ { value: '3', label: 'three' },
40
+ { value: '4', label: 'four' },
41
+ { value: '5', label: 'five' },
42
+ { value: '6', label: 'six' },
43
+ { value: '7', label: 'seven' },
44
+ { value: '8', label: 'eight' },
45
+ { value: '9', label: 'nine' },
46
+ { value: '10', label: 'ten' },
47
+ ]
48
+
49
+ export default {
50
+ title: 'SimpleSingleSelectField (Final Form)',
51
+ component: SimpleSingleSelectFieldFF,
52
+ decorators: [formDecorator],
53
+ parameters: { docs: { description: { component: description } } },
54
+ argTypes: {
55
+ input: { ...inputArgType },
56
+ meta: { ...metaArgType },
57
+ },
58
+ }
59
+
60
+ export const Default = () => (
61
+ <Field
62
+ required
63
+ component={SimpleSingleSelectFieldFF}
64
+ name="story"
65
+ label="Do you agree?"
66
+ options={options}
67
+ validate={hasValue}
68
+ />
69
+ )
70
+
71
+ export const InitialValue = () => (
72
+ <Field
73
+ component={SimpleSingleSelectFieldFF}
74
+ name="story"
75
+ label="Do you agree?"
76
+ options={options}
77
+ initialValue="4"
78
+ />
79
+ )
@@ -0,0 +1,82 @@
1
+ import '@testing-library/jest-dom'
2
+ import { Button } from '@dhis2-ui/button'
3
+ import { render, fireEvent, screen } from '@testing-library/react'
4
+ import React from 'react'
5
+ import { Field, Form } from 'react-final-form'
6
+ import { hasValue } from '../validators/index.js'
7
+ import { SimpleSingleSelectFieldFF } from './SimpleSingleSelectFieldFF.js'
8
+
9
+ describe('<SimpleSingleSelectFieldFF/>', () => {
10
+ beforeEach(jest.resetAllMocks)
11
+ it("should use FF's input for value selection", () => {
12
+ const onSubmit = jest.fn()
13
+
14
+ render(
15
+ <Form onSubmit={onSubmit}>
16
+ {(formRenderProps) => (
17
+ <form onSubmit={formRenderProps.handleSubmit}>
18
+ <Field
19
+ component={SimpleSingleSelectFieldFF}
20
+ name="story"
21
+ label="Label text"
22
+ options={[
23
+ { value: '', label: 'None' },
24
+ { value: 'foo', label: 'Foo' },
25
+ { value: 'bar', label: 'Bar' },
26
+ ]}
27
+ />
28
+
29
+ <Button primary type="submit">
30
+ Submit
31
+ </Button>
32
+ </form>
33
+ )}
34
+ </Form>
35
+ )
36
+
37
+ fireEvent.click(screen.getByRole('combobox'))
38
+ fireEvent.click(screen.getByText('Foo'))
39
+ fireEvent.click(screen.getByRole('button'))
40
+
41
+ expect(onSubmit).toHaveBeenCalledTimes(1)
42
+ expect(onSubmit).toHaveBeenCalledWith(
43
+ { story: 'foo' },
44
+ expect.anything(),
45
+ expect.anything()
46
+ )
47
+ })
48
+
49
+ it('should display the validation error', () => {
50
+ const onSubmit = jest.fn()
51
+
52
+ render(
53
+ <Form onSubmit={onSubmit}>
54
+ {(formRenderProps) => (
55
+ <form onSubmit={formRenderProps.handleSubmit}>
56
+ <Field
57
+ required
58
+ component={SimpleSingleSelectFieldFF}
59
+ name="story"
60
+ label="Label text"
61
+ validate={hasValue}
62
+ options={[
63
+ { value: '', label: 'None' },
64
+ { value: 'foo', label: 'Foo' },
65
+ { value: 'bar', label: 'Bar' },
66
+ ]}
67
+ />
68
+
69
+ <Button primary type="submit">
70
+ Submit
71
+ </Button>
72
+ </form>
73
+ )}
74
+ </Form>
75
+ )
76
+
77
+ fireEvent.click(screen.getByRole('button'))
78
+
79
+ const error = screen.getByText('Please provide a value')
80
+ expect(error).not.toBeNull()
81
+ })
82
+ })
@@ -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 { SingleSelectFieldFF } from './SingleSelectFieldFF.js'
6
+
7
+ const defaultOptions = [{ value: 'initial', label: 'Initial' }]
8
+
9
+ export default {
10
+ title: 'Testing:SingleSelectFieldFF',
11
+ decorators: [formDecorator],
12
+ parameters: { options: { showPanel: false } },
13
+ }
14
+ export const Required = (_, { cypressProps }) => (
15
+ <Field
16
+ required
17
+ name="singleSelect"
18
+ label="Single select"
19
+ component={SingleSelectFieldFF}
20
+ validate={hasValue}
21
+ options={cypressProps.options || defaultOptions}
22
+ />
23
+ )
@@ -0,0 +1,70 @@
1
+ import { SingleSelectOption, SingleSelectField } 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 SingleSelectFieldFF = ({
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
+ <SingleSelectField
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={input.value || ''}
41
+ >
42
+ {options.map((option) => (
43
+ <SingleSelectOption key={option.value} {...option} />
44
+ ))}
45
+ </SingleSelectField>
46
+ )
47
+ }
48
+
49
+ SingleSelectFieldFF.propTypes = {
50
+ /** `input` props received from Final Form `Field` */
51
+ input: inputPropType.isRequired,
52
+ /** `meta` props received from Final Form `Field` */
53
+ meta: metaPropType.isRequired,
54
+ options: PropTypes.arrayOf(
55
+ PropTypes.shape({
56
+ label: PropTypes.string,
57
+ value: PropTypes.string,
58
+ })
59
+ ).isRequired,
60
+
61
+ error: PropTypes.bool,
62
+ loading: PropTypes.bool,
63
+ showLoadingStatus: PropTypes.bool,
64
+ showValidStatus: PropTypes.bool,
65
+ valid: PropTypes.bool,
66
+ validationText: PropTypes.string,
67
+
68
+ onBlur: PropTypes.func,
69
+ onFocus: PropTypes.func,
70
+ }
@@ -0,0 +1,77 @@
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 { SingleSelectFieldFF } from './SingleSelectFieldFF.js'
6
+
7
+ const description = `
8
+ The \`SingleSelectFieldFF\` is a wrapper around a \`SingleSelectField\` 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={SingleSelectFieldFF} />\`. See the code samples below for examples.
15
+
16
+ #### Props
17
+
18
+ The props shown in the table below are generally provided to the \`SingleSelectFieldFF\` wrapper by the Final Form \`Field\`.
19
+
20
+ Note that any props beyond the API of the \`Field\` component will be spread to the \`SingleSelectFieldFF\`, which passes any extra props to the underlying \`SingleSelectField\` using \`{...rest}\`.
21
+
22
+ Therefore, to add any props to the \`SingleSelectFieldFF\` or \`SingleSelectField\`, add those props to the parent Final Form \`Field\` component.
23
+
24
+ Also see \`SingleSelect\` and \`SingleSelectField\` for notes about props and implementation.
25
+
26
+ \`\`\`js
27
+ import { SingleSelectFieldFF } 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
+ export default {
49
+ title: 'Single Select Field (Final Form)',
50
+ component: SingleSelectFieldFF,
51
+ decorators: [formDecorator],
52
+ parameters: { docs: { description: { component: description } } },
53
+ argTypes: {
54
+ input: { ...inputArgType },
55
+ meta: { ...metaArgType },
56
+ },
57
+ }
58
+
59
+ export const Default = () => (
60
+ <Field
61
+ component={SingleSelectFieldFF}
62
+ name="agree"
63
+ label="Do you agree?"
64
+ options={options}
65
+ />
66
+ )
67
+
68
+ export const InitialValue = () => (
69
+ <Field
70
+ component={SingleSelectFieldFF}
71
+ name="agree"
72
+ label="Do you agree?"
73
+ options={options}
74
+ initialValue="4"
75
+ />
76
+ )
77
+ InitialValue.storyName = 'InitialValue'
@@ -0,0 +1,22 @@
1
+ import { Given, When, Then } from '@badeball/cypress-cucumber-preprocessor'
2
+
3
+ Given('the SingleSelect has one option', () => {
4
+ const options = [{ value: 'Value', label: 'Label' }]
5
+
6
+ cy.wrap(options).as('options')
7
+ cy.window().then((win) => {
8
+ win.updateCypressProps({ options })
9
+ })
10
+ })
11
+
12
+ When('the user selects the first option', () => {
13
+ cy.get('[data-test="dhis2-uicore-select-input"]').selectSelectNthOption(0)
14
+ })
15
+
16
+ Then("the form state's value equals the first option's value", () => {
17
+ cy.get('@options').then((options) => {
18
+ cy.getFormValue('singleSelect').then((actualValue) => {
19
+ expect(actualValue).to.deep.equal(options[0].value)
20
+ })
21
+ })
22
+ })
@@ -0,0 +1,7 @@
1
+ Feature: The SingleSelect can set a value
2
+
3
+ Scenario: The user clicks the first option
4
+ Given a required SingleSelect with no selected value
5
+ And the SingleSelect has one option
6
+ When the user selects the first option
7
+ Then the form state's value equals the first option's value
@@ -0,0 +1,6 @@
1
+ import { Given } from '@badeball/cypress-cucumber-preprocessor'
2
+
3
+ Given('a required SingleSelect with no selected value', () => {
4
+ cy.visitStory('Testing:SingleSelectFieldFF', 'Required')
5
+ cy.verifyFormValue('singleSelect', undefined)
6
+ })
@@ -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 SingleSelect field displays an error when invalid
2
+
3
+ Scenario: Form is submitted with none of the options selected
4
+ Given a required SingleSelect with no selected value
5
+ When the user submits the form
6
+ Then an error message is shown
@@ -0,0 +1,52 @@
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 { SwitchFieldFF } from './SwitchFieldFF.js'
6
+
7
+ // https://github.com/final-form/react-final-form-arrays/issues/111
8
+ const initialValue = ['yes']
9
+
10
+ export default { title: 'Testing:SwitchFieldFF', decorators: [formDecorator] }
11
+ export const Unchecked = () => (
12
+ <Field
13
+ component={SwitchFieldFF}
14
+ className="switch"
15
+ name="switch"
16
+ label="Label text"
17
+ validate={hasValue}
18
+ required
19
+ type="checkbox"
20
+ />
21
+ )
22
+ export const Checked = () => (
23
+ <Field
24
+ component={SwitchFieldFF}
25
+ className="switch"
26
+ name="switch"
27
+ label="Label text"
28
+ initialValue={true}
29
+ type="checkbox"
30
+ />
31
+ )
32
+ export const UncheckedWithValue = () => (
33
+ <Field
34
+ component={SwitchFieldFF}
35
+ className="switch"
36
+ name="switch"
37
+ label="Label text"
38
+ value="yes"
39
+ type="checkbox"
40
+ />
41
+ )
42
+ export const CheckedWithValue = () => (
43
+ <Field
44
+ component={SwitchFieldFF}
45
+ className="switch"
46
+ name="switch"
47
+ label="Label text"
48
+ value="yes"
49
+ initialValue={initialValue}
50
+ type="checkbox"
51
+ />
52
+ )
@@ -0,0 +1,52 @@
1
+ import { SwitchField } from '@dhis2-ui/switch'
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 SwitchFieldFF = ({
15
+ error,
16
+ input,
17
+ meta,
18
+ showValidStatus,
19
+ valid,
20
+ validationText,
21
+ onBlur,
22
+ onFocus,
23
+ ...rest
24
+ }) => (
25
+ <SwitchField
26
+ {...rest}
27
+ checked={input.checked}
28
+ name={input.name}
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
+ SwitchFieldFF.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
+ }