71 lines
2.6 KiB
TypeScript
71 lines
2.6 KiB
TypeScript
"use client";
|
|
|
|
import { useState } from "react";
|
|
import { AnimatePresence, motion } from "framer-motion";
|
|
import { useTrack } from "@/hooks/use-track";
|
|
import { TRACKING_EVENTS } from "@/lib/tracking-events";
|
|
|
|
export function ScreenshotGallery({ slug, shots }: { slug: string; shots: string[] }) {
|
|
const [active, setActive] = useState<number | null>(null);
|
|
const { track } = useTrack();
|
|
|
|
const open = (idx: number) => {
|
|
setActive(idx);
|
|
track(TRACKING_EVENTS.galleryOpen, { slug, index: idx });
|
|
};
|
|
|
|
const move = (delta: number) => {
|
|
setActive((prev) => {
|
|
if (prev === null) return 0;
|
|
const next = (prev + delta + shots.length) % shots.length;
|
|
track(TRACKING_EVENTS.galleryNavigate, { slug, from: prev, to: next });
|
|
return next;
|
|
});
|
|
};
|
|
|
|
return (
|
|
<>
|
|
<section className="grid gap-4 md:grid-cols-3">
|
|
{shots.map((_, idx) => (
|
|
<button
|
|
key={idx}
|
|
onClick={() => open(idx)}
|
|
className="card-glass group relative grid h-40 place-items-center overflow-hidden text-sm text-slate-300"
|
|
>
|
|
<div className="absolute inset-0 bg-gradient-to-br from-cyan-300/20 via-transparent to-violet-300/20 opacity-70 transition group-hover:opacity-100" />
|
|
截图预览 {idx + 1}
|
|
</button>
|
|
))}
|
|
</section>
|
|
<AnimatePresence>
|
|
{active !== null && (
|
|
<motion.div
|
|
initial={{ opacity: 0 }}
|
|
animate={{ opacity: 1 }}
|
|
exit={{ opacity: 0 }}
|
|
className="fixed inset-0 z-50 grid place-items-center bg-slate-950/78 p-4"
|
|
onClick={() => setActive(null)}
|
|
>
|
|
<motion.div
|
|
initial={{ scale: 0.98, opacity: 0 }}
|
|
animate={{ scale: 1, opacity: 1 }}
|
|
exit={{ scale: 0.98, opacity: 0 }}
|
|
className="card-glass w-full max-w-3xl"
|
|
onClick={(e) => e.stopPropagation()}
|
|
>
|
|
<div className="grid h-[58vh] place-items-center rounded-2xl border border-white/10 bg-slate-900/40">
|
|
<p className="text-lg text-slate-200">截图大图 {active + 1}</p>
|
|
</div>
|
|
<div className="mt-3 flex items-center justify-between">
|
|
<button className="cta-sub" onClick={() => move(-1)}>上一张</button>
|
|
<p className="text-xs text-slate-400">{active + 1} / {shots.length}</p>
|
|
<button className="cta-sub" onClick={() => move(1)}>下一张</button>
|
|
</div>
|
|
</motion.div>
|
|
</motion.div>
|
|
)}
|
|
</AnimatePresence>
|
|
</>
|
|
);
|
|
}
|