wix-style-react
Version:
158 lines (131 loc) • 4.53 kB
JavaScript
import React from 'react';
import PropTypes from 'prop-types';
import { buildChildrenObject } from 'wix-ui-core/dist/src/utils';
import { dataHooks } from './constants';
import { st, classes } from './Dropzone.st.css';
/** Defines a region in the page where files can be dropped */
class Dropzone extends React.PureComponent {
state = {
isDragActive: false,
};
static displayName = 'Dropzone';
static propTypes = {
/** Applies a data-hook HTML attribute that can be used in the tests. */
dataHook: PropTypes.string,
/** Specifies a CSS class name to be appended to the component’s root element. */
className: PropTypes.string,
/** Defines an event handler which is called when files are dropped over the dropzone. Dropped files are supplied as an argument to the function. */
onDrop: PropTypes.func.isRequired,
/** Accepts `<Dropzone.Overlay />` or `<Dropzone.Content />`. Both of them can contain any required content. */
children: (props, propName) => {
const childrenArr = React.Children.toArray(props[propName]);
const childrenObj = buildChildrenObject(childrenArr, {
Overlay: null,
Content: null,
});
if (!childrenObj.Content) {
return new Error(
'Invalid children provided, <Dropzone.Content /> must be provided',
);
}
if (!childrenObj.Overlay) {
return new Error(
'Invalid children provided, <Dropzone.Overlay /> must be provided',
);
}
return childrenArr.reduce((err, child) => {
if (
!err &&
child.type.displayName !== 'Dropzone.Content' &&
child.type.displayName !== 'Dropzone.Overlay'
) {
return new Error(
`Invalid children provided, unknown child <${
child.type.displayName || child.type
} /> supplied`,
);
}
return err;
}, false);
},
};
/**
* An overlay element to be displayed during a drag over the content
*/
static Overlay = ({ children }) => (
<div
data-hook={dataHooks.dropzoneOverlay}
className={classes.dropzoneOverlay}
>
{children}
</div>
);
/**
* An content element on which a file can be dragged over
*/
static Content = ({ children }) => (
<div data-hook={dataHooks.dropzoneContent}>{children}</div>
);
static _overrideEventDefaults = event => {
event.preventDefault();
event.stopPropagation();
};
/** https://spin.atomicobject.com/2018/09/13/file-uploader-react-typescript/ */
_dragEventCounter = 0;
_eventHasFiles = event => {
/** DataTransfer object is defined here: https://developer.mozilla.org/en-US/docs/Web/API/DataTransfer */
return event.dataTransfer
? Array.from(event.dataTransfer.items).some(item => {
return item.kind === 'file';
})
: !!(event.target && event.target.files);
};
_onDragEnter = event => {
Dropzone._overrideEventDefaults(event);
this._dragEventCounter++;
/** We only want to show the overlay when files are dragged over the dropzone */
return this._eventHasFiles(event) && this.setState({ isDragActive: true });
};
_onDragLeave = event => {
Dropzone._overrideEventDefaults(event);
this._dragEventCounter--;
return (
this._dragEventCounter === 0 && this.setState({ isDragActive: false })
);
};
_onDrop = event => {
Dropzone._overrideEventDefaults(event);
this._dragEventCounter = 0;
if (this._eventHasFiles(event)) {
const files = event.dataTransfer
? Array.from(event.dataTransfer.items).map(item => item.getAsFile())
: event.target.files;
this.setState({ isDragActive: false });
return this.props.onDrop(files);
}
};
render() {
const { children, dataHook, className } = this.props;
const { isDragActive } = this.state;
const childrenObj = buildChildrenObject(children, {
Content: null,
Overlay: null,
});
return (
<div
data-hook={dataHook}
className={st(classes.root, className)}
onDrop={this._onDrop}
onDragEnter={this._onDragEnter}
onDragLeave={this._onDragLeave}
onDragOver={Dropzone._overrideEventDefaults}
>
{isDragActive && childrenObj.Overlay}
{childrenObj.Content}
</div>
);
}
}
Dropzone.Content.displayName = 'Dropzone.Content';
Dropzone.Overlay.displayName = 'Dropzone.Overlay';
export default Dropzone;