feat: dropzone dragging over effect
This commit is contained in:
parent
6a965ecd66
commit
045308dcef
5 changed files with 82 additions and 91 deletions
51
src/components/dropzone.tsx
Normal file
51
src/components/dropzone.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
|
|
@ -2,12 +2,13 @@ import { useRouter } from "next/navigation";
|
|||
import Image from "next/image";
|
||||
|
||||
import { useCallback, useState } from "react";
|
||||
import { FileWithPath, useDropzone } from "react-dropzone";
|
||||
import { FileWithPath } from "react-dropzone";
|
||||
|
||||
import { Icon } from "@iconify/react";
|
||||
import dayjs from "dayjs";
|
||||
|
||||
import SubmitDialogButton from "./submit-dialog-button";
|
||||
import Dropzone from "../dropzone";
|
||||
|
||||
export default function ProfilePictureSettings() {
|
||||
const router = useRouter();
|
||||
|
|
@ -41,14 +42,6 @@ export default function ProfilePictureSettings() {
|
|||
setNewPicture(acceptedFiles[0]);
|
||||
}, []);
|
||||
|
||||
const { getRootProps, getInputProps } = useDropzone({
|
||||
onDrop: handleDrop,
|
||||
maxFiles: 1,
|
||||
accept: {
|
||||
"image/*": [".png", ".jpg", ".jpeg", ".bmp", ".webp", ".heic"],
|
||||
},
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="grid grid-cols-2">
|
||||
<div>
|
||||
|
|
@ -58,32 +51,21 @@ export default function ProfilePictureSettings() {
|
|||
|
||||
<div className="flex flex-col">
|
||||
<div className="flex justify-end">
|
||||
<div
|
||||
{...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">
|
||||
Drag and drop your profile picture here
|
||||
<br />
|
||||
or click to open
|
||||
</p>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
<Dropzone onDrop={handleDrop} options={{ maxFiles: 1 }}>
|
||||
<p className="text-center text-xs">
|
||||
Drag and drop your profile picture here
|
||||
<br />
|
||||
or click to open
|
||||
</p>
|
||||
|
||||
<Image
|
||||
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 className="flex justify-end gap-1 mt-2">
|
||||
|
|
|
|||
|
|
@ -3,8 +3,7 @@
|
|||
import { redirect } from "next/navigation";
|
||||
|
||||
import { useCallback, useEffect, useRef, useState } from "react";
|
||||
import { FileWithPath, useDropzone } from "react-dropzone";
|
||||
import { Icon } from "@iconify/react";
|
||||
import { FileWithPath } from "react-dropzone";
|
||||
import { Mii } from "@prisma/client";
|
||||
|
||||
import { nameSchema, tagsSchema } from "@/lib/schemas";
|
||||
|
|
@ -14,6 +13,7 @@ import ImageList from "./image-list";
|
|||
import LikeButton from "../like-button";
|
||||
import Carousel from "../carousel";
|
||||
import SubmitButton from "../submit-button";
|
||||
import Dropzone from "../dropzone";
|
||||
|
||||
interface Props {
|
||||
mii: Mii;
|
||||
|
|
@ -33,14 +33,6 @@ export default function EditForm({ mii, likes }: Props) {
|
|||
[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 [name, setName] = useState(mii.name);
|
||||
|
|
@ -197,20 +189,13 @@ export default function EditForm({ mii, likes }: Props) {
|
|||
</div>
|
||||
|
||||
<div className="max-w-md w-full self-center">
|
||||
<div
|
||||
{...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} />
|
||||
<Dropzone onDrop={handleDrop}>
|
||||
<p className="text-center text-sm">
|
||||
Drag and drop your images here
|
||||
<br />
|
||||
or click to open
|
||||
</p>
|
||||
</div>
|
||||
</Dropzone>
|
||||
</div>
|
||||
|
||||
<ImageList files={files} setFiles={setFiles} />
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
import { redirect } from "next/navigation";
|
||||
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import { FileWithPath, useDropzone } from "react-dropzone";
|
||||
import { FileWithPath } from "react-dropzone";
|
||||
import { Icon } from "@iconify/react";
|
||||
|
||||
import qrcode from "qrcode-generator";
|
||||
|
|
@ -21,6 +21,7 @@ import SubmitTutorialButton from "../tutorial/submit";
|
|||
import LikeButton from "../like-button";
|
||||
import Carousel from "../carousel";
|
||||
import SubmitButton from "../submit-button";
|
||||
import Dropzone from "../dropzone";
|
||||
|
||||
export default function SubmitForm() {
|
||||
const [files, setFiles] = useState<FileWithPath[]>([]);
|
||||
|
|
@ -33,14 +34,6 @@ export default function SubmitForm() {
|
|||
[files.length]
|
||||
);
|
||||
|
||||
const { getRootProps, getInputProps } = useDropzone({
|
||||
onDrop: handleDrop,
|
||||
maxFiles: 3,
|
||||
accept: {
|
||||
"image/*": [".png", ".jpg", ".jpeg", ".bmp", ".webp"],
|
||||
},
|
||||
});
|
||||
|
||||
const [isQrScannerOpen, setIsQrScannerOpen] = useState(false);
|
||||
const [studioUrl, setStudioUrl] = useState<string | undefined>();
|
||||
const [generatedQrCodeUrl, setGeneratedQrCodeUrl] = useState<string | undefined>();
|
||||
|
|
@ -234,20 +227,13 @@ export default function SubmitForm() {
|
|||
</div>
|
||||
|
||||
<div className="max-w-md w-full self-center">
|
||||
<div
|
||||
{...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} />
|
||||
<Dropzone onDrop={handleDrop}>
|
||||
<p className="text-center text-sm">
|
||||
Drag and drop your images here
|
||||
<br />
|
||||
or click to open
|
||||
</p>
|
||||
</div>
|
||||
</Dropzone>
|
||||
</div>
|
||||
|
||||
<ImageList files={files} setFiles={setFiles} />
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
"use client";
|
||||
|
||||
import { useCallback, useRef } from "react";
|
||||
import { FileWithPath, useDropzone } from "react-dropzone";
|
||||
import { Icon } from "@iconify/react";
|
||||
import { FileWithPath } from "react-dropzone";
|
||||
import jsQR from "jsqr";
|
||||
import Dropzone from "../dropzone";
|
||||
|
||||
interface Props {
|
||||
setQrBytesRaw: React.Dispatch<React.SetStateAction<number[]>>;
|
||||
|
|
@ -12,7 +12,7 @@ interface Props {
|
|||
export default function QrUpload({ setQrBytesRaw }: Props) {
|
||||
const canvasRef = useRef<HTMLCanvasElement>(null);
|
||||
|
||||
const onDrop = useCallback(
|
||||
const handleDrop = useCallback(
|
||||
(acceptedFiles: FileWithPath[]) => {
|
||||
acceptedFiles.forEach((file) => {
|
||||
// Scan QR code
|
||||
|
|
@ -44,30 +44,17 @@ export default function QrUpload({ setQrBytesRaw }: Props) {
|
|||
[setQrBytesRaw]
|
||||
);
|
||||
|
||||
const { getRootProps, getInputProps } = useDropzone({
|
||||
onDrop,
|
||||
accept: {
|
||||
"image/*": [".png", ".jpg", ".jpeg", ".bmp", ".webp", ".heic"],
|
||||
},
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="max-w-md w-full">
|
||||
<div
|
||||
{...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} />
|
||||
<Dropzone onDrop={handleDrop} options={{ maxFiles: 1 }}>
|
||||
<p className="text-center text-sm">
|
||||
Drag and drop your QR code image here
|
||||
<br />
|
||||
or click to open
|
||||
</p>
|
||||
</div>
|
||||
</Dropzone>
|
||||
|
||||
{/* Canvas is used to scan the QR code */}
|
||||
<canvas ref={canvasRef} className="hidden" />
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
Loading…
Reference in a new issue