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 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({
|
<p className="text-center text-xs">
|
||||||
className:
|
Drag and drop your profile picture here
|
||||||
"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",
|
<br />
|
||||||
})}
|
or click to open
|
||||||
>
|
</p>
|
||||||
{newPicture ? (
|
|
||||||
<Image
|
<Image
|
||||||
src={URL.createObjectURL(newPicture)}
|
src={newPicture ? URL.createObjectURL(newPicture) : "/guest.webp"}
|
||||||
alt="new profile picture"
|
alt="new profile picture"
|
||||||
width={128}
|
width={96}
|
||||||
height={128}
|
height={96}
|
||||||
className="rounded-full aspect-square border-2 border-amber-500 object-cover"
|
className="rounded-full aspect-square border-2 border-amber-500 object-cover"
|
||||||
/>
|
/>
|
||||||
) : (
|
</Dropzone>
|
||||||
<>
|
|
||||||
<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>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex justify-end gap-1 mt-2">
|
<div className="flex justify-end gap-1 mt-2">
|
||||||
|
|
|
||||||
|
|
@ -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} />
|
||||||
|
|
|
||||||
|
|
@ -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} />
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue