@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.
- package/package.json +12 -11
- package/src/CheckboxFieldFF/CheckboxFieldFF.e2e.stories.js +52 -0
- package/src/CheckboxFieldFF/CheckboxFieldFF.js +52 -0
- package/src/CheckboxFieldFF/CheckboxFieldFF.prod.stories.js +142 -0
- package/src/CheckboxFieldFF/features/can_toggle_a_boolean/index.js +19 -0
- package/src/CheckboxFieldFF/features/can_toggle_a_boolean.feature +11 -0
- package/src/CheckboxFieldFF/features/can_toggle_a_value/index.js +21 -0
- package/src/CheckboxFieldFF/features/can_toggle_a_value.feature +11 -0
- package/src/CheckboxFieldFF/features/common/index.js +5 -0
- package/src/CheckboxFieldFF/features/displays_error/index.js +18 -0
- package/src/CheckboxFieldFF/features/displays_error.feature +6 -0
- package/src/FieldGroupFF/FieldGroupFF.js +42 -0
- package/src/FieldGroupFF/FieldGroupFF.prod.stories.js +49 -0
- package/src/FileInputFieldFF/FileInputFieldFF.e2e.stories.js +272 -0
- package/src/FileInputFieldFF/FileInputFieldFF.js +100 -0
- package/src/FileInputFieldFF/FileInputFieldFF.prod.stories.js +95 -0
- package/src/FileInputFieldFF/features/accepts_file/index.js +40 -0
- package/src/FileInputFieldFF/features/accepts_file.feature +13 -0
- package/src/FileInputFieldFF/features/common/index.js +9 -0
- package/src/FileInputFieldFF/features/displays_error/index.js +18 -0
- package/src/FileInputFieldFF/features/displays_error.feature +8 -0
- package/src/InputFieldFF/InputFieldFF.e2e.stories.js +19 -0
- package/src/InputFieldFF/InputFieldFF.js +58 -0
- package/src/InputFieldFF/InputFieldFF.prod.stories.js +102 -0
- package/src/InputFieldFF/features/can_set_a_value/index.js +14 -0
- package/src/InputFieldFF/features/can_set_a_value.feature +6 -0
- package/src/InputFieldFF/features/displays_error/index.js +15 -0
- package/src/InputFieldFF/features/displays_error.feature +6 -0
- package/src/MultiSelectFieldFF/MultiSelectFieldFF.e2e.stories.js +27 -0
- package/src/MultiSelectFieldFF/MultiSelectFieldFF.js +72 -0
- package/src/MultiSelectFieldFF/MultiSelectFieldFF.prod.stories.js +79 -0
- package/src/MultiSelectFieldFF/features/can_set_a_value/index.js +38 -0
- package/src/MultiSelectFieldFF/features/can_set_a_value.feature +14 -0
- package/src/MultiSelectFieldFF/features/common/index.js +7 -0
- package/src/MultiSelectFieldFF/features/displays_error/index.js +10 -0
- package/src/MultiSelectFieldFF/features/displays_error.feature +6 -0
- package/src/RadioFieldFF/RadioFieldFF.e2e.stories.js +39 -0
- package/src/RadioFieldFF/RadioFieldFF.js +52 -0
- package/src/RadioFieldFF/RadioFieldFF.prod.stories.js +104 -0
- package/src/RadioFieldFF/features/can_set_a_value/index.js +24 -0
- package/src/RadioFieldFF/features/can_set_a_value.feature +7 -0
- package/src/RadioFieldFF/features/common/index.js +9 -0
- package/src/RadioFieldFF/features/displays_error/index.js +13 -0
- package/src/RadioFieldFF/features/displays_error.feature +6 -0
- package/src/SimpleSingleSelectFieldFF/SimpleSingleSelectFieldFF.js +172 -0
- package/src/SimpleSingleSelectFieldFF/SimpleSingleSelectFieldFF.prod.stories.js +79 -0
- package/src/SimpleSingleSelectFieldFF/SimpleSingleSelectFieldFF.test.js +82 -0
- package/src/SingleSelectFieldFF/SingleSelectFieldFF.e2e.stories.js +23 -0
- package/src/SingleSelectFieldFF/SingleSelectFieldFF.js +70 -0
- package/src/SingleSelectFieldFF/SingleSelectFieldFF.prod.stories.js +77 -0
- package/src/SingleSelectFieldFF/features/can_set_a_value/index.js +22 -0
- package/src/SingleSelectFieldFF/features/can_set_a_value.feature +7 -0
- package/src/SingleSelectFieldFF/features/common/index.js +6 -0
- package/src/SingleSelectFieldFF/features/displays_error/index.js +10 -0
- package/src/SingleSelectFieldFF/features/displays_error.feature +6 -0
- package/src/SwitchFieldFF/SwitchFieldFF.e2e.stories.js +52 -0
- package/src/SwitchFieldFF/SwitchFieldFF.js +52 -0
- package/src/SwitchFieldFF/SwitchFieldFF.prod.stories.js +148 -0
- package/src/SwitchFieldFF/features/can_toggle_a_boolean/index.js +19 -0
- package/src/SwitchFieldFF/features/can_toggle_a_boolean.feature +11 -0
- package/src/SwitchFieldFF/features/can_toggle_a_value/index.js +21 -0
- package/src/SwitchFieldFF/features/can_toggle_a_value.feature +11 -0
- package/src/SwitchFieldFF/features/common/index.js +5 -0
- package/src/SwitchFieldFF/features/displays_error/index.js +18 -0
- package/src/SwitchFieldFF/features/displays_error.feature +6 -0
- package/src/TextAreaFieldFF/TextAreaFieldFF.e2e.stories.js +23 -0
- package/src/TextAreaFieldFF/TextAreaFieldFF.js +57 -0
- package/src/TextAreaFieldFF/TextAreaFieldFF.prod.stories.js +111 -0
- package/src/TextAreaFieldFF/features/can_set_a_value/index.js +14 -0
- package/src/TextAreaFieldFF/features/can_set_a_value.feature +6 -0
- package/src/TextAreaFieldFF/features/displays_error/index.js +15 -0
- package/src/TextAreaFieldFF/features/displays_error.feature +6 -0
- package/src/__tests__/__snapshots__/index.test.js.snap +65 -0
- package/src/__tests__/index.test.js +37 -0
- package/src/formDecorator.js +80 -0
- package/src/index.js +28 -0
- package/src/locales/ar/translations.json +30 -0
- package/src/locales/ar_IQ/translations.json +30 -0
- package/src/locales/ckb/translations.json +30 -0
- package/src/locales/cs/translations.json +30 -0
- package/src/locales/da/translations.json +30 -0
- package/src/locales/en/translations.json +30 -0
- package/src/locales/es/translations.json +30 -0
- package/src/locales/es_419/translations.json +30 -0
- package/src/locales/fr/translations.json +30 -0
- package/src/locales/hi_IN/translations.json +30 -0
- package/src/locales/id/translations.json +30 -0
- package/src/locales/index.js +84 -0
- package/src/locales/km/translations.json +30 -0
- package/src/locales/ko_KR/translations.json +30 -0
- package/src/locales/lo/translations.json +30 -0
- package/src/locales/my/translations.json +30 -0
- package/src/locales/nb/translations.json +30 -0
- package/src/locales/nl/translations.json +30 -0
- package/src/locales/prs/translations.json +30 -0
- package/src/locales/ps/translations.json +30 -0
- package/src/locales/pt/translations.json +30 -0
- package/src/locales/pt_BR/translations.json +30 -0
- package/src/locales/ro/translations.json +30 -0
- package/src/locales/ru/translations.json +30 -0
- package/src/locales/si/translations.json +30 -0
- package/src/locales/sv/translations.json +30 -0
- package/src/locales/tet/translations.json +30 -0
- package/src/locales/tg/translations.json +30 -0
- package/src/locales/uk/translations.json +30 -0
- package/src/locales/ur/translations.json +30 -0
- package/src/locales/uz_Latn/translations.json +30 -0
- package/src/locales/uz_UZ_Cyrl/translations.json +30 -0
- package/src/locales/uz_UZ_Latn/translations.json +30 -0
- package/src/locales/vi/translations.json +30 -0
- package/src/locales/zh/translations.json +30 -0
- package/src/locales/zh_CN/translations.json +30 -0
- package/src/shared/helpers/createBlurHandler.js +9 -0
- package/src/shared/helpers/createChangeHandler.js +21 -0
- package/src/shared/helpers/createFocusHandler.js +9 -0
- package/src/shared/helpers/createSelectChangeHandler.js +6 -0
- package/src/shared/helpers/createToggleChangeHandler.js +9 -0
- package/src/shared/helpers/getValidationText.js +21 -0
- package/src/shared/helpers/hasError.js +3 -0
- package/src/shared/helpers/isLoading.js +4 -0
- package/src/shared/helpers/isValid.js +4 -0
- package/src/shared/helpers.js +9 -0
- package/src/shared/propTypes.js +48 -0
- package/src/transformers/arrayWithIdObjects.js +8 -0
- package/src/transformers/index.js +1 -0
- package/src/validators/__tests__/alphaNumeric.test.js +29 -0
- package/src/validators/__tests__/boolean.test.js +23 -0
- package/src/validators/__tests__/composeValidators.test.js +23 -0
- package/src/validators/__tests__/createCharacterLengthRange.test.js +59 -0
- package/src/validators/__tests__/createEqualTo.test.js +43 -0
- package/src/validators/__tests__/createMaxCharacterLength.test.js +24 -0
- package/src/validators/__tests__/createMaxNumber.test.js +21 -0
- package/src/validators/__tests__/createMinCharacterLength.test.js +24 -0
- package/src/validators/__tests__/createMinNumber.test.js +21 -0
- package/src/validators/__tests__/createNumberRange.test.js +68 -0
- package/src/validators/__tests__/createPattern.test.js +40 -0
- package/src/validators/__tests__/dhis2Password.test.js +51 -0
- package/src/validators/__tests__/dhis2Username.test.js +75 -0
- package/src/validators/__tests__/email.test.js +83 -0
- package/src/validators/__tests__/hasValue.test.js +21 -0
- package/src/validators/__tests__/integer.test.js +48 -0
- package/src/validators/__tests__/internationalPhoneNumber.test.js +49 -0
- package/src/validators/__tests__/number.test.js +39 -0
- package/src/validators/__tests__/string.test.js +29 -0
- package/src/validators/__tests__/url.test.js +106 -0
- package/src/validators/alphaNumeric.js +15 -0
- package/src/validators/boolean.js +11 -0
- package/src/validators/composeValidators.js +8 -0
- package/src/validators/createCharacterLengthRange.js +27 -0
- package/src/validators/createEqualTo.js +16 -0
- package/src/validators/createMaxCharacterLength.js +13 -0
- package/src/validators/createMaxNumber.js +13 -0
- package/src/validators/createMinCharacterLength.js +13 -0
- package/src/validators/createMinNumber.js +13 -0
- package/src/validators/createNumberRange.js +28 -0
- package/src/validators/createPattern.js +22 -0
- package/src/validators/dhis2Password.js +81 -0
- package/src/validators/dhis2Username.js +16 -0
- package/src/validators/email.js +38 -0
- package/src/validators/hasValue.js +8 -0
- package/src/validators/helpers/index.js +23 -0
- package/src/validators/index.js +20 -0
- package/src/validators/integer.js +20 -0
- package/src/validators/internationalPhoneNumber.js +56 -0
- package/src/validators/number.js +9 -0
- package/src/validators/string.js +9 -0
- package/src/validators/test-helpers/index.js +21 -0
- package/src/validators/url.js +15 -0
|
@@ -0,0 +1,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,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,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
|
+
}
|