mirror of
https://github.com/streamich/react-use.git
synced 2026-01-18 14:06:52 +00:00
feat: 🎸 add useDrop hook
This commit is contained in:
parent
6655092cae
commit
6e415cfdef
47
src/__stories__/useDrop.story.tsx
Normal file
47
src/__stories__/useDrop.story.tsx
Normal 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 />);
|
||||
@ -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
112
src/useDrop.ts
Normal 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;
|
||||
Loading…
x
Reference in New Issue
Block a user