From 5acdc502001f4b3e1abbc2b99286c5a75ca4646e Mon Sep 17 00:00:00 2001 From: trafficlunar Date: Wed, 9 Apr 2025 18:24:25 +0100 Subject: [PATCH] feat: image viewer --- src/app/components/carousel.tsx | 5 ++- src/app/components/image-viewer.tsx | 65 +++++++++++++++++++++++++++++ src/app/mii/[slug]/page.tsx | 6 +-- 3 files changed, 71 insertions(+), 5 deletions(-) create mode 100644 src/app/components/image-viewer.tsx diff --git a/src/app/components/carousel.tsx b/src/app/components/carousel.tsx index 82b150a..c294d7e 100644 --- a/src/app/components/carousel.tsx +++ b/src/app/components/carousel.tsx @@ -1,10 +1,11 @@ "use client"; -import Image from "next/image"; import { useCallback, useEffect, useState } from "react"; import useEmblaCarousel from "embla-carousel-react"; import { Icon } from "@iconify/react"; +import ImageViewer from "./image-viewer"; + interface Props { images: string[]; className?: string; @@ -31,7 +32,7 @@ export default function Carousel({ images, className }: Props) {
{images.map((src, index) => (
- mii image +
))}
diff --git a/src/app/components/image-viewer.tsx b/src/app/components/image-viewer.tsx new file mode 100644 index 0000000..d8ac853 --- /dev/null +++ b/src/app/components/image-viewer.tsx @@ -0,0 +1,65 @@ +"use client"; + +import Image from "next/image"; +import { useEffect, useState } from "react"; +import { createPortal } from "react-dom"; +import { Icon } from "@iconify/react"; + +interface Props { + src: string; + alt: string; + width: number; + height: number; + className?: string; +} + +export default function ImageViewer({ src, alt, width, height, className }: Props) { + const [isOpen, setIsOpen] = useState(false); + const [isVisible, setIsVisible] = useState(false); + + const close = () => { + setIsVisible(false); + setTimeout(() => { + setIsOpen(false); + }, 300); // duration matches animation timing + }; + + useEffect(() => { + if (isOpen) { + // slight delay to trigger animation + setTimeout(() => setIsVisible(true), 10); + } + }, [isOpen]); + + return ( + <> + {alt} setIsOpen(true)} /> + + {isOpen && + createPortal( +
+
+ +
+
+ +
+ {alt} +
+
, + document.body + )} + + ); +} diff --git a/src/app/mii/[slug]/page.tsx b/src/app/mii/[slug]/page.tsx index 539e8e4..3ffaaba 100644 --- a/src/app/mii/[slug]/page.tsx +++ b/src/app/mii/[slug]/page.tsx @@ -1,4 +1,3 @@ -import Image from "next/image"; import Link from "next/link"; import { redirect } from "next/navigation"; @@ -7,6 +6,7 @@ import { prisma } from "@/lib/prisma"; import Carousel from "@/app/components/carousel"; import LikeButton from "@/app/components/like-button"; +import ImageViewer from "@/app/components/image-viewer"; interface Props { params: Promise<{ slug: string }>; @@ -108,12 +108,12 @@ export default async function MiiPage({ params }: Props) {
{images.map((src, index) => ( - mii screenshot ))}