Skip to content

Commit

Permalink
feat: 🎸 add useDrop hook
Browse files Browse the repository at this point in the history
  • Loading branch information
streamich committed Mar 27, 2019
1 parent 6655092 commit 6e415cf
Show file tree
Hide file tree
Showing 3 changed files with 161 additions and 0 deletions.
47 changes: 47 additions & 0 deletions src/__stories__/useDrop.story.tsx
@@ -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 />);
2 changes: 2 additions & 0 deletions src/index.ts
Expand Up @@ -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';
Expand Down Expand Up @@ -64,6 +65,7 @@ export {
useAudio,
useBattery,
useBoolean,
useDrop,
useClickAway,
useCounter,
useCss,
Expand Down
112 changes: 112 additions & 0 deletions src/useDrop.ts
@@ -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;

0 comments on commit 6e415cf

Please sign in to comment.