Skip to content

Commit 75172d9

Browse files
committed
feat: Create thumbnail images where the active image auto-scrolls into view
1 parent dafa48d commit 75172d9

File tree

6 files changed

+67
-9
lines changed

6 files changed

+67
-9
lines changed

package-lock.json

Lines changed: 15 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@
4747
"@release-it/conventional-changelog": "^9.0.4",
4848
"@testing-library/react": "^16.0.0",
4949
"@types/react": "^19.0.4",
50+
"@types/react-dom": "^19.0.3",
5051
"dotenv-cli": "^8.0.0",
5152
"husky": "^9.1.7",
5253
"jest": "^29.7.0",

src/ImageGallery.test.tsx

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,3 +110,12 @@ test("image gallery renders correctly with custom styles and fixed caption", ()
110110
/>
111111
);
112112
});
113+
114+
test("image gallery works with custom thumbnail border", () => {
115+
render(
116+
<ImageGallery
117+
imagesInfoArray={imagesArray}
118+
thumbnailBorder="medium dashed pink"
119+
/>
120+
);
121+
});

src/ImageGallery.tsx

Lines changed: 32 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import React, { ReactElement, useRef, useState, useEffect } from "react";
2+
import { flushSync } from "react-dom";
23
import { ImageGalleryPropsType } from "./ImageGallery.types";
34
import { imageGalleryStyles } from "./ImageGalleryStyles";
45

@@ -8,6 +9,7 @@ export function ImageGallery({
89
columnWidth = 230,
910
gapSize = 24,
1011
fixedCaption = false,
12+
thumbnailBorder = "3px solid #fff",
1113
customStyles = {},
1214
}: ImageGalleryPropsType) {
1315
const [imageSrc, setImageSrc] = useState<string | undefined>(undefined);
@@ -16,6 +18,7 @@ export function ImageGallery({
1618
const [fullscreen, setFullscreen] = useState(false);
1719
const dialogRef = useRef<HTMLDialogElement | null>(null);
1820
const lightboxRef = useRef<HTMLElement | null>(null);
21+
const activeThumbImgRef = useRef<HTMLImageElement | null>(null);
1922
const defaultStyles = imageGalleryStyles(
2023
columnCount,
2124
columnWidth,
@@ -70,6 +73,14 @@ export function ImageGallery({
7073
}
7174
}
7275

76+
function scrollActiveThumbImgIntoView() {
77+
activeThumbImgRef.current?.scrollIntoView({
78+
behavior: "smooth",
79+
block: "nearest",
80+
inline: "center",
81+
});
82+
}
83+
7384
function switchFullScreen(on: boolean) {
7485
if (on) {
7586
lightboxRef.current?.requestFullscreen().catch((error) => {
@@ -234,7 +245,10 @@ export function ImageGallery({
234245
...modalSlideBtnStyle,
235246
}}
236247
title="Previous image"
237-
onClick={() => changeSlide(-1)}
248+
onClick={() => {
249+
flushSync(() => changeSlide(-1));
250+
scrollActiveThumbImgIntoView();
251+
}}
238252
>
239253
{SvgElement(
240254
<path
@@ -257,7 +271,10 @@ export function ImageGallery({
257271
...modalSlideBtnStyle,
258272
}}
259273
title="Next image"
260-
onClick={() => changeSlide(1)}
274+
onClick={() => {
275+
flushSync(() => changeSlide(1));
276+
scrollActiveThumbImgIntoView();
277+
}}
261278
>
262279
{SvgElement(
263280
<path
@@ -267,7 +284,19 @@ export function ImageGallery({
267284
)}
268285
</button>
269286
</section>
270-
<section style={modalThumbnailSectionStyle}>I love you!</section>
287+
<section style={modalThumbnailSectionStyle}>
288+
{imagesInfoArray.map((imageInfo, index) => (
289+
<img
290+
ref={slideNumber - 1 === index ? activeThumbImgRef : null}
291+
style={{
292+
border: slideNumber - 1 === index ? thumbnailBorder : "",
293+
}}
294+
key={imageInfo.id}
295+
src={imageInfo.src}
296+
alt={imageInfo.alt}
297+
/>
298+
))}
299+
</section>
271300
</article>
272301
</dialog>
273302
);

src/ImageGallery.types.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,5 +25,6 @@ export interface ImageGalleryPropsType {
2525
columnWidth?: string | number;
2626
gapSize?: number;
2727
fixedCaption?: boolean;
28+
thumbnailBorder?: string;
2829
customStyles?: ImageGalleryStylesType;
2930
}

src/ImageGalleryStyles.ts

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@ export function imageGalleryStyles(
66
gapSize?: number,
77
fixedCaption?: boolean
88
): ImageGalleryStylesType {
9-
const modalSlideShowSectionHeight = "82vh";
10-
const modalThumbnailSectionHeight = "18vh";
9+
const modalSlideShowSectionHeight = "80vh";
10+
const modalThumbnailSectionHeight = "20vh";
1111
const galleryContainerStyle: React.CSSProperties = {
1212
columnCount,
1313
columnWidth: `${columnWidth}px`,
@@ -93,10 +93,6 @@ export function imageGalleryStyles(
9393
width: "inherit",
9494
height: `${modalSlideShowSectionHeight}`,
9595
};
96-
const modalThumbnailSectionStyle: React.CSSProperties = {
97-
backgroundColor: "blue",
98-
height: `${modalThumbnailSectionHeight}`,
99-
};
10096
const modalImageStyle: React.CSSProperties = {
10197
margin: "auto",
10298
maxWidth: "100vw",
@@ -114,6 +110,13 @@ export function imageGalleryStyles(
114110
userSelect: "none",
115111
WebkitUserSelect: "none",
116112
};
113+
const modalThumbnailSectionStyle: React.CSSProperties = {
114+
display: "flex",
115+
overflow: "hidden",
116+
height: `${modalThumbnailSectionHeight}`,
117+
paddingBlock: "12px",
118+
columnGap: "7px",
119+
};
117120
return {
118121
galleryContainerStyle,
119122
imageBtnStyle,

0 commit comments

Comments
 (0)