diff --git a/src/__stories__/useDrop.story.tsx b/src/__stories__/useDrop.story.tsx
new file mode 100644
index 00000000..78f0879e
--- /dev/null
+++ b/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 (
+
+
Drop anywhere on page
+
+
+ - See logs in
Actions tab.
+ - Drag in and drop files.
+ Cmd + V paste text here.
+ - Drag in images from other tabs.
+ - Drag in link from navigation bar.
+ - Below is state returned by the hook:
+
+
{JSON.stringify(state, null, 4)}
+
+
+ );
+};
+
+storiesOf('UI|useDrop', module).add('Default', () => );
diff --git a/src/index.ts b/src/index.ts
index 17425fd3..55a3844f 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -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,
diff --git a/src/useDrop.ts b/src/useDrop.ts
new file mode 100644
index 00000000..352bcf83
--- /dev/null
+++ b/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) => (
+ 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(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;