@n-ivan/react-justified-gallery 1.0.2 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +11 -27
- package/dist/JustifiedGallery.d.ts +1 -1
- package/dist/justified-gallery.js +46 -141
- package/dist/types.d.ts +0 -16
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
# react-justified-gallery
|
|
2
2
|
|
|
3
|
-
A React component for creating beautiful justified image galleries with
|
|
3
|
+
A React component for creating beautiful justified image galleries with CSS flexbox layout.
|
|
4
4
|
|
|
5
5
|
## Features
|
|
6
6
|
|
|
7
7
|
- **Perfect Justification** - Images fill the full container width while preserving aspect ratios
|
|
8
|
-
- **Fully Responsive** -
|
|
8
|
+
- **Fully Responsive** - Pure CSS handles resize automatically
|
|
9
9
|
- **Flexible Rendering** - Custom render prop for complete control over image display
|
|
10
|
+
- **SSR-Friendly** - No client-side measurement needed
|
|
11
|
+
- **Zero Re-renders** - No state changes on container resize
|
|
10
12
|
|
|
11
13
|
## Installation
|
|
12
14
|
|
|
@@ -17,7 +19,7 @@ npm install @n-ivan/react-justified-gallery
|
|
|
17
19
|
## Quick Start
|
|
18
20
|
|
|
19
21
|
```tsx
|
|
20
|
-
import { JustifiedGallery } from 'react-justified-gallery';
|
|
22
|
+
import { JustifiedGallery } from '@n-ivan/react-justified-gallery';
|
|
21
23
|
|
|
22
24
|
const images = [
|
|
23
25
|
[
|
|
@@ -59,7 +61,6 @@ type GalleryData = ImageData[][];
|
|
|
59
61
|
| `images` | `ImageData[][]` | **required** | 2D array of image data |
|
|
60
62
|
| `gap` | `number` | `8` | Gap between images in pixels |
|
|
61
63
|
| `renderImage` | `function` | - | Custom render function for images |
|
|
62
|
-
| `resizeDebounce` | `number` | `150` | Debounce delay for resize (ms) |
|
|
63
64
|
| `lazyLoad` | `boolean` | `true` | Enable native lazy loading |
|
|
64
65
|
| `onImageLoad` | `function` | - | Callback when image loads |
|
|
65
66
|
| `onImageError` | `function` | - | Callback when image fails |
|
|
@@ -72,30 +73,13 @@ When using the `renderImage` prop, you receive the following properties:
|
|
|
72
73
|
|
|
73
74
|
```typescript
|
|
74
75
|
interface RenderImageProps {
|
|
75
|
-
image: ImageData;
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
originalWidth: number; // Original image width
|
|
79
|
-
originalHeight: number; // Original image height
|
|
80
|
-
rowIndex: number; // Row index (0-based)
|
|
81
|
-
imageIndex: number; // Image index in row (0-based)
|
|
82
|
-
isFirstInRow: boolean; // First image in row
|
|
83
|
-
isLastInRow: boolean; // Last image in row
|
|
76
|
+
image: ImageData; // Original image data
|
|
77
|
+
rowIndex: number; // Row index (0-based)
|
|
78
|
+
imageIndex: number; // Image index in row (0-based)
|
|
84
79
|
}
|
|
85
80
|
```
|
|
86
81
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
The component calculates the optimal height for each row based on:
|
|
90
|
-
|
|
91
|
-
1. **Total aspect ratio** of all images in the row
|
|
92
|
-
2. **Available width** (container width minus gaps)
|
|
93
|
-
3. **Individual aspect ratios** of each image
|
|
94
|
-
|
|
95
|
-
This ensures:
|
|
96
|
-
- All images in a row have the same height
|
|
97
|
-
- Each image maintains its original aspect ratio
|
|
98
|
-
- The row fills the complete container width
|
|
82
|
+
The wrapper div already has the correct `flex` and `aspect-ratio` styles applied, so your custom content just needs to fill `width: 100%` and `height: 100%`.
|
|
99
83
|
|
|
100
84
|
## Development
|
|
101
85
|
|
|
@@ -144,8 +128,8 @@ import type {
|
|
|
144
128
|
## Browser Support
|
|
145
129
|
|
|
146
130
|
Modern browsers with support for:
|
|
147
|
-
-
|
|
148
|
-
-
|
|
131
|
+
- CSS `aspect-ratio` property (Chrome 88+, Firefox 89+, Safari 15+, Edge 88+)
|
|
132
|
+
- CSS Flexbox
|
|
149
133
|
- React 18+
|
|
150
134
|
|
|
151
135
|
## License
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
import type { JustifiedGalleryProps } from './types';
|
|
2
|
-
export declare function JustifiedGallery({ images, gap, renderImage,
|
|
2
|
+
export declare function JustifiedGallery({ images, gap, renderImage, lazyLoad, onImageLoad, onImageError, containerStyle, rowStyle, }: JustifiedGalleryProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -1,171 +1,76 @@
|
|
|
1
|
-
import { jsx as
|
|
2
|
-
|
|
3
|
-
function
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
}, i;
|
|
13
|
-
}
|
|
14
|
-
function B(o) {
|
|
15
|
-
const t = T(null), [e, i] = C(0), n = F(() => {
|
|
16
|
-
if (t.current) {
|
|
17
|
-
const l = t.current.getBoundingClientRect().width;
|
|
18
|
-
i(l);
|
|
19
|
-
}
|
|
20
|
-
}, []);
|
|
21
|
-
return G(() => {
|
|
22
|
-
const l = t.current;
|
|
23
|
-
if (!l) return;
|
|
24
|
-
n();
|
|
25
|
-
const h = k(n, o), c = new ResizeObserver(() => {
|
|
26
|
-
h();
|
|
27
|
-
});
|
|
28
|
-
return c.observe(l), () => {
|
|
29
|
-
c.disconnect(), h.cancel();
|
|
30
|
-
};
|
|
31
|
-
}, [n, o]), { containerRef: t, width: e };
|
|
32
|
-
}
|
|
33
|
-
function D(o) {
|
|
34
|
-
for (let t = 0; t < o.length; t++) {
|
|
35
|
-
const e = o[t];
|
|
36
|
-
if (e.length === 0)
|
|
37
|
-
throw new Error(`Row ${t} is empty. All rows must contain at least one image.`);
|
|
38
|
-
for (let i = 0; i < e.length; i++) {
|
|
39
|
-
const n = e[i];
|
|
40
|
-
if (typeof n.width != "number" || n.width <= 0)
|
|
41
|
-
throw new Error(
|
|
42
|
-
`Image at row ${t}, index ${i} has invalid width. Expected positive number, got: ${n.width}`
|
|
43
|
-
);
|
|
44
|
-
if (typeof n.height != "number" || n.height <= 0)
|
|
45
|
-
throw new Error(
|
|
46
|
-
`Image at row ${t}, index ${i} has invalid height. Expected positive number, got: ${n.height}`
|
|
47
|
-
);
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
function O(o, t, e) {
|
|
52
|
-
return D(o), o.map((i) => {
|
|
53
|
-
const n = e * (i.length - 1), l = t - n, h = i.reduce((a, s) => a + s.width / s.height, 0), c = l / h;
|
|
54
|
-
return {
|
|
55
|
-
images: i.map((a) => {
|
|
56
|
-
const s = a.width / a.height, f = c * s;
|
|
57
|
-
return {
|
|
58
|
-
image: a,
|
|
59
|
-
computedWidth: f,
|
|
60
|
-
computedHeight: c
|
|
61
|
-
};
|
|
62
|
-
}),
|
|
63
|
-
height: c
|
|
64
|
-
};
|
|
65
|
-
});
|
|
66
|
-
}
|
|
67
|
-
const _ = 8, x = 150;
|
|
68
|
-
function P({
|
|
69
|
-
images: o,
|
|
70
|
-
gap: t = _,
|
|
71
|
-
renderImage: e,
|
|
72
|
-
resizeDebounce: i = x,
|
|
73
|
-
lazyLoad: n = !0,
|
|
74
|
-
onImageLoad: l,
|
|
75
|
-
onImageError: h,
|
|
76
|
-
containerStyle: c,
|
|
77
|
-
rowStyle: w
|
|
1
|
+
import { jsx as r } from "react/jsx-runtime";
|
|
2
|
+
const o = 8;
|
|
3
|
+
function d({
|
|
4
|
+
images: $,
|
|
5
|
+
gap: y = o,
|
|
6
|
+
renderImage: p,
|
|
7
|
+
lazyLoad: f = !0,
|
|
8
|
+
onImageLoad: h,
|
|
9
|
+
onImageError: c,
|
|
10
|
+
containerStyle: u,
|
|
11
|
+
rowStyle: a
|
|
78
12
|
}) {
|
|
79
|
-
|
|
80
|
-
l == null || l(u, r);
|
|
81
|
-
}, R = (u) => (r) => {
|
|
82
|
-
h == null || h(u, r);
|
|
83
|
-
}, E = (u) => {
|
|
84
|
-
const { image: r, computedWidth: m, computedHeight: d } = u;
|
|
85
|
-
return /* @__PURE__ */ g(
|
|
86
|
-
"img",
|
|
87
|
-
{
|
|
88
|
-
src: r.src,
|
|
89
|
-
alt: r.alt ?? "",
|
|
90
|
-
width: m,
|
|
91
|
-
height: d,
|
|
92
|
-
loading: n ? "lazy" : void 0,
|
|
93
|
-
onLoad: $(r),
|
|
94
|
-
onError: R(r),
|
|
95
|
-
style: {
|
|
96
|
-
display: "block",
|
|
97
|
-
width: m,
|
|
98
|
-
height: d
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
);
|
|
102
|
-
};
|
|
103
|
-
return s === 0 ? /* @__PURE__ */ g(
|
|
104
|
-
"div",
|
|
105
|
-
{
|
|
106
|
-
ref: a,
|
|
107
|
-
role: "region",
|
|
108
|
-
"aria-label": "Image gallery",
|
|
109
|
-
style: {
|
|
110
|
-
width: "100%",
|
|
111
|
-
...c
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
) : /* @__PURE__ */ g(
|
|
13
|
+
return /* @__PURE__ */ r(
|
|
115
14
|
"div",
|
|
116
15
|
{
|
|
117
|
-
ref: a,
|
|
118
16
|
role: "region",
|
|
119
17
|
"aria-label": "Image gallery",
|
|
120
18
|
style: {
|
|
121
19
|
width: "100%",
|
|
122
|
-
...
|
|
20
|
+
...u
|
|
123
21
|
},
|
|
124
|
-
children:
|
|
22
|
+
children: $.map((e, l) => /* @__PURE__ */ r(
|
|
125
23
|
"div",
|
|
126
24
|
{
|
|
127
25
|
role: "group",
|
|
128
|
-
"aria-label": `Gallery row ${
|
|
26
|
+
"aria-label": `Gallery row ${l + 1}`,
|
|
129
27
|
style: {
|
|
130
28
|
display: "flex",
|
|
131
|
-
gap:
|
|
132
|
-
marginBottom:
|
|
133
|
-
...
|
|
29
|
+
gap: y,
|
|
30
|
+
marginBottom: l < $.length - 1 ? y : 0,
|
|
31
|
+
...a
|
|
134
32
|
},
|
|
135
|
-
children:
|
|
136
|
-
const
|
|
137
|
-
|
|
138
|
-
computedWidth: b,
|
|
139
|
-
computedHeight: y,
|
|
140
|
-
originalWidth: p.width,
|
|
141
|
-
originalHeight: p.height,
|
|
142
|
-
rowIndex: r,
|
|
143
|
-
imageIndex: d,
|
|
144
|
-
isFirstInRow: W,
|
|
145
|
-
isLastInRow: A
|
|
146
|
-
}, H = e ? e(v) : E(v);
|
|
147
|
-
return /* @__PURE__ */ g(
|
|
33
|
+
children: e.map((t, i) => {
|
|
34
|
+
const b = t.width / t.height;
|
|
35
|
+
return p ? /* @__PURE__ */ r(
|
|
148
36
|
"div",
|
|
149
37
|
{
|
|
150
38
|
tabIndex: 0,
|
|
151
39
|
role: "img",
|
|
152
|
-
"aria-label":
|
|
40
|
+
"aria-label": t.alt || `Image ${l + 1}-${i + 1}`,
|
|
153
41
|
style: {
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
flexShrink: 0
|
|
42
|
+
flex: `${b} 1 0`,
|
|
43
|
+
aspectRatio: `${t.width} / ${t.height}`
|
|
157
44
|
},
|
|
158
|
-
children:
|
|
45
|
+
children: p({ image: t, rowIndex: l, imageIndex: i })
|
|
46
|
+
},
|
|
47
|
+
i
|
|
48
|
+
) : /* @__PURE__ */ r(
|
|
49
|
+
"img",
|
|
50
|
+
{
|
|
51
|
+
src: t.src,
|
|
52
|
+
alt: t.alt ?? "",
|
|
53
|
+
tabIndex: 0,
|
|
54
|
+
role: "img",
|
|
55
|
+
"aria-label": t.alt || `Image ${l + 1}-${i + 1}`,
|
|
56
|
+
loading: f ? "lazy" : void 0,
|
|
57
|
+
onLoad: (s) => h == null ? void 0 : h(t, s),
|
|
58
|
+
onError: (s) => c == null ? void 0 : c(t, s),
|
|
59
|
+
style: {
|
|
60
|
+
flex: `${b} 1 0`,
|
|
61
|
+
aspectRatio: `${t.width} / ${t.height}`,
|
|
62
|
+
objectFit: "cover"
|
|
63
|
+
}
|
|
159
64
|
},
|
|
160
|
-
|
|
65
|
+
i
|
|
161
66
|
);
|
|
162
67
|
})
|
|
163
68
|
},
|
|
164
|
-
|
|
69
|
+
l
|
|
165
70
|
))
|
|
166
71
|
}
|
|
167
72
|
);
|
|
168
73
|
}
|
|
169
74
|
export {
|
|
170
|
-
|
|
75
|
+
d as JustifiedGallery
|
|
171
76
|
};
|
package/dist/types.d.ts
CHANGED
|
@@ -8,32 +8,16 @@ export interface ImageData {
|
|
|
8
8
|
}
|
|
9
9
|
export interface RenderImageProps {
|
|
10
10
|
image: ImageData;
|
|
11
|
-
computedWidth: number;
|
|
12
|
-
computedHeight: number;
|
|
13
|
-
originalWidth: number;
|
|
14
|
-
originalHeight: number;
|
|
15
11
|
rowIndex: number;
|
|
16
12
|
imageIndex: number;
|
|
17
|
-
isFirstInRow: boolean;
|
|
18
|
-
isLastInRow: boolean;
|
|
19
13
|
}
|
|
20
14
|
export interface JustifiedGalleryProps {
|
|
21
15
|
images: ImageData[][];
|
|
22
16
|
gap?: number;
|
|
23
17
|
renderImage?: (props: RenderImageProps) => ReactNode;
|
|
24
|
-
resizeDebounce?: number;
|
|
25
18
|
lazyLoad?: boolean;
|
|
26
19
|
onImageLoad?: (image: ImageData, event: SyntheticEvent<HTMLImageElement>) => void;
|
|
27
20
|
onImageError?: (image: ImageData, event: SyntheticEvent<HTMLImageElement>) => void;
|
|
28
21
|
containerStyle?: CSSProperties;
|
|
29
22
|
rowStyle?: CSSProperties;
|
|
30
23
|
}
|
|
31
|
-
export interface ComputedImage {
|
|
32
|
-
image: ImageData;
|
|
33
|
-
computedWidth: number;
|
|
34
|
-
computedHeight: number;
|
|
35
|
-
}
|
|
36
|
-
export interface ComputedRow {
|
|
37
|
-
images: ComputedImage[];
|
|
38
|
-
height: number;
|
|
39
|
-
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@n-ivan/react-justified-gallery",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.0",
|
|
4
4
|
"description": "A React component for creating justified image galleries",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"react",
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
"images",
|
|
10
10
|
"layout"
|
|
11
11
|
],
|
|
12
|
-
"homepage": "https://
|
|
12
|
+
"homepage": "https://n-ivan.github.io/react-justified-gallery/",
|
|
13
13
|
"bugs": {
|
|
14
14
|
"url": "https://github.com/n-ivan/react-justified-gallery/issues"
|
|
15
15
|
},
|
|
@@ -33,6 +33,7 @@
|
|
|
33
33
|
],
|
|
34
34
|
"scripts": {
|
|
35
35
|
"build": "tsc --project tsconfig.build.json && vite build",
|
|
36
|
+
"build:demo": "vite build --config demo/vite.config.ts",
|
|
36
37
|
"dev": "vite",
|
|
37
38
|
"demo": "vite --config demo/vite.config.ts",
|
|
38
39
|
"test": "vitest",
|