or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

core-components.mddynamic-content.mdindex.mdlayout-components.mdnavigation-components.mdspecialized-layouts.mdtable-components.mdutilities.md

specialized-layouts.mddocs/

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

```