feat: 🎸 add useDrop hook

This commit is contained in:
streamich 2019-03-27 19:34:33 +01:00
parent 6655092cae
commit 6e415cfdef
3 changed files with 161 additions and 0 deletions

View File

@ -0,0 +1,47 @@
import * as React from 'react';
import {storiesOf} from '@storybook/react';
import {action} from '@storybook/addon-actions';
import {useDrop} from '..';
const Demo = () => {
const state = useDrop({
onFiles: action('onFiles'),
onUri: action('onUri'),
onText: action('onText'),
});
const style: React.CSSProperties = {
width: 300,
height: 200,
margin: '50px auto',
border: '1px dotted #000',
textAlign: 'center',
lineHeight: '200px',
...(state.over
? {
border: '1px dotted green',
outline: '3px solid yellow',
background: '#f8f8f8',
}
: {}),
};
return (
<div>
<div style={style}>Drop anywhere on page</div>
<div style={{maxWidth: 300, margin: '0 auto'}}>
<ul style={{margin: 0, padding: '10px 18px'}}>
<li>See logs in <code>Actions</code> tab.</li>
<li>Drag in and drop files.</li>
<li><code>Cmd + V</code> paste text here.</li>
<li>Drag in images from other tabs.</li>
<li>Drag in link from navigation bar.</li>
<li>Below is state returned by the hook:</li>
</ul>
<pre>{JSON.stringify(state, null, 4)}</pre>
</div>
</div>
);
};
storiesOf('UI|useDrop', module).add('Default', () => <Demo />);

View File

@ -4,6 +4,7 @@ import useAsyncRetry from './useAsyncRetry';
import useAudio from './useAudio';
import useBattery from './useBattery';
import useBoolean from './useBoolean';
import useDrop from './useDrop';
import useCounter from './useCounter';
import useCss from './useCss';
import useDebounce from './useDebounce';
@ -64,6 +65,7 @@ export {
useAudio,
useBattery,
useBoolean,
useDrop,
useClickAway,
useCounter,
useCss,

112
src/useDrop.ts Normal file
View File

@ -0,0 +1,112 @@
import * as React from 'react';
import useRefMounted from './useRefMounted';
const {useState, useMemo, useCallback, useEffect} = React;
export interface DropAreaState {
over: boolean;
}
export interface DropAreaBond {
onDragOver: React.DragEventHandler;
onDragEnter: React.DragEventHandler;
onDragLeave: React.DragEventHandler;
onDrop: React.DragEventHandler;
onPaste: React.ClipboardEventHandler;
}
export interface DropAreaOptions {
onFiles?: (files: File[], event?) => void;
onText?: (text: string, event?) => void;
onUri?: (url: string, event?) => void;
}
const noop = () => {};
const defaultState: DropAreaState = {
over: false,
};
const createProcess = (options: DropAreaOptions, mounted: React.RefObject<boolean>) => (
dataTransfer: DataTransfer,
event,
) => {
const uri = dataTransfer.getData('text/uri-list');
if (uri) {
(options.onUri || noop)(uri, event);
return;
}
if (dataTransfer.files && dataTransfer.files.length) {
(options.onFiles || noop)(Array.from(dataTransfer.files), event);
return;
}
if (dataTransfer.items && dataTransfer.items.length) {
dataTransfer.items[0].getAsString((text) => {
if (mounted.current) {
(options.onText || noop)(text, event);
}
});
}
};
const useDrop = (options: DropAreaOptions = {}): DropAreaState => {
const {onFiles, onText, onUri} = options;
const mounted = useRefMounted();
const [over, setOverRaw] = useState<boolean>(false);
const setOver = useCallback(setOverRaw, []);
const process = useMemo(() => createProcess(options, mounted), [onFiles, onText, onUri]);
useEffect(() => {
const onDragOver = (event) => {
event.preventDefault();
};
const onDragEnter = (event) => {
event.preventDefault();
setOver(true);
};
const onDragLeave = () => {
setOver(true);
};
const onDragExit = () => {
setOver(false);
};
const onDrop = (event) => {
event.preventDefault();
setOver(false);
process(event.dataTransfer, event);
};
const onPaste = (event) => {
process(event.clipboardData, event);
};
window.addEventListener('dragover', onDragOver);
window.addEventListener('dragenter', onDragEnter);
window.addEventListener('dragleave', onDragLeave);
window.addEventListener('dragexit', onDragExit);
window.addEventListener('drop', onDrop);
if (onText) {
window.addEventListener('paste', onPaste);
}
return () => {
window.removeEventListener('dragover', onDragOver);
window.removeEventListener('dragenter', onDragEnter);
window.removeEventListener('dragleave', onDragLeave);
window.removeEventListener('dragexit', onDragExit);
window.removeEventListener('drop', onDrop);
window.removeEventListener('paste', onPaste);
};
}, [process]);
return {over};
};
export default useDrop;