Появилась однажды задача - сделать область для загрузки файлов, с помощью 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: ссылки из статьи