@vectara/vectara-ui
Version:
Vectara's design system, codified as a React and Sass component library
70 lines (69 loc) • 3.97 kB
JavaScript
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
import { useEffect, useRef, useState } from "react";
import { createPortal } from "react-dom";
import { BiUpload } from "react-icons/bi";
import { VuiScreenBlock } from "../screenBlock/ScreenBlock";
import { VuiFlexContainer } from "../flex/FlexContainer";
import { VuiFlexItem } from "../flex/FlexItem";
import { VuiIcon } from "../icon/Icon";
import { VuiText } from "../typography/Text";
const defaultMessage = (_jsxs(VuiFlexContainer, Object.assign({ direction: "column", alignItems: "center", justifyContent: "center" }, { children: [_jsx(VuiFlexItem, { children: _jsx(VuiIcon, Object.assign({ size: "xxxl", color: "empty" }, { children: _jsx(BiUpload, {}) })) }), _jsx(VuiFlexItem, { children: _jsx(VuiText, Object.assign({ align: "center", size: "l" }, { children: _jsx("p", { children: "Drop files to add to upload" }) })) })] })));
export const VuiFileDropTarget = ({ onFilesDropped, scopeRef, message = defaultMessage }) => {
const [isDragging, setIsDragging] = useState(false);
const dragCounterRef = useRef(0);
useEffect(() => {
var _a;
const target = (_a = scopeRef === null || scopeRef === void 0 ? void 0 : scopeRef.current) !== null && _a !== void 0 ? _a : document;
// Reset whenever the target changes (e.g. when scopeRef attaches or the parent
// swaps between scoped and global modes) so a stale counter doesn't suppress events.
dragCounterRef.current = 0;
setIsDragging(false);
const handleDragEnter = (e) => {
var _a;
e.preventDefault();
dragCounterRef.current += 1;
// Only show the overlay for drags that carry files, not e.g. selected text.
if (dragCounterRef.current === 1 && ((_a = e.dataTransfer) === null || _a === void 0 ? void 0 : _a.items) && e.dataTransfer.items.length > 0) {
setIsDragging(true);
}
};
const handleDragLeave = (e) => {
e.preventDefault();
dragCounterRef.current -= 1;
if (dragCounterRef.current === 0) {
setIsDragging(false);
}
};
const handleDragOver = (e) => {
e.preventDefault();
};
const handleDrop = (e) => {
var _a;
e.preventDefault();
setIsDragging(false);
dragCounterRef.current = 0;
const files = (_a = e.dataTransfer) === null || _a === void 0 ? void 0 : _a.files;
if (files && files.length > 0) {
onFilesDropped(Array.from(files));
}
};
target.addEventListener("dragenter", handleDragEnter);
target.addEventListener("dragleave", handleDragLeave);
target.addEventListener("dragover", handleDragOver);
target.addEventListener("drop", handleDrop);
return () => {
target.removeEventListener("dragenter", handleDragEnter);
target.removeEventListener("dragleave", handleDragLeave);
target.removeEventListener("dragover", handleDragOver);
target.removeEventListener("drop", handleDrop);
};
}, [scopeRef, onFilesDropped]);
if (!isDragging)
return null;
// Scoped mode: overlay covers only the parent element.
if (scopeRef === null || scopeRef === void 0 ? void 0 : scopeRef.current) {
return createPortal(_jsx("div", Object.assign({ className: "vuiFileDropTarget__scopedOverlay" }, { children: _jsx("div", Object.assign({ className: "vuiFileDropTarget__message" }, { children: message })) })), scopeRef.current);
}
// Global mode: full-screen overlay.
return (_jsx(VuiScreenBlock, Object.assign({ color: "primary" }, { children: _jsx("div", Object.assign({ className: "vuiFileDropTarget__messageContainer" }, { children: _jsx("div", Object.assign({ className: "vuiFileDropTarget__message" }, { children: message })) })) })));
};