[feature-75] update drop zone UI

This commit is contained in:
MathieuG-P
2024-10-16 19:11:57 +02:00
parent be347dc6ba
commit 4e6948693d
10 changed files with 108 additions and 62 deletions
@@ -204,11 +204,8 @@ export function MapsPlaylistsPanel({ version, isActive }: Props) {
onDrop={event => {
handleFileDrop(event.dataTransfer.files);
}}
overlay={
<div className="text-3xl pointer-events-none">
Drop map files here 😃
</div>
}
text={t("pages.version-viewer.maps.tabs.maps.drop-zone.text")}
subtext={t("pages.version-viewer.maps.tabs.maps.drop-zone.subtext")}
>
<LocalMapsListPanel className="w-full h-full shrink-0" isActive={isActive && tabIndex === 0} ref={mapsRef} version={version} filter={mapFilter} search={search} linkedState={mapsLinkedState} />
</Dropzone>
@@ -1,72 +1,88 @@
import React, { ReactNode, useState } from "react";
import { AnimatePresence, motion } from "framer-motion";
import { DragEvent, ReactNode, useCallback, useState } from "react";
import { BsmImage } from "./bsm-image.component";
import BeatImpatient from "../../../../assets/images/apngs/beat-impatient.png";
import { useThemeColor } from "renderer/hooks/use-theme-color.hook";
import Color from "color";
type Props = Readonly<{
children?: ReactNode;
className?: string;
overlay?: ReactNode;
overlayColor?: string;
overlayZIndex?: number;
onDrop?: (event: React.DragEvent<HTMLDivElement>) => void;
text?: string;
subtext?: string;
onDrop?: (event: DragEvent<HTMLDivElement>) => void;
}>;
// Supports drop and drop functionality for files.
export function Dropzone({
children,
className,
overlay,
overlayColor,
overlayZIndex,
onDrop
text,
subtext,
onDrop,
}: Props) {
const [dragging, setDragging] = useState(false);
const [dragCount, setDragCount] = useState(0);
const [dropZoneDragging, setDropZoneDragging] = useState(false);
const themeColor = useThemeColor("first-color");
const color = new Color(themeColor).lighten(.25).saturate(.8).hex();
const normalizeZIndex = (zIndex?: number) => {
return (!zIndex || zIndex <= 0) ? 50 : zIndex;
}
const handleDrop = useCallback((event: DragEvent<HTMLDivElement>) => {
event.preventDefault();
event.stopPropagation();
setDragCount(0);
if (onDrop) onDrop(event);
}, []);
const renderFileOverlay = () => {
return (
<div
className="absolute w-full h-full flex items-center justify-center"
style={{
backgroundColor: overlayColor || "#000000CC",
zIndex: normalizeZIndex(overlayZIndex),
}}
onDrop={event => {
event.preventDefault();
event.stopPropagation();
setDragging(false);
if (onDrop) onDrop(event);
}}
onDragOver={event => {
event.preventDefault();
event.stopPropagation();
}}
onDragEnter={event => {
// Should consume the event 1 more time to avoid multiple onDragEnter
// triggers by the parent div
event.preventDefault();
event.stopPropagation();
}}
onDragLeave={event => {
event.preventDefault();
event.stopPropagation();
setDragging(false);
}}
>
{overlay}
</div>
)
}
const onDragOver = useCallback((event: DragEvent<HTMLDivElement>) => {
event.preventDefault();
event.stopPropagation();
}, []);
const onDragEnter = useCallback((event: DragEvent<HTMLDivElement>) => {
event.preventDefault();
event.stopPropagation();
setDragCount(prev => prev + 1);
}, []);
const onDragLeave = useCallback((event: DragEvent<HTMLDivElement>) => {
event.preventDefault();
event.stopPropagation();
setDragCount(prev => prev - 1);
}, []);
return (
<div className={className} onDragEnter={event => {
event.preventDefault();
event.stopPropagation();
setDragging(true);
}}>
{dragging && renderFileOverlay()}
<div className={className} onDragEnter={onDragEnter} onDragLeave={onDragLeave}>
<AnimatePresence>
{dragCount ? (
<div onDragEnter={onDragEnter} onDragLeave={onDragLeave} onDrop={onDragLeave} className="absolute size-full top-0 right-0 z-10">
<motion.div className="absolute size-full top-0 right-0 bg-black" initial={{ opacity: 0 }} animate={{ opacity: .6 }} exit={{ opacity: 0 }}/>
<motion.div className="absolute size-full top-0 right-0 flex justify-center items-center p-5" initial={{ y: "100%" }} animate={{ y: "0" }} exit={{ y: "100%" }}>
<div className="h-72 max-h-full w-full max-w-lg sm:h-96 sm:max-w-2xl bg-theme-3 rounded-md shadow-black shadow-lg p-7">
<div
onDragOver={onDragOver}
onDrop={handleDrop}
onDragEnter={() => setDropZoneDragging(true)}
onDragLeave={() => setDropZoneDragging(false)}
className="relative size-full z-[1] border-dashed border-3 rounded-md flex flex-col gap-1 justify-center items-center"
style={{ borderColor: color }}
>
<div className="absolute size-full top-0 right-0 pointer-events-none -z-[1] transition-opacity duration-150" style={{
backgroundColor: "transparent",
backgroundImage: `radial-gradient(${color} 1px, transparent 0)`,
backgroundSize: "60px 60px",
backgroundPosition: "5px",
opacity: dropZoneDragging ? .7 : .2,
}}/>
<BsmImage className="size-28 pointer-events-none" image={BeatImpatient}/>
<span className="text-2xl font-bold pointer-events-none">{text}</span>
<span className="pointer-events-none">{subtext}</span>
</div>
</div>
</motion.div>
</div>
): undefined}
</AnimatePresence>
{children}
</div>
);