added project gallery behavior

This commit is contained in:
Hunter W.
2026-05-31 09:44:59 -04:00
parent 59e7589a29
commit 28a672c82b
8 changed files with 1053 additions and 640 deletions

View File

@@ -0,0 +1,155 @@
import { useState, useEffect } from "react";
type Props = {
images: string[];
title: string;
expansionHandler: (height: number) => void;
};
export default function SimpleGallery({ images, title, expansionHandler }: Props) {
const [active, setActive] = useState(0);
const [enlarged, setEnlarged] = useState(false);
let timerRef: any | null = null;
if (images.length === 0) return null;
function galleryImage(src: string, alt: string) {
return (
<div><img
id="gallery-image"
src={images[active]}
alt={`${title} screenshot ${active + 1}`}
style={enlarged ? {
maxWidth: "100%",
maxHeight: "90vh",
borderRadius: 6,
objectFit: "contain",
boxShadow: "0 8px 40px rgba(0,0,0,0.6)",
transition: "all 0.3s ease",} : {
width: "100%",
borderRadius: 4,
objectFit: "cover",
maxHeight: 250,
display: "block",
transition: "all 0.8s ease",
zIndex: 1,
}}
/>
<button
onClick={() => {
setEnlarged(!enlarged);
clearTimeout(timerRef);
timerRef = setTimeout(() => {
const imgElement = document.getElementById("gallery-image") as HTMLImageElement | null;
imgElement?.height && expansionHandler( !enlarged ? imgElement.height + 70 : 0);
}, 200); // delay to allow for CSS transition
}}
title={enlarged ? "Shrink image" : "Enlarge image"}
style={
enlarged ? {
position: "absolute",
top: 8,
right: 8,
width: 28,
height: 28,
borderRadius: 4,
border: "none",
background: "rgba(0,0,0,0.55)",
color: "#fff",
cursor: "pointer",
display: "flex",
alignItems: "center",
justifyContent: "center",
fontSize: 18,
lineHeight: 1,
backdropFilter: "blur(4px)",
zIndex: 1001,
transition: "background 0.15s ease",
} : {
position: "absolute",
bottom: 8,
right: 8,
width: 28,
height: 28,
borderRadius: 4,
border: "none",
background: "rgba(0,0,0,0.55)",
color: "#fff",
cursor: "pointer",
display: "flex",
alignItems: "center",
justifyContent: "center",
padding: 0,
backdropFilter: "blur(4px)",
transition: "background 0.15s ease",
}}
onMouseEnter={e => (e.currentTarget.style.background = "rgba(0,0,0,0.8)")}
onMouseLeave={e => (e.currentTarget.style.background = "rgba(0,0,0,0.55)")}>
{!enlarged && <svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M1 5V1H5" stroke="white" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round"/>
<path d="M13 9V13H9" stroke="white" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round"/>
<path d="M1 1L5.5 5.5" stroke="white" strokeWidth="1.5" strokeLinecap="round"/>
<path d="M13 13L8.5 8.5" stroke="white" strokeWidth="1.5" strokeLinecap="round"/>
</svg>}
{enlarged && <svg viewBox="0 0 60 60" xmlns="http://www.w3.org/2000/svg" width="60" height="60">
<line x1="15" y1="15" x2="45" y2="45" stroke="currentColor" strokeWidth="6" strokeLinecap="round"/>
<line x1="45" y1="15" x2="15" y2="45" stroke="currentColor" strokeWidth="6" strokeLinecap="round"/>
</svg>}
</button></div>
)
}
useEffect(() => {
return () => {
if (timerRef) {
clearTimeout(timerRef);
}
};
}, []);
return (
<>
<div style={{ marginBottom: 16 }}>
<div style={{ position: "relative", display: "inline-block", width: "100%" }}>
{galleryImage(images[active], `${title} screenshot ${active + 1}`)}
</div>
{images.length > 1 && (
<div style={{ display: "flex", gap: 6, marginTop: 8, flexWrap: "wrap" }}>
{images.map((src, i) => (
<img
key={i}
src={src}
alt={`${title} thumbnail ${i + 1}`}
onClick={() =>{
setActive(i)
clearTimeout(timerRef);
timerRef = setTimeout(() => {
const imgElement = document.getElementById("gallery-image") as HTMLImageElement | null;
imgElement?.height && expansionHandler(imgElement.height + 70);
}, 200); // delay to allow for CSS transition
}}
style={{
width: 48,
height: 36,
objectFit: "cover",
borderRadius: 3,
cursor: "pointer",
border: `2px solid ${i === active ? "var(--accent)" : "var(--border)"}`,
opacity: i === active ? 1 : 0.6,
transition: "all 0.15s ease",
pointerEvents: "auto",
}}
/>
))}
</div>
)}
</div>
</>
);
}