diff --git a/README.md b/README.md
index 40b6a3611e..04781ba94c 100644
--- a/README.md
+++ b/README.md
@@ -56,7 +56,7 @@
- [`useAudio`](./docs/useAudio.md) — plays audio and exposes its controls. [![][img-demo]](https://codesandbox.io/s/2o4lo6rqy)
- [`useClickAway`](./docs/useClickAway.md) — triggers callback when user clicks outside target area.
- [`useCss`](./docs/useCss.md) — dynamically adjusts CSS.
- - [`useDrop`](./docs/useDrop.md) — tracks file, link and copy-paste drops.
+ - [`useDrop` and `useDropArea`](./docs/useDrop.md) — tracks file, link and copy-paste drops.
- [`useSpeech`](./docs/useSpeech.md) — synthesizes speech from a text string. [![][img-demo]](https://codesandbox.io/s/n090mqz69m)
- [`useVideo`](./docs/useVideo.md) — plays video, tracks its state, and exposes playback controls. [![][img-demo]](https://streamich.github.io/react-use/?path=/story/ui-usevideo--demo)
- [`useWait`](./docs/useWait.md) — complex waiting management for UIs.
diff --git a/docs/useDrop.md b/docs/useDrop.md
index 9f0e438aa0..5fee420849 100644
--- a/docs/useDrop.md
+++ b/docs/useDrop.md
@@ -1,10 +1,15 @@
-# `useDrop`
+# `useDrop` and `useDropArea`
-Triggers on file, link drop and copy-paste onto the page.
+Triggers on file, link drop and copy-paste.
+
+`useDrop` tracks events for the whole page, `useDropArea` tracks drop events
+for a specific element.
## Usage
+`useDrop`:
+
```jsx
import {useDrop} from 'react-use';
@@ -22,3 +27,23 @@ const Demo = () => {
);
};
```
+
+`useDropArea`:
+
+```jsx
+import {useDropArea} from 'react-use';
+
+const Demo = () => {
+ const [bond, state] = useDropArea({
+ onFiles: files => console.log('files', files),
+ onUri: uri => console.log('uri', uri),
+ onText: text => console.log('text', text),
+ });
+
+ return (
+
+ Drop something here.
+
+ );
+};
+```
diff --git a/package.json b/package.json
index cc3c389b25..712668c1a6 100644
--- a/package.json
+++ b/package.json
@@ -75,6 +75,10 @@
}
},
"release": {
+ "branches": ["master", {
+ "name": "next",
+ "prerelease": "rc"
+ }],
"verifyConditions": [
"@semantic-release/changelog",
"@semantic-release/npm",
diff --git a/src/__stories__/useDropArea.story.tsx b/src/__stories__/useDropArea.story.tsx
new file mode 100644
index 0000000000..fa231038eb
--- /dev/null
+++ b/src/__stories__/useDropArea.story.tsx
@@ -0,0 +1,50 @@
+import * as React from 'react';
+import {storiesOf} from '@storybook/react';
+import {action} from '@storybook/addon-actions';
+import {useDropArea} from '..';
+import ShowDocs from '../util/ShowDocs';
+
+const Demo = () => {
+ const [bond, state] = useDropArea({
+ onFiles: action('onFiles'),
+ onUri: action('onUri'),
+ onText: action('onText'),
+ });
+
+ const style: React.CSSProperties = {
+ width: 300,
+ height: 200,
+ margin: '50px auto',
+ border: '1px solid #000',
+ textAlign: 'center',
+ lineHeight: '200px',
+ ...(state.over
+ ? {
+ border: '1px solid green',
+ outline: '3px solid yellow',
+ background: '#f8f8f8',
+ }
+ : {}),
+ };
+
+ return (
+
+
Drop here
+
+
+ - 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|useDropArea', module)
+ .add('Docs', () => )
+ .add('Default', () => );
diff --git a/src/index.ts b/src/index.ts
index 9046830812..bef549a943 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -5,6 +5,7 @@ import useAudio from './useAudio';
import useBattery from './useBattery';
import useBoolean from './useBoolean';
import useDrop from './useDrop';
+import useDropArea from './useDropArea';
import useCounter from './useCounter';
import useCss from './useCss';
import useDebounce from './useDebounce';
@@ -67,6 +68,7 @@ export {
useBattery,
useBoolean,
useDrop,
+ useDropArea,
useClickAway,
useCounter,
useCss,
diff --git a/src/useDropArea.ts b/src/useDropArea.ts
index 9710b7094e..2a6c8356a2 100644
--- a/src/useDropArea.ts
+++ b/src/useDropArea.ts
@@ -1,14 +1,85 @@
-import * as React from 'react';
-
-const useDropArea = (el: React.ReactElement) => {
- if (process.env.NODE_ENV !== 'production') {
- if (!React.isValidElement(el)) {
- throw new TypeError(
- 'useDropArea first argument must be a valid ' +
- 'React element, such as .'
- );
- }
+import {useMemo, useState} from 'react';
+import useRefMounted from './useRefMounted';
+
+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 createBond = (process, setOver): DropAreaBond => ({
+ onDragOver: (event) => {
+ event.preventDefault();
+ },
+ onDragEnter: (event) => {
+ event.preventDefault();
+ setOver(true);
+ },
+ onDragLeave: () => {
+ setOver(false);
+ },
+ onDrop: (event) => {
+ event.preventDefault();
+ event.persist();
+ setOver(false);
+ process(event.dataTransfer, event);
+ },
+ onPaste: (event) => {
+ event.persist();
+ process(event.clipboardData, event);
+ },
+});
+
+const useDropArea = (options: DropAreaOptions = {}): [DropAreaBond, DropAreaState] => {
+ const {onFiles, onText, onUri} = options;
+ const mounted = useRefMounted();
+ const [over, setOver] = useState(false);
+ const process = useMemo(() => createProcess(options, mounted), [onFiles, onText, onUri]);
+ const bond: DropAreaBond = useMemo(() => createBond(process, setOver), [process, setOver]);
+
+ return [bond, {over}];
};
export default useDropArea;