116 lines
3.5 KiB
TypeScript
116 lines
3.5 KiB
TypeScript
"use client";
|
|
|
|
import { cn } from "@/lib/utils";
|
|
import React from "react";
|
|
import { ArtworkImageCard } from "./ArtworkImageCard";
|
|
|
|
type ArtworkGalleryItem = {
|
|
id: string;
|
|
name: string;
|
|
altText: string | null;
|
|
okLabL: number | null;
|
|
file: { fileKey: string };
|
|
metadata: { width: number; height: number } | null;
|
|
tags: { id: string; name: string }[];
|
|
};
|
|
|
|
type FitMode =
|
|
| { mode: "fixedWidth"; width: number } // height varies
|
|
| { mode: "fixedHeight"; height: number }; // width varies
|
|
|
|
function getOverlayTextClass(okLabL: number | null | undefined) {
|
|
return "text-white";
|
|
}
|
|
|
|
function getOverlayBgClass(okLabL: number | null | undefined) {
|
|
return "bg-black/45";
|
|
}
|
|
|
|
type OpenSheet = "alt" | "tags" | null;
|
|
|
|
const BUTTON_BAR_HEIGHT = 36;
|
|
|
|
export default function ArtworkThumbGallery({
|
|
items,
|
|
hrefBase = "/artworks",
|
|
fit = { mode: "fixedWidth", width: 400 },
|
|
}: {
|
|
items: ArtworkGalleryItem[];
|
|
hrefBase?: string;
|
|
fit?: FitMode;
|
|
}) {
|
|
const [openSheet, setOpenSheet] = React.useState<Record<string, OpenSheet>>({});
|
|
|
|
const toggleSheet = (id: string, which: Exclude<OpenSheet, null>) => {
|
|
setOpenSheet((prev) => {
|
|
const current = prev[id] ?? null;
|
|
// toggle off if same, switch if different
|
|
return { ...prev, [id]: current === which ? null : which };
|
|
});
|
|
};
|
|
|
|
if (items.length === 0) {
|
|
return <p className="text-muted-foreground italic">No artworks found.</p>;
|
|
}
|
|
|
|
return (
|
|
<div
|
|
className="grid gap-3.5 justify-center"
|
|
style={{
|
|
gridTemplateColumns: "repeat(auto-fit, minmax(260px, 1fr))",
|
|
}}
|
|
>
|
|
{items.map((a) => {
|
|
const textClass = getOverlayTextClass(a.okLabL);
|
|
const bgClass = getOverlayBgClass(a.okLabL);
|
|
|
|
const w = a.metadata?.width ?? 4;
|
|
const h = a.metadata?.height ?? 3;
|
|
|
|
const tileStyle: React.CSSProperties =
|
|
fit.mode === "fixedWidth"
|
|
? { aspectRatio: `${w} / ${h}` }
|
|
: { height: fit.height, aspectRatio: `${w} / ${h}` };
|
|
|
|
const sheet = openSheet[a.id] ?? null;
|
|
|
|
return (
|
|
<div key={a.id} className="w-full" style={tileStyle}>
|
|
<div className="relative h-full w-full">
|
|
<ArtworkImageCard
|
|
mode="tile"
|
|
href={`${hrefBase}/single/${a.id}?from=animal-studies`}
|
|
src={`/api/image/thumbnail/${a.file.fileKey}.webp`}
|
|
alt={a.altText ?? a.name ?? "Artwork"}
|
|
width={a.metadata?.width ?? 0}
|
|
height={a.metadata?.height ?? 0}
|
|
aspectRatio={`${w} / ${h}`}
|
|
className="h-full w-full rounded-md"
|
|
imageClassName="object-cover"
|
|
sizes="(min-width: 1280px) 20vw, (min-width: 1024px) 25vw, (min-width: 768px) 33vw, (min-width: 640px) 50vw, 100vw"
|
|
/>
|
|
|
|
{/* Title overlay (restored) */}
|
|
<div
|
|
className={cn(
|
|
"pointer-events-none absolute left-0 right-0 top-0 px-3 py-2",
|
|
bgClass,
|
|
"backdrop-blur-[1px]"
|
|
)}
|
|
>
|
|
<div className={cn("truncate text-sm font-medium", textClass)}>{a.name}</div>
|
|
</div>
|
|
|
|
{/* Bottom reserved bar (if you need it later) */}
|
|
<div
|
|
className="absolute left-0 right-0 bottom-0 z-20 flex items-center justify-between px-2"
|
|
style={{ height: BUTTON_BAR_HEIGHT }}
|
|
/>
|
|
</div>
|
|
</div>
|
|
);
|
|
})}
|
|
</div>
|
|
);
|
|
}
|