Use window-level drag detection for audit list dropzone
The previous approach attached drag handlers to a content div that didn't fill the viewport, making the dropzone hard to target. Now drag detection uses window-level events with a counter for correct nested element handling, and the full-screen overlay itself becomes the react-dropzone drop target. Signed-off-by: Bryan Frimin <bryan@getprobo.com>
This commit is contained in:
@@ -21,7 +21,7 @@ import {
|
|||||||
Tr,
|
Tr,
|
||||||
useDialogRef,
|
useDialogRef,
|
||||||
} from "@probo/ui";
|
} from "@probo/ui";
|
||||||
import { useCallback, useState } from "react";
|
import { useCallback, useEffect, useRef, useState } from "react";
|
||||||
import { useDropzone } from "react-dropzone";
|
import { useDropzone } from "react-dropzone";
|
||||||
import {
|
import {
|
||||||
graphql,
|
graphql,
|
||||||
@@ -110,10 +110,14 @@ export default function AuditsPage(props: Props) {
|
|||||||
|
|
||||||
const canCreateAudit = data.node.canCreateAudit;
|
const canCreateAudit = data.node.canCreateAudit;
|
||||||
const [droppedFile, setDroppedFile] = useState<File | null>(null);
|
const [droppedFile, setDroppedFile] = useState<File | null>(null);
|
||||||
|
const [isDragging, setIsDragging] = useState(false);
|
||||||
const dropDialogRef = useDialogRef();
|
const dropDialogRef = useDialogRef();
|
||||||
|
const dragCounterRef = useRef(0);
|
||||||
|
|
||||||
const onDrop = useCallback(
|
const onDrop = useCallback(
|
||||||
(acceptedFiles: File[]) => {
|
(acceptedFiles: File[]) => {
|
||||||
|
setIsDragging(false);
|
||||||
|
dragCounterRef.current = 0;
|
||||||
if (!canCreateAudit || acceptedFiles.length === 0) return;
|
if (!canCreateAudit || acceptedFiles.length === 0) return;
|
||||||
setDroppedFile(acceptedFiles[0]);
|
setDroppedFile(acceptedFiles[0]);
|
||||||
dropDialogRef.current?.open();
|
dropDialogRef.current?.open();
|
||||||
@@ -121,7 +125,48 @@ export default function AuditsPage(props: Props) {
|
|||||||
[canCreateAudit, dropDialogRef],
|
[canCreateAudit, dropDialogRef],
|
||||||
);
|
);
|
||||||
|
|
||||||
const { getRootProps, getInputProps, isDragActive } = useDropzone({
|
useEffect(() => {
|
||||||
|
if (!canCreateAudit) return;
|
||||||
|
|
||||||
|
const handleDragEnter = (e: DragEvent) => {
|
||||||
|
e.preventDefault();
|
||||||
|
dragCounterRef.current++;
|
||||||
|
if (e.dataTransfer?.types.includes("Files")) {
|
||||||
|
setIsDragging(true);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDragLeave = (e: DragEvent) => {
|
||||||
|
e.preventDefault();
|
||||||
|
dragCounterRef.current--;
|
||||||
|
if (dragCounterRef.current === 0) {
|
||||||
|
setIsDragging(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDragOver = (e: DragEvent) => {
|
||||||
|
e.preventDefault();
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDrop = () => {
|
||||||
|
setIsDragging(false);
|
||||||
|
dragCounterRef.current = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
window.addEventListener("dragenter", handleDragEnter);
|
||||||
|
window.addEventListener("dragleave", handleDragLeave);
|
||||||
|
window.addEventListener("dragover", handleDragOver);
|
||||||
|
window.addEventListener("drop", handleDrop);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
window.removeEventListener("dragenter", handleDragEnter);
|
||||||
|
window.removeEventListener("dragleave", handleDragLeave);
|
||||||
|
window.removeEventListener("dragover", handleDragOver);
|
||||||
|
window.removeEventListener("drop", handleDrop);
|
||||||
|
};
|
||||||
|
}, [canCreateAudit]);
|
||||||
|
|
||||||
|
const { getRootProps, getInputProps } = useDropzone({
|
||||||
noClick: true,
|
noClick: true,
|
||||||
noKeyboard: true,
|
noKeyboard: true,
|
||||||
accept: { "application/pdf": [".pdf"] },
|
accept: { "application/pdf": [".pdf"] },
|
||||||
@@ -135,10 +180,13 @@ export default function AuditsPage(props: Props) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div {...getRootProps()} className="relative space-y-6">
|
<div className="space-y-6">
|
||||||
|
{isDragging && canCreateAudit && (
|
||||||
|
<div
|
||||||
|
{...getRootProps()}
|
||||||
|
className="border-primary bg-primary/5 pointer-events-auto fixed inset-0 z-40 flex flex-col items-center justify-center border-2 border-dashed"
|
||||||
|
>
|
||||||
<input {...getInputProps()} />
|
<input {...getInputProps()} />
|
||||||
{isDragActive && canCreateAudit && (
|
|
||||||
<div className="border-primary bg-primary/5 pointer-events-none absolute inset-0 z-40 flex flex-col items-center justify-center rounded-xl border-2 border-dashed">
|
|
||||||
<IconUpload className="text-primary mb-2 size-8" />
|
<IconUpload className="text-primary mb-2 size-8" />
|
||||||
<p className="text-primary text-sm font-medium">
|
<p className="text-primary text-sm font-medium">
|
||||||
{__("Drop a PDF to create an audit with a report")}
|
{__("Drop a PDF to create an audit with a report")}
|
||||||
|
|||||||
Reference in New Issue
Block a user