0
# Specialized Layout Components
1
2
Advanced components for complex layout requirements including arbitrary positioning, masonry layouts, and multi-grid arrangements.
3
4
## Capabilities
5
6
### Collection Component
7
8
Renders arbitrarily positioned and sized cells efficiently, perfect for complex layouts where items don't follow a regular grid pattern.
9
10
```javascript { .api }
11
/**
12
* Renders arbitrarily positioned cells efficiently
13
* @param props - Collection configuration
14
*/
15
function Collection(props: {
16
/** Aria label for accessibility */
17
'aria-label'?: string;
18
/** Total number of cells */
19
cellCount: number;
20
/** Cell rendering function */
21
cellRenderer: (params: {index: number, key: string, style: object}) => React.Node;
22
/** Function that returns position and size for each cell */
23
cellSizeAndPositionGetter: (params: {index: number}) => {
24
height: number,
25
width: number,
26
x: number,
27
y: number
28
};
29
/** Optional CSS class name */
30
className?: string;
31
/** Height constraint for collection */
32
height: number;
33
/** Horizontal offset for scrolling */
34
horizontalOverscanSize?: number;
35
/** Renderer when no content is present */
36
noContentRenderer?: () => React.Node;
37
/** Callback when section is rendered */
38
onSectionRendered?: (params: {indices: number[]}) => void;
39
/** Scroll event callback */
40
onScroll?: (params: {clientHeight: number, clientWidth: number, scrollHeight: number, scrollLeft: number, scrollTop: number, scrollWidth: number}) => void;
41
/** Horizontal scroll offset */
42
scrollLeft?: number;
43
/** Vertical scroll offset */
44
scrollTop?: number;
45
/** Inline styles */
46
style?: object;
47
/** Vertical offset for scrolling */
48
verticalOverscanSize?: number;
49
/** Width constraint for collection */
50
width: number;
51
}): React.Component;
52
```
53
54
**Usage Examples:**
55
56
```javascript
57
import React from 'react';
58
import { Collection, AutoSizer } from 'react-virtualized';
59
60
// Basic Collection with random positioning
61
function RandomCollection({ items }) {
62
const cellSizeAndPositionGetter = ({ index }) => {
63
const item = items[index];
64
return {
65
height: item.height,
66
width: item.width,
67
x: item.x,
68
y: item.y
69
};
70
};
71
72
const cellRenderer = ({ index, key, style }) => (
73
<div key={key} style={style} className="collection-cell">
74
<div className="cell-content">
75
<h4>{items[index].title}</h4>
76
<p>{items[index].description}</p>
77
</div>
78
</div>
79
);
80
81
return (
82
<div style={{ height: 600, width: 800 }}>
83
<Collection
84
cellCount={items.length}
85
cellRenderer={cellRenderer}
86
cellSizeAndPositionGetter={cellSizeAndPositionGetter}
87
height={600}
88
width={800}
89
/>
90
</div>
91
);
92
}
93
94
// Interactive Collection with drag-and-drop positioning
95
function InteractiveCollection({ items, onItemMove }) {
96
const [positions, setPositions] = useState(
97
items.reduce((acc, item, index) => {
98
acc[index] = { x: item.x, y: item.y };
99
return acc;
100
}, {})
101
);
102
103
const cellSizeAndPositionGetter = ({ index }) => {
104
const position = positions[index];
105
return {
106
height: 120,
107
width: 200,
108
x: position.x,
109
y: position.y
110
};
111
};
112
113
const cellRenderer = ({ index, key, style }) => {
114
const handleMouseDown = (e) => {
115
// Implement drag logic here
116
const startX = e.clientX - positions[index].x;
117
const startY = e.clientY - positions[index].y;
118
119
const handleMouseMove = (e) => {
120
const newX = e.clientX - startX;
121
const newY = e.clientY - startY;
122
123
setPositions(prev => ({
124
...prev,
125
[index]: { x: newX, y: newY }
126
}));
127
};
128
129
const handleMouseUp = () => {
130
document.removeEventListener('mousemove', handleMouseMove);
131
document.removeEventListener('mouseup', handleMouseUp);
132
onItemMove?.(index, positions[index]);
133
};
134
135
document.addEventListener('mousemove', handleMouseMove);
136
document.addEventListener('mouseup', handleMouseUp);
137
};
138
139
return (
140
<div
141
key={key}
142
style={{
143
...style,
144
cursor: 'move',
145
border: '2px solid #ddd',
146
borderRadius: '4px',
147
backgroundColor: 'white'
148
}}
149
onMouseDown={handleMouseDown}
150
className="draggable-cell"
151
>
152
<div style={{ padding: 10 }}>
153
<h4>{items[index].title}</h4>
154
<p>{items[index].content}</p>
155
</div>
156
</div>
157
);
158
};
159
160
return (
161
<div style={{ height: 600, width: 800, position: 'relative' }}>
162
<Collection
163
cellCount={items.length}
164
cellRenderer={cellRenderer}
165
cellSizeAndPositionGetter={cellSizeAndPositionGetter}
166
height={600}
167
width={800}
168
/>
169
</div>
170
);
171
}
172
```
173
174
### Masonry Component
175
176
Renders dynamic, Pinterest-style masonry layouts where items flow into columns based on their heights.
177
178
```javascript { .api }
179
/**
180
* Renders Pinterest-style masonry layout
181
* @param props - Masonry configuration
182
*/
183
function Masonry(props: {
184
/** Total number of cells */
185
cellCount: number;
186
/** Cache for measuring cell dimensions */
187
cellMeasurerCache?: CellMeasurerCache;
188
/** Position manager for cell placement */
189
cellPositioner: object;
190
/** Cell rendering function */
191
cellRenderer: (params: {index: number, key: string, parent: object, style: object}) => React.Node;
192
/** Optional CSS class name */
193
className?: string;
194
/** Height constraint for masonry */
195
height: number;
196
/** Optional id attribute */
197
id?: string;
198
/** Callback when cells are rendered */
199
onCellsRendered?: (params: {startIndex: number, stopIndex: number}) => void;
200
/** Scroll event callback */
201
onScroll?: (params: {clientHeight: number, scrollHeight: number, scrollTop: number}) => void;
202
/** Number of extra cells to render */
203
overscanByPixels?: number;
204
/** Callback when scroll position changes */
205
onScrollToChange?: (params: {align: string, scrollTop: number}) => void;
206
/** Column index to scroll to */
207
scrollToIndex?: number;
208
/** Scroll alignment behavior */
209
scrollToAlignment?: 'auto' | 'end' | 'start' | 'center';
210
/** Vertical scroll offset */
211
scrollTop?: number;
212
/** Inline styles */
213
style?: object;
214
/** Tab index for focus */
215
tabIndex?: number;
216
/** Width constraint for masonry */
217
width: number;
218
}): React.Component;
219
```
220
221
### createMasonryCellPositioner Function
222
223
Creates a position manager for masonry layouts, handling the column-based positioning logic.
224
225
```javascript { .api }
226
/**
227
* Creates a cell positioner for masonry layouts
228
* @param params - Positioner configuration
229
*/
230
function createMasonryCellPositioner(params: {
231
/** Cache for cell measurements */
232
cellMeasurerCache: CellMeasurerCache;
233
/** Number of columns in the masonry */
234
columnCount: number;
235
/** Width of each column */
236
columnWidth: number;
237
/** Space between items */
238
spacer?: number;
239
}): {
240
/** Reset the positioner state */
241
reset: (params: {columnCount: number, columnWidth: number, spacer?: number}) => void;
242
};
243
```
244
245
**Usage Examples:**
246
247
```javascript
248
import React, { useMemo } from 'react';
249
import {
250
Masonry,
251
CellMeasurer,
252
CellMeasurerCache,
253
createMasonryCellPositioner,
254
AutoSizer
255
} from 'react-virtualized';
256
257
// Basic masonry layout
258
function BasicMasonry({ items }) {
259
const cache = useMemo(() => new CellMeasurerCache({
260
defaultHeight: 200,
261
fixedWidth: true
262
}), []);
263
264
const cellPositioner = useMemo(() =>
265
createMasonryCellPositioner({
266
cellMeasurerCache: cache,
267
columnCount: 3,
268
columnWidth: 200,
269
spacer: 10
270
}), [cache]
271
);
272
273
const cellRenderer = ({ index, key, parent, style }) => (
274
<CellMeasurer
275
cache={cache}
276
index={index}
277
key={key}
278
parent={parent}
279
>
280
<div style={style} className="masonry-item">
281
<img
282
src={items[index].imageUrl}
283
alt={items[index].title}
284
style={{ width: '100%', height: 'auto' }}
285
/>
286
<div className="item-content">
287
<h3>{items[index].title}</h3>
288
<p>{items[index].description}</p>
289
</div>
290
</div>
291
</CellMeasurer>
292
);
293
294
return (
295
<div style={{ height: 600, width: 630 }}>
296
<Masonry
297
cellCount={items.length}
298
cellMeasurerCache={cache}
299
cellPositioner={cellPositioner}
300
cellRenderer={cellRenderer}
301
height={600}
302
width={630}
303
/>
304
</div>
305
);
306
}
307
308
// Responsive masonry with dynamic columns
309
function ResponsiveMasonry({ items }) {
310
const [columnCount, setColumnCount] = useState(3);
311
312
const cache = useMemo(() => new CellMeasurerCache({
313
defaultHeight: 250,
314
fixedWidth: true
315
}), []);
316
317
const cellPositioner = useMemo(() => {
318
const columnWidth = Math.floor((800 - (columnCount + 1) * 10) / columnCount);
319
320
return createMasonryCellPositioner({
321
cellMeasurerCache: cache,
322
columnCount,
323
columnWidth,
324
spacer: 10
325
});
326
}, [cache, columnCount]);
327
328
// Reset positioner when column count changes
329
useEffect(() => {
330
const columnWidth = Math.floor((800 - (columnCount + 1) * 10) / columnCount);
331
cellPositioner.reset({
332
columnCount,
333
columnWidth,
334
spacer: 10
335
});
336
}, [cellPositioner, columnCount]);
337
338
const cellRenderer = ({ index, key, parent, style }) => {
339
const item = items[index];
340
341
return (
342
<CellMeasurer
343
cache={cache}
344
index={index}
345
key={key}
346
parent={parent}
347
>
348
{({ measure, registerChild }) => (
349
<div
350
ref={registerChild}
351
style={{
352
...style,
353
borderRadius: '8px',
354
overflow: 'hidden',
355
backgroundColor: 'white',
356
boxShadow: '0 2px 8px rgba(0,0,0,0.1)'
357
}}
358
className="responsive-masonry-item"
359
>
360
<img
361
src={item.imageUrl}
362
alt={item.title}
363
onLoad={measure}
364
style={{ width: '100%', height: 'auto', display: 'block' }}
365
/>
366
<div style={{ padding: 12 }}>
367
<h4 style={{ margin: '0 0 8px 0' }}>{item.title}</h4>
368
<p style={{ margin: 0, color: '#666' }}>{item.description}</p>
369
{item.tags && (
370
<div style={{ marginTop: 8 }}>
371
{item.tags.map(tag => (
372
<span key={tag} className="tag">{tag}</span>
373
))}
374
</div>
375
)}
376
</div>
377
</div>
378
)}
379
</CellMeasurer>
380
);
381
};
382
383
return (
384
<div>
385
<div style={{ marginBottom: 20 }}>
386
<label>
387
Columns:
388
<select
389
value={columnCount}
390
onChange={(e) => setColumnCount(Number(e.target.value))}
391
>
392
<option value={2}>2</option>
393
<option value={3}>3</option>
394
<option value={4}>4</option>
395
<option value={5}>5</option>
396
</select>
397
</label>
398
</div>
399
400
<div style={{ height: 600, width: 800 }}>
401
<Masonry
402
cellCount={items.length}
403
cellMeasurerCache={cache}
404
cellPositioner={cellPositioner}
405
cellRenderer={cellRenderer}
406
height={600}
407
width={800}
408
/>
409
</div>
410
</div>
411
);
412
}
413
```
414
415
### MultiGrid Component
416
417
Grid component with fixed columns and/or rows, creating frozen panes similar to spreadsheet applications.
418
419
```javascript { .api }
420
/**
421
* Grid with fixed columns and/or rows
422
* @param props - MultiGrid configuration
423
*/
424
function MultiGrid(props: {
425
/** Optional CSS class name */
426
className?: string;
427
/** Class name for bottom-left grid */
428
classNameBottomLeftGrid?: string;
429
/** Class name for bottom-right grid */
430
classNameBottomRightGrid?: string;
431
/** Class name for top-left grid */
432
classNameTopLeftGrid?: string;
433
/** Class name for top-right grid */
434
classNameTopRightGrid?: string;
435
/** Total number of columns */
436
columnCount: number;
437
/** Fixed width or dynamic width function for columns */
438
columnWidth: number | ((params: {index: number}) => number);
439
/** Enable fixed columns on the right instead of left */
440
enableFixedColumnScroll?: boolean;
441
/** Enable fixed rows on the bottom instead of top */
442
enableFixedRowScroll?: boolean;
443
/** Number of fixed columns */
444
fixedColumnCount?: number;
445
/** Number of fixed rows */
446
fixedRowCount?: number;
447
/** Height constraint for grid */
448
height: number;
449
/** Renderer when no content is present */
450
noContentRenderer?: () => React.Node;
451
/** Callback when section is rendered */
452
onSectionRendered?: (params: {columnStartIndex: number, columnStopIndex: number, rowStartIndex: number, rowStopIndex: number}) => void;
453
/** Scroll event callback */
454
onScroll?: (params: {clientHeight: number, clientWidth: number, scrollHeight: number, scrollLeft: number, scrollTop: number, scrollWidth: number}) => void;
455
/** Number of extra columns to render */
456
overscanColumnCount?: number;
457
/** Number of extra rows to render */
458
overscanRowCount?: number;
459
/** Total number of rows */
460
rowCount: number;
461
/** Fixed height or dynamic height function for rows */
462
rowHeight: number | ((params: {index: number}) => number);
463
/** Cell rendering function */
464
cellRenderer: (params: {columnIndex: number, key: string, rowIndex: number, style: object}) => React.Node;
465
/** Column index to scroll to */
466
scrollToColumn?: number;
467
/** Row index to scroll to */
468
scrollToRow?: number;
469
/** Scroll alignment behavior */
470
scrollToAlignment?: 'auto' | 'end' | 'start' | 'center';
471
/** Horizontal scroll offset */
472
scrollLeft?: number;
473
/** Vertical scroll offset */
474
scrollTop?: number;
475
/** Inline styles */
476
style?: object;
477
/** Style for bottom-left grid */
478
styleBottomLeftGrid?: object;
479
/** Style for bottom-right grid */
480
styleBottomRightGrid?: object;
481
/** Style for top-left grid */
482
styleTopLeftGrid?: object;
483
/** Style for top-right grid */
484
styleTopRightGrid?: object;
485
/** Width constraint for grid */
486
width: number;
487
}): React.Component;
488
```
489
490
**Usage Examples:**
491
492
```javascript
493
import React from 'react';
494
import { MultiGrid, AutoSizer } from 'react-virtualized';
495
496
// Basic MultiGrid with frozen headers
497
function SpreadsheetGrid({ data, columnHeaders, rowHeaders }) {
498
const cellRenderer = ({ columnIndex, key, rowIndex, style }) => {
499
let content;
500
let className = 'cell';
501
502
if (columnIndex === 0 && rowIndex === 0) {
503
// Top-left corner
504
content = '';
505
className = 'cell header-cell corner-cell';
506
} else if (rowIndex === 0) {
507
// Column headers
508
content = columnHeaders[columnIndex - 1];
509
className = 'cell header-cell column-header';
510
} else if (columnIndex === 0) {
511
// Row headers
512
content = rowHeaders[rowIndex - 1];
513
className = 'cell header-cell row-header';
514
} else {
515
// Data cells
516
content = data[rowIndex - 1][columnIndex - 1];
517
className = 'cell data-cell';
518
}
519
520
return (
521
<div key={key} className={className} style={style}>
522
{content}
523
</div>
524
);
525
};
526
527
return (
528
<div style={{ height: 500, width: '100%' }}>
529
<AutoSizer>
530
{({ height, width }) => (
531
<MultiGrid
532
cellRenderer={cellRenderer}
533
columnCount={columnHeaders.length + 1}
534
columnWidth={({ index }) => index === 0 ? 80 : 120}
535
fixedColumnCount={1}
536
fixedRowCount={1}
537
height={height}
538
rowCount={data.length + 1}
539
rowHeight={40}
540
width={width}
541
classNameTopLeftGrid="grid-top-left"
542
classNameTopRightGrid="grid-top-right"
543
classNameBottomLeftGrid="grid-bottom-left"
544
classNameBottomRightGrid="grid-bottom-right"
545
/>
546
)}
547
</AutoSizer>
548
</div>
549
);
550
}
551
552
// Advanced MultiGrid with custom styling
553
function AdvancedMultiGrid({ salesData }) {
554
const months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun'];
555
const salespeople = ['Alice', 'Bob', 'Charlie', 'Diana'];
556
557
const getColumnWidth = ({ index }) => {
558
if (index === 0) return 100; // Names column
559
return 80; // Data columns
560
};
561
562
const getRowHeight = ({ index }) => {
563
if (index === 0) return 50; // Header row
564
return 35; // Data rows
565
};
566
567
const cellRenderer = ({ columnIndex, key, rowIndex, style }) => {
568
let content = '';
569
let className = 'multigrid-cell';
570
571
if (columnIndex === 0 && rowIndex === 0) {
572
content = 'Salesperson';
573
className += ' corner-header';
574
} else if (rowIndex === 0) {
575
content = months[columnIndex - 1];
576
className += ' column-header';
577
} else if (columnIndex === 0) {
578
content = salespeople[rowIndex - 1];
579
className += ' row-header';
580
} else {
581
const value = salesData[rowIndex - 1][columnIndex - 1];
582
content = `$${value.toLocaleString()}`;
583
className += ' data-cell';
584
585
// Add performance-based styling
586
if (value > 50000) className += ' high-performance';
587
else if (value > 30000) className += ' medium-performance';
588
else className += ' low-performance';
589
}
590
591
return (
592
<div key={key} className={className} style={style}>
593
{content}
594
</div>
595
);
596
};
597
598
return (
599
<div className="sales-dashboard">
600
<h2>Sales Performance Dashboard</h2>
601
<div style={{ height: 400, width: 600 }}>
602
<MultiGrid
603
cellRenderer={cellRenderer}
604
columnCount={months.length + 1}
605
columnWidth={getColumnWidth}
606
fixedColumnCount={1}
607
fixedRowCount={1}
608
height={400}
609
rowCount={salespeople.length + 1}
610
rowHeight={getRowHeight}
611
width={600}
612
styleBottomLeftGrid={{ borderRight: '2px solid #ddd' }}
613
styleTopLeftGrid={{ borderRight: '2px solid #ddd', borderBottom: '2px solid #ddd' }}
614
styleTopRightGrid={{ borderBottom: '2px solid #ddd' }}
615
/>
616
</div>
617
</div>
618
);
619
}
620
```