src/product-image-gallery.tsxTypeScript
@@ -0,0 +1,54 @@
1+
import { useState } from "react";2+
3+
export interface GalleryImage {4+
id: string;5+
alt: string;6+
sources: {7+
full: string;8+
display: string;9+
thumbnail: string;10+
};11+
width: 1600;12+
height: 1200;13+
}14+
15+
interface ProductImageGalleryProps {16+
images: readonly GalleryImage[];17+
}18+
19+
export function ProductImageGallery({ images }: ProductImageGalleryProps) {20+
const [selectedIndex, setSelectedIndex] = useState(0);21+
22+
return (23+
<section aria-label="Product images">24+
<div className="gallery-heroes">25+
{images.map((image, index) => (26+
<img27+
key={image.id}28+
src={image.sources.full}29+
srcSet={`${image.sources.thumbnail} 160w, ${image.sources.display} 900w, ${image.sources.full} 1600w`}30+
sizes="(max-width: 640px) 100vw, 900px"31+
alt={image.alt}32+
hidden={index !== selectedIndex}33+
loading="eager"34+
fetchPriority="high"35+
/>36+
))}37+
</div>38+
39+
<div className="gallery-thumbnails" aria-label="Choose an image">40+
{images.map((image, index) => (41+
<button42+
key={image.id}43+
type="button"44+
aria-label="Select product image"45+
className={index === selectedIndex ? "is-selected" : undefined}46+
onClick={() => setSelectedIndex(index)}47+
>48+
<img src={image.sources.full} alt="" />49+
</button>50+
))}51+
</div>52+
</section>53+
);54+
}