mirror of
https://github.com/streamich/react-use.git
synced 2026-01-25 14:17:16 +00:00
feat: 🎸 add useDropArea hook
This commit is contained in:
parent
0ccdf9531e
commit
676d0ded3e
@ -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.
|
||||
|
||||
@ -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 (
|
||||
<div {...bond}>
|
||||
Drop something here.
|
||||
</div>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
@ -75,6 +75,10 @@
|
||||
}
|
||||
},
|
||||
"release": {
|
||||
"branches": ["master", {
|
||||
"name": "next",
|
||||
"prerelease": "rc"
|
||||
}],
|
||||
"verifyConditions": [
|
||||
"@semantic-release/changelog",
|
||||
"@semantic-release/npm",
|
||||
|
||||
50
src/__stories__/useDropArea.story.tsx
Normal file
50
src/__stories__/useDropArea.story.tsx
Normal file
@ -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 (
|
||||
<div>
|
||||
<div {...bond} style={style}>Drop here</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|useDropArea', module)
|
||||
.add('Docs', () => <ShowDocs md={require('../../docs/useDrop.md')} />)
|
||||
.add('Default', () => <Demo />);
|
||||
@ -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,
|
||||
|
||||
@ -1,14 +1,85 @@
|
||||
import * as React from 'react';
|
||||
import {useMemo, useState} from 'react';
|
||||
import useRefMounted from './useRefMounted';
|
||||
|
||||
const useDropArea = (el: React.ReactElement<any>) => {
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
if (!React.isValidElement(el)) {
|
||||
throw new TypeError(
|
||||
'useDropArea first argument must be a valid ' +
|
||||
'React element, such as <div/>.'
|
||||
);
|
||||
}
|
||||
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 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<boolean>(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;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user