Skip to content

Commit

Permalink
fix: refactored image upload plguin, better UX
Browse files Browse the repository at this point in the history
  • Loading branch information
fluid-design-io committed Oct 25, 2023
1 parent c748ab2 commit d9ec106
Show file tree
Hide file tree
Showing 10 changed files with 602 additions and 296 deletions.
190 changes: 99 additions & 91 deletions apps/web/components/core/image-drag-and-drop.tsx
Original file line number Diff line number Diff line change
@@ -1,103 +1,111 @@
import { cn } from "@ui/lib/utils";
import { ImageIcon } from "lucide-react";
import { useEffect, useRef, useState } from "react";
import { forwardRef, useEffect, useRef, useState } from "react";

export default function ImageDragAndDrop({
onDrop,
onDragOver,
onDragLeave,
className,
dropAreaStyles,
}: {
onDrop: (files: FileList) => void;
className?: string;
dropAreaStyles?: string;
onDragOver?: (e: React.DragEvent) => void;
onDragLeave?: (e: React.DragEvent) => void;
}) {
const drop = useRef(null);
const [isDropping, setIsDropping] = useState(false);
const ImageDragAndDrop = forwardRef(
(
{
onDrop,
onDragOver,
onDragLeave,
className,
dropAreaStyles,
}: {
onDrop: (files: FileList) => void;
className?: string;
dropAreaStyles?: string;
onDragOver?: (e: React.DragEvent) => void;
onDragLeave?: (e: React.DragEvent) => void;
},
ref?: React.ForwardedRef<HTMLInputElement>,
) => {
const drop = useRef(null);
const [isDropping, setIsDropping] = useState(false);

const handleDragOver = (e) => {
e.preventDefault();
e.stopPropagation();
setIsDropping(true);
};

const handleDragLeave = (e) => {
e.preventDefault();
e.stopPropagation();
setIsDropping(false);
};
const handleDragOver = (e) => {
e.preventDefault();
e.stopPropagation();
setIsDropping(true);
};

const handleDrop = (e) => {
e.preventDefault();
e.stopPropagation();
setIsDropping(false);
const { files } = e.dataTransfer;
const handleDragLeave = (e) => {
e.preventDefault();
e.stopPropagation();
setIsDropping(false);
};

if (files && files.length) {
onDrop(files);
}
};
useEffect(() => {
drop?.current?.addEventListener("dragover", handleDragOver);
drop?.current?.addEventListener("drop", handleDrop);
drop?.current?.addEventListener("dragleave", handleDragLeave);
const handleDrop = (e) => {
e.preventDefault();
e.stopPropagation();
setIsDropping(false);
const { files } = e.dataTransfer;

return () => {
drop?.current?.removeEventListener("dragover", handleDragOver);
drop?.current?.removeEventListener("drop", handleDrop);
drop?.current?.removeEventListener("dragleave", handleDragLeave);
if (files && files.length) {
onDrop(files);
}
};
}, []);
useEffect(() => {
drop?.current?.addEventListener("dragover", handleDragOver);
drop?.current?.addEventListener("drop", handleDrop);
drop?.current?.addEventListener("dragleave", handleDragLeave);

return (
<div
className={cn(
"flex w-full items-center justify-center rounded-lg border border-dashed border-border/75 px-6 py-10",
isDropping && "border-primary bg-primary/40",
className,
)}
ref={drop}
>
<div className={cn("rounded px-6 py-4 text-center", dropAreaStyles)}>
<ImageIcon
className="drop-icon mx-auto h-12 w-12 text-muted-foreground/75"
aria-hidden="true"
/>
<div className="flex justify-center text-sm leading-6 text-muted-foreground/75">
<label
htmlFor="file-upload"
className="relative cursor-pointer rounded-md bg-muted px-2 font-semibold text-foreground focus-within:outline-none focus-within:ring-2 focus-within:ring-primary focus-within:ring-offset-2 focus-within:ring-offset-gray-900 hover:text-primary"
return () => {
drop?.current?.removeEventListener("dragover", handleDragOver);
drop?.current?.removeEventListener("drop", handleDrop);
drop?.current?.removeEventListener("dragleave", handleDragLeave);
};
}, []);

return (
<div
className={cn(
"flex w-full items-center justify-center rounded-lg border border-dashed border-border/75 px-6 py-10",
isDropping && "border-primary bg-primary/40",
className,
)}
ref={drop}
>
<div className={cn("rounded px-6 py-4 text-center", dropAreaStyles)}>
<ImageIcon
className="drop-icon mx-auto h-12 w-12 text-muted-foreground/75"
aria-hidden="true"
/>
<div className="flex justify-center text-sm leading-6 text-muted-foreground/75">
<label
htmlFor="file-upload"
className="relative cursor-pointer rounded-md bg-muted px-2 font-semibold text-foreground focus-within:outline-none focus-within:ring-2 focus-within:ring-primary focus-within:ring-offset-2 focus-within:ring-offset-gray-900 hover:text-primary"
>
<span className="inline">
Upload
<span className="sr-only sm:not-sr-only">an image</span>
</span>
<input
ref={ref}
id="file-upload"
name="file-upload"
type="file"
className="sr-only"
accept="image/*"
onChange={(e) => {
if (e.target.files && e.target.files.length) {
onDrop(e.target.files);
}
}}
/>
</label>
<p className="sr-only sm:not-sr-only sm:pl-1">or drag and drop</p>
</div>
<p
className={cn("text-xs leading-5 text-muted-foreground/75", {
"opacity-0": isDropping,
})}
>
<span className="inline">
Upload
<span className="sr-only sm:not-sr-only">an image</span>
</span>
<input
id="file-upload"
name="file-upload"
type="file"
className="sr-only"
accept="image/*"
onChange={(e) => {
if (e.target.files && e.target.files.length) {
onDrop(e.target.files);
}
}}
/>
</label>
<p className="sr-only sm:not-sr-only sm:pl-1">or drag and drop</p>
PNG, JPG, GIF up to 10MB
</p>
</div>
<p
className={cn("text-xs leading-5 text-muted-foreground/75", {
"opacity-0": isDropping,
})}
>
PNG, JPG, GIF up to 10MB
</p>
</div>
</div>
);
}
);
},
);

export default ImageDragAndDrop;
2 changes: 1 addition & 1 deletion apps/web/components/core/toobar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ function Toolbar() {
<div
className={cn(
"mx-auto max-w-[120rem] ",
"relative z-40 flex select-none flex-row items-center justify-between bg-background-accent py-2 transition-colors",
"relative z-[41] flex select-none flex-row items-center justify-between bg-background-accent py-2 transition-colors sm:z-40",
"fixed inset-x-0 bottom-0 w-full border-t border-t-border",
"sm:border-t-none sm:sticky sm:inset-x-auto sm:bottom-auto sm:top-0 sm:border-none",
"site-padding",
Expand Down
36 changes: 34 additions & 2 deletions apps/web/components/toolbar/index.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,42 @@
"use client";

import ToolbarUploadImage from "./upload-image";
import ToolbarDownloadBasePalette from "./download-base-palette";
import ToolbarShareableLink from "./shareable-link";
import { Fragment } from "react";
import { useToolStore } from "@/store/toolStore";
import { createPortal } from "react-dom";

import dynamic from "next/dynamic";
import useStore from "@/store/useStore";
import { AnimatePresence } from "framer-motion";

// export like <Toolbar.UploadImage />
const UploadImaagePluginDialogContent = dynamic(
() => import("@/components/toolbar/plugin/upload-image.plugin"),
{
loading: () => null,
ssr: false,
},
);

const ToolbarPrimative = ({ children }) => children;
const ToolbarPrimative = ({ children }) => {
const openImageColorExtractor = useStore(
useToolStore,
(state) => state.openImageColorExtractor,
);
return (
<Fragment>
{children}
{!!openImageColorExtractor &&
createPortal(
<UploadImaagePluginDialogContent
key={`uipd-${openImageColorExtractor}`}
/>,
document.body,
)}
</Fragment>
);
};

ToolbarPrimative.displayName = "Toolbar";

Expand Down
Loading

0 comments on commit d9ec106

Please sign in to comment.