Появилась однажды задача - сделать область для загрузки файлов, с помощью drag-and-drop.
Была выбрана библиотека react-dnd, по причине её простоты, минималистичности и низкого порога входа.
Фича реализуется с помощью это либы и двух кастомных компонентов:
DndContainer - компонент обёртка, для того, чтобы прокидывать вниз контекст для работы react-dnd
DropTarget - компонент для непосредственной реализации drag-and-drop функционала
Итак, рецепт!
react-dnd
Просто ставим её в зависимости и идём дальше.
DndContainer
Объявляем провайдер и пробрасываем в children наш будущий компонент DropTarget.
import React from 'react'; import { DndProvider } from 'react-dnd'; import { HTML5Backend } from 'react-dnd-html5-backend'; import DropTarget from '../drop-target'; const DndContainer = (props: { onDrop: (files: File[]) => void; disabled: boolean; title?: string; hoverTitle?: string; }) => { const { onDrop, disabled, title = 'Перетащите сюда файлы', hoverTitle = 'Отпустите для загрузки', } = props; return ( <DndProvider backend={HTML5Backend}> <DropTarget onDrop={onDrop} disabled={disabled} title={title} hoverTitle={hoverTitle} /> </DndProvider> ); }; export default DndContainer;
DropTarget
Компонент для реализации drag-and-drop функционала
import React from 'react'; import { useDrop } from 'react-dnd'; import { NativeTypes } from 'react-dnd-html5-backend'; import Styles from './index.css'; const DropTarget = (props: { onDrop: (files: File[]) => void; disabled: boolean; title: string; hoverTitle: string; }) => { const { onDrop, disabled, title, hoverTitle } = props; const [{ canDrop, isOver }, drop] = useDrop( () => ({ accept: [NativeTypes.FILE], drop: (item: { files: File[] }) => { onDrop(item.files); }, canDrop: () => !disabled, collect: (monitor) => ({ isOver: monitor.isOver(), canDrop: monitor.canDrop(), }), }), [onDrop] ); const isActive = canDrop && isOver; return ( <div ref={drop} className={Styles.wrapper} data-disabled={disabled}> <div className={Styles.dropTarget} data-active={isActive}> {isActive ? hoverTitle : title} </div> </div> ); }; export default DropTarget;
Стили кастомные, но для примера оставлю:
.wrapper { position: relative; } .wrapper[data-disabled='true'] { cursor: not-allowed; } .wrapper[data-disabled='true']::after { content: ''; position: absolute; top: 0; left: 0; bottom: 0; right: 0; background-color: rgba(255, 255, 255, 0.35); } .dropTarget { display: flex; justify-content: center; align-items: center; padding: 40px 20px; margin: 20px 0; border: 1px dashed black; border-radius: 5px; user-select: none; color: black; } .dropTarget[data-active='true'] { border: 1px dashed #0022f5; color: #0022f5; }
Применение
Используем как совершенно обычный компонент:
type Props = { handleFilesDrop: (files: File[]) => void; title?: string; } const DnD = (props: Props) => { const { handleFilesDrop, title = 'Перетащите сюда медиафайлы' } = props; const shouldBeDisabled = /* some condition, based on state/props or whatever you need */; return ( <div className={Styles.dnd}> <DndContainer title={title} onDrop={handleFilesDrop} disabled={shouldBeDisabled} /> </div> ); };
Итого
Выводов не планировалось, это просто минималистичный рецептик приготовления для быстрого сетапа функционала dnd в приложении))
Спасибо за чтение и удачи в реализации ваших dnd фич)
PS: ссылки из статьи
