Skip to content

Commit

Permalink
feat(FileInput): Add FileInput component (#1021)
Browse files Browse the repository at this point in the history
cedricmessiant authored May 31, 2019

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
1 parent ab3f100 commit 90e07e7
Showing 7 changed files with 288 additions and 1 deletion.
3 changes: 2 additions & 1 deletion docs/styleguide.config.js
Original file line number Diff line number Diff line change
@@ -30,7 +30,8 @@ module.exports = {
'../react/Radio/index.jsx',
'../react/SelectBox/SelectBox.jsx',
'../react/Textarea/index.jsx',
'../react/Toggle/index.jsx'
'../react/Toggle/index.jsx',
'../react/FileInput/index.jsx'
]
},
{
41 changes: 41 additions & 0 deletions react/FileInput/Readme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
This component is used to display a customizable input file. It's just a wrapper of input[type=file] and props will be passed to it (so you can use `accept`, `multiple` and others to alter its behavior).

### Default

```jsx
<FileInput className="file-selector" onChange={console.log}>
<span role="button">Click me to choose file</span>
</FileInput>
```

You render what you want:

```jsx
<FileInput onChange={console.log}>
<Icon icon="file" role="button" />
</FileInput>
```

### Multiple files

```jsx
<FileInput multiple onChange={console.log}>
<span role="button">Click me to choose files</span>
</FileInput>
```

### Only accept images

```jsx
<FileInput accept="image/*" multiple onChange={console.log}>
<span>Click me to choose an image</span>
</FileInput>
```

### Simple input file

If you want a classic input file, just set `hidden` prop to `false`:

```jsx
<FileInput hidden={false} onChange={console.log} />
```
86 changes: 86 additions & 0 deletions react/FileInput/__snapshots__/index.spec.jsx.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`FileInput component should pass props to the input 1`] = `
<label
className="styles__c-file-input___YNZSh"
disabled={false}
>
<span
aria-controls="file-input-123"
onKeyPress={[Function]}
role="button"
tabIndex={0}
>
Click me
</span>
<input
accept="image/*"
hidden={true}
id="file-input-123"
multiple={false}
onChange={[Function]}
type="file"
/>
</label>
`;

exports[`FileInput component should render a default file selector 1`] = `
<label
className="styles__c-file-input___YNZSh"
disabled={false}
>
<input
hidden={false}
id="file-input-123"
multiple={false}
onChange={[Function]}
type="file"
/>
</label>
`;

exports[`FileInput component should render a disabled file selector 1`] = `
<label
className="styles__c-file-input___YNZSh"
disabled={true}
>
<span
aria-controls="file-input-123"
onKeyPress={[Function]}
role="button"
tabIndex={0}
>
Click me
</span>
<input
hidden={true}
id="file-input-123"
multiple={false}
onChange={[Function]}
type="file"
/>
</label>
`;

exports[`FileInput component should render a file selector 1`] = `
<label
className="styles__c-file-input___YNZSh"
disabled={false}
>
<span
aria-controls="file-input-123"
onKeyPress={[Function]}
role="button"
tabIndex={0}
>
Click me
</span>
<input
hidden={true}
id="file-input-123"
multiple={false}
onChange={[Function]}
type="file"
/>
</label>
`;
71 changes: 71 additions & 0 deletions react/FileInput/index.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import cx from 'classnames'
import React from 'react'
import PropTypes from 'prop-types'
import uniqueId from 'lodash/uniqueId'

import styles from './styles.styl'

const FileInput = ({
children,
className,
disabled,
hidden,
multiple,
onChange,
...inputProps
}) => {
const fileinputId = uniqueId('file-input')
const fileinputRef = React.createRef()
return (
<label
disabled={disabled}
className={cx(styles['c-file-input'], className)}
>
{React.Children.map(children, child =>
React.cloneElement(child, {
'aria-controls': fileinputId,
role: 'button',
tabIndex: 0,
onKeyPress: event => {
if (event.key === 'Enter' || event.key === ' ') {
event.preventDefault()
fileinputRef.current.click()
}
}
})
)}
<input
ref={fileinputRef}
id={fileinputId}
hidden={hidden}
type="file"
multiple={multiple}
onChange={e => {
if (multiple) {
onChange(Array.from(e.target.files))
} else {
onChange(Array.from(e.target.files)[0])
}
}}
{...inputProps}
/>
</label>
)
}

FileInput.propTypes = {
children: PropTypes.node,
className: PropTypes.string,
disabled: PropTypes.bool,
hidden: PropTypes.bool,
multiple: PropTypes.bool,
onChange: PropTypes.func.isRequired
}

FileInput.defaultProps = {
disabled: false,
hidden: true,
multiple: false
}

export default FileInput
85 changes: 85 additions & 0 deletions react/FileInput/index.spec.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import React from 'react'
import uniqueId from 'lodash/uniqueId'

import FileInput from './index'

jest.mock('lodash/uniqueId')

describe('FileInput component', () => {
const onChangeSpy = jest.fn()
const pic1 = new File(['aaaa'], 'pic1.jpg')
const pic2 = new File(['bbbb'], 'pic1.jpg')

beforeEach(() => {
jest.resetModules()
uniqueId.mockReturnValue('file-input-123')
})

afterEach(() => {
jest.restoreAllMocks()
})

it('should render a file selector', () => {
const component = shallow(
<FileInput onChange={onChangeSpy}>
<span>Click me</span>
</FileInput>
).getElement()
expect(component).toMatchSnapshot()
})

it('should render a default file selector', () => {
const component = shallow(
<FileInput hidden={false} onChange={onChangeSpy} />
).getElement()
expect(component).toMatchSnapshot()
})

it('should render a disabled file selector', () => {
const component = shallow(
<FileInput disabled onChange={onChangeSpy}>
<span>Click me</span>
</FileInput>
).getElement()
expect(component).toMatchSnapshot()
})

it('should pass props to the input', () => {
const component = shallow(
<FileInput accept="image/*" onChange={onChangeSpy}>
<span>Click me</span>
</FileInput>
).getElement()
expect(component).toMatchSnapshot()
})

it('should process selected file on change', () => {
const filelist = [pic1]
const component = shallow(
<FileInput accept="image/*" onChange={onChangeSpy}>
<span>Click me</span>
</FileInput>
)
component.find('input').simulate('change', {
target: {
files: filelist
}
})
expect(onChangeSpy).toHaveBeenCalledWith(pic1)
})

it('should process selected files on change if it is multiple', () => {
const filelist = [pic1, pic2]
const component = shallow(
<FileInput accept="image/*" multiple onChange={onChangeSpy}>
<span>Click me</span>
</FileInput>
)
component.find('input').simulate('change', {
target: {
files: filelist
}
})
expect(onChangeSpy).toHaveBeenCalledWith([pic1, pic2])
})
})
2 changes: 2 additions & 0 deletions react/FileInput/styles.styl
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
.c-file-input
cursor pointer
1 change: 1 addition & 0 deletions react/index.js
Original file line number Diff line number Diff line change
@@ -69,3 +69,4 @@ export { default as Infos } from './Infos'
export { default as AppIcon } from './AppIcon'
export { default as Filename } from './Filename'
export { default as Viewer } from './Viewer/ViewerExposer'
export { default as FileInput } from './FileInput'

0 comments on commit 90e07e7

Please sign in to comment.