import { useRef, useState } from 'react'; interface ProductGalleryProps { images: string[]; alt: string; } /** * The square photo on a product card, with arrows when there's more than one shot. * * Replaces react-awesome-slider, which hasn't been published since 2020 and pins peer deps to * React 16 — the same job in a fraction of the code, and one less unmaintained dependency in a * build we gate on CVEs. Behaviour is what the old cards did: one image at a time, square crop, * arrows only when they'd do something — plus swipe and keyboard, which the old slider had on touch * devices and the first version of this did not. */ /** Past this many pixels a horizontal drag counts as a swipe rather than a tap or a page scroll. */ const SWIPE_THRESHOLD = 40; const ProductGallery: React.FC = ({ images, alt }) => { const [index, setIndex] = useState(0); const many = images.length > 1; const touchStart = useRef<{ x: number; y: number } | null>(null); const step = (delta: number) => setIndex((i) => (i + delta + images.length) % images.length); const onTouchStart = (e: React.TouchEvent) => { const t = e.touches[0]; touchStart.current = { x: t.clientX, y: t.clientY }; }; const onTouchEnd = (e: React.TouchEvent) => { const start = touchStart.current; touchStart.current = null; if (!start || !many) return; const t = e.changedTouches[0]; const dx = t.clientX - start.x; const dy = t.clientY - start.y; // Ignore anything more vertical than horizontal — that is the page being scrolled, not a swipe. if (Math.abs(dx) < SWIPE_THRESHOLD || Math.abs(dx) <= Math.abs(dy)) return; step(dx < 0 ? 1 : -1); }; return (
{ if (e.key === 'ArrowLeft') { e.preventDefault(); step(-1); } if (e.key === 'ArrowRight') { e.preventDefault(); step(1); } } : undefined} tabIndex={many ? 0 : undefined} role={many ? 'group' : undefined} aria-roledescription={many ? 'carousel' : undefined} aria-label={many ? `${alt} — ${images.length} photos` : undefined} > {images.map((src, i) => ( {i ))} {many && ( <> {/* Which of how many. The old slider ran with bullets off, but once a card can be swiped there is otherwise nothing to say it holds more than one photo. */} )}
); }; export default ProductGallery;