react-native-image-collage 0.2.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/LICENSE +21 -0
- package/README.md +564 -0
- package/dist/CollageImage.d.ts +7 -0
- package/dist/CollageImage.d.ts.map +1 -0
- package/dist/CollageImage.js +49 -0
- package/dist/CollageTile.d.ts +18 -0
- package/dist/CollageTile.d.ts.map +1 -0
- package/dist/CollageTile.js +58 -0
- package/dist/CollageWithViewer.d.ts +4 -0
- package/dist/CollageWithViewer.d.ts.map +1 -0
- package/dist/CollageWithViewer.js +65 -0
- package/dist/ImageCollage.d.ts +4 -0
- package/dist/ImageCollage.d.ts.map +1 -0
- package/dist/ImageCollage.js +135 -0
- package/dist/ImageCollageWithViewer.d.ts +4 -0
- package/dist/ImageCollageWithViewer.d.ts.map +1 -0
- package/dist/ImageCollageWithViewer.js +59 -0
- package/dist/ImageViewer.d.ts +4 -0
- package/dist/ImageViewer.d.ts.map +1 -0
- package/dist/ImageViewer.js +76 -0
- package/dist/constants.d.ts +13 -0
- package/dist/constants.d.ts.map +1 -0
- package/dist/constants.js +13 -0
- package/dist/expo/createExpoImageRenderer.d.ts +9 -0
- package/dist/expo/createExpoImageRenderer.d.ts.map +1 -0
- package/dist/expo/createExpoImageRenderer.js +20 -0
- package/dist/expo/index.d.ts +15 -0
- package/dist/expo/index.d.ts.map +1 -0
- package/dist/expo/index.js +59 -0
- package/dist/hooks/useContainerWidth.d.ts +10 -0
- package/dist/hooks/useContainerWidth.d.ts.map +1 -0
- package/dist/hooks/useContainerWidth.js +22 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +35 -0
- package/dist/types.d.ts +93 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/utils/imageSources.d.ts +11 -0
- package/dist/utils/imageSources.d.ts.map +1 -0
- package/dist/utils/imageSources.js +131 -0
- package/dist/utils/layoutHeight.d.ts +9 -0
- package/dist/utils/layoutHeight.d.ts.map +1 -0
- package/dist/utils/layoutHeight.js +41 -0
- package/dist/utils/renderCollageLayouts.d.ts +97 -0
- package/dist/utils/renderCollageLayouts.d.ts.map +1 -0
- package/dist/utils/renderCollageLayouts.js +183 -0
- package/dist/viewer/ImageCollageWithViewer.d.ts +4 -0
- package/dist/viewer/ImageCollageWithViewer.d.ts.map +1 -0
- package/dist/viewer/ImageCollageWithViewer.js +43 -0
- package/dist/viewer/ImageViewer.d.ts +5 -0
- package/dist/viewer/ImageViewer.d.ts.map +1 -0
- package/dist/viewer/ImageViewer.js +85 -0
- package/dist/viewer/index.d.ts +4 -0
- package/dist/viewer/index.d.ts.map +1 -0
- package/dist/viewer/index.js +8 -0
- package/package.json +68 -0
- package/src/CollageImage.tsx +41 -0
- package/src/CollageTile.tsx +69 -0
- package/src/CollageWithViewer.tsx +53 -0
- package/src/ImageCollage.tsx +168 -0
- package/src/constants.ts +11 -0
- package/src/expo/createExpoImageRenderer.tsx +43 -0
- package/src/expo/index.tsx +99 -0
- package/src/hooks/useContainerWidth.ts +29 -0
- package/src/index.ts +42 -0
- package/src/types.ts +120 -0
- package/src/utils/imageSources.ts +170 -0
- package/src/utils/layoutHeight.ts +54 -0
- package/src/utils/renderCollageLayouts.tsx +329 -0
- package/src/viewer/ImageCollageWithViewer.tsx +24 -0
- package/src/viewer/ImageViewer.tsx +93 -0
- package/src/viewer/index.ts +10 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Faisal Khawaj
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,564 @@
|
|
|
1
|
+
# react-native-image-collage
|
|
2
|
+
|
|
3
|
+
An easy-to-use collage layout component for React Native — similar to how **Facebook / Instagram show images in a post**.
|
|
4
|
+
|
|
5
|
+
Supports **1 to N images**, automatic layouts, `+N` overflow badges, optional full-screen viewer, and works with **React Native CLI** and **Expo**.
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install react-native-image-collage
|
|
9
|
+
# or
|
|
10
|
+
yarn add react-native-image-collage
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## Table of contents
|
|
16
|
+
|
|
17
|
+
- [Quick start](#quick-start)
|
|
18
|
+
- [Layouts](#layouts)
|
|
19
|
+
- [Use cases](#use-cases)
|
|
20
|
+
- [Single image](#1-single-image)
|
|
21
|
+
- [Two images](#2-two-images-side-by-side)
|
|
22
|
+
- [Three images](#3-three-images-facebook-style)
|
|
23
|
+
- [Four images](#4-four-images-2×2-grid)
|
|
24
|
+
- [Five or more images (+N overflow)](#5-five-or-more-images-n-overflow)
|
|
25
|
+
- [Custom overflow count](#6-custom-overflow-count)
|
|
26
|
+
- [Tap to open handler](#7-tap-to-open-handler)
|
|
27
|
+
- [Collage + built-in full-screen viewer](#8-collage--built-in-full-screen-viewer)
|
|
28
|
+
- [Collage + your own gallery](#9-collage--your-own-gallery)
|
|
29
|
+
- [Expo with blurhash & caching](#10-expo-with-blurhash--caching)
|
|
30
|
+
- [Custom image component (FastImage, etc.)](#11-custom-image-component-fastimage-etc)
|
|
31
|
+
- [Local images (require)](#12-local-images-require)
|
|
32
|
+
- [Images with aspect ratio](#13-images-with-aspect-ratio)
|
|
33
|
+
- [Inside a card or padded container](#14-inside-a-card-or-padded-container)
|
|
34
|
+
- [Entry points](#entry-points)
|
|
35
|
+
- [Props](#props)
|
|
36
|
+
- [Image input formats](#image-input-formats)
|
|
37
|
+
- [Exports](#exports)
|
|
38
|
+
- [Local development](#local-development)
|
|
39
|
+
- [License](#license)
|
|
40
|
+
|
|
41
|
+
---
|
|
42
|
+
|
|
43
|
+
## Quick start
|
|
44
|
+
|
|
45
|
+
```tsx
|
|
46
|
+
import React from "react";
|
|
47
|
+
import { View, StyleSheet } from "react-native";
|
|
48
|
+
import { ImageCollage } from "react-native-image-collage";
|
|
49
|
+
|
|
50
|
+
const images = [
|
|
51
|
+
{
|
|
52
|
+
uri: "https://picsum.photos/206",
|
|
53
|
+
aspectRatio: 1.5,
|
|
54
|
+
},
|
|
55
|
+
"https://picsum.photos/207",
|
|
56
|
+
"https://picsum.photos/208",
|
|
57
|
+
];
|
|
58
|
+
|
|
59
|
+
export default function PostImages() {
|
|
60
|
+
return (
|
|
61
|
+
<View style={styles.container}>
|
|
62
|
+
<ImageCollage
|
|
63
|
+
images={images}
|
|
64
|
+
spacing={2}
|
|
65
|
+
borderRadius={8}
|
|
66
|
+
layoutMinHeight={180}
|
|
67
|
+
layoutMaxHeight={400}
|
|
68
|
+
onImagePress={(index) => console.log("Tapped image", index)}
|
|
69
|
+
/>
|
|
70
|
+
</View>
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const styles = StyleSheet.create({
|
|
75
|
+
container: { paddingHorizontal: 16 },
|
|
76
|
+
});
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
> Width is measured from the parent container automatically. Height is computed from width and image count (or aspect ratio when available).
|
|
80
|
+
|
|
81
|
+
---
|
|
82
|
+
|
|
83
|
+
## Layouts
|
|
84
|
+
|
|
85
|
+
| Images | Layout |
|
|
86
|
+
|--------|--------|
|
|
87
|
+
| **1** | Full-width single image |
|
|
88
|
+
| **2** | Side by side |
|
|
89
|
+
| **3** | One large left, two stacked right (Facebook style) |
|
|
90
|
+
| **4** | 2×2 grid |
|
|
91
|
+
| **5+** | 2×2 grid with `+N` on the last visible tile |
|
|
92
|
+
|
|
93
|
+
```
|
|
94
|
+
1 image 2 images 3 images 4 images
|
|
95
|
+
┌──────────┐ ┌────┬────┐ ┌────┬────┐ ┌────┬────┐
|
|
96
|
+
│ │ │ │ │ │ │ 2 │ │ 1 │ 2 │
|
|
97
|
+
│ 1 │ │ 1 │ 2 │ │ 1 ├────┤ ├────┼────┤
|
|
98
|
+
│ │ │ │ │ │ │ 3 │ │ 3 │ 4 │
|
|
99
|
+
└──────────┘ └────┴────┘ └────┴────┘ └────┴────┘
|
|
100
|
+
|
|
101
|
+
5+ images (default maxVisibleImages=4)
|
|
102
|
+
┌────┬────┐
|
|
103
|
+
│ 1 │ 2 │
|
|
104
|
+
├────┼────┤
|
|
105
|
+
│ 3 │+2 │ ← 4th tile shows last image with +2 overlay (6 total)
|
|
106
|
+
└────┴────┘
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
---
|
|
110
|
+
|
|
111
|
+
## Use cases
|
|
112
|
+
|
|
113
|
+
### 1. Single image
|
|
114
|
+
|
|
115
|
+
```tsx
|
|
116
|
+
<ImageCollage
|
|
117
|
+
images={[
|
|
118
|
+
{
|
|
119
|
+
uri: "https://picsum.photos/205",
|
|
120
|
+
aspectRatio: 1.5,
|
|
121
|
+
},
|
|
122
|
+
]}
|
|
123
|
+
spacing={2}
|
|
124
|
+
/>
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
When `aspectRatio` is provided (or measured), height adapts to the image proportions.
|
|
128
|
+
|
|
129
|
+
---
|
|
130
|
+
|
|
131
|
+
### 2. Two images (side by side)
|
|
132
|
+
|
|
133
|
+
```tsx
|
|
134
|
+
<ImageCollage
|
|
135
|
+
images={[
|
|
136
|
+
"https://picsum.photos/200",
|
|
137
|
+
"https://picsum.photos/201",
|
|
138
|
+
]}
|
|
139
|
+
spacing={2}
|
|
140
|
+
/>
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
---
|
|
144
|
+
|
|
145
|
+
### 3. Three images (Facebook style)
|
|
146
|
+
|
|
147
|
+
```tsx
|
|
148
|
+
<ImageCollage images={photoUrls} spacing={2} borderRadius={8} />
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
One image on the left, two stacked on the right — the classic social feed layout.
|
|
152
|
+
|
|
153
|
+
---
|
|
154
|
+
|
|
155
|
+
### 4. Four images (2×2 grid)
|
|
156
|
+
|
|
157
|
+
```tsx
|
|
158
|
+
<ImageCollage images={photoUrls} spacing={2} />
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
---
|
|
162
|
+
|
|
163
|
+
### 5. Five or more images (+N overflow)
|
|
164
|
+
|
|
165
|
+
By default, up to **4 tiles** are shown. Extra images are indicated with a `+N` badge on the last tile.
|
|
166
|
+
|
|
167
|
+
```tsx
|
|
168
|
+
// 6 images → shows 4 tiles, last tile displays "+2"
|
|
169
|
+
<ImageCollage images={sixPhotoUrls} />
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
| Total images | What you see |
|
|
173
|
+
|--------------|--------------|
|
|
174
|
+
| 5 | 2×2 grid, `+1` on 4th tile |
|
|
175
|
+
| 6 | 2×2 grid, `+2` on 4th tile |
|
|
176
|
+
| 10 | 2×2 grid, `+6` on 4th tile |
|
|
177
|
+
|
|
178
|
+
---
|
|
179
|
+
|
|
180
|
+
### 6. Custom overflow count
|
|
181
|
+
|
|
182
|
+
Use `maxVisibleImages` to control how many tiles appear before the `+N` badge.
|
|
183
|
+
|
|
184
|
+
```tsx
|
|
185
|
+
// 4 images → 3-tile layout, "+1" on the 3rd tile
|
|
186
|
+
<ImageCollage images={photoUrls} maxVisibleImages={3} />
|
|
187
|
+
|
|
188
|
+
// 3 images → 2-tile row, "+1" on the 2nd tile
|
|
189
|
+
<ImageCollage images={photoUrls} maxVisibleImages={2} />
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
| Total images | `maxVisibleImages` | Result |
|
|
193
|
+
|--------------|-------------------|--------|
|
|
194
|
+
| 4 | `3` | 3-tile layout, `+1` on 3rd tile |
|
|
195
|
+
| 5 | `4` | 2×2 grid, `+1` on 4th tile (default) |
|
|
196
|
+
| 6 | `4` | 2×2 grid, `+2` on 4th tile |
|
|
197
|
+
| 3 | `2` | 2-tile row, `+1` on 2nd tile |
|
|
198
|
+
|
|
199
|
+
Formula: **`+N = totalImages - maxVisibleImages`**
|
|
200
|
+
|
|
201
|
+
---
|
|
202
|
+
|
|
203
|
+
### 7. Tap to open handler
|
|
204
|
+
|
|
205
|
+
```tsx
|
|
206
|
+
<ImageCollage
|
|
207
|
+
images={photoUrls}
|
|
208
|
+
onImagePress={(index) => {
|
|
209
|
+
// index = 0-based position of the tapped tile
|
|
210
|
+
navigation.navigate("PhotoDetail", { index });
|
|
211
|
+
}}
|
|
212
|
+
/>
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
---
|
|
216
|
+
|
|
217
|
+
### 8. Collage + built-in full-screen viewer
|
|
218
|
+
|
|
219
|
+
Opens a zoomable full-screen gallery when a tile is tapped.
|
|
220
|
+
|
|
221
|
+
```bash
|
|
222
|
+
npm install react-native-image-viewing
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
```tsx
|
|
226
|
+
import { ImageCollageWithViewer } from "react-native-image-collage/viewer";
|
|
227
|
+
|
|
228
|
+
<ImageCollageWithViewer
|
|
229
|
+
images={photoUrls}
|
|
230
|
+
spacing={2}
|
|
231
|
+
borderRadius={8}
|
|
232
|
+
viewerProps={{
|
|
233
|
+
swipeToCloseEnabled: true,
|
|
234
|
+
doubleTapToZoomEnabled: true,
|
|
235
|
+
showCloseButton: true,
|
|
236
|
+
showIndexFooter: true,
|
|
237
|
+
closeButtonLabel: "Close",
|
|
238
|
+
}}
|
|
239
|
+
/>
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
---
|
|
243
|
+
|
|
244
|
+
### 9. Collage + your own gallery
|
|
245
|
+
|
|
246
|
+
Bring your own lightbox / gallery component.
|
|
247
|
+
|
|
248
|
+
```tsx
|
|
249
|
+
import { CollageWithViewer } from "react-native-image-collage";
|
|
250
|
+
|
|
251
|
+
<CollageWithViewer
|
|
252
|
+
images={photoUrls}
|
|
253
|
+
spacing={2}
|
|
254
|
+
renderViewer={({ images, visible, imageIndex, onRequestClose }) => (
|
|
255
|
+
<MyGallery
|
|
256
|
+
uris={images.map((img) => img.uri)}
|
|
257
|
+
visible={visible}
|
|
258
|
+
initialIndex={imageIndex}
|
|
259
|
+
onClose={onRequestClose}
|
|
260
|
+
/>
|
|
261
|
+
)}
|
|
262
|
+
/>
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
---
|
|
266
|
+
|
|
267
|
+
### 10. Expo with blurhash & caching
|
|
268
|
+
|
|
269
|
+
Uses `expo-image` for better performance, disk caching, and blurhash placeholders.
|
|
270
|
+
|
|
271
|
+
```bash
|
|
272
|
+
npx expo install react-native-image-collage expo-image
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
```tsx
|
|
276
|
+
import { ImageCollageWithViewer } from "react-native-image-collage/expo";
|
|
277
|
+
|
|
278
|
+
<ImageCollageWithViewer
|
|
279
|
+
images={photoUrls}
|
|
280
|
+
spacing={2}
|
|
281
|
+
blurhash="LEHV6nWB2yk8pyo0adR*.7kCMdnj"
|
|
282
|
+
prioritizeFirstImage
|
|
283
|
+
/>
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
> `blurhash` and `prioritizeFirstImage` are only available on the `/expo` entry.
|
|
287
|
+
|
|
288
|
+
---
|
|
289
|
+
|
|
290
|
+
### 11. Custom image component (FastImage, etc.)
|
|
291
|
+
|
|
292
|
+
```tsx
|
|
293
|
+
import { ImageCollage } from "react-native-image-collage";
|
|
294
|
+
import FastImage from "react-native-fast-image";
|
|
295
|
+
|
|
296
|
+
<ImageCollage
|
|
297
|
+
images={photoUrls}
|
|
298
|
+
renderImage={({ source, style }) => (
|
|
299
|
+
<FastImage
|
|
300
|
+
source={source}
|
|
301
|
+
style={style}
|
|
302
|
+
resizeMode={FastImage.resizeMode.cover}
|
|
303
|
+
/>
|
|
304
|
+
)}
|
|
305
|
+
/>
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
---
|
|
309
|
+
|
|
310
|
+
### 12. Local images (require)
|
|
311
|
+
|
|
312
|
+
```tsx
|
|
313
|
+
<ImageCollage
|
|
314
|
+
images={[
|
|
315
|
+
require("./assets/photo1.png"),
|
|
316
|
+
require("./assets/photo2.png"),
|
|
317
|
+
]}
|
|
318
|
+
/>
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
---
|
|
322
|
+
|
|
323
|
+
### 13. Images with aspect ratio
|
|
324
|
+
|
|
325
|
+
Pass `aspectRatio` to skip network measurement and get accurate height faster.
|
|
326
|
+
|
|
327
|
+
```tsx
|
|
328
|
+
const images = [
|
|
329
|
+
{ uri: "https://picsum.photos/208", aspectRatio: 1.91 },
|
|
330
|
+
{ uri: "https://picsum.photos/206", aspectRatio: 1 },
|
|
331
|
+
{ uri: "https://picsum.photos/204", aspectRatio: 0.75 },
|
|
332
|
+
];
|
|
333
|
+
|
|
334
|
+
<ImageCollage images={images} />
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
If `aspectRatio` is omitted, remote and local images are measured automatically (`measureAspectRatios` defaults to `true`).
|
|
338
|
+
|
|
339
|
+
---
|
|
340
|
+
|
|
341
|
+
### 14. Inside a card or padded container
|
|
342
|
+
|
|
343
|
+
The collage measures its **parent width** via `onLayout` — no need to pass screen width manually.
|
|
344
|
+
|
|
345
|
+
```tsx
|
|
346
|
+
<View style={{ paddingHorizontal: 16 }}>
|
|
347
|
+
<ImageCollage images={photoUrls} spacing={2} />
|
|
348
|
+
</View>
|
|
349
|
+
```
|
|
350
|
+
|
|
351
|
+
Optional overrides:
|
|
352
|
+
|
|
353
|
+
```tsx
|
|
354
|
+
// Fixed width
|
|
355
|
+
<ImageCollage images={photoUrls} width={320} />
|
|
356
|
+
|
|
357
|
+
// Fixed height
|
|
358
|
+
<ImageCollage images={photoUrls} height={280} />
|
|
359
|
+
```
|
|
360
|
+
|
|
361
|
+
---
|
|
362
|
+
|
|
363
|
+
## Entry points
|
|
364
|
+
|
|
365
|
+
| Import | Requires | Best for |
|
|
366
|
+
|--------|----------|----------|
|
|
367
|
+
| `react-native-image-collage` | Nothing extra | RN CLI, Expo, custom setups |
|
|
368
|
+
| `react-native-image-collage/viewer` | `react-native-image-viewing` | Built-in full-screen viewer |
|
|
369
|
+
| `react-native-image-collage/expo` | `expo-image` | Blurhash, caching, priority loading |
|
|
370
|
+
|
|
371
|
+
---
|
|
372
|
+
|
|
373
|
+
## Props
|
|
374
|
+
|
|
375
|
+
### `ImageCollage`
|
|
376
|
+
|
|
377
|
+
| Prop | Type | Default | Description |
|
|
378
|
+
|------|------|---------|-------------|
|
|
379
|
+
| `images` | `CollageImageInput[]` | **required** | Array of image URLs, sources, or `{ uri, aspectRatio }` objects |
|
|
380
|
+
| `onImagePress` | `(index: number) => void` | — | Called when a tile is tapped. Receives the 0-based image index |
|
|
381
|
+
| `spacing` | `number` | `6` | Gap between tiles (px) |
|
|
382
|
+
| `borderRadius` | `number` | `12` | Corner radius of each tile |
|
|
383
|
+
| `layoutMinHeight` | `number` | `200` | Minimum collage height |
|
|
384
|
+
| `layoutMaxHeight` | `number` | `520` | Maximum collage height |
|
|
385
|
+
| `height` | `number` | auto | Fixed height. When omitted, height is computed from container width |
|
|
386
|
+
| `width` | `number` | measured | Explicit container width. When omitted, measured from parent via `onLayout` |
|
|
387
|
+
| `maxVisibleImages` | `number` | `4` | Max tiles before `+N` overflow badge on the last tile |
|
|
388
|
+
| `placeholderColor` | `string` | `#E8E8E8` | Background color behind tiles while images load |
|
|
389
|
+
| `measureAspectRatios` | `boolean` | `true` | Automatically measure images when `aspectRatio` is not provided |
|
|
390
|
+
| `renderImage` | `CollageImageRenderer` | RN `Image` | Custom image component (FastImage, expo-image, etc.) |
|
|
391
|
+
| `getImagePriority` | `(index) => 'low' \| 'normal' \| 'high'` | — | Per-tile load priority hint for custom renderers |
|
|
392
|
+
| `style` | `ViewStyle` | — | Style applied to the collage container |
|
|
393
|
+
| `horizontalInset` | `number` | `0` | **Deprecated.** Fallback screen inset before first layout measure |
|
|
394
|
+
|
|
395
|
+
---
|
|
396
|
+
|
|
397
|
+
### `ImageCollageWithViewer`
|
|
398
|
+
Import from `react-native-image-collage/viewer`
|
|
399
|
+
|
|
400
|
+
Accepts **all `ImageCollage` props**, plus:
|
|
401
|
+
|
|
402
|
+
| Prop | Type | Default | Description |
|
|
403
|
+
|------|------|---------|-------------|
|
|
404
|
+
| `onImagePress` | `(index: number) => void` | — | Called when a tile is tapped, before the viewer opens |
|
|
405
|
+
| `viewerProps` | `object` | — | Props passed to the built-in viewer (see below) |
|
|
406
|
+
| `renderViewer` | `CollageViewerRenderer` | built-in | Replace the default full-screen viewer |
|
|
407
|
+
|
|
408
|
+
#### `viewerProps`
|
|
409
|
+
|
|
410
|
+
| Prop | Type | Default | Description |
|
|
411
|
+
|------|------|---------|-------------|
|
|
412
|
+
| `swipeToCloseEnabled` | `boolean` | `true` | Swipe down to close |
|
|
413
|
+
| `doubleTapToZoomEnabled` | `boolean` | `true` | Double-tap to zoom |
|
|
414
|
+
| `presentationStyle` | `string` | `'fullScreen'` | iOS modal style |
|
|
415
|
+
| `showCloseButton` | `boolean` | `true` | Show close button header |
|
|
416
|
+
| `showIndexFooter` | `boolean` | `true` | Show `1 / N` index footer |
|
|
417
|
+
| `closeButtonLabel` | `string` | `'Close'` | Close button text |
|
|
418
|
+
|
|
419
|
+
---
|
|
420
|
+
|
|
421
|
+
### `CollageWithViewer`
|
|
422
|
+
Import from `react-native-image-collage`
|
|
423
|
+
|
|
424
|
+
| Prop | Type | Default | Description |
|
|
425
|
+
|------|------|---------|-------------|
|
|
426
|
+
| `renderViewer` | `CollageViewerRenderer` | **required** | Your gallery / lightbox component |
|
|
427
|
+
| `onImagePress` | `(index: number) => void` | — | Called when a tile is tapped, before viewer opens |
|
|
428
|
+
|
|
429
|
+
Plus all `ImageCollage` props.
|
|
430
|
+
|
|
431
|
+
---
|
|
432
|
+
|
|
433
|
+
### `ImageViewer` (standalone)
|
|
434
|
+
Import from `react-native-image-collage/viewer`
|
|
435
|
+
|
|
436
|
+
| Prop | Type | Default | Description |
|
|
437
|
+
|------|------|---------|-------------|
|
|
438
|
+
| `images` | `{ uri: string }[]` | **required** | Images for the viewer |
|
|
439
|
+
| `visible` | `boolean` | **required** | Whether the viewer is open |
|
|
440
|
+
| `onRequestClose` | `() => void` | **required** | Called when viewer should close |
|
|
441
|
+
| `imageIndex` | `number` | `0` | Initially displayed image |
|
|
442
|
+
| `swipeToCloseEnabled` | `boolean` | `true` | Swipe down to close |
|
|
443
|
+
| `doubleTapToZoomEnabled` | `boolean` | `true` | Double-tap to zoom |
|
|
444
|
+
| `presentationStyle` | `string` | `'fullScreen'` | iOS modal style |
|
|
445
|
+
| `showCloseButton` | `boolean` | `true` | Show close button |
|
|
446
|
+
| `showIndexFooter` | `boolean` | `true` | Show index footer |
|
|
447
|
+
| `closeButtonLabel` | `string` | `'Close'` | Close button label |
|
|
448
|
+
|
|
449
|
+
---
|
|
450
|
+
|
|
451
|
+
### Expo-only props
|
|
452
|
+
Import from `react-native-image-collage/expo`
|
|
453
|
+
|
|
454
|
+
Available on `ImageCollage` and `ImageCollageWithViewer`:
|
|
455
|
+
|
|
456
|
+
| Prop | Type | Default | Description |
|
|
457
|
+
|------|------|---------|-------------|
|
|
458
|
+
| `blurhash` | `string` | built-in default | Blurhash placeholder while images load |
|
|
459
|
+
| `prioritizeFirstImage` | `boolean` | `true` | Load the first image with high priority |
|
|
460
|
+
|
|
461
|
+
---
|
|
462
|
+
|
|
463
|
+
## Image input formats
|
|
464
|
+
|
|
465
|
+
`images` accepts any of the following per item:
|
|
466
|
+
|
|
467
|
+
```tsx
|
|
468
|
+
// 1. URL string
|
|
469
|
+
"https://picsum.photos/200"
|
|
470
|
+
|
|
471
|
+
// 2. Object with optional aspect ratio (recommended for network images)
|
|
472
|
+
{ uri: "https://picsum.photos/201", aspectRatio: 1.5 }
|
|
473
|
+
|
|
474
|
+
// 3. React Native image source (local require, headers, etc.)
|
|
475
|
+
require("./photo.png")
|
|
476
|
+
{ uri: "https://example.com/photo.jpg", headers: { Authorization: "..." } }
|
|
477
|
+
```
|
|
478
|
+
|
|
479
|
+
---
|
|
480
|
+
|
|
481
|
+
## Exports
|
|
482
|
+
|
|
483
|
+
### Components
|
|
484
|
+
|
|
485
|
+
| Component | Entry |
|
|
486
|
+
|-----------|-------|
|
|
487
|
+
| `ImageCollage` | `.` / `/expo` |
|
|
488
|
+
| `CollageTile` | `.` |
|
|
489
|
+
| `CollageImage` | `.` |
|
|
490
|
+
| `CollageWithViewer` | `.` |
|
|
491
|
+
| `ImageCollageWithViewer` | `/viewer` / `/expo` |
|
|
492
|
+
| `ImageViewer` | `/viewer` |
|
|
493
|
+
|
|
494
|
+
### Utilities
|
|
495
|
+
|
|
496
|
+
```tsx
|
|
497
|
+
import {
|
|
498
|
+
normalizeImages,
|
|
499
|
+
resolveImagesWithAspectRatios,
|
|
500
|
+
toViewerImages,
|
|
501
|
+
getRemoteUri,
|
|
502
|
+
computeLayoutHeight,
|
|
503
|
+
useContainerWidth,
|
|
504
|
+
createExpoImageRenderer, // /expo only
|
|
505
|
+
createDefaultViewerRenderer, // /viewer only
|
|
506
|
+
} from "react-native-image-collage";
|
|
507
|
+
```
|
|
508
|
+
|
|
509
|
+
### Constants
|
|
510
|
+
|
|
511
|
+
```tsx
|
|
512
|
+
import {
|
|
513
|
+
DEFAULT_SPACING, // 6
|
|
514
|
+
DEFAULT_BORDER_RADIUS, // 12
|
|
515
|
+
DEFAULT_LAYOUT_MIN_HEIGHT, // 200
|
|
516
|
+
DEFAULT_LAYOUT_MAX_HEIGHT, // 520
|
|
517
|
+
DEFAULT_MAX_VISIBLE_IMAGES,// 4
|
|
518
|
+
DEFAULT_PLACEHOLDER_COLOR, // #E8E8E8
|
|
519
|
+
} from "react-native-image-collage";
|
|
520
|
+
```
|
|
521
|
+
|
|
522
|
+
---
|
|
523
|
+
|
|
524
|
+
## Local development
|
|
525
|
+
|
|
526
|
+
```bash
|
|
527
|
+
git clone <repo>
|
|
528
|
+
cd react-native-image-collage
|
|
529
|
+
npm install
|
|
530
|
+
npm run build
|
|
531
|
+
npm run typecheck
|
|
532
|
+
```
|
|
533
|
+
|
|
534
|
+
### Example app (manual testing)
|
|
535
|
+
|
|
536
|
+
An Expo playground lives in `example/`. It is **not** published to npm.
|
|
537
|
+
|
|
538
|
+
```bash
|
|
539
|
+
npm run example
|
|
540
|
+
# or
|
|
541
|
+
cd example && npm install && npx expo start
|
|
542
|
+
```
|
|
543
|
+
|
|
544
|
+
Use the on-screen controls to change image count, `maxVisibleImages`, and entry point (`/viewer`, `/expo`).
|
|
545
|
+
|
|
546
|
+
If you see `private properties are not supported`, stop Metro and run:
|
|
547
|
+
|
|
548
|
+
```bash
|
|
549
|
+
cd example && npx expo start --clear
|
|
550
|
+
```
|
|
551
|
+
|
|
552
|
+
Ensure **Expo Go matches SDK 54** on your physical device.
|
|
553
|
+
|
|
554
|
+
### Link into another app
|
|
555
|
+
|
|
556
|
+
```bash
|
|
557
|
+
npm install /path/to/react-native-image-collage
|
|
558
|
+
```
|
|
559
|
+
|
|
560
|
+
---
|
|
561
|
+
|
|
562
|
+
## License
|
|
563
|
+
|
|
564
|
+
MIT
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { StyleProp, ImageStyle, type ImageSourcePropType } from "react-native";
|
|
3
|
+
import type { CollageImageRenderProps, CollageImageRenderer } from "./types";
|
|
4
|
+
export declare const CollageImage: React.NamedExoticComponent<CollageImageRenderProps>;
|
|
5
|
+
export declare function renderCollageImage(props: CollageImageRenderProps, renderImage?: CollageImageRenderer, style?: StyleProp<ImageStyle>): React.JSX.Element;
|
|
6
|
+
export type { ImageSourcePropType };
|
|
7
|
+
//# sourceMappingURL=CollageImage.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"CollageImage.d.ts","sourceRoot":"","sources":["../src/CollageImage.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAe,MAAM,OAAO,CAAC;AACpC,OAAO,EAGL,SAAS,EAET,UAAU,EACV,KAAK,mBAAmB,EACzB,MAAM,cAAc,CAAC;AACtB,OAAO,KAAK,EAAE,uBAAuB,EAAE,oBAAoB,EAAE,MAAM,SAAS,CAAC;AAE7E,eAAO,MAAM,YAAY,qDAavB,CAAC;AAEH,wBAAgB,kBAAkB,CAChC,KAAK,EAAE,uBAAuB,EAC9B,WAAW,CAAC,EAAE,oBAAoB,EAClC,KAAK,CAAC,EAAE,SAAS,CAAC,UAAU,CAAC,qBAS9B;AAED,YAAY,EAAE,mBAAmB,EAAE,CAAC"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.CollageImage = void 0;
|
|
37
|
+
exports.renderCollageImage = renderCollageImage;
|
|
38
|
+
const react_1 = __importStar(require("react"));
|
|
39
|
+
const react_native_1 = require("react-native");
|
|
40
|
+
exports.CollageImage = (0, react_1.memo)(function CollageImage({ source, style, transition = react_native_1.Platform.OS === "android" ? 80 : 150, }) {
|
|
41
|
+
return (<react_native_1.Image source={source} resizeMode="cover" fadeDuration={transition} style={[react_native_1.StyleSheet.absoluteFill, style]}/>);
|
|
42
|
+
});
|
|
43
|
+
function renderCollageImage(props, renderImage, style) {
|
|
44
|
+
const imageProps = style ? { ...props, style } : props;
|
|
45
|
+
if (renderImage) {
|
|
46
|
+
return renderImage(imageProps);
|
|
47
|
+
}
|
|
48
|
+
return <exports.CollageImage {...imageProps}/>;
|
|
49
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { StyleProp, ViewStyle, type ImageSourcePropType } from "react-native";
|
|
3
|
+
import type { CollageImageRenderer, ImagePriority } from "./types";
|
|
4
|
+
type CollageTileProps = {
|
|
5
|
+
source: ImageSourcePropType;
|
|
6
|
+
remoteUri?: string;
|
|
7
|
+
index: number;
|
|
8
|
+
onPress?: (index: number) => void;
|
|
9
|
+
borderRadius: number;
|
|
10
|
+
style?: StyleProp<ViewStyle>;
|
|
11
|
+
priority?: ImagePriority;
|
|
12
|
+
placeholderColor: string;
|
|
13
|
+
renderImage?: CollageImageRenderer;
|
|
14
|
+
transition?: number;
|
|
15
|
+
};
|
|
16
|
+
export declare const CollageTile: React.NamedExoticComponent<CollageTileProps>;
|
|
17
|
+
export {};
|
|
18
|
+
//# sourceMappingURL=CollageTile.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"CollageTile.d.ts","sourceRoot":"","sources":["../src/CollageTile.tsx"],"names":[],"mappings":"AAAA,OAAO,KAA4B,MAAM,OAAO,CAAC;AACjD,OAAO,EAGL,SAAS,EAET,SAAS,EACT,KAAK,mBAAmB,EACzB,MAAM,cAAc,CAAC;AAGtB,OAAO,KAAK,EACV,oBAAoB,EACpB,aAAa,EACd,MAAM,SAAS,CAAC;AAEjB,KAAK,gBAAgB,GAAG;IACtB,MAAM,EAAE,mBAAmB,CAAC;IAC5B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAClC,YAAY,EAAE,MAAM,CAAC;IACrB,KAAK,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IAC7B,QAAQ,CAAC,EAAE,aAAa,CAAC;IACzB,gBAAgB,EAAE,MAAM,CAAC;IACzB,WAAW,CAAC,EAAE,oBAAoB,CAAC;IACnC,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB,CAAC;AAEF,eAAO,MAAM,WAAW,8CAmCtB,CAAC"}
|