feat: dropzone dragging over effect

This commit is contained in:
trafficlunar 2025-05-24 12:04:37 +01:00
parent 6a965ecd66
commit 045308dcef
5 changed files with 82 additions and 91 deletions

View file

@ -0,0 +1,51 @@
"use client";
import { ReactNode, useState } from "react";
import { DropzoneOptions, FileWithPath, useDropzone } from "react-dropzone";
import { Icon } from "@iconify/react";
interface Props {
onDrop: (acceptedFiles: FileWithPath[]) => void;
options?: DropzoneOptions;
children?: ReactNode;
}
export default function Dropzone({ onDrop, options, children }: Props) {
const [isDraggingOver, setIsDraggingOver] = useState(false);
const handleDrop = (acceptedFiles: FileWithPath[]) => {
setIsDraggingOver(false);
onDrop(acceptedFiles);
};
const { getRootProps, getInputProps } = useDropzone({
onDrop: handleDrop,
maxFiles: 3,
accept: {
"image/*": [".png", ".jpg", ".jpeg", ".bmp", ".webp", ".heic"],
},
...options,
});
return (
<div
{...getRootProps()}
onDragOver={() => setIsDraggingOver(true)}
onDragLeave={() => setIsDraggingOver(false)}
className={`relative bg-orange-200 flex flex-col justify-center items-center gap-2 p-4 rounded-xl border-2 border-dashed border-amber-500 select-none h-full transition-all duration-200 ${
isDraggingOver && "scale-105 brightness-90 shadow-xl"
}`}
>
{/* Used to transition from border-dashed to border-solid */}
<div
className={`absolute inset-0 rounded-[10px] outline-2 outline-amber-500 transition-opacity duration-300 ${
isDraggingOver ? "opacity-100" : "opacity-0"
}`}
></div>
<input {...getInputProps({ multiple: options?.maxFiles ? options.maxFiles > 1 : false })} />
<Icon icon="material-symbols:upload" fontSize={48} />
{children}
</div>
);
}

View file

@ -2,12 +2,13 @@ import { useRouter } from "next/navigation";
import Image from "next/image"; import Image from "next/image";
import { useCallback, useState } from "react"; import { useCallback, useState } from "react";
import { FileWithPath, useDropzone } from "react-dropzone"; import { FileWithPath } from "react-dropzone";
import { Icon } from "@iconify/react"; import { Icon } from "@iconify/react";
import dayjs from "dayjs"; import dayjs from "dayjs";
import SubmitDialogButton from "./submit-dialog-button"; import SubmitDialogButton from "./submit-dialog-button";
import Dropzone from "../dropzone";
export default function ProfilePictureSettings() { export default function ProfilePictureSettings() {
const router = useRouter(); const router = useRouter();
@ -41,14 +42,6 @@ export default function ProfilePictureSettings() {
setNewPicture(acceptedFiles[0]); setNewPicture(acceptedFiles[0]);
}, []); }, []);
const { getRootProps, getInputProps } = useDropzone({
onDrop: handleDrop,
maxFiles: 1,
accept: {
"image/*": [".png", ".jpg", ".jpeg", ".bmp", ".webp", ".heic"],
},
});
return ( return (
<div className="grid grid-cols-2"> <div className="grid grid-cols-2">
<div> <div>
@ -58,32 +51,21 @@ export default function ProfilePictureSettings() {
<div className="flex flex-col"> <div className="flex flex-col">
<div className="flex justify-end"> <div className="flex justify-end">
<div <Dropzone onDrop={handleDrop} options={{ maxFiles: 1 }}>
{...getRootProps({
className:
"bg-orange-200 flex flex-col justify-center items-center gap-2 p-4 rounded-xl border border-2 border-dashed border-amber-500 select-none h-full w-sm",
})}
>
{newPicture ? (
<Image
src={URL.createObjectURL(newPicture)}
alt="new profile picture"
width={128}
height={128}
className="rounded-full aspect-square border-2 border-amber-500 object-cover"
/>
) : (
<>
<input {...getInputProps({ multiple: false })} />
<Icon icon="material-symbols:upload" fontSize={32} />
<p className="text-center text-xs"> <p className="text-center text-xs">
Drag and drop your profile picture here Drag and drop your profile picture here
<br /> <br />
or click to open or click to open
</p> </p>
</>
)} <Image
</div> src={newPicture ? URL.createObjectURL(newPicture) : "/guest.webp"}
alt="new profile picture"
width={96}
height={96}
className="rounded-full aspect-square border-2 border-amber-500 object-cover"
/>
</Dropzone>
</div> </div>
<div className="flex justify-end gap-1 mt-2"> <div className="flex justify-end gap-1 mt-2">

View file

@ -3,8 +3,7 @@
import { redirect } from "next/navigation"; import { redirect } from "next/navigation";
import { useCallback, useEffect, useRef, useState } from "react"; import { useCallback, useEffect, useRef, useState } from "react";
import { FileWithPath, useDropzone } from "react-dropzone"; import { FileWithPath } from "react-dropzone";
import { Icon } from "@iconify/react";
import { Mii } from "@prisma/client"; import { Mii } from "@prisma/client";
import { nameSchema, tagsSchema } from "@/lib/schemas"; import { nameSchema, tagsSchema } from "@/lib/schemas";
@ -14,6 +13,7 @@ import ImageList from "./image-list";
import LikeButton from "../like-button"; import LikeButton from "../like-button";
import Carousel from "../carousel"; import Carousel from "../carousel";
import SubmitButton from "../submit-button"; import SubmitButton from "../submit-button";
import Dropzone from "../dropzone";
interface Props { interface Props {
mii: Mii; mii: Mii;
@ -33,14 +33,6 @@ export default function EditForm({ mii, likes }: Props) {
[files.length] [files.length]
); );
const { getRootProps, getInputProps } = useDropzone({
onDrop: handleDrop,
maxFiles: 3,
accept: {
"image/*": [".png", ".jpg", ".jpeg", ".bmp", ".webp"],
},
});
const [error, setError] = useState<string | undefined>(undefined); const [error, setError] = useState<string | undefined>(undefined);
const [name, setName] = useState(mii.name); const [name, setName] = useState(mii.name);
@ -197,20 +189,13 @@ export default function EditForm({ mii, likes }: Props) {
</div> </div>
<div className="max-w-md w-full self-center"> <div className="max-w-md w-full self-center">
<div <Dropzone onDrop={handleDrop}>
{...getRootProps({
className:
"bg-orange-200 flex flex-col justify-center items-center gap-2 p-4 rounded-xl border border-2 border-dashed border-amber-500 select-none h-full",
})}
>
<input {...getInputProps()} />
<Icon icon="material-symbols:upload" fontSize={48} />
<p className="text-center text-sm"> <p className="text-center text-sm">
Drag and drop your images here Drag and drop your images here
<br /> <br />
or click to open or click to open
</p> </p>
</div> </Dropzone>
</div> </div>
<ImageList files={files} setFiles={setFiles} /> <ImageList files={files} setFiles={setFiles} />

View file

@ -3,7 +3,7 @@
import { redirect } from "next/navigation"; import { redirect } from "next/navigation";
import { useCallback, useEffect, useState } from "react"; import { useCallback, useEffect, useState } from "react";
import { FileWithPath, useDropzone } from "react-dropzone"; import { FileWithPath } from "react-dropzone";
import { Icon } from "@iconify/react"; import { Icon } from "@iconify/react";
import qrcode from "qrcode-generator"; import qrcode from "qrcode-generator";
@ -21,6 +21,7 @@ import SubmitTutorialButton from "../tutorial/submit";
import LikeButton from "../like-button"; import LikeButton from "../like-button";
import Carousel from "../carousel"; import Carousel from "../carousel";
import SubmitButton from "../submit-button"; import SubmitButton from "../submit-button";
import Dropzone from "../dropzone";
export default function SubmitForm() { export default function SubmitForm() {
const [files, setFiles] = useState<FileWithPath[]>([]); const [files, setFiles] = useState<FileWithPath[]>([]);
@ -33,14 +34,6 @@ export default function SubmitForm() {
[files.length] [files.length]
); );
const { getRootProps, getInputProps } = useDropzone({
onDrop: handleDrop,
maxFiles: 3,
accept: {
"image/*": [".png", ".jpg", ".jpeg", ".bmp", ".webp"],
},
});
const [isQrScannerOpen, setIsQrScannerOpen] = useState(false); const [isQrScannerOpen, setIsQrScannerOpen] = useState(false);
const [studioUrl, setStudioUrl] = useState<string | undefined>(); const [studioUrl, setStudioUrl] = useState<string | undefined>();
const [generatedQrCodeUrl, setGeneratedQrCodeUrl] = useState<string | undefined>(); const [generatedQrCodeUrl, setGeneratedQrCodeUrl] = useState<string | undefined>();
@ -234,20 +227,13 @@ export default function SubmitForm() {
</div> </div>
<div className="max-w-md w-full self-center"> <div className="max-w-md w-full self-center">
<div <Dropzone onDrop={handleDrop}>
{...getRootProps({
className:
"bg-orange-200 flex flex-col justify-center items-center gap-2 p-4 rounded-xl border border-2 border-dashed border-amber-500 select-none h-full",
})}
>
<input {...getInputProps()} />
<Icon icon="material-symbols:upload" fontSize={48} />
<p className="text-center text-sm"> <p className="text-center text-sm">
Drag and drop your images here Drag and drop your images here
<br /> <br />
or click to open or click to open
</p> </p>
</div> </Dropzone>
</div> </div>
<ImageList files={files} setFiles={setFiles} /> <ImageList files={files} setFiles={setFiles} />

View file

@ -1,9 +1,9 @@
"use client"; "use client";
import { useCallback, useRef } from "react"; import { useCallback, useRef } from "react";
import { FileWithPath, useDropzone } from "react-dropzone"; import { FileWithPath } from "react-dropzone";
import { Icon } from "@iconify/react";
import jsQR from "jsqr"; import jsQR from "jsqr";
import Dropzone from "../dropzone";
interface Props { interface Props {
setQrBytesRaw: React.Dispatch<React.SetStateAction<number[]>>; setQrBytesRaw: React.Dispatch<React.SetStateAction<number[]>>;
@ -12,7 +12,7 @@ interface Props {
export default function QrUpload({ setQrBytesRaw }: Props) { export default function QrUpload({ setQrBytesRaw }: Props) {
const canvasRef = useRef<HTMLCanvasElement>(null); const canvasRef = useRef<HTMLCanvasElement>(null);
const onDrop = useCallback( const handleDrop = useCallback(
(acceptedFiles: FileWithPath[]) => { (acceptedFiles: FileWithPath[]) => {
acceptedFiles.forEach((file) => { acceptedFiles.forEach((file) => {
// Scan QR code // Scan QR code
@ -44,30 +44,17 @@ export default function QrUpload({ setQrBytesRaw }: Props) {
[setQrBytesRaw] [setQrBytesRaw]
); );
const { getRootProps, getInputProps } = useDropzone({
onDrop,
accept: {
"image/*": [".png", ".jpg", ".jpeg", ".bmp", ".webp", ".heic"],
},
});
return ( return (
<div className="max-w-md w-full"> <div className="max-w-md w-full">
<div <Dropzone onDrop={handleDrop} options={{ maxFiles: 1 }}>
{...getRootProps({
className:
"bg-orange-200 flex flex-col justify-center items-center gap-2 p-4 rounded-xl border border-2 border-dashed border-amber-500 select-none h-full",
})}
>
<input {...getInputProps({ multiple: false })} />
<Icon icon="material-symbols:upload" fontSize={48} />
<p className="text-center text-sm"> <p className="text-center text-sm">
Drag and drop your QR code image here Drag and drop your QR code image here
<br /> <br />
or click to open or click to open
</p> </p>
</div> </Dropzone>
{/* Canvas is used to scan the QR code */}
<canvas ref={canvasRef} className="hidden" /> <canvas ref={canvasRef} className="hidden" />
</div> </div>
); );