Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

File picker component #50

Closed
wants to merge 61 commits into from
Closed
Show file tree
Hide file tree
Changes from 18 commits
Commits
Show all changes
61 commits
Select commit Hold shift + click to select a range
a620ed7
Bump react as hooks weren't supported until 16.8 and we're generally …
roblevintennis Aug 9, 2019
3c67b3c
Initial scaffolding for file picker. It actually does accept files an…
roblevintennis Aug 9, 2019
41b7799
Merge github.com:mavenlink/design-system into file-picker-component
roblevintennis Aug 12, 2019
4d2c737
Remove needless import
roblevintennis Aug 12, 2019
2d60700
Fix the issue with file open dialog opening twice on a single click
roblevintennis Aug 12, 2019
8e8dd01
Single and multiple file select examples
roblevintennis Aug 12, 2019
049606a
Adds single and multi examples
roblevintennis Aug 12, 2019
d29d361
Cleanup
roblevintennis Aug 12, 2019
eed85ae
styleguidist 9.0.4 still works. Higher versions use React.Fragment <>…
roblevintennis Aug 12, 2019
e413e3a
Removeing label onClick which appears not to be semantic / accessible…
roblevintennis Aug 12, 2019
06eae27
Adds type sizes
roblevintennis Aug 12, 2019
83b1dda
Starting to spike on Nathan's design a bit
roblevintennis Aug 12, 2019
73bc6bf
Looks so much nicer once you get the dashed border on it
roblevintennis Aug 12, 2019
10dc2cb
Fix lint issues
roblevintennis Aug 13, 2019
e695c00
Remove the background and border now that we're using a <button> for …
roblevintennis Aug 13, 2019
787c38a
Deal with case where the user clicks the 'Cancel' button on the nati…
roblevintennis Aug 13, 2019
c4cbfe1
Upgrade react-test-renderer / enzyme to support Hooks. Adds @testing-…
roblevintennis Aug 13, 2019
9e3e4dc
Adds onBeforeFileRemoved prop. Adds initial spec.
roblevintennis Aug 13, 2019
ac3b071
API coverage
roblevintennis Aug 13, 2019
b03352f
Fix lint issues
roblevintennis Aug 13, 2019
aa8cd17
Adds some functional testing too
roblevintennis Aug 14, 2019
76641bd
Lint fixes
roblevintennis Aug 14, 2019
34842bd
Adds the upload and file icons
roblevintennis Aug 14, 2019
f6c4fa6
Adds the new neutral palette colors for fills and strokes on icons
roblevintennis Aug 14, 2019
e747109
Adds upload and file icons to the file picker. Fix extra line in icon…
roblevintennis Aug 14, 2019
2336b2e
Proper styling for the upload icon
roblevintennis Aug 14, 2019
c5a3b32
Corresponding stroke-none added to icon component
roblevintennis Aug 14, 2019
2bd7480
Adds SVGs to the Jest moduleNameMapper to prevent unrecognized file i…
roblevintennis Aug 14, 2019
8cece36
Implements receiveFilesChangedUpdates so that the user of the file pi…
roblevintennis Aug 14, 2019
69ac678
Code cleanup
roblevintennis Aug 14, 2019
fcabf67
Adds a nice example of how to use the receiveFilesChangedUpdates call…
roblevintennis Aug 14, 2019
0ff2cc4
Renamed the callback prop to receiveFilesChanged and improve the docs.
roblevintennis Aug 14, 2019
1d307ac
Snagging the accessible colors caution red
roblevintennis Aug 15, 2019
6c7ef0f
Adds a way to inject error messaging to the file picker. If you resel…
roblevintennis Aug 15, 2019
556655f
Spec for Error API
roblevintennis Aug 15, 2019
4ee18c1
rename error to errorMessage to prevent naming collision
roblevintennis Aug 15, 2019
503fee1
Extract out the error handling into its own useError hook.
roblevintennis Aug 15, 2019
4f002a3
console log the actual File API files which may be a bit more interes…
roblevintennis Aug 15, 2019
dc99309
Adds accessibility. The 'dropzone' area is now tabbable and you can a…
roblevintennis Aug 16, 2019
41e67a7
Some lint based tweaks
roblevintennis Aug 16, 2019
3d09374
jsx-a11y override...role button for file-picker's label allows it to …
roblevintennis Aug 16, 2019
ce995a6
And we have draggable drop-zone folks
roblevintennis Aug 16, 2019
c7a8d84
Update eslint
roblevintennis Aug 16, 2019
73073ce
Cleanup the file picker and fix the props.dropzoneClasses failures
roblevintennis Aug 16, 2019
f630d40
Refactors the error handling. useError now will take a validator func…
roblevintennis Aug 19, 2019
e39ac94
Indicate in the documentation that you can either use the file picker…
roblevintennis Aug 19, 2019
368d50a
Prune todo comment
roblevintennis Aug 19, 2019
89d8a32
Italics are doing weird things in styleguidist
roblevintennis Aug 19, 2019
d6a9b18
Cross browser solution (fixes Firefox issue where the file dialog doe…
roblevintennis Aug 19, 2019
68ee789
Favor using an override directive from top-level .eslintrc for each o…
roblevintennis Aug 20, 2019
e4404e6
Fixes issue with no highlight happening when you drag a file over the…
roblevintennis Aug 20, 2019
07b8b2f
Use an unordered list for the FileList since it's more semantic then …
roblevintennis Aug 20, 2019
5e5501b
Fix some lint issues
roblevintennis Aug 20, 2019
9bc41dd
Include file extensions on imports. Testing in the Visual Studio / cu…
roblevintennis Aug 21, 2019
eb7d938
Ensures we are always explicit about extensions to avoid overly confi…
juanca Aug 1, 2019
2fc31d2
Bumping
roblevintennis Aug 21, 2019
abe5880
Remove unused from cherry picking
roblevintennis Aug 21, 2019
87d3b49
Fixes another import without extension
roblevintennis Aug 21, 2019
9a80bd8
Fix typo for svgo
roblevintennis Aug 21, 2019
69af234
Fixes an issue with styleguidist per their issue tracker recommendations
roblevintennis Aug 21, 2019
8786ab7
Fix last of lint errors
roblevintennis Aug 21, 2019
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 10 additions & 8 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,14 +48,15 @@
"@babel/polyfill": "^7.4.4",
"@babel/preset-env": "^7.2.3",
"@babel/preset-react": "^7.0.0",
"@testing-library/react": "^9.1.1",
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Kent Dodds popular testing framework:
https://testing-library.com/docs/react-testing-library/intro

We're using this in bigmaven already and it's got some things that make it easier to test uncontrolled components too.

"babel-core": "^7.0.0-bridge.0",
"babel-eslint": "^10.0.1",
"babel-jest": "^23.6.0",
"babel-loader": "^8.0.5",
"css-loader": "^2.1.0",
"cypress": "^3.1.4",
"enzyme": "^3.8.0",
"enzyme-adapter-react-16": "^1.7.1",
"enzyme": "^3.10.0",
"enzyme-adapter-react-16": "^1.14.0",
"eslint": "^4.3.0",
"eslint-config-mavenlint-react": "^2.0.0",
"eslint-plugin-cypress": "^2.2.0",
Expand All @@ -64,19 +65,20 @@
"jest": "^23.6.0",
"postcss-loader": "^3.0.0",
"postcss-preset-env": "^6.5.0",
"react": "^16.7.0",
"react-dom": "^16.7.0",
"react": "^16.9.0",
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

React Hooks support started in 16.8 and also we might as well keep up (bigmaven is already using hooks for example!)

"react-dom": "^16.9.0",
"react-ga": "^2.5.6",
"react-styleguidist": "^8.0.6",
"react-styleguidist": "9.0.4",
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interestingly, we cannot upgrade more then this for the time being because we do not yet support the React.Fragment shorthand and they started using this in a couple of places 😛

"react-test-renderer": "^16.9.0",
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Upgraded some of these libraries to be compatible with React Hooks:
enzymejs/enzyme#1938

"react-transition-group": "^2.5.3",
"style-loader": "^0.23.1",
"svg-sprite-loader": "^3.8.0",
"svgo": "^1.3,0",
"svgo-loader": "^2.1.0",
"stylelint": "^10.1.0",
"stylelint-config-css-modules": "^1.4.0",
"stylelint-config-standard": "^18.3.0",
"stylelint-css-modules": "^0.9.0",
"svg-sprite-loader": "^3.8.0",
"svgo": "^1.3,0",
"svgo-loader": "^2.1.0",
"url-loader": "^1.1.2",
"wait-on": "^3.2.0",
"webpack": "^4.28.3"
Expand Down
94 changes: 94 additions & 0 deletions src/components/file-picker/file-picker.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
@import "../../styles/colors.css";
@import "../../styles/spacing.css";
@import "../../styles/typography.css";

.title,
.label,
.filename,
.remove {
font-family: var(--mavenlink-type-font-family);
}

.dropzone {
display: flex;
align-items: center;
justify-content: center;
width: 100%;
text-align: center;
background: var(--palette-neutral-x-light);
min-height: 100px;
vertical-align: middle;
border: 2px dashed var(--palette-grey-light);
}

.title,
.label {
display: block;
margin: 0 0 var(--spacing-small);
color: var(--palette-grey-dark);
cursor: pointer;
}

.filename,
.label {
font-size: var(--mavenlink-type-base-size);
}

.label {
flex: 1;
line-height: 100px;
}

.title {
font-size: var(--mavenlink-type-small-size);
}

.file {
position: absolute;
width: 1px;
height: 1px;
margin: -1px;
border: 0;
padding: 0;
clip: rect(0 0 0 0);
overflow: hidden;
}

.file:focus + .label {
outline: 1px dotted var(--palette-grey-dark);
outline: -webkit-focus-ring-color auto 5px;
}

.file-list {
display: flex;
flex-direction: column;
}

.file-list-button {
display: flex;
flex-direction: row;
border-bottom: 1px solid var(--palette-grey-light);
line-height: var(--spacing-x-large);
}

.icon {
color: green;
flex: 0 0 50px;
}

.filename {
flex-grow: 1;
text-align: left;
color: var(--palette-grey-x-dark);
}

.remove {
flex: 0 0 50px;
cursor: pointer;
background: transparent;
border: none;
font-weight: bold;
color: var(--palette-grey-base);
text-align: center;
font-size: 18px;
}
105 changes: 105 additions & 0 deletions src/components/file-picker/file-picker.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
import PropTypes from 'prop-types';
import React, { useState, useRef } from 'react';
import styles from './file-picker.css';

// TODOs
//
// What file types will we support?
// https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/file#Unique_file_type_specifiers
//
// How do we want to handle disabling as file(s) is uploading?
//
// Do we want to allow overriding of: onFilesChanged and onRemoveFile
// Or, do we want to have onBeforeFilesChanged, onAfterFilesChanged?
//
// v2 supports drag n drop?
// https://developer.mozilla.org/en-US/docs/Web/API/File/Using_files_from_web_applications#Selecting_files_using_drag_and_drop
//
const FilePicker = (props) => {
const {
dropzoneClasses,
labelClasses,
fileClasses,
fileListClasses,
id,
title,
onBeforeFileRemoved,
...rest
} = props;

const [files, setFiles] = useState([]);
// const [uploading, setUploading] = useState(false);
const inputFile = useRef(null);

const onFilesChanged = (e) => {
const selectedFiles = e.currentTarget.files;
// User may have clicked 'Cancel' on the native file dialog
if (selectedFiles.length) {
const currentFiles = [];
Array.from(selectedFiles).map(file => currentFiles.push(file));
setFiles(currentFiles);
}
};

const onRemoveFile = (e, file) => {
e.preventDefault();
if (props.onBeforeFileRemoved) {
props.onBeforeFileRemoved.call(this, file);
}
const currentFiles = files.filter(f => f.name !== file.name);
setFiles(currentFiles);
};

const getFilesList = () => {
if (files.length) {
return files.map((file) => {
return (
<section className={styles['file-list-button']} key={file.name}>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's convert this list of <section>s into a list of <li>s

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in: 07b8b2f

<span className={styles.icon}>ICON</span>
<span className={styles.filename}>{file.name}</span>
<button onClick={e => onRemoveFile(e, file)} className={styles.remove}>&times;</button>
</section>
);
});
}
return '';
};

return (
<React.Fragment>
<span className={styles.title}>{props.title}</span>
<section className={props.fileListClasses}>
{getFilesList()}
</section>
<section className={props.dropzoneClasses}>
<label htmlFor={props.id} className={props.labelClasses}>Upload Files
<input onChange={e => onFilesChanged(e)} type="file" id={props.id} className={props.fileClasses} ref={inputFile} {...rest} />
</label>
</section>
</React.Fragment>
);
};

FilePicker.propTypes = {
dropzoneClasses: PropTypes.string,
labelClasses: PropTypes.string,
fileClasses: PropTypes.string,
fileListClasses: PropTypes.string,
id: PropTypes.string.isRequired,
title: PropTypes.string.isRequired,
multiple: PropTypes.string,
onBeforeFileRemoved: PropTypes.func,
};

FilePicker.defaultProps = {
dropzoneClasses: styles.dropzone,
labelClasses: styles.label,
fileClasses: styles.file,
fileListClasses: styles['file-list'],
id: undefined,
title: undefined,
multiple: undefined,
onBeforeFileRemoved: undefined,
};

export default FilePicker;
10 changes: 10 additions & 0 deletions src/components/file-picker/file-picker.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
Default usage

```jsx
<FilePicker id="le-picker-single" title="Attach Files" />
```
Multiple

```jsx
<FilePicker id="le-picker-multi" title="Attach Multiple Files" multiple='multiple' />
```
22 changes: 22 additions & 0 deletions src/components/file-picker/file-picker.test.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import React from 'react';
import { render, fireEvent, cleanup } from '@testing-library/react';
import renderer from 'react-test-renderer';
import FilePicker from './file-picker';

describe('FilePicker', () => {
describe('API', () => {
it('title', function () {
const expected = 'Attach Le Files';
const { getByText } = render(<FilePicker id="123" title={ expected } />);
expect(getByText(expected).textContent).toContain(expected);
});

it('multiple', function () {
const testRenderer = renderer.create(
<FilePicker multiple='multiple' id="123" title='yo' />
);
const testInstance = testRenderer.root;
expect(testInstance.findByType('input').props.multiple).toBe('multiple');
});
});
});
3 changes: 3 additions & 0 deletions src/components/file-picker/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import FilePicker from './file-picker';

export default FilePicker;
5 changes: 3 additions & 2 deletions src/styles/typography.css
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,10 @@
--mavenlink-type-font-weight: 400;
--mavenlink-type-font-color: var(--palette-neutral-x-dark);
--mavenlink-type-font-style: normal;
--mavenlink-type-base-size: 16px;
--mavenlink-type-button-font-size: 14px;
--mavenlink-type-body-font-size: 12px;
--mavenlink-type-base-size: 14px;
--mavenlink-type-small-size: 12px;
--mavenlink-type-button-font-size: var(--mavenlink-type-base-size);
--mavenlink-type-text-rendering: optimizeLegibility;
--mavenlink-type-line-height: 1.5;
--mavenlink-type-header-line-height: 1.2;
Expand Down
Loading