@dhis2-ui/file-input 10.16.2 → 10.16.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dhis2-ui/file-input",
|
|
3
|
-
"version": "10.16.
|
|
3
|
+
"version": "10.16.3",
|
|
4
4
|
"description": "UI FileInput",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -34,19 +34,20 @@
|
|
|
34
34
|
},
|
|
35
35
|
"dependencies": {
|
|
36
36
|
"@dhis2/prop-types": "^3.1.2",
|
|
37
|
-
"@dhis2-ui/button": "10.16.
|
|
38
|
-
"@dhis2-ui/field": "10.16.
|
|
39
|
-
"@dhis2-ui/label": "10.16.
|
|
40
|
-
"@dhis2-ui/loader": "10.16.
|
|
41
|
-
"@dhis2-ui/status-icon": "10.16.
|
|
42
|
-
"@dhis2/ui-constants": "10.16.
|
|
43
|
-
"@dhis2/ui-icons": "10.16.
|
|
37
|
+
"@dhis2-ui/button": "10.16.3",
|
|
38
|
+
"@dhis2-ui/field": "10.16.3",
|
|
39
|
+
"@dhis2-ui/label": "10.16.3",
|
|
40
|
+
"@dhis2-ui/loader": "10.16.3",
|
|
41
|
+
"@dhis2-ui/status-icon": "10.16.3",
|
|
42
|
+
"@dhis2/ui-constants": "10.16.3",
|
|
43
|
+
"@dhis2/ui-icons": "10.16.3",
|
|
44
44
|
"classnames": "^2.3.1",
|
|
45
45
|
"prop-types": "^15.7.2"
|
|
46
46
|
},
|
|
47
47
|
"files": [
|
|
48
48
|
"build",
|
|
49
|
-
"types"
|
|
49
|
+
"types",
|
|
50
|
+
"src"
|
|
50
51
|
],
|
|
51
52
|
"devDependencies": {
|
|
52
53
|
"@dhis2/d2-i18n": "^1.1.0",
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { render, fireEvent, screen } from '@testing-library/react'
|
|
2
|
+
import React from 'react'
|
|
3
|
+
import { FileInput } from '../file-input.js'
|
|
4
|
+
|
|
5
|
+
describe('<FileInput />', () => {
|
|
6
|
+
it('should call the onKeyDown callback when provided', () => {
|
|
7
|
+
const onKeyDown = jest.fn()
|
|
8
|
+
|
|
9
|
+
render(
|
|
10
|
+
<FileInput
|
|
11
|
+
name="foo"
|
|
12
|
+
value="bar"
|
|
13
|
+
checked={false}
|
|
14
|
+
onKeyDown={onKeyDown}
|
|
15
|
+
/>
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
fireEvent.keyDown(screen.getByRole('button'), {})
|
|
19
|
+
|
|
20
|
+
expect(onKeyDown).toHaveBeenCalledTimes(1)
|
|
21
|
+
|
|
22
|
+
const input = screen.getByTestId('dhis2-uicore-fileinput-input')
|
|
23
|
+
expect(onKeyDown).toHaveBeenCalledWith(
|
|
24
|
+
{ name: 'foo', files: input.files },
|
|
25
|
+
expect.objectContaining({})
|
|
26
|
+
)
|
|
27
|
+
})
|
|
28
|
+
})
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { Given, When, Then } from '@badeball/cypress-cucumber-preprocessor'
|
|
2
|
+
|
|
3
|
+
Given('a FileInput with multiple and onChange handler is rendered', () => {
|
|
4
|
+
cy.visitStory('FileInput', 'With on change and multiple')
|
|
5
|
+
})
|
|
6
|
+
|
|
7
|
+
When('the user selected multiple files', () => {
|
|
8
|
+
cy.get('[data-test="dhis2-uicore-fileinput"] input').uploadMultipleFiles(
|
|
9
|
+
[
|
|
10
|
+
{ fileType: 'md', fixture: 'FileInput/file.md' },
|
|
11
|
+
{ fileType: 'txt', fixture: 'FileInput/file.txt' },
|
|
12
|
+
],
|
|
13
|
+
true
|
|
14
|
+
)
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
Then("the onChange handler's payload contains multiple files", () => {
|
|
18
|
+
cy.window().should((win) => {
|
|
19
|
+
const calls = win.onChange.getCalls()
|
|
20
|
+
const callArgs = calls[0].args
|
|
21
|
+
const payload = callArgs[0]
|
|
22
|
+
const files = payload.files
|
|
23
|
+
|
|
24
|
+
expect(files).to.have.lengthOf(2)
|
|
25
|
+
|
|
26
|
+
const file1 = files[0]
|
|
27
|
+
expect(file1.name).to.equal('file.md')
|
|
28
|
+
|
|
29
|
+
const file2 = files[1]
|
|
30
|
+
expect(file2.name).to.equal('file.txt')
|
|
31
|
+
})
|
|
32
|
+
})
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
Feature: The FileInput can handle multiple files
|
|
2
|
+
|
|
3
|
+
Scenario: The user selects multiple files
|
|
4
|
+
Given a FileInput with multiple and onChange handler is rendered
|
|
5
|
+
And the FileInput does not have any files
|
|
6
|
+
When the user selected multiple files
|
|
7
|
+
Then the onChange handler is called
|
|
8
|
+
Then the onChange handler's payload contains multiple files
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { Given, When, Then } from '@badeball/cypress-cucumber-preprocessor'
|
|
2
|
+
|
|
3
|
+
Given('an FileInput with initialFocus and onBlur handler is rendered', () => {
|
|
4
|
+
cy.visitStory('FileInput', 'With initial focus and on blur')
|
|
5
|
+
})
|
|
6
|
+
|
|
7
|
+
When('the FileInput is blurred', () => {
|
|
8
|
+
cy.focused()
|
|
9
|
+
cy.get('[data-test="dhis2-uicore-fileinput"] button').blur()
|
|
10
|
+
})
|
|
11
|
+
|
|
12
|
+
Then('the onBlur handler is called', () => {
|
|
13
|
+
cy.window().then((win) => {
|
|
14
|
+
cy.get('[data-test="dhis2-uicore-fileinput"] input').should(
|
|
15
|
+
(fileInput) => {
|
|
16
|
+
expect(win.onBlur).to.be.calledWith({
|
|
17
|
+
name: 'upload',
|
|
18
|
+
files: fileInput[0].files,
|
|
19
|
+
})
|
|
20
|
+
}
|
|
21
|
+
)
|
|
22
|
+
})
|
|
23
|
+
})
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { Given, When, Then } from '@badeball/cypress-cucumber-preprocessor'
|
|
2
|
+
|
|
3
|
+
Given('a FileInput with onChange handler is rendered', () => {
|
|
4
|
+
cy.visitStory('FileInput', 'With on change')
|
|
5
|
+
})
|
|
6
|
+
|
|
7
|
+
When('a file is selected', () => {
|
|
8
|
+
cy.get('[data-test="dhis2-uicore-fileinput"] input').uploadSingleFile(
|
|
9
|
+
'.md',
|
|
10
|
+
'FileInput/file.md'
|
|
11
|
+
)
|
|
12
|
+
})
|
|
13
|
+
|
|
14
|
+
Then("the onChange handler's payload contains the file", () => {
|
|
15
|
+
cy.window().should((win) => {
|
|
16
|
+
const calls = win.onChange.getCalls()
|
|
17
|
+
const callArgs = calls[0].args
|
|
18
|
+
const payload = callArgs[0]
|
|
19
|
+
const files = payload.files
|
|
20
|
+
|
|
21
|
+
expect(files).to.have.lengthOf(1)
|
|
22
|
+
|
|
23
|
+
const file1 = files[0]
|
|
24
|
+
expect(file1.name).to.equal('file.md')
|
|
25
|
+
})
|
|
26
|
+
})
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
Feature: The FIleInput has an onChange api
|
|
2
|
+
|
|
3
|
+
Scenario: The user selects a file
|
|
4
|
+
Given a FileInput with onChange handler is rendered
|
|
5
|
+
And the FileInput does not have any files
|
|
6
|
+
When a file is selected
|
|
7
|
+
Then the onChange handler is called
|
|
8
|
+
Then the onChange handler's payload contains the file
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { Given, When, Then } from '@badeball/cypress-cucumber-preprocessor'
|
|
2
|
+
|
|
3
|
+
Given('a FileInput with onFocus handler is rendered', () => {
|
|
4
|
+
cy.visitStory('FileInput', 'With on focus')
|
|
5
|
+
})
|
|
6
|
+
|
|
7
|
+
When('the FileInput is focused', () => {
|
|
8
|
+
cy.get('[data-test="dhis2-uicore-fileinput"] button').focus()
|
|
9
|
+
})
|
|
10
|
+
|
|
11
|
+
Then('the onFocus handler is called', () => {
|
|
12
|
+
cy.window().then((win) => {
|
|
13
|
+
cy.get('[data-test="dhis2-uicore-fileinput"] input').should(
|
|
14
|
+
(fileInput) => {
|
|
15
|
+
expect(win.onFocus).to.be.calledWith({
|
|
16
|
+
name: 'upload',
|
|
17
|
+
files: fileInput[0].files,
|
|
18
|
+
})
|
|
19
|
+
}
|
|
20
|
+
)
|
|
21
|
+
})
|
|
22
|
+
})
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { Given, Then } from '@badeball/cypress-cucumber-preprocessor'
|
|
2
|
+
|
|
3
|
+
Given('the FileInput does not have any files', () => {
|
|
4
|
+
cy.get('[data-test="dhis2-uicore-fileinput"] input').then(($input) => {
|
|
5
|
+
const files = $input[0].files
|
|
6
|
+
expect(files).to.have.lengthOf(0)
|
|
7
|
+
})
|
|
8
|
+
})
|
|
9
|
+
|
|
10
|
+
Then('the onChange handler is called', () => {
|
|
11
|
+
cy.window().should((win) => {
|
|
12
|
+
const calls = win.onChange.getCalls()
|
|
13
|
+
expect(calls).to.have.lengthOf(1)
|
|
14
|
+
|
|
15
|
+
const callArgs = calls[0].args
|
|
16
|
+
expect(callArgs).to.have.lengthOf(2)
|
|
17
|
+
|
|
18
|
+
const payload = callArgs[0]
|
|
19
|
+
expect(Object.keys(payload)).to.include.members(['files', 'name'])
|
|
20
|
+
})
|
|
21
|
+
})
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import { FileInput } from './index.js'
|
|
3
|
+
|
|
4
|
+
window.onBlur = window.Cypress && window.Cypress.cy.stub()
|
|
5
|
+
window.onFocus = window.Cypress && window.Cypress.cy.stub()
|
|
6
|
+
window.onChange = window.Cypress && window.Cypress.cy.stub()
|
|
7
|
+
|
|
8
|
+
const onChange = (payload, event) => {
|
|
9
|
+
// NOTE: if files is not transformed into an array,
|
|
10
|
+
// cypress will get an empty file list!
|
|
11
|
+
window.onChange(
|
|
12
|
+
{
|
|
13
|
+
...payload,
|
|
14
|
+
files: [...payload.files],
|
|
15
|
+
},
|
|
16
|
+
event
|
|
17
|
+
)
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export default { title: 'FileInput' }
|
|
21
|
+
export const WithOnChange = () => (
|
|
22
|
+
<FileInput onChange={onChange} buttonLabel="Upload file" name="upload" />
|
|
23
|
+
)
|
|
24
|
+
export const WithOnChangeAndMultiple = () => (
|
|
25
|
+
<FileInput
|
|
26
|
+
name="upload"
|
|
27
|
+
onChange={onChange}
|
|
28
|
+
buttonLabel="Upload files"
|
|
29
|
+
multiple
|
|
30
|
+
/>
|
|
31
|
+
)
|
|
32
|
+
export const WithInitialFocusAndOnBlur = () => (
|
|
33
|
+
<FileInput
|
|
34
|
+
buttonLabel="Upload file"
|
|
35
|
+
name="upload"
|
|
36
|
+
initialFocus
|
|
37
|
+
onBlur={window.onBlur}
|
|
38
|
+
/>
|
|
39
|
+
)
|
|
40
|
+
export const WithOnFocus = () => (
|
|
41
|
+
<FileInput
|
|
42
|
+
buttonLabel="Upload file"
|
|
43
|
+
name="upload"
|
|
44
|
+
onFocus={window.onFocus}
|
|
45
|
+
/>
|
|
46
|
+
)
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import { colors, spacers, sharedPropTypes } from '@dhis2/ui-constants'
|
|
2
|
+
import { IconUpload24 } from '@dhis2/ui-icons'
|
|
3
|
+
import { Button } from '@dhis2-ui/button'
|
|
4
|
+
import { StatusIcon } from '@dhis2-ui/status-icon'
|
|
5
|
+
import cx from 'classnames'
|
|
6
|
+
import PropTypes from 'prop-types'
|
|
7
|
+
import React, { createRef, Component } from 'react'
|
|
8
|
+
|
|
9
|
+
class FileInput extends Component {
|
|
10
|
+
static defaultProps = {
|
|
11
|
+
accept: '*',
|
|
12
|
+
dataTest: 'dhis2-uicore-fileinput',
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
ref = createRef()
|
|
16
|
+
|
|
17
|
+
handleClick = () => {
|
|
18
|
+
this.ref.current.click()
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
handleChange = (e) => {
|
|
22
|
+
if (this.props.onChange) {
|
|
23
|
+
this.props.onChange(this.createHandlerPayload(), e)
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// reset the file input so it won't prevent on-change
|
|
27
|
+
// if the same file was added in a second attempt
|
|
28
|
+
this.ref.current.value = ''
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
handleBlur = (e) => {
|
|
32
|
+
if (this.props.onBlur) {
|
|
33
|
+
this.props.onBlur(this.createHandlerPayload(), e)
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
handleFocus = (e) => {
|
|
38
|
+
if (this.props.onFocus) {
|
|
39
|
+
this.props.onFocus(this.createHandlerPayload(), e)
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
handleKeyDown = (e) => {
|
|
44
|
+
if (this.props.onKeyDown) {
|
|
45
|
+
this.props.onKeyDown(this.createHandlerPayload(), e)
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
createHandlerPayload() {
|
|
50
|
+
return {
|
|
51
|
+
files: this.ref.current.files,
|
|
52
|
+
name: this.props.name,
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
render() {
|
|
57
|
+
const {
|
|
58
|
+
accept = '*',
|
|
59
|
+
buttonLabel,
|
|
60
|
+
className,
|
|
61
|
+
dataTest = 'dhis2-uicore-fileinput',
|
|
62
|
+
disabled,
|
|
63
|
+
error,
|
|
64
|
+
initialFocus,
|
|
65
|
+
large,
|
|
66
|
+
multiple,
|
|
67
|
+
name,
|
|
68
|
+
small,
|
|
69
|
+
tabIndex,
|
|
70
|
+
valid,
|
|
71
|
+
warning,
|
|
72
|
+
} = this.props
|
|
73
|
+
|
|
74
|
+
return (
|
|
75
|
+
<div className={cx('file-input', className)} data-test={dataTest}>
|
|
76
|
+
<div>
|
|
77
|
+
<input
|
|
78
|
+
id={name}
|
|
79
|
+
name={name}
|
|
80
|
+
type="file"
|
|
81
|
+
ref={this.ref}
|
|
82
|
+
onChange={this.handleChange}
|
|
83
|
+
accept={accept}
|
|
84
|
+
multiple={multiple}
|
|
85
|
+
disabled={disabled}
|
|
86
|
+
data-test={`${dataTest}-input`}
|
|
87
|
+
/>
|
|
88
|
+
<Button
|
|
89
|
+
disabled={disabled}
|
|
90
|
+
icon={<IconUpload24 color={colors.grey700} />}
|
|
91
|
+
initialFocus={initialFocus}
|
|
92
|
+
large={large}
|
|
93
|
+
onBlur={this.handleBlur}
|
|
94
|
+
onClick={this.handleClick}
|
|
95
|
+
onFocus={this.handleFocus}
|
|
96
|
+
onKeyDown={this.handleKeyDown}
|
|
97
|
+
small={small}
|
|
98
|
+
tabIndex={tabIndex}
|
|
99
|
+
type="button"
|
|
100
|
+
>
|
|
101
|
+
{buttonLabel}
|
|
102
|
+
</Button>
|
|
103
|
+
</div>
|
|
104
|
+
<StatusIcon error={error} valid={valid} warning={warning} />
|
|
105
|
+
|
|
106
|
+
<style jsx>{`
|
|
107
|
+
input {
|
|
108
|
+
display: none;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
.file-input {
|
|
112
|
+
display: flex;
|
|
113
|
+
align-items: center;
|
|
114
|
+
gap: ${spacers.dp8};
|
|
115
|
+
padding-bottom: ${spacers.dp4};
|
|
116
|
+
}
|
|
117
|
+
`}</style>
|
|
118
|
+
</div>
|
|
119
|
+
)
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
FileInput.propTypes = {
|
|
124
|
+
/**
|
|
125
|
+
* The `accept` attribute of the [native file input](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/file#accept)
|
|
126
|
+
*/
|
|
127
|
+
accept: PropTypes.string,
|
|
128
|
+
buttonLabel: PropTypes.string,
|
|
129
|
+
className: PropTypes.string,
|
|
130
|
+
dataTest: PropTypes.string,
|
|
131
|
+
disabled: PropTypes.bool,
|
|
132
|
+
/** Input status. Mutually exclusive with `warning` and `valid` */
|
|
133
|
+
error: sharedPropTypes.statusPropType,
|
|
134
|
+
initialFocus: PropTypes.bool,
|
|
135
|
+
/** Button size. Mutually exclusive with `small` */
|
|
136
|
+
large: sharedPropTypes.sizePropType,
|
|
137
|
+
/**
|
|
138
|
+
* The `multiple` attribute of the [native file input](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/file#multiple)
|
|
139
|
+
*/
|
|
140
|
+
multiple: PropTypes.bool,
|
|
141
|
+
name: PropTypes.string,
|
|
142
|
+
/** Button size. Mutually exclusive with `large` */
|
|
143
|
+
small: sharedPropTypes.sizePropType,
|
|
144
|
+
tabIndex: PropTypes.string,
|
|
145
|
+
/** Input status. Mutually exclusive with `warning` and `error` */
|
|
146
|
+
valid: sharedPropTypes.statusPropType,
|
|
147
|
+
/** Input status. Mutually exclusive with `valid` and `error` */
|
|
148
|
+
warning: sharedPropTypes.statusPropType,
|
|
149
|
+
/** Called with signature `(object, event)` */
|
|
150
|
+
onBlur: PropTypes.func,
|
|
151
|
+
/** Called with signature `(object, event)` */
|
|
152
|
+
onChange: PropTypes.func,
|
|
153
|
+
/** Called with signature `(object, event)` */
|
|
154
|
+
onFocus: PropTypes.func,
|
|
155
|
+
/** Called with signature `(object, event)` */
|
|
156
|
+
onKeyDown: PropTypes.func,
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
export { FileInput }
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { sharedPropTypes } from '@dhis2/ui-constants'
|
|
2
|
+
import React from 'react'
|
|
3
|
+
import { FileInput } from './index.js'
|
|
4
|
+
|
|
5
|
+
const subtitle = `The file input component allows users to select and upload files from their local machine.`
|
|
6
|
+
|
|
7
|
+
const description = `
|
|
8
|
+
Use a file input component in forms and interfaces wherever a user needs to be able to select and upload a file from their local machine.
|
|
9
|
+
|
|
10
|
+
\`\`\`js
|
|
11
|
+
import { FileInput } from '@dhis2/ui'
|
|
12
|
+
\`\`\`
|
|
13
|
+
`
|
|
14
|
+
|
|
15
|
+
const onChange = (payload, event) => {
|
|
16
|
+
console.log('onChange payload', payload)
|
|
17
|
+
console.log('onChange event', event)
|
|
18
|
+
|
|
19
|
+
// NOTE: if files is not transformed into an array,
|
|
20
|
+
// cypress will get an empty file list!
|
|
21
|
+
window.onChange &&
|
|
22
|
+
window.onChange(
|
|
23
|
+
{
|
|
24
|
+
...payload,
|
|
25
|
+
files: [...payload.files],
|
|
26
|
+
},
|
|
27
|
+
event
|
|
28
|
+
)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const { sizeArgType, statusArgType } = sharedPropTypes
|
|
32
|
+
|
|
33
|
+
export default {
|
|
34
|
+
title: 'File Input',
|
|
35
|
+
component: FileInput,
|
|
36
|
+
// Default args for each story unless overridden
|
|
37
|
+
args: { buttonLabel: 'Upload file', name: 'upload', onChange },
|
|
38
|
+
argTypes: {
|
|
39
|
+
valid: { ...statusArgType },
|
|
40
|
+
warning: { ...statusArgType },
|
|
41
|
+
error: { ...statusArgType },
|
|
42
|
+
small: { ...sizeArgType },
|
|
43
|
+
large: { ...sizeArgType },
|
|
44
|
+
},
|
|
45
|
+
parameters: {
|
|
46
|
+
componentSubtitle: subtitle,
|
|
47
|
+
docs: { description: { component: description } },
|
|
48
|
+
},
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const Template = (args) => <FileInput {...args} />
|
|
52
|
+
|
|
53
|
+
export const Default = Template.bind({})
|
|
54
|
+
|
|
55
|
+
export const Multiple = Template.bind({})
|
|
56
|
+
Multiple.args = { multiple: true, buttonLabel: 'Upload files' }
|
|
57
|
+
|
|
58
|
+
export const Disabled = Template.bind({})
|
|
59
|
+
Disabled.args = { disabled: true }
|
|
60
|
+
|
|
61
|
+
export const Sizes = (args) => (
|
|
62
|
+
<>
|
|
63
|
+
<FileInput {...args} buttonLabel="Default size" name="default" />
|
|
64
|
+
<FileInput {...args} small buttonLabel="Small" name="small" />
|
|
65
|
+
<FileInput {...args} large buttonLabel="Large" name="large" />
|
|
66
|
+
</>
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
export const Statuses = (args) => (
|
|
70
|
+
<>
|
|
71
|
+
<FileInput {...args} buttonLabel="Default" name="default" />
|
|
72
|
+
<FileInput {...args} buttonLabel="Valid" name="valid" valid />
|
|
73
|
+
<FileInput {...args} buttonLabel="Warning" name="warning" warning />
|
|
74
|
+
<FileInput {...args} buttonLabel="Error" name="error" error />
|
|
75
|
+
</>
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
export const RTL = (args) => (
|
|
79
|
+
<div dir="rtl">
|
|
80
|
+
<FileInput {...args} buttonLabel="Default" name="default" />
|
|
81
|
+
</div>
|
|
82
|
+
)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { FileInput } from './file-input.js'
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { render, fireEvent, screen } from '@testing-library/react'
|
|
2
|
+
import React from 'react'
|
|
3
|
+
import { FileInputField } from '../file-input-field.js'
|
|
4
|
+
|
|
5
|
+
describe('<FileInputField />', () => {
|
|
6
|
+
it('should call the onKeyDown callback when provided', () => {
|
|
7
|
+
const onKeyDown = jest.fn()
|
|
8
|
+
|
|
9
|
+
render(
|
|
10
|
+
<FileInputField
|
|
11
|
+
label="Label"
|
|
12
|
+
name="foo"
|
|
13
|
+
value="bar"
|
|
14
|
+
checked={false}
|
|
15
|
+
onKeyDown={onKeyDown}
|
|
16
|
+
/>
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
fireEvent.keyDown(screen.getByRole('button'), {})
|
|
20
|
+
|
|
21
|
+
expect(onKeyDown).toHaveBeenCalledTimes(1)
|
|
22
|
+
|
|
23
|
+
const input = screen.getByTestId('dhis2-uicore-fileinput-input')
|
|
24
|
+
expect(onKeyDown).toHaveBeenCalledWith(
|
|
25
|
+
{ name: 'foo', files: input.files },
|
|
26
|
+
expect.objectContaining({})
|
|
27
|
+
)
|
|
28
|
+
})
|
|
29
|
+
})
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { Given, Then } from '@badeball/cypress-cucumber-preprocessor'
|
|
2
|
+
|
|
3
|
+
Given('a FileInputField with label and a required flag is rendered', () => {
|
|
4
|
+
cy.visitStory('FileInputField', 'With label and required')
|
|
5
|
+
})
|
|
6
|
+
|
|
7
|
+
Then('the required indicator is visible', () => {
|
|
8
|
+
cy.get('[data-test="dhis2-uicore-label-required"]').should('be.visible')
|
|
9
|
+
})
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { Given, Then } from '@badeball/cypress-cucumber-preprocessor'
|
|
2
|
+
|
|
3
|
+
Given('a default FileInputField is rendered', () => {
|
|
4
|
+
cy.visitStory('FileInputField', 'Default')
|
|
5
|
+
})
|
|
6
|
+
|
|
7
|
+
Then('the default button label is visible', () => {
|
|
8
|
+
cy.contains('Upload a file').should('be.visible')
|
|
9
|
+
})
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { Given, Then } from '@badeball/cypress-cucumber-preprocessor'
|
|
2
|
+
|
|
3
|
+
Given('a default FileInputField is rendered', () => {
|
|
4
|
+
cy.visitStory('FileInputField', 'Default')
|
|
5
|
+
})
|
|
6
|
+
|
|
7
|
+
Then('the default placeholder is visible', () => {
|
|
8
|
+
cy.contains('No file uploaded yet').should('be.visible')
|
|
9
|
+
})
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import { FileInputField } from './file-input-field.js'
|
|
3
|
+
|
|
4
|
+
export default { title: 'FileInputField' }
|
|
5
|
+
export const WithLabelAndRequired = () => (
|
|
6
|
+
<FileInputField
|
|
7
|
+
name="upload"
|
|
8
|
+
label="upload something"
|
|
9
|
+
buttonLabel="Upload file"
|
|
10
|
+
required
|
|
11
|
+
/>
|
|
12
|
+
)
|
|
13
|
+
export const Default = () => (
|
|
14
|
+
<FileInputField name="upload" label="upload something" />
|
|
15
|
+
)
|