Review an efficient product image gallery

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+ <img
27+ 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+ <button
42+ 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+}