@cleverbamboo/react-virtual-masonry 1.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 [Your Name]
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,343 @@
1
+ # React Virtual Masonry
2
+
3
+ [English](./README.md) | [简体中文](./README.zh-CN.md)
4
+
5
+ A high-performance React virtual scrolling masonry layout library with support for waterfall and equal-height layouts.
6
+
7
+ ## ✨ Features
8
+
9
+ - 🚀 **High-Performance Virtual Scrolling** - Only renders visible elements, supports massive datasets
10
+ - 📐 **Multiple Layout Modes**
11
+ - Waterfall Layout (Pinterest Style) - Variable width and height, automatically finds shortest column
12
+ - Equal Height Layout (Google Photos Style) - Same height per row, width adapts to original aspect ratio
13
+ - Dynamic Layout - Supports switching layout types based on API response
14
+ - 🎯 **Smart Preloading** - Infinite scroll based on IntersectionObserver with customizable preload distance
15
+ - 🎨 **Fully Customizable** - Custom render functions, loading states, spacing, and more
16
+ - 📱 **Responsive Design** - Automatically adapts to container width changes using ResizeObserver
17
+ - ⚡ **RAF Optimized** - Uses requestAnimationFrame for smooth scrolling performance
18
+ - 🔧 **TypeScript Support** - Complete TypeScript type definitions
19
+ - 🪶 **Zero Dependencies** - No external dependencies except React
20
+
21
+ ## 📦 Installation
22
+
23
+ ```bash
24
+ npm install react-virtual-masonry
25
+ # or
26
+ yarn add react-virtual-masonry
27
+ # or
28
+ pnpm add react-virtual-masonry
29
+ ```
30
+
31
+ ## 🎯 Quick Start
32
+
33
+ ### 1. Waterfall Layout (Pinterest Style)
34
+
35
+ Perfect for image galleries, product listings, etc.
36
+
37
+ ```tsx
38
+ import { VirtualMasonry } from 'react-virtual-masonry';
39
+
40
+ function ImageGallery() {
41
+ // Data loading function
42
+ const loadData = async (page: number, pageSize: number) => {
43
+ const response = await fetch(`/api/images?page=${page}&size=${pageSize}`);
44
+ const data = await response.json();
45
+ return {
46
+ data: data.items, // Data array
47
+ hasMore: data.hasMore, // Whether there's more data
48
+ };
49
+ };
50
+
51
+ return (
52
+ <VirtualMasonry
53
+ loadData={loadData}
54
+ pageSize={30}
55
+ minColumnWidth={200}
56
+ maxColumnWidth={350}
57
+ gap={16}
58
+ renderItem={(item) => (
59
+ <div
60
+ style={{
61
+ position: 'absolute',
62
+ left: item.x,
63
+ top: item.y,
64
+ width: item.width,
65
+ height: item.height,
66
+ }}
67
+ >
68
+ <img src={item.url} alt={item.title} />
69
+ </div>
70
+ )}
71
+ />
72
+ );
73
+ }
74
+ ```
75
+
76
+ ### 2. Equal Height Layout (Google Photos Style)
77
+
78
+ Perfect for photo albums, media galleries, etc.
79
+
80
+ ```tsx
81
+ import { FullWidthEqualHeightMasonry } from 'react-virtual-masonry';
82
+
83
+ function PhotoAlbum() {
84
+ const loadData = async (page: number, pageSize: number) => {
85
+ const response = await fetch(`/api/photos?page=${page}&size=${pageSize}`);
86
+ const data = await response.json();
87
+ return {
88
+ data: data.items,
89
+ hasMore: data.hasMore,
90
+ };
91
+ };
92
+
93
+ return (
94
+ <FullWidthEqualHeightMasonry
95
+ loadData={loadData}
96
+ pageSize={30}
97
+ targetRowHeight={245}
98
+ sizeRange={[230, 260]}
99
+ gap={8}
100
+ renderItem={(item) => (
101
+ <div
102
+ style={{
103
+ position: 'absolute',
104
+ left: item.x,
105
+ top: item.y,
106
+ width: item.width,
107
+ height: item.height,
108
+ }}
109
+ >
110
+ <img src={item.url} alt={item.title} />
111
+ </div>
112
+ )}
113
+ />
114
+ );
115
+ }
116
+ ```
117
+
118
+ ### 3. Dynamic Layout
119
+
120
+ Automatically switches between waterfall and equal-height layouts based on API response.
121
+
122
+ ```tsx
123
+ import { DynamicMasonryView } from 'react-virtual-masonry';
124
+
125
+ function Gallery() {
126
+ const loadData = async (page: number, pageSize: number) => {
127
+ const response = await fetch(`/api/gallery?page=${page}&size=${pageSize}`);
128
+ const data = await response.json();
129
+
130
+ // First request returns layout type
131
+ if (page === 1) {
132
+ return {
133
+ data: data.items,
134
+ hasMore: data.hasMore,
135
+ isMasonry: data.layoutType === 'waterfall', // true for waterfall, false for equal-height
136
+ };
137
+ }
138
+
139
+ return {
140
+ data: data.items,
141
+ hasMore: data.hasMore,
142
+ };
143
+ };
144
+
145
+ return (
146
+ <DynamicMasonryView
147
+ loadData={loadData}
148
+ pageSize={30}
149
+ waterfallConfig={{
150
+ minColumnWidth: 200,
151
+ maxColumnWidth: 350,
152
+ gap: 16,
153
+ }}
154
+ equalHeightConfig={{
155
+ targetRowHeight: 245,
156
+ sizeRange: [230, 260],
157
+ gap: 8,
158
+ }}
159
+ renderItem={(item, index, isMasonry) => (
160
+ <div
161
+ style={{
162
+ position: 'absolute',
163
+ left: item.x,
164
+ top: item.y,
165
+ width: item.width,
166
+ height: item.height,
167
+ }}
168
+ >
169
+ <img src={item.url} alt={item.title} />
170
+ </div>
171
+ )}
172
+ />
173
+ );
174
+ }
175
+ ```
176
+
177
+ ## 📖 API Documentation
178
+
179
+ ### VirtualMasonry (Waterfall Layout)
180
+
181
+ | Property | Type | Default | Description |
182
+ |----------|------|---------|-------------|
183
+ | `loadData` | `(page: number, pageSize: number) => Promise<{data: any[], hasMore: boolean}>` | Required | Data loading function |
184
+ | `renderItem` | `(item: any, index: number) => React.ReactNode` | Required | Item render function |
185
+ | `pageSize` | `number` | `50` | Items per page |
186
+ | `minColumnWidth` | `number` | `200` | Minimum column width |
187
+ | `maxColumnWidth` | `number` | - | Maximum column width |
188
+ | `gap` | `number` | `16` | Gap between items |
189
+ | `buffer` | `number` | `1500` | Buffer size (px) |
190
+ | `loadMoreThreshold` | `number` | `800` | Preload threshold (px) |
191
+ | `mapSize` | `(raw: any) => {width: number, height: number}` | - | Map data to dimensions |
192
+ | `enableAnimation` | `boolean` | `true` | Enable animations |
193
+
194
+ ### FullWidthEqualHeightMasonry (Equal Height Layout)
195
+
196
+ | Property | Type | Default | Description |
197
+ |----------|------|---------|-------------|
198
+ | `loadData` | `(page: number, pageSize: number) => Promise<{data: any[], hasMore: boolean}>` | Required | Data loading function |
199
+ | `renderItem` | `(item: any, index: number) => React.ReactNode` | Required | Item render function |
200
+ | `pageSize` | `number` | `50` | Items per page |
201
+ | `targetRowHeight` | `number` | `245` | Target row height |
202
+ | `sizeRange` | `[number, number]` | `[230, 260]` | Row height range |
203
+ | `maxItemWidth` | `number` | `975` | Maximum item width |
204
+ | `maxStretchRatio` | `number` | `1.5` | Maximum stretch ratio |
205
+ | `gap` | `number` | `8` | Gap between items |
206
+ | `buffer` | `number` | `1500` | Buffer size (px) |
207
+ | `loadMoreThreshold` | `number` | `500` | Preload threshold (px) |
208
+ | `mapSize` | `(raw: any) => {width: number, height: number}` | - | Map data to dimensions |
209
+ | `enableAnimation` | `boolean` | `true` | Enable animations |
210
+
211
+ ### DynamicMasonryView (Dynamic Layout)
212
+
213
+ | Property | Type | Default | Description |
214
+ |----------|------|---------|-------------|
215
+ | `isMasonry` | `boolean` | - | Controlled mode: whether to use waterfall layout |
216
+ | `defaultIsMasonry` | `boolean` | `true` | Uncontrolled mode: default layout type |
217
+ | `enableAnimation` | `boolean` | `true` | Enable animations |
218
+ | `loadData` | `LoadDataFn` | Required | Data loading function (can return isMasonry on first call) |
219
+ | `renderItem` | `(item: any, index: number, isMasonry: boolean) => React.ReactNode` | Required | Render function, isMasonry indicates current layout |
220
+ | `waterfallConfig` | `WaterfallConfig` | `{}` | Waterfall config (minColumnWidth, maxColumnWidth, gap, buffer) |
221
+ | `equalHeightConfig` | `EqualHeightConfig` | `{}` | Equal-height config (targetRowHeight, sizeRange, maxItemWidth, maxStretchRatio, gap, buffer) |
222
+ | `pageSize` | `number` | `50` | Items per page |
223
+ | `mapSize` | `(raw: any) => {width: number, height: number}` | - | Map dimensions |
224
+ | `renderInitialLoader` | `() => React.ReactNode` | - | Initial loading state (shown before layout type is determined) |
225
+ | `onLayoutTypeLoaded` | `(isMasonry: boolean) => void` | - | Layout type loaded callback |
226
+ | `onError` | `(error: Error) => void` | - | Error callback |
227
+
228
+ ## 🎨 Custom Styling
229
+
230
+ ### Custom Item Rendering
231
+
232
+ ```tsx
233
+ <VirtualMasonry
234
+ // ... other props
235
+ renderItem={(item) => (
236
+ <div
237
+ style={{
238
+ position: 'absolute',
239
+ left: item.x,
240
+ top: item.y,
241
+ width: item.width,
242
+ height: item.height,
243
+ borderRadius: '8px',
244
+ overflow: 'hidden',
245
+ boxShadow: '0 2px 8px rgba(0,0,0,0.1)',
246
+ }}
247
+ >
248
+ <img
249
+ src={item.url}
250
+ alt={item.title}
251
+ style={{
252
+ width: '100%',
253
+ height: '100%',
254
+ objectFit: 'cover',
255
+ }}
256
+ />
257
+ <div style={{
258
+ position: 'absolute',
259
+ bottom: 0,
260
+ left: 0,
261
+ right: 0,
262
+ padding: '12px',
263
+ background: 'linear-gradient(transparent, rgba(0,0,0,0.7))',
264
+ color: 'white',
265
+ }}>
266
+ <h3>{item.title}</h3>
267
+ <p>{item.description}</p>
268
+ </div>
269
+ </div>
270
+ )}
271
+ />
272
+ ```
273
+
274
+ ## 🔧 Advanced Usage
275
+
276
+ ### Data Mapping
277
+
278
+ If your data structure doesn't have `width` and `height` fields, use `mapSize`:
279
+
280
+ ```tsx
281
+ <VirtualMasonry
282
+ // ... other props
283
+ mapSize={(item) => ({
284
+ width: item.imageWidth,
285
+ height: item.imageHeight,
286
+ })}
287
+ />
288
+ ```
289
+
290
+ ### Controlled Dynamic Layout
291
+
292
+ ```tsx
293
+ function App() {
294
+ const [isMasonry, setIsMasonry] = useState(true);
295
+
296
+ return (
297
+ <>
298
+ <button onClick={() => setIsMasonry(!isMasonry)}>
299
+ Switch Layout
300
+ </button>
301
+ <DynamicMasonryView
302
+ isMasonry={isMasonry}
303
+ // ... other props
304
+ />
305
+ </>
306
+ );
307
+ }
308
+ ```
309
+
310
+ ## 🎯 Use Cases
311
+
312
+ - 📷 **Image Galleries** - Photo albums, image search results
313
+ - 🛍️ **E-commerce** - Product listings, shopping galleries
314
+ - 📰 **Content Feeds** - News feeds, blog posts, social media
315
+ - 🎨 **Portfolio Sites** - Design portfolios, artwork showcases
316
+ - 📱 **Media Libraries** - Video thumbnails, media collections
317
+
318
+ ## 🚀 Performance
319
+
320
+ - **Virtual Scrolling**: Only renders visible items, handles 10,000+ items smoothly
321
+ - **Smart Preloading**: Loads next page before reaching the end
322
+ - **RAF Optimization**: Smooth 60fps scrolling
323
+ - **Responsive**: Automatically adapts to window resize
324
+ - **Memory Efficient**: Minimal memory footprint
325
+
326
+ ## 🤝 Contributing
327
+
328
+ Contributions are welcome! Please feel free to submit a Pull Request.
329
+
330
+ ## 📄 License
331
+
332
+ MIT License - see the [LICENSE](LICENSE) file for details.
333
+
334
+ ## 🙏 Acknowledgments
335
+
336
+ Inspired by:
337
+ - Pinterest's waterfall layout
338
+ - Google Photos' justified layout
339
+ - React Virtualized
340
+
341
+ ---
342
+
343
+ Made with ❤️ by [Your Name]