From a806003bc6bcd150bfb4bd23acc3fa56b758b84c Mon Sep 17 00:00:00 2001 From: trafficlunar Date: Mon, 31 Mar 2025 18:04:53 +0100 Subject: [PATCH] feat: image carousels --- package.json | 1 + pnpm-lock.yaml | 28 ++++++++++++++++ src/app/components/carousel.tsx | 57 +++++++++++++++++++++++++++++++++ src/app/components/mii-list.tsx | 8 ++--- src/app/mii/[slug]/page.tsx | 4 ++- 5 files changed, 93 insertions(+), 5 deletions(-) create mode 100644 src/app/components/carousel.tsx diff --git a/package.json b/package.json index b174ebf..8dfd447 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,7 @@ "dependencies": { "@auth/prisma-adapter": "2.7.2", "@prisma/client": "^6.5.0", + "embla-carousel-react": "^8.5.2", "next": "15.2.4", "next-auth": "5.0.0-beta.25", "react": "^19.0.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ef65071..3747f31 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -14,6 +14,9 @@ importers: '@prisma/client': specifier: ^6.5.0 version: 6.5.0(prisma@6.5.0(typescript@5.8.2))(typescript@5.8.2) + embla-carousel-react: + specifier: ^8.5.2 + version: 8.5.2(react@19.0.0) next: specifier: 15.2.4 version: 15.2.4(react-dom@19.0.0(react@19.0.0))(react@19.0.0) @@ -962,6 +965,19 @@ packages: resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} engines: {node: '>= 0.4'} + embla-carousel-react@8.5.2: + resolution: {integrity: sha512-Tmx+uY3MqseIGdwp0ScyUuxpBgx5jX1f7od4Cm5mDwg/dptEiTKf9xp6tw0lZN2VA9JbnVMl/aikmbc53c6QFA==} + peerDependencies: + react: ^16.8.0 || ^17.0.1 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc + + embla-carousel-reactive-utils@8.5.2: + resolution: {integrity: sha512-QC8/hYSK/pEmqEdU1IO5O+XNc/Ptmmq7uCB44vKplgLKhB/l0+yvYx0+Cv0sF6Ena8Srld5vUErZkT+yTahtDg==} + peerDependencies: + embla-carousel: 8.5.2 + + embla-carousel@8.5.2: + resolution: {integrity: sha512-xQ9oVLrun/eCG/7ru3R+I5bJ7shsD8fFwLEY7yPe27/+fDHCNj0OT5EoG5ZbFyOxOcG6yTwW8oTz/dWyFnyGpg==} + emoji-regex@9.2.2: resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} @@ -2766,6 +2782,18 @@ snapshots: es-errors: 1.3.0 gopd: 1.2.0 + embla-carousel-react@8.5.2(react@19.0.0): + dependencies: + embla-carousel: 8.5.2 + embla-carousel-reactive-utils: 8.5.2(embla-carousel@8.5.2) + react: 19.0.0 + + embla-carousel-reactive-utils@8.5.2(embla-carousel@8.5.2): + dependencies: + embla-carousel: 8.5.2 + + embla-carousel@8.5.2: {} + emoji-regex@9.2.2: {} enhanced-resolve@5.18.1: diff --git a/src/app/components/carousel.tsx b/src/app/components/carousel.tsx new file mode 100644 index 0000000..d867c89 --- /dev/null +++ b/src/app/components/carousel.tsx @@ -0,0 +1,57 @@ +"use client"; + +import { useCallback, useEffect, useState } from "react"; +import useEmblaCarousel from "embla-carousel-react"; +import { Icon } from "@iconify/react"; + +interface Props { + images: string[]; + className?: string; +} + +export default function Carousel({ images, className }: Props) { + const [emblaRef, emblaApi] = useEmblaCarousel(); + const [selectedIndex, setSelectedIndex] = useState(0); + const [scrollSnaps, setScrollSnaps] = useState([]); + + useEffect(() => { + if (!emblaApi) return; + setScrollSnaps(emblaApi.scrollSnapList()); + emblaApi.on("select", () => setSelectedIndex(emblaApi.selectedScrollSnap())); + }, [emblaApi]); + + const scrollTo = useCallback((index: number) => emblaApi && emblaApi.scrollTo(index), [emblaApi]); + const scrollPrev = useCallback(() => emblaApi && emblaApi.scrollPrev(), [emblaApi]); + const scrollNext = useCallback(() => emblaApi && emblaApi.scrollNext(), [emblaApi]); + + return ( +
+
+
+ {images.map((src, index) => ( +
+ +
+ ))} +
+
+ + + + +
+ {scrollSnaps.map((_, index) => ( +
+
+ ); +} diff --git a/src/app/components/mii-list.tsx b/src/app/components/mii-list.tsx index a89690c..634d2c2 100644 --- a/src/app/components/mii-list.tsx +++ b/src/app/components/mii-list.tsx @@ -1,10 +1,11 @@ +import Link from "next/link"; import { Prisma } from "@prisma/client"; import { auth } from "@/lib/auth"; import { prisma } from "@/lib/prisma"; +import Carousel from "./carousel"; import LikeButton from "./like-button"; -import Link from "next/link"; interface Props { searchParams: Promise<{ [key: string]: string | string[] | undefined }>; @@ -120,9 +121,8 @@ export default async function MiiList({ searchParams, userId }: Props) { key={mii.id} className="flex flex-col bg-zinc-50 rounded-3xl border-2 border-zinc-300 shadow-lg p-3 transition hover:scale-105 hover:bg-cyan-100 hover:border-cyan-600" > - - mii - + +
{mii.name} diff --git a/src/app/mii/[slug]/page.tsx b/src/app/mii/[slug]/page.tsx index 25ad627..8515485 100644 --- a/src/app/mii/[slug]/page.tsx +++ b/src/app/mii/[slug]/page.tsx @@ -3,6 +3,7 @@ import Link from "next/link"; import { auth } from "@/lib/auth"; import { prisma } from "@/lib/prisma"; +import Carousel from "@/app/components/carousel"; import LikeButton from "@/app/components/like-button"; interface Props { @@ -38,7 +39,8 @@ export default async function ProfilePage({ params }: Props) { return (
- mii + +

{mii?.name}