@dhis2-ui/file-input 10.16.1 → 10.16.3-alpha.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +10 -9
- package/src/file-input/__tests__/file-input.test.js +28 -0
- package/src/file-input/features/accepts_multiple_files/index.js +32 -0
- package/src/file-input/features/accepts_multiple_files.feature +8 -0
- package/src/file-input/features/can_be_blurred/index.js +23 -0
- package/src/file-input/features/can_be_blurred.feature +6 -0
- package/src/file-input/features/can_be_changed/index.js +26 -0
- package/src/file-input/features/can_be_changed.feature +8 -0
- package/src/file-input/features/can_be_focused/index.js +22 -0
- package/src/file-input/features/can_be_focused.feature +6 -0
- package/src/file-input/features/common/index.js +21 -0
- package/src/file-input/file-input.e2e.stories.js +46 -0
- package/src/file-input/file-input.js +159 -0
- package/src/file-input/file-input.prod.stories.js +82 -0
- package/src/file-input/index.js +1 -0
- package/src/file-input-field/__tests__/file-input-field.test.js +29 -0
- package/src/file-input-field/features/can_be_required/index.js +9 -0
- package/src/file-input-field/features/can_be_required.feature +5 -0
- package/src/file-input-field/features/has_default_button_label/index.js +9 -0
- package/src/file-input-field/features/has_default_button_label.feature +5 -0
- package/src/file-input-field/features/has_default_placeholder/index.js +9 -0
- package/src/file-input-field/features/has_default_placeholder.feature +5 -0
- package/src/file-input-field/file-input-field.e2e.stories.js +15 -0
- package/src/file-input-field/file-input-field.js +139 -0
- package/src/file-input-field/file-input-field.prod.stories.js +167 -0
- package/src/file-input-field/index.js +1 -0
- package/src/file-input-field-with-list/__tests__/file-input-field-with-list.test.js +30 -0
- package/src/file-input-field-with-list/features/common/index.js +18 -0
- package/src/file-input-field-with-list/features/deduplicates_the_file_list/index.js +43 -0
- package/src/file-input-field-with-list/features/deduplicates_the_file_list.feature +8 -0
- package/src/file-input-field-with-list/features/disables_button_when_full/index.js +13 -0
- package/src/file-input-field-with-list/features/disables_button_when_full.feature +6 -0
- package/src/file-input-field-with-list/features/displays_files_in_a_list/index.js +7 -0
- package/src/file-input-field-with-list/features/displays_files_in_a_list.feature +5 -0
- package/src/file-input-field-with-list/features/files_can_be_removed/index.js +24 -0
- package/src/file-input-field-with-list/features/files_can_be_removed.feature +7 -0
- package/src/file-input-field-with-list/features/has_default_button_label/index.js +9 -0
- package/src/file-input-field-with-list/features/has_default_button_label.feature +5 -0
- package/src/file-input-field-with-list/features/has_default_placeholder/index.js +9 -0
- package/src/file-input-field-with-list/features/has_default_placeholder.feature +5 -0
- package/src/file-input-field-with-list/features/has_default_remove_text/index.js +9 -0
- package/src/file-input-field-with-list/features/has_default_remove_text.feature +5 -0
- package/src/file-input-field-with-list/file-input-field-with-list.e2e.stories.js +73 -0
- package/src/file-input-field-with-list/file-input-field-with-list.js +188 -0
- package/src/file-input-field-with-list/file-input-field-with-list.prod.stories.js +67 -0
- package/src/file-input-field-with-list/file-list-item-with-remove.js +34 -0
- package/src/file-input-field-with-list/index.js +1 -0
- package/src/file-list/features/accepts_cancel_text/index.js +14 -0
- package/src/file-list/features/accepts_cancel_text.feature +5 -0
- package/src/file-list/features/accepts_label/index.js +11 -0
- package/src/file-list/features/accepts_label.feature +5 -0
- package/src/file-list/features/accepts_remove_text/index.js +11 -0
- package/src/file-list/features/accepts_remove_text.feature +5 -0
- package/src/file-list/features/can_be_removed/index.js +15 -0
- package/src/file-list/features/can_be_removed.feature +6 -0
- package/src/file-list/features/file-list-item-accepts_children/index.js +10 -0
- package/src/file-list/features/file-list-item-accepts_children.feature +5 -0
- package/src/file-list/features/file-list-placeholder-accepts_children/index.js +12 -0
- package/src/file-list/features/file-list-placeholder-accepts_children.feature +5 -0
- package/src/file-list/features/loading_can_be_cancelled/index.js +15 -0
- package/src/file-list/features/loading_can_be_cancelled.feature +6 -0
- package/src/file-list/file-list-item.e2e.stories.js +39 -0
- package/src/file-list/file-list-item.js +131 -0
- package/src/file-list/file-list-placeholder.e2e.stories.js +7 -0
- package/src/file-list/file-list-placeholder.js +27 -0
- package/src/file-list/file-list.e2e.stories.js +5 -0
- package/src/file-list/file-list.js +28 -0
- package/src/file-list/index.js +3 -0
- package/src/index.js +9 -0
- package/src/locales/ar/translations.json +5 -0
- package/src/locales/ar_IQ/translations.json +5 -0
- package/src/locales/ckb/translations.json +5 -0
- package/src/locales/cs/translations.json +5 -0
- package/src/locales/da/translations.json +5 -0
- package/src/locales/en/translations.json +5 -0
- package/src/locales/es/translations.json +5 -0
- package/src/locales/es_419/translations.json +5 -0
- package/src/locales/fr/translations.json +5 -0
- package/src/locales/hi_IN/translations.json +5 -0
- package/src/locales/id/translations.json +5 -0
- package/src/locales/index.js +82 -0
- package/src/locales/km/translations.json +5 -0
- package/src/locales/ko_KR/translations.json +5 -0
- package/src/locales/lo/translations.json +5 -0
- package/src/locales/my/translations.json +5 -0
- package/src/locales/nb/translations.json +5 -0
- package/src/locales/nl/translations.json +5 -0
- package/src/locales/prs/translations.json +5 -0
- package/src/locales/ps/translations.json +5 -0
- package/src/locales/pt/translations.json +5 -0
- package/src/locales/pt_BR/translations.json +5 -0
- package/src/locales/ru/translations.json +5 -0
- package/src/locales/si/translations.json +5 -0
- package/src/locales/sv/translations.json +5 -0
- package/src/locales/tet/translations.json +5 -0
- package/src/locales/tg/translations.json +5 -0
- package/src/locales/uk/translations.json +5 -0
- package/src/locales/ur/translations.json +5 -0
- package/src/locales/uz_Latn/translations.json +5 -0
- package/src/locales/uz_UZ_Cyrl/translations.json +5 -0
- package/src/locales/uz_UZ_Latn/translations.json +5 -0
- package/src/locales/vi/translations.json +5 -0
- package/src/locales/zh/translations.json +5 -0
- package/src/locales/zh_CN/translations.json +5 -0
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
import { sharedPropTypes } from '@dhis2/ui-constants'
|
|
2
|
+
import PropTypes from 'prop-types'
|
|
3
|
+
import React, { Component } from 'react'
|
|
4
|
+
import { FileInputField } from '../file-input-field/index.js'
|
|
5
|
+
import i18n from '../locales/index.js'
|
|
6
|
+
import { FileListItemWithRemove } from './file-list-item-with-remove.js'
|
|
7
|
+
|
|
8
|
+
// TODO: i18n
|
|
9
|
+
const translate = (prop, interpolationObject) => {
|
|
10
|
+
if (typeof prop === 'function') {
|
|
11
|
+
return prop(interpolationObject)
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
return prop
|
|
15
|
+
}
|
|
16
|
+
class FileInputFieldWithList extends Component {
|
|
17
|
+
handleChange = ({ files: fileList }, event) => {
|
|
18
|
+
const { onChange, name } = this.props
|
|
19
|
+
|
|
20
|
+
onChange({ files: this.updateFileArray(fileList), name: name }, event)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* @param {FileList} fileList
|
|
25
|
+
* @returns {File[]}
|
|
26
|
+
*/
|
|
27
|
+
updateFileArray(fileList) {
|
|
28
|
+
const { multiple, files } = this.props
|
|
29
|
+
// Spread immutable FileList instance onto array
|
|
30
|
+
const newFiles = [...fileList]
|
|
31
|
+
|
|
32
|
+
if (!multiple) {
|
|
33
|
+
return newFiles
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return files
|
|
37
|
+
.filter(
|
|
38
|
+
(file) =>
|
|
39
|
+
!newFiles.some(
|
|
40
|
+
(x) =>
|
|
41
|
+
x.name === file.name &&
|
|
42
|
+
x.lastModified === file.lastModified &&
|
|
43
|
+
x.size === file.size &&
|
|
44
|
+
x.type === file.type
|
|
45
|
+
)
|
|
46
|
+
)
|
|
47
|
+
.concat(newFiles)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
handleRemove = ({ file: fileToRemove }, event) => {
|
|
51
|
+
const { files, onChange, name } = this.props
|
|
52
|
+
|
|
53
|
+
onChange(
|
|
54
|
+
{
|
|
55
|
+
files: files.filter((file) => file !== fileToRemove),
|
|
56
|
+
name,
|
|
57
|
+
},
|
|
58
|
+
event
|
|
59
|
+
)
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
static defaultProps = {
|
|
63
|
+
accept: '*',
|
|
64
|
+
dataTest: 'dhis2-uiwidgets-fileinputfield',
|
|
65
|
+
|
|
66
|
+
buttonLabel: () => i18n.t('Upload a file'),
|
|
67
|
+
placeholder: () => i18n.t('No file uploaded yet'),
|
|
68
|
+
removeText: () => i18n.t('Remove'),
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
render() {
|
|
72
|
+
const {
|
|
73
|
+
accept,
|
|
74
|
+
buttonLabel = FileInputFieldWithList.defaultProps.buttonLabel,
|
|
75
|
+
className,
|
|
76
|
+
dataTest = 'dhis2-uiwidgets-fileinputfieldwithlist',
|
|
77
|
+
disabled,
|
|
78
|
+
error,
|
|
79
|
+
files = [],
|
|
80
|
+
helpText,
|
|
81
|
+
initialFocus,
|
|
82
|
+
label,
|
|
83
|
+
large,
|
|
84
|
+
multiple,
|
|
85
|
+
name,
|
|
86
|
+
onBlur,
|
|
87
|
+
onFocus,
|
|
88
|
+
onKeyDown,
|
|
89
|
+
placeholder = FileInputFieldWithList.defaultProps.placeholder,
|
|
90
|
+
removeText = FileInputFieldWithList.defaultProps.removeText,
|
|
91
|
+
required,
|
|
92
|
+
small,
|
|
93
|
+
tabIndex,
|
|
94
|
+
valid,
|
|
95
|
+
validationText,
|
|
96
|
+
warning,
|
|
97
|
+
} = this.props
|
|
98
|
+
|
|
99
|
+
return (
|
|
100
|
+
<FileInputField
|
|
101
|
+
accept={accept}
|
|
102
|
+
buttonLabel={translate(buttonLabel)}
|
|
103
|
+
className={className}
|
|
104
|
+
dataTest={dataTest}
|
|
105
|
+
disabled={disabled || (!multiple && files.length >= 1)}
|
|
106
|
+
error={error}
|
|
107
|
+
helpText={helpText}
|
|
108
|
+
initialFocus={initialFocus}
|
|
109
|
+
label={label}
|
|
110
|
+
large={large}
|
|
111
|
+
multiple={multiple}
|
|
112
|
+
name={name}
|
|
113
|
+
onBlur={onBlur}
|
|
114
|
+
onChange={this.handleChange}
|
|
115
|
+
onFocus={onFocus}
|
|
116
|
+
onKeyDown={onKeyDown}
|
|
117
|
+
placeholder={translate(placeholder)}
|
|
118
|
+
required={required}
|
|
119
|
+
small={small}
|
|
120
|
+
tabIndex={tabIndex}
|
|
121
|
+
valid={valid}
|
|
122
|
+
validationText={validationText}
|
|
123
|
+
warning={warning}
|
|
124
|
+
>
|
|
125
|
+
{files.length > 0 &&
|
|
126
|
+
files.map((file) => (
|
|
127
|
+
<FileListItemWithRemove
|
|
128
|
+
key={file.name}
|
|
129
|
+
label={file.name}
|
|
130
|
+
removeText={translate(removeText)}
|
|
131
|
+
onRemove={this.handleRemove}
|
|
132
|
+
file={file}
|
|
133
|
+
/>
|
|
134
|
+
))}
|
|
135
|
+
</FileInputField>
|
|
136
|
+
)
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
FileInputFieldWithList.propTypes = {
|
|
141
|
+
/** Called with signature `({ name: string, files: [File] }, event)` */
|
|
142
|
+
onChange: PropTypes.func.isRequired,
|
|
143
|
+
/** The `accept` attribute of the [native file input](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/file#accept) */
|
|
144
|
+
accept: PropTypes.string,
|
|
145
|
+
/** Text on the button */
|
|
146
|
+
buttonLabel: PropTypes.oneOfType([PropTypes.string, PropTypes.func]),
|
|
147
|
+
className: PropTypes.string,
|
|
148
|
+
dataTest: PropTypes.string,
|
|
149
|
+
/** Disables the button */
|
|
150
|
+
disabled: PropTypes.bool,
|
|
151
|
+
/** Applies 'error' styling to the button and validation text. Mutually exclusive with `warning` and `valid` props */
|
|
152
|
+
error: sharedPropTypes.statusPropType,
|
|
153
|
+
files: PropTypes.arrayOf(PropTypes.instanceOf(File)),
|
|
154
|
+
/** Useful guiding text for the user */
|
|
155
|
+
helpText: PropTypes.string,
|
|
156
|
+
initialFocus: PropTypes.bool,
|
|
157
|
+
/** A descriptive label above the button */
|
|
158
|
+
label: PropTypes.string,
|
|
159
|
+
/** Size of the button. Mutually exclusive with the `small` prop */
|
|
160
|
+
large: sharedPropTypes.sizePropType,
|
|
161
|
+
/** The `multiple` attribute of the [native file input](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/file#multiple) */
|
|
162
|
+
multiple: PropTypes.bool,
|
|
163
|
+
/** Name associated with input. Passed to event handler callbacks */
|
|
164
|
+
name: PropTypes.string,
|
|
165
|
+
/** Placeholder below the button */
|
|
166
|
+
placeholder: PropTypes.oneOfType([PropTypes.string, PropTypes.func]),
|
|
167
|
+
/** Text used for the button that removes a file from the list */
|
|
168
|
+
removeText: PropTypes.oneOfType([PropTypes.string, PropTypes.func]),
|
|
169
|
+
/** Adds an asterisk to indicate this field is required */
|
|
170
|
+
required: PropTypes.bool,
|
|
171
|
+
/** Size of the button. Mutually exclusive with the `large` prop */
|
|
172
|
+
small: sharedPropTypes.sizePropType,
|
|
173
|
+
tabIndex: PropTypes.string,
|
|
174
|
+
/** Applies 'valid' styling to the button and validation text. Mutually exclusive with `warning` and `error` props */
|
|
175
|
+
valid: sharedPropTypes.statusPropType,
|
|
176
|
+
/** Text below the button that provides validation feedback */
|
|
177
|
+
validationText: PropTypes.string,
|
|
178
|
+
/** Applies 'warning' styling to the button and validation text. Mutually exclusive with `valid` and `error` props */
|
|
179
|
+
warning: sharedPropTypes.statusPropType,
|
|
180
|
+
/** Called with signature `({ name: string, files: [] }, event)` */
|
|
181
|
+
onBlur: PropTypes.func,
|
|
182
|
+
/** Called with signature `({ name: string, files: [] }, event)` */
|
|
183
|
+
onFocus: PropTypes.func,
|
|
184
|
+
/** Called with signature `({ name: string, files: [] }, event)` */
|
|
185
|
+
onKeyDown: PropTypes.func,
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
export { FileInputFieldWithList }
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { sharedPropTypes } from '@dhis2/ui-constants'
|
|
2
|
+
import React from 'react'
|
|
3
|
+
import { FileInputFieldWithList } from './file-input-field-with-list.js'
|
|
4
|
+
|
|
5
|
+
const description = `
|
|
6
|
+
A FileInputField with logic for creating a dynamic list of removable files from an array of \`File\` objects.
|
|
7
|
+
|
|
8
|
+
\`\`\`js
|
|
9
|
+
import { FileInputFieldWithList } from '@dhis2/ui'
|
|
10
|
+
\`\`\`
|
|
11
|
+
`
|
|
12
|
+
|
|
13
|
+
const files = new Array(10)
|
|
14
|
+
.fill('dummy-file-name')
|
|
15
|
+
.map((name, i) => new File([], `${name}-${i + 1}.txt`))
|
|
16
|
+
|
|
17
|
+
const onChange = ({ files }) => {
|
|
18
|
+
console.log('files: ', files)
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export default {
|
|
22
|
+
title: 'File Input Field With List',
|
|
23
|
+
component: FileInputFieldWithList,
|
|
24
|
+
parameters: { docs: { description: { component: description } } },
|
|
25
|
+
args: {
|
|
26
|
+
// Handle default props bug (see Transfer stories)
|
|
27
|
+
dataTest: 'dhis2-uiwidgets-fileinputfieldwithlist',
|
|
28
|
+
files: [],
|
|
29
|
+
buttonLabel: 'Upload a file',
|
|
30
|
+
placeholder: 'No file uploaded yet',
|
|
31
|
+
removeText: 'Remove',
|
|
32
|
+
multiple: true,
|
|
33
|
+
onChange: onChange,
|
|
34
|
+
name: 'uploadName',
|
|
35
|
+
},
|
|
36
|
+
argTypes: {
|
|
37
|
+
small: { ...sharedPropTypes.sizeArgType },
|
|
38
|
+
large: { ...sharedPropTypes.sizeArgType },
|
|
39
|
+
valid: { ...sharedPropTypes.statusArgType },
|
|
40
|
+
warning: { ...sharedPropTypes.statusArgType },
|
|
41
|
+
error: { ...sharedPropTypes.statusArgType },
|
|
42
|
+
},
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const Template = (args) => <FileInputFieldWithList {...args} />
|
|
46
|
+
|
|
47
|
+
export const Default = Template.bind({})
|
|
48
|
+
Default.args = {
|
|
49
|
+
buttonLabel: 'Upload file (custom label)',
|
|
50
|
+
files: files,
|
|
51
|
+
removeText: 'Custom remove text',
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export const DefaultButtonLabelAndRemoveText = Template.bind({})
|
|
55
|
+
DefaultButtonLabelAndRemoveText.args = { files: files }
|
|
56
|
+
DefaultButtonLabelAndRemoveText.storyName =
|
|
57
|
+
'Default: buttonLabel and removeText'
|
|
58
|
+
|
|
59
|
+
export const DefaultPlaceholder = Template.bind({})
|
|
60
|
+
DefaultPlaceholder.storyName = 'Default: placeholder'
|
|
61
|
+
|
|
62
|
+
export const RTL = (args) => (
|
|
63
|
+
<div dir="rtl">
|
|
64
|
+
<Template {...args} />
|
|
65
|
+
</div>
|
|
66
|
+
)
|
|
67
|
+
RTL.args = { files: files }
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import PropTypes from 'prop-types'
|
|
2
|
+
import React, { Component } from 'react'
|
|
3
|
+
import { FileListItem } from '../file-list/index.js'
|
|
4
|
+
|
|
5
|
+
class FileListItemWithRemove extends Component {
|
|
6
|
+
handleRemove = (event) => {
|
|
7
|
+
const { onRemove, file } = this.props
|
|
8
|
+
|
|
9
|
+
onRemove({ file }, event)
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
render() {
|
|
13
|
+
const { label, removeText, className } = this.props
|
|
14
|
+
|
|
15
|
+
return (
|
|
16
|
+
<FileListItem
|
|
17
|
+
label={label}
|
|
18
|
+
removeText={removeText}
|
|
19
|
+
onRemove={this.handleRemove}
|
|
20
|
+
className={className}
|
|
21
|
+
/>
|
|
22
|
+
)
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
FileListItemWithRemove.propTypes = {
|
|
27
|
+
label: PropTypes.string.isRequired,
|
|
28
|
+
removeText: PropTypes.string.isRequired,
|
|
29
|
+
onRemove: PropTypes.func.isRequired,
|
|
30
|
+
className: PropTypes.string,
|
|
31
|
+
file: PropTypes.instanceOf(File),
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export { FileListItemWithRemove }
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { FileInputFieldWithList } from './file-input-field-with-list.js'
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { Given, Then } from '@badeball/cypress-cucumber-preprocessor'
|
|
2
|
+
|
|
3
|
+
Given(
|
|
4
|
+
'a loading FileListItem with onCancel handler and cancelText is rendered',
|
|
5
|
+
() => {
|
|
6
|
+
cy.visitStory('FileListItem', 'Loading with on cancel and cancel text')
|
|
7
|
+
}
|
|
8
|
+
)
|
|
9
|
+
|
|
10
|
+
Then('the cancelText will be visible', () => {
|
|
11
|
+
cy.get('[data-test="dhis2-uicore-filelistitem-cancel"]')
|
|
12
|
+
.contains('Cancel')
|
|
13
|
+
.should('be.visible')
|
|
14
|
+
})
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { Given, Then } from '@badeball/cypress-cucumber-preprocessor'
|
|
2
|
+
|
|
3
|
+
Given('a FileListItem with label is rendered', () => {
|
|
4
|
+
cy.visitStory('FileListItem', 'With label')
|
|
5
|
+
})
|
|
6
|
+
|
|
7
|
+
Then('the label will be visible', () => {
|
|
8
|
+
cy.get('[data-test="dhis2-uicore-filelistitem"]')
|
|
9
|
+
.contains('Label')
|
|
10
|
+
.should('be.visible')
|
|
11
|
+
})
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { Given, Then } from '@badeball/cypress-cucumber-preprocessor'
|
|
2
|
+
|
|
3
|
+
Given('a FileListItem with removeText is rendered', () => {
|
|
4
|
+
cy.visitStory('FileListItem', 'With remove text')
|
|
5
|
+
})
|
|
6
|
+
|
|
7
|
+
Then('the removeText will be visible', () => {
|
|
8
|
+
cy.get('[data-test="dhis2-uicore-filelistitem-remove"]')
|
|
9
|
+
.contains('Remove')
|
|
10
|
+
.should('be.visible')
|
|
11
|
+
})
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { Given, When, Then } from '@badeball/cypress-cucumber-preprocessor'
|
|
2
|
+
|
|
3
|
+
Given('a FileListItem with Onremove handler is rendered', () => {
|
|
4
|
+
cy.visitStory('FileListItem', 'With on remove')
|
|
5
|
+
})
|
|
6
|
+
|
|
7
|
+
When('the user clicks on the remove text', () => {
|
|
8
|
+
cy.get('[data-test="dhis2-uicore-filelistitem-remove"]').click()
|
|
9
|
+
})
|
|
10
|
+
|
|
11
|
+
Then('the onRemove handler is called', () => {
|
|
12
|
+
cy.window().should((win) => {
|
|
13
|
+
expect(win.onRemove).to.be.calledWith({})
|
|
14
|
+
})
|
|
15
|
+
})
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { Given, Then } from '@badeball/cypress-cucumber-preprocessor'
|
|
2
|
+
|
|
3
|
+
Given('a FileList with children is rendered', () => {
|
|
4
|
+
cy.visitStory('FileList', 'With children')
|
|
5
|
+
cy.get('[data-test="dhis2-uicore-filelist"]').should('be.visible')
|
|
6
|
+
})
|
|
7
|
+
|
|
8
|
+
Then('the children are visible', () => {
|
|
9
|
+
cy.contains('I am a child').should('be.visible')
|
|
10
|
+
})
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { Given, Then } from '@badeball/cypress-cucumber-preprocessor'
|
|
2
|
+
|
|
3
|
+
Given('a FileListPlaceholder with children is rendered', () => {
|
|
4
|
+
cy.visitStory('FileListPlaceholder', 'With children')
|
|
5
|
+
cy.get('[data-test="dhis2-uicore-filelistplaceholder"]').should(
|
|
6
|
+
'be.visible'
|
|
7
|
+
)
|
|
8
|
+
})
|
|
9
|
+
|
|
10
|
+
Then('the children are visible', () => {
|
|
11
|
+
cy.contains('I am a child').should('be.visible')
|
|
12
|
+
})
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { Given, When, Then } from '@badeball/cypress-cucumber-preprocessor'
|
|
2
|
+
|
|
3
|
+
Given('a loading FileListItem with onCancel handler is rendered', () => {
|
|
4
|
+
cy.visitStory('FileListItem', 'Loading with on cancel')
|
|
5
|
+
})
|
|
6
|
+
|
|
7
|
+
When('the user clicks on the cancel text', () => {
|
|
8
|
+
cy.get('[data-test="dhis2-uicore-filelistitem-cancel"]').click()
|
|
9
|
+
})
|
|
10
|
+
|
|
11
|
+
Then('the onCancel handler is called', () => {
|
|
12
|
+
cy.window().should((win) => {
|
|
13
|
+
expect(win.onCancel).to.be.calledWith({})
|
|
14
|
+
})
|
|
15
|
+
})
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import { FileListItem } from './file-list-item.js'
|
|
3
|
+
|
|
4
|
+
window.onRemove = window.Cypress && window.Cypress.cy.stub()
|
|
5
|
+
window.onCancel = window.Cypress && window.Cypress.cy.stub()
|
|
6
|
+
|
|
7
|
+
export default { title: 'FileListItem' }
|
|
8
|
+
export const WithOnRemove = () => (
|
|
9
|
+
<FileListItem
|
|
10
|
+
label="File list item"
|
|
11
|
+
onRemove={window.onRemove}
|
|
12
|
+
removeText="Remove"
|
|
13
|
+
/>
|
|
14
|
+
)
|
|
15
|
+
export const LoadingWithOnCancel = () => (
|
|
16
|
+
<FileListItem
|
|
17
|
+
loading
|
|
18
|
+
label="File list item"
|
|
19
|
+
removeText="Remove"
|
|
20
|
+
onCancel={window.onCancel}
|
|
21
|
+
cancelText="Cancel"
|
|
22
|
+
/>
|
|
23
|
+
)
|
|
24
|
+
export const LoadingWithOnCancelAndCancelText = () => (
|
|
25
|
+
<FileListItem
|
|
26
|
+
loading
|
|
27
|
+
label="File list item"
|
|
28
|
+
removeText="Remove"
|
|
29
|
+
onCancel={() => {}}
|
|
30
|
+
onRemove={() => {}}
|
|
31
|
+
cancelText="Cancel"
|
|
32
|
+
/>
|
|
33
|
+
)
|
|
34
|
+
export const WithLabel = () => (
|
|
35
|
+
<FileListItem label="Label" removeText="Remove" onRemove={() => {}} />
|
|
36
|
+
)
|
|
37
|
+
export const WithRemoveText = () => (
|
|
38
|
+
<FileListItem label="Label" removeText="Remove" onRemove={() => {}} />
|
|
39
|
+
)
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import { theme, colors, spacers } from '@dhis2/ui-constants'
|
|
2
|
+
import { IconAttachment16 } from '@dhis2/ui-icons'
|
|
3
|
+
import { CircularLoader } from '@dhis2-ui/loader'
|
|
4
|
+
import cx from 'classnames'
|
|
5
|
+
import PropTypes from 'prop-types'
|
|
6
|
+
import React from 'react'
|
|
7
|
+
|
|
8
|
+
const FileListItem = ({
|
|
9
|
+
className,
|
|
10
|
+
label,
|
|
11
|
+
onRemove,
|
|
12
|
+
removeText,
|
|
13
|
+
loading,
|
|
14
|
+
onCancel,
|
|
15
|
+
cancelText,
|
|
16
|
+
dataTest = 'dhis2-uicore-filelistitem',
|
|
17
|
+
}) => {
|
|
18
|
+
const handleKeyDown = (event) => {
|
|
19
|
+
if (!onRemove) {
|
|
20
|
+
return
|
|
21
|
+
}
|
|
22
|
+
if (
|
|
23
|
+
event.key === 'Enter' ||
|
|
24
|
+
event.key === 'Backspace' ||
|
|
25
|
+
event.key === 'Delete'
|
|
26
|
+
) {
|
|
27
|
+
onRemove({}, event)
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return (
|
|
32
|
+
<p className={cx('selected-file', className)} data-test={dataTest}>
|
|
33
|
+
<span className="icon">
|
|
34
|
+
{loading ? (
|
|
35
|
+
<CircularLoader extrasmall />
|
|
36
|
+
) : (
|
|
37
|
+
<IconAttachment16 color={colors.grey700} />
|
|
38
|
+
)}
|
|
39
|
+
</span>
|
|
40
|
+
|
|
41
|
+
<span className="text">
|
|
42
|
+
<span className="label">{label}</span>
|
|
43
|
+
|
|
44
|
+
{loading && onCancel && cancelText && (
|
|
45
|
+
<span
|
|
46
|
+
className="action"
|
|
47
|
+
onClick={(event) => onCancel({}, event)}
|
|
48
|
+
data-test={`${dataTest}-cancel`}
|
|
49
|
+
>
|
|
50
|
+
{cancelText}
|
|
51
|
+
</span>
|
|
52
|
+
)}
|
|
53
|
+
|
|
54
|
+
{!loading && (
|
|
55
|
+
<span
|
|
56
|
+
className="action"
|
|
57
|
+
onClick={(event) => onRemove({}, event)}
|
|
58
|
+
data-test={`${dataTest}-remove`}
|
|
59
|
+
tabIndex={0}
|
|
60
|
+
onKeyDown={handleKeyDown}
|
|
61
|
+
>
|
|
62
|
+
{removeText}
|
|
63
|
+
</span>
|
|
64
|
+
)}
|
|
65
|
+
</span>
|
|
66
|
+
|
|
67
|
+
<style jsx>{`
|
|
68
|
+
p {
|
|
69
|
+
display: flex;
|
|
70
|
+
margin: 0;
|
|
71
|
+
padding-top: ${spacers.dp4};
|
|
72
|
+
}
|
|
73
|
+
span {
|
|
74
|
+
display: inline-block;
|
|
75
|
+
}
|
|
76
|
+
.icon {
|
|
77
|
+
margin-inline-end: 4px;
|
|
78
|
+
flex-grow: 0;
|
|
79
|
+
flex-shrink: 0;
|
|
80
|
+
display: flex;
|
|
81
|
+
padding-top: 1px;
|
|
82
|
+
}
|
|
83
|
+
.text {
|
|
84
|
+
flex-grow: 1;
|
|
85
|
+
flex-shrink: 1;
|
|
86
|
+
}
|
|
87
|
+
.label {
|
|
88
|
+
font-size: 14px;
|
|
89
|
+
color: ${colors.grey900};
|
|
90
|
+
word-break: break-all;
|
|
91
|
+
}
|
|
92
|
+
.label::after {
|
|
93
|
+
content: ' ';
|
|
94
|
+
display: inline-block;
|
|
95
|
+
width: 12px;
|
|
96
|
+
}
|
|
97
|
+
.action {
|
|
98
|
+
font-size: 12px;
|
|
99
|
+
line-height: 12px;
|
|
100
|
+
margin-top: 0;
|
|
101
|
+
text-decoration: underline;
|
|
102
|
+
cursor: pointer;
|
|
103
|
+
color: ${colors.grey700};
|
|
104
|
+
}
|
|
105
|
+
.action:hover {
|
|
106
|
+
color: ${colors.red700};
|
|
107
|
+
}
|
|
108
|
+
.action:active {
|
|
109
|
+
color: ${colors.red800};
|
|
110
|
+
}
|
|
111
|
+
.action:focus {
|
|
112
|
+
outline: 3px solid ${theme.focus};
|
|
113
|
+
outline-offset: 2px;
|
|
114
|
+
}
|
|
115
|
+
`}</style>
|
|
116
|
+
</p>
|
|
117
|
+
)
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
FileListItem.propTypes = {
|
|
121
|
+
onRemove: PropTypes.func.isRequired,
|
|
122
|
+
cancelText: PropTypes.string,
|
|
123
|
+
className: PropTypes.string,
|
|
124
|
+
dataTest: PropTypes.string,
|
|
125
|
+
label: PropTypes.string,
|
|
126
|
+
loading: PropTypes.bool,
|
|
127
|
+
removeText: PropTypes.string,
|
|
128
|
+
onCancel: PropTypes.func,
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export { FileListItem }
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { colors, spacers } from '@dhis2/ui-constants'
|
|
2
|
+
import PropTypes from 'prop-types'
|
|
3
|
+
import React from 'react'
|
|
4
|
+
|
|
5
|
+
const FileListPlaceholder = ({
|
|
6
|
+
children,
|
|
7
|
+
dataTest = 'dhis2-uicore-filelistplaceholder',
|
|
8
|
+
}) => (
|
|
9
|
+
<p data-test={dataTest}>
|
|
10
|
+
{children}
|
|
11
|
+
<style jsx>{`
|
|
12
|
+
p {
|
|
13
|
+
margin: 0;
|
|
14
|
+
padding-top: ${spacers.dp4};
|
|
15
|
+
font-size: 14px;
|
|
16
|
+
color: ${colors.grey700};
|
|
17
|
+
}
|
|
18
|
+
`}</style>
|
|
19
|
+
</p>
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
FileListPlaceholder.propTypes = {
|
|
23
|
+
children: PropTypes.string,
|
|
24
|
+
dataTest: PropTypes.string,
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export { FileListPlaceholder }
|