-
Notifications
You must be signed in to change notification settings - Fork 310
/
Gallery.js
133 lines (124 loc) · 4.32 KB
/
Gallery.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
import React, { useState, useLayoutEffect, useRef, useMemo } from 'react';
import PropTypes from 'prop-types';
import ResizeObserver from 'resize-observer-polyfill';
import Photo, { photoPropType } from './Photo';
import { computeColumnLayout } from './layouts/columns';
import { computeRowLayout } from './layouts/justified';
import { findIdealNodeSearch } from './utils/findIdealNodeSearch';
const Gallery = React.memo(function Gallery({
photos,
onClick,
direction,
margin,
limitNodeSearch,
targetRowHeight,
columns,
renderImage,
}) {
const [containerWidth, setContainerWidth] = useState(0);
const galleryEl = useRef(null);
useLayoutEffect(() => {
let animationFrameID = null;
const observer = new ResizeObserver(entries => {
// only do something if width changes
const newWidth = entries[0].contentRect.width;
if (containerWidth !== newWidth) {
// put in an animation frame to stop "benign errors" from
// ResizObserver https://stackoverflow.com/questions/49384120/resizeobserver-loop-limit-exceeded
animationFrameID = window.requestAnimationFrame(() => {
setContainerWidth(Math.floor(newWidth));
});
}
});
observer.observe(galleryEl.current);
return () => {
observer.disconnect();
window.cancelAnimationFrame(animationFrameID);
};
});
const handleClick = (event, { index }) => {
onClick(event, {
index,
photo: photos[index],
previous: photos[index - 1] || null,
next: photos[index + 1] || null,
});
};
// no containerWidth until after first render with refs, skip calculations and render nothing
if (!containerWidth) return <div ref={galleryEl}> </div>;
// subtract 1 pixel because the browser may round up a pixel
const width = containerWidth - 1;
let galleryStyle, thumbs;
if (direction === 'row') {
// allow user to calculate limitNodeSearch from containerWidth
if (typeof limitNodeSearch === 'function') {
limitNodeSearch = limitNodeSearch(containerWidth);
}
if (typeof targetRowHeight === 'function') {
targetRowHeight = targetRowHeight(containerWidth);
}
// set how many neighboring nodes the graph will visit
if (limitNodeSearch === undefined) {
limitNodeSearch = 2;
if (containerWidth >= 450) {
limitNodeSearch = findIdealNodeSearch({ containerWidth, targetRowHeight });
}
}
galleryStyle = { display: 'flex', flexWrap: 'wrap', flexDirection: 'row' };
thumbs = computeRowLayout({ containerWidth: width, limitNodeSearch, targetRowHeight, margin, photos });
}
if (direction === 'column') {
// allow user to calculate columns from containerWidth
if (typeof columns === 'function') {
columns = columns(containerWidth);
}
// set default breakpoints if user doesn't specify columns prop
if (columns === undefined) {
columns = 1;
if (containerWidth >= 500) columns = 2;
if (containerWidth >= 900) columns = 3;
if (containerWidth >= 1500) columns = 4;
}
galleryStyle = { position: 'relative' };
thumbs = computeColumnLayout({ containerWidth: width, columns, margin, photos });
galleryStyle.height = thumbs[thumbs.length - 1].containerHeight;
}
const renderComponent = renderImage || Photo;
return (
<div className="react-photo-gallery--gallery">
<div ref={galleryEl} style={galleryStyle}>
{thumbs.map((thumb, index) => {
const { left, top, containerHeight, ...photo } = thumb;
return renderComponent({
left,
top,
key: thumb.key || thumb.src,
containerHeight,
index,
margin,
direction,
onClick: onClick ? handleClick : null,
photo,
});
})}
</div>
</div>
);
});
Gallery.propTypes = {
photos: PropTypes.arrayOf(photoPropType).isRequired,
direction: PropTypes.string,
onClick: PropTypes.func,
columns: PropTypes.oneOfType([PropTypes.func, PropTypes.number]),
targetRowHeight: PropTypes.oneOfType([PropTypes.func, PropTypes.number]),
limitNodeSearch: PropTypes.oneOfType([PropTypes.func, PropTypes.number]),
margin: PropTypes.number,
renderImage: PropTypes.func,
};
Gallery.defaultProps = {
margin: 2,
direction: 'row',
targetRowHeight: 300,
};
export { Photo };
export default Gallery;