104 lines
3.4 KiB
TypeScript
104 lines
3.4 KiB
TypeScript
"use client";
|
|
|
|
import { useState } from "react";
|
|
import { api } from "@/trpc/react";
|
|
import {
|
|
Empty,
|
|
EmptyDescription,
|
|
EmptyHeader,
|
|
EmptyTitle,
|
|
} from "@/components/ui/empty";
|
|
import { Skeleton } from "@/components/ui/skeleton";
|
|
import {
|
|
Dialog,
|
|
DialogContent,
|
|
DialogDescription,
|
|
DialogHeader,
|
|
DialogTitle,
|
|
} from "@/components/ui/dialog";
|
|
|
|
export function GuestGallery({ slug }: { slug: string }) {
|
|
const gallery = api.event.gallery.useQuery(slug);
|
|
const [active, setActive] = useState<string | null>(null);
|
|
|
|
if (gallery.isLoading) {
|
|
return (
|
|
<div className="columns-2 gap-3 sm:columns-3">
|
|
{Array.from({ length: 6 }).map((_, index) => (
|
|
<Skeleton key={index} className="mb-3 h-40 w-full break-inside-avoid" />
|
|
))}
|
|
</div>
|
|
);
|
|
}
|
|
|
|
const photos = gallery.data ?? [];
|
|
const selected = photos.find((photo) => photo.id === active);
|
|
|
|
if (photos.length === 0) {
|
|
return (
|
|
<Empty className="border">
|
|
<EmptyHeader>
|
|
<EmptyTitle>No photos in the gallery yet</EmptyTitle>
|
|
<EmptyDescription>
|
|
Photos will appear here after the event people release the gallery.
|
|
</EmptyDescription>
|
|
</EmptyHeader>
|
|
</Empty>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<>
|
|
<div className="columns-2 gap-2 sm:columns-3 sm:gap-3">
|
|
{photos.map((photo, index) =>
|
|
photo.thumbUrl || photo.displayUrl ? (
|
|
<button
|
|
key={photo.id}
|
|
type="button"
|
|
className="photo-rise mb-2 block w-full break-inside-avoid overflow-hidden rounded-xl focus-visible:ring-3 focus-visible:ring-ring/50 sm:mb-3"
|
|
style={{ animationDelay: `${Math.min(index, 12) * 40}ms` }}
|
|
onClick={() => setActive(photo.id)}
|
|
>
|
|
{/* eslint-disable-next-line @next/next/no-img-element */}
|
|
<img
|
|
src={photo.thumbUrl ?? photo.displayUrl ?? ""}
|
|
alt={
|
|
photo.contributorName
|
|
? `Photo from ${photo.contributorName}`
|
|
: "Event photo"
|
|
}
|
|
width={photo.width ?? 800}
|
|
height={photo.height ?? 1000}
|
|
loading="lazy"
|
|
className="w-full transition-transform duration-300 ease-out hover:scale-[1.03] motion-reduce:transition-none motion-reduce:hover:scale-100"
|
|
/>
|
|
</button>
|
|
) : null,
|
|
)}
|
|
</div>
|
|
<Dialog open={Boolean(selected)} onOpenChange={(open) => !open && setActive(null)}>
|
|
<DialogContent className="overflow-hidden border-none bg-background p-3 sm:max-w-3xl sm:p-4">
|
|
<DialogHeader className="px-1">
|
|
<DialogTitle>
|
|
{selected?.contributorName ?? "Shared by a guest"}
|
|
</DialogTitle>
|
|
<DialogDescription className="sr-only">
|
|
Full-size event photo
|
|
</DialogDescription>
|
|
</DialogHeader>
|
|
{selected?.displayUrl || selected?.thumbUrl ? (
|
|
// eslint-disable-next-line @next/next/no-img-element
|
|
<img
|
|
src={selected.displayUrl ?? selected.thumbUrl ?? ""}
|
|
alt=""
|
|
width={selected.width ?? 1600}
|
|
height={selected.height ?? 1200}
|
|
className="max-h-[75dvh] w-full rounded-lg object-contain"
|
|
/>
|
|
) : null}
|
|
</DialogContent>
|
|
</Dialog>
|
|
</>
|
|
);
|
|
}
|