@dhis2/ui-forms 10.16.2 → 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,272 @@
1
+ import React from 'react'
2
+ import { Field } from 'react-final-form'
3
+ import { CheckboxFieldFF } from '../CheckboxFieldFF/CheckboxFieldFF.js'
4
+ import { FieldGroupFF } from '../FieldGroupFF/FieldGroupFF.js'
5
+ import { formDecorator } from '../formDecorator.js'
6
+ import { InputFieldFF } from '../InputFieldFF/InputFieldFF.js'
7
+ import { MultiSelectFieldFF } from '../MultiSelectFieldFF/MultiSelectFieldFF.js'
8
+ import { RadioFieldFF } from '../RadioFieldFF/RadioFieldFF.js'
9
+ import { SingleSelectFieldFF } from '../SingleSelectFieldFF/SingleSelectFieldFF.js'
10
+ import { SwitchFieldFF } from '../SwitchFieldFF/SwitchFieldFF.js'
11
+ import { TextAreaFieldFF } from '../TextAreaFieldFF/TextAreaFieldFF.js'
12
+ import { composeValidators, email, hasValue } from '../validators/index.js'
13
+ import { FileInputFieldFF } from './FileInputFieldFF.js'
14
+
15
+ const Form = ({ values }) => {
16
+ return (
17
+ <div style={{ maxWidth: 830 }}>
18
+ <Field
19
+ name="gender"
20
+ label="Gender"
21
+ component={SingleSelectFieldFF}
22
+ initialValue=""
23
+ options={[
24
+ { value: '', label: 'Please choose' },
25
+ { value: 'mr', label: 'Mr.' },
26
+ { value: 'ms', label: 'Ms.' },
27
+ { value: 'other', label: 'Other' },
28
+ {
29
+ value: 'unknown',
30
+ label: "I'd rather not say",
31
+ },
32
+ ]}
33
+ />
34
+
35
+ <Field
36
+ required
37
+ label="First name"
38
+ name="fname"
39
+ validate={hasValue}
40
+ component={InputFieldFF}
41
+ helpText="Please enter your first name, excluding middle names"
42
+ />
43
+
44
+ <Field
45
+ required
46
+ label="Last name"
47
+ name="lname"
48
+ validate={hasValue}
49
+ component={InputFieldFF}
50
+ helpText="Please enter your first name, excluding middle names"
51
+ />
52
+
53
+ <Field
54
+ name="subscribe"
55
+ initialValue={true}
56
+ type="checkbox"
57
+ label="I want to receive updated and notifications about the latest changes?"
58
+ component={SwitchFieldFF}
59
+ />
60
+
61
+ {values.subscribe && (
62
+ <Field
63
+ required={values.subscribe}
64
+ label="E-mail address"
65
+ name="email1"
66
+ validate={composeValidators(email, (value) => {
67
+ if (values.subscribe && !value) {
68
+ return 'You need to provide an e-mail address'
69
+ }
70
+ })}
71
+ component={InputFieldFF}
72
+ helpText="Please enter the e-mail address you want us to send the updates to"
73
+ />
74
+ )}
75
+
76
+ {values.subscribe && (
77
+ <Field
78
+ disabled={!values.subscribe}
79
+ required={values.subscribe}
80
+ label="E-mail address confirmation"
81
+ name="email2"
82
+ validate={composeValidators(email, hasValue)}
83
+ component={InputFieldFF}
84
+ helpText="Please confirm your e-mail address"
85
+ />
86
+ )}
87
+
88
+ <FieldGroupFF
89
+ name="food"
90
+ label="Food"
91
+ required
92
+ helpText="If we ever gather for food, what meal type would you like to eat"
93
+ >
94
+ <Field
95
+ type="radio"
96
+ name="food"
97
+ required
98
+ component={RadioFieldFF}
99
+ value="anything"
100
+ label="Don't care"
101
+ validate={hasValue}
102
+ />
103
+
104
+ <Field
105
+ type="radio"
106
+ name="food"
107
+ component={RadioFieldFF}
108
+ required
109
+ value="vegan"
110
+ label="Vegan"
111
+ validate={hasValue}
112
+ />
113
+
114
+ <Field
115
+ type="radio"
116
+ name="food"
117
+ component={RadioFieldFF}
118
+ required
119
+ value="vegetarian"
120
+ label="Vegetarian"
121
+ validate={hasValue}
122
+ />
123
+
124
+ <Field
125
+ type="radio"
126
+ name="food"
127
+ component={RadioFieldFF}
128
+ required
129
+ value="fish"
130
+ label="Fish"
131
+ validate={hasValue}
132
+ />
133
+
134
+ <Field
135
+ type="radio"
136
+ name="food"
137
+ required
138
+ component={RadioFieldFF}
139
+ value="Halal"
140
+ label="halal"
141
+ validate={hasValue}
142
+ />
143
+ </FieldGroupFF>
144
+
145
+ <FieldGroupFF
146
+ name="pizzaToppings"
147
+ label="Pizza toppings"
148
+ helpText="If we ever order pizza, what ingredients would you like on top"
149
+ >
150
+ <Field
151
+ type="checkbox"
152
+ name="pizzaToppings"
153
+ component={CheckboxFieldFF}
154
+ label="Everything"
155
+ value="everything"
156
+ />
157
+
158
+ <Field
159
+ type="checkbox"
160
+ name="pizzaToppings"
161
+ component={CheckboxFieldFF}
162
+ label="Ham"
163
+ value="ham"
164
+ />
165
+
166
+ <Field
167
+ type="checkbox"
168
+ name="pizzaToppings"
169
+ component={CheckboxFieldFF}
170
+ label="Salami"
171
+ value="salami"
172
+ />
173
+
174
+ <Field
175
+ type="checkbox"
176
+ name="pizzaToppings"
177
+ component={CheckboxFieldFF}
178
+ label="Pineapple"
179
+ value="pineapple"
180
+ />
181
+
182
+ <Field
183
+ type="checkbox"
184
+ name="pizzaToppings"
185
+ component={CheckboxFieldFF}
186
+ label="Bellpepper"
187
+ value="bellpepper"
188
+ />
189
+ </FieldGroupFF>
190
+
191
+ <Field
192
+ label="Sandwich toppings"
193
+ name="sandwhichToppings"
194
+ component={MultiSelectFieldFF}
195
+ options={[
196
+ { value: '', label: 'All of the options' },
197
+ { value: 'ham', label: 'Ham' },
198
+ { value: 'salami', label: 'Salami' },
199
+ { value: 'pineapple', label: 'Pineapple' },
200
+ { value: 'bellpepper', label: 'Bellpepper' },
201
+ ]}
202
+ helpText="If we ever order sandwiches, what ingredients would you like on top"
203
+ />
204
+
205
+ <Field
206
+ name="message"
207
+ label="If you want to tell us anything, just add your message here"
208
+ component={TextAreaFieldFF}
209
+ />
210
+
211
+ <Field
212
+ name="fileTxt"
213
+ accept=".txt"
214
+ label="If you want to send us a txt file, please attach it here"
215
+ className="fileTxt"
216
+ validate={(files) => {
217
+ if (!files) {
218
+ return undefined
219
+ }
220
+
221
+ const [file] = files
222
+ if (file.type !== 'text/plain') {
223
+ return `The file you provided is not a txt file, received "${file.type}"`
224
+ }
225
+ }}
226
+ component={FileInputFieldFF}
227
+ />
228
+
229
+ <Field
230
+ multiple
231
+ accept="image/jpg"
232
+ name="fileJpgs"
233
+ label="If you want to send us some picture file, please attach it here"
234
+ validate={(files) => {
235
+ if (!files) {
236
+ return undefined
237
+ }
238
+
239
+ return files.reduce((error, file) => {
240
+ if (error) {
241
+ return error
242
+ }
243
+ if (file.type !== 'application/jpg') {
244
+ return `One of the files is not a jpg, received "${file.type}"`
245
+ }
246
+ }, undefined)
247
+ }}
248
+ component={FileInputFieldFF}
249
+ />
250
+
251
+ <Field
252
+ required
253
+ name="tnc"
254
+ label="I accept the terms and conditions"
255
+ validate={hasValue}
256
+ component={CheckboxFieldFF}
257
+ type="checkbox"
258
+ className="checkbox"
259
+ />
260
+ </div>
261
+ )
262
+ }
263
+
264
+ export default {
265
+ title: 'Testing:FileInput',
266
+ decorators: [formDecorator],
267
+ parameters: { options: { showPanel: false } },
268
+ }
269
+
270
+ export const StandardForm = (_, { formRenderProps }) => (
271
+ <Form {...formRenderProps} />
272
+ )
@@ -0,0 +1,100 @@
1
+ import { FileListItem, FileInputField } from '@dhis2-ui/file-input'
2
+ import PropTypes from 'prop-types'
3
+ import React from 'react'
4
+ import i18n from '../locales/index.js'
5
+ import { hasError, isValid, getValidationText } from '../shared/helpers.js'
6
+ import { inputPropType, metaPropType } from '../shared/propTypes.js'
7
+
8
+ const btnLabel = i18n.t('Upload file')
9
+ const btnLabelMulti = i18n.t('Upload files')
10
+
11
+ const dedupeAndConcat = (currentFiles, newFileList) => {
12
+ return [...currentFiles, ...newFileList].reduceRight(
13
+ (acc, file) => {
14
+ if (!acc.unique.has(file.name)) {
15
+ acc.unique.add(file.name)
16
+ acc.files.unshift(file)
17
+ }
18
+ return acc
19
+ },
20
+ { files: [], unique: new Set() }
21
+ ).files
22
+ }
23
+
24
+ const createChangeHandler =
25
+ (input, multifile) =>
26
+ ({ files }) => {
27
+ // A JavaScript FileList instance is read-only, so we cannot add files to it
28
+ // FileList also doesn't have a .map method so by destructuring the FileList
29
+ // instance into an array we can add, remove and map
30
+ const currentFiles = Array.isArray(input.value) ? input.value : []
31
+ const value = multifile
32
+ ? dedupeAndConcat(currentFiles, files)
33
+ : [...files]
34
+
35
+ input.onChange(value)
36
+ }
37
+
38
+ const createRemoveHandler = (input, fileToDelete) => () => {
39
+ const files = input.value.filter((file) => file !== fileToDelete)
40
+ const value = files.length > 0 ? files : ''
41
+
42
+ input.onChange(value)
43
+ }
44
+
45
+ export const FileInputFieldFF = ({
46
+ buttonLabel,
47
+ disabled,
48
+ error,
49
+ input,
50
+ meta,
51
+ multifile,
52
+ showValidStatus,
53
+ valid,
54
+ validationText,
55
+ ...rest
56
+ }) => {
57
+ const files = input.value || []
58
+
59
+ return (
60
+ <FileInputField
61
+ {...rest}
62
+ onChange={createChangeHandler(input, multifile)}
63
+ buttonLabel={buttonLabel || (multifile ? btnLabelMulti : btnLabel)}
64
+ disabled={disabled || (!multifile && files.length >= 1)}
65
+ multiple={multifile}
66
+ name={input.name}
67
+ error={hasError(meta, error)}
68
+ valid={isValid(meta, valid, showValidStatus)}
69
+ validationText={getValidationText(meta, validationText, error)}
70
+ >
71
+ {files.map((file) => (
72
+ <FileListItem
73
+ key={file.name}
74
+ label={file.name}
75
+ onRemove={createRemoveHandler(input, file)}
76
+ removeText={i18n.t('Remove')}
77
+ />
78
+ ))}
79
+ </FileInputField>
80
+ )
81
+ }
82
+
83
+ FileInputFieldFF.propTypes = {
84
+ /** `input` props provided by Final Form `Field` */
85
+ input: inputPropType.isRequired,
86
+ /** `meta` props provided by Final Form `Field` */
87
+ meta: metaPropType.isRequired,
88
+
89
+ buttonLabel: PropTypes.string,
90
+ disabled: PropTypes.bool,
91
+ error: PropTypes.bool,
92
+ multifile: PropTypes.bool,
93
+ showValidStatus: PropTypes.bool,
94
+ valid: PropTypes.bool,
95
+ validationText: PropTypes.string,
96
+ value: PropTypes.oneOfType([
97
+ PropTypes.arrayOf(PropTypes.instanceOf(File)),
98
+ PropTypes.oneOf(['']),
99
+ ]),
100
+ }
@@ -0,0 +1,95 @@
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 { FileInputFieldFF } from './FileInputFieldFF.js'
7
+
8
+ const description = `
9
+ The \`FileInputFieldFF\` is a wrapper around a \`FileInputField\` that enables it to work with Final Form, the preferred library in DHIS 2 apps for form validation and utilities.
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={FileInputFieldFF} />\`. See the code samples below for examples.
16
+
17
+ #### Props
18
+
19
+ The props shown in the table below are generally provided to the \`FileInputFieldFF\` wrapper by the Final Form \`Field\`.
20
+
21
+ Note that any props beyond the API of the \`Field\` component will be spread to the \`FileInputFieldFF\`, which passes any extra props to the underlying \`FileInputField\` using \`{...rest}\`.
22
+
23
+ Therefore, to add any props to the \`FileInputFieldFF\` or \`FileInputField\`, add those props to the parent Final Form \`Field\` component.
24
+
25
+ Also see \`FileInput\` and \`FileInputField\` for notes about props and implementation.
26
+
27
+ \`\`\`js
28
+ import { FileInputFieldFF } from '@dhis2/ui'
29
+ \`\`\`
30
+
31
+ Press **Submit** to see the form values logged to the console.
32
+ `
33
+
34
+ const files = [new File([], 'file1.txt'), new File([], 'file2.txt')]
35
+
36
+ export default {
37
+ title: 'File Input Field (Final Form)',
38
+ component: FileInputFieldFF,
39
+ decorators: [formDecorator],
40
+ parameters: { docs: { description: { component: description } } },
41
+ argTypes: {
42
+ input: { ...inputArgType },
43
+ meta: { ...metaArgType },
44
+ },
45
+ }
46
+
47
+ export const Default = () => (
48
+ <Field
49
+ component={FileInputFieldFF}
50
+ name="upload"
51
+ label="This is a file upload"
52
+ />
53
+ )
54
+
55
+ export const Required = () => (
56
+ <Field
57
+ component={FileInputFieldFF}
58
+ name="upload"
59
+ label="This is a file upload"
60
+ required
61
+ validate={hasValue}
62
+ />
63
+ )
64
+
65
+ export const Multifile = () => (
66
+ <Field
67
+ component={FileInputFieldFF}
68
+ name="upload"
69
+ label="This is a file upload"
70
+ multifile
71
+ />
72
+ )
73
+
74
+ export const WithValues = () => (
75
+ <Field
76
+ component={FileInputFieldFF}
77
+ name="upload"
78
+ label="This is a file upload"
79
+ required
80
+ multifile
81
+ initialValue={files}
82
+ validate={hasValue}
83
+ />
84
+ )
85
+ WithValues.storyName = 'With values'
86
+
87
+ export const PreventPlaceholder = () => (
88
+ <Field
89
+ component={FileInputFieldFF}
90
+ name="upload"
91
+ label="This is a file upload"
92
+ placeholder=""
93
+ />
94
+ )
95
+ PreventPlaceholder.storyName = 'Prevent placeholder'
@@ -0,0 +1,40 @@
1
+ import { Given, When, Then } from '@badeball/cypress-cucumber-preprocessor'
2
+
3
+ Given('a multi-file IputField is rendered', () => {
4
+ cy.visitStory('Testing:FileInput', 'Standard form')
5
+ })
6
+
7
+ When('a file is provided', () => {
8
+ cy.get('[name="fileTxt"]').uploadSingleFile('txt', 'FileInput/file.txt')
9
+ })
10
+
11
+ When('more than one files are provided', () => {
12
+ cy.get('[name="fileJpgs"]').uploadMultipleFiles(
13
+ [
14
+ { fileType: 'txt', fixture: 'FileInput/file.txt' },
15
+ { fileType: 'md', fixture: 'FileInput/file.md' },
16
+ ],
17
+ true
18
+ )
19
+ })
20
+
21
+ Then('the form state contains that file', () => {
22
+ cy.window().then((win) => {
23
+ const { fileTxt } = win.formValues
24
+ expect(fileTxt).to.have.lengthOf(1)
25
+
26
+ const [file] = fileTxt
27
+ expect(file.name).to.equal('file.txt')
28
+ })
29
+ })
30
+
31
+ Then('the form state contains those files', () => {
32
+ cy.window().then((win) => {
33
+ const { fileJpgs } = win.formValues
34
+ expect(fileJpgs).to.have.lengthOf(2)
35
+
36
+ const [file1, file2] = fileJpgs
37
+ expect(file1.name).to.equal('file.txt')
38
+ expect(file2.name).to.equal('file.md')
39
+ })
40
+ })
@@ -0,0 +1,13 @@
1
+ Feature: The FileInput accepts files
2
+
3
+ Scenario: The user provides a file
4
+ Given a single-file FileInput is rendered
5
+ And the InputField does not contain any files
6
+ When a file is provided
7
+ Then the form state contains that file
8
+
9
+ Scenario: The user provides multiple files
10
+ Given a multi-file IputField is rendered
11
+ And the InputField does not contain any files
12
+ When more than one files are provided
13
+ Then the form state contains those files
@@ -0,0 +1,9 @@
1
+ import { Given } from '@badeball/cypress-cucumber-preprocessor'
2
+
3
+ Given('a single-file FileInput is rendered', () => {
4
+ cy.visitStory('Testing:FileInput', 'Standard form')
5
+ })
6
+
7
+ Given('the InputField does not contain any files', () => {
8
+ cy.verifyFormValue('fileTxt', undefined)
9
+ })
@@ -0,0 +1,18 @@
1
+ import { When, Then } from '@badeball/cypress-cucumber-preprocessor'
2
+
3
+ When('a file with the wrong file type is provided', () => {
4
+ cy.get('[name="fileTxt"]').uploadSingleFile('md', 'FileInput/file.md')
5
+ })
6
+
7
+ When('the user submits the form', () => {
8
+ cy.get('button[type="submit"]').click()
9
+ })
10
+
11
+ Then('an error message is shown', () => {
12
+ cy.get('.fileTxt')
13
+ .get('[data-test="dhis2-uiwidgets-fileinputfield-validation"]')
14
+ .should(
15
+ 'contain',
16
+ 'The file you provided is not a txt file, received "md"'
17
+ )
18
+ })
@@ -0,0 +1,8 @@
1
+ Feature: The FileInput field displays an error when invalid
2
+
3
+ Scenario: The user provides files with the wrong file type
4
+ Given a single-file FileInput is rendered
5
+ And the InputField does not contain any files
6
+ When a file with the wrong file type is provided
7
+ And the user submits the form
8
+ Then an error message is shown
@@ -0,0 +1,19 @@
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 { InputFieldFF } from './InputFieldFF.js'
6
+
7
+ export default { title: 'Testing:InputFieldFF', decorators: [formDecorator] }
8
+ export const Default = () => (
9
+ <Field component={InputFieldFF} name="agree" label="Do you agree?" />
10
+ )
11
+ export const Required = () => (
12
+ <Field
13
+ name="agree"
14
+ component={InputFieldFF}
15
+ required
16
+ validate={hasValue}
17
+ label="Do you agree?"
18
+ />
19
+ )
@@ -0,0 +1,58 @@
1
+ import { InputField } from '@dhis2-ui/input'
2
+ import PropTypes from 'prop-types'
3
+ import React from 'react'
4
+ import {
5
+ createChangeHandler,
6
+ createFocusHandler,
7
+ createBlurHandler,
8
+ hasError,
9
+ isLoading,
10
+ isValid,
11
+ getValidationText,
12
+ } from '../shared/helpers.js'
13
+ import { metaPropType, inputPropType } from '../shared/propTypes.js'
14
+
15
+ export const InputFieldFF = ({
16
+ input,
17
+ meta,
18
+ error,
19
+ showValidStatus,
20
+ valid,
21
+ validationText,
22
+ onBlur,
23
+ onFocus,
24
+ loading,
25
+ showLoadingStatus,
26
+ ...rest
27
+ }) => (
28
+ <InputField
29
+ {...rest}
30
+ name={input.name}
31
+ type={input.type}
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={createChangeHandler(input)}
38
+ onBlur={createBlurHandler(input, onBlur)}
39
+ value={input.value}
40
+ />
41
+ )
42
+
43
+ InputFieldFF.propTypes = {
44
+ /** `input` props received from Final Form `Field` */
45
+ input: inputPropType.isRequired,
46
+ /** `meta` props received from Final Form `Field` */
47
+ meta: metaPropType.isRequired,
48
+
49
+ error: PropTypes.bool,
50
+ loading: PropTypes.bool,
51
+ showLoadingStatus: PropTypes.bool,
52
+ showValidStatus: PropTypes.bool,
53
+ valid: PropTypes.bool,
54
+ validationText: PropTypes.string,
55
+
56
+ onBlur: PropTypes.func,
57
+ onFocus: PropTypes.func,
58
+ }