or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

context-hooks.mddocument-management.mdindex.mdnavigation-components.mdpage-rendering.md

navigation-components.mddocs/

0

# Navigation Components

1

2

Navigation components for PDF outline (table of contents) and thumbnail displays to enhance user experience and provide quick document navigation.

3

4

## Capabilities

5

6

### Outline Component

7

8

Displays PDF document outline (table of contents/bookmarks) with hierarchical navigation structure.

9

10

```typescript { .api }

11

/**

12

* Displays an outline (table of contents) for the PDF document

13

* Should be placed inside <Document /> or passed explicit pdf prop

14

* @param props - Outline configuration and event handlers

15

* @returns React element rendering the outline or null if no outline exists

16

*/

17

function Outline(props: OutlineProps): React.ReactElement | null;

18

19

interface OutlineProps {

20

/** Function called when outline item is clicked */

21

onItemClick?: (args: OnItemClickArgs) => void;

22

/** Function called when outline loads successfully */

23

onLoadSuccess?: (outline: PDFOutline | null) => void;

24

/** Function called when outline fails to load */

25

onLoadError?: (error: Error) => void;

26

/** Class names for styling */

27

className?: ClassName;

28

/** React ref for the outline container div */

29

inputRef?: React.Ref<HTMLDivElement>;

30

/** PDF document proxy (usually provided by Document context) */

31

pdf?: PDFDocumentProxy | false;

32

}

33

34

type PDFOutline = OutlineNode[] | null;

35

36

interface OutlineNode {

37

/** Title of the outline item */

38

title: string;

39

/** Destination reference */

40

dest: Dest | null;

41

/** Child outline items */

42

items: OutlineNode[];

43

/** Whether the item is bold */

44

bold?: boolean;

45

/** Whether the item is italic */

46

italic?: boolean;

47

/** Item color in RGB */

48

color?: number[];

49

}

50

51

interface OnItemClickArgs {

52

/** Destination reference for navigation */

53

dest?: Dest;

54

/** Zero-based page index */

55

pageIndex: number;

56

/** One-based page number */

57

pageNumber: number;

58

}

59

```

60

61

**Usage Examples:**

62

63

```typescript

64

import { Document, Page, Outline } from "react-pdf";

65

66

// Basic outline display

67

function PDFWithOutline() {

68

return (

69

<div style={{ display: 'flex' }}>

70

<Document file="document-with-toc.pdf">

71

<div style={{ width: '200px', padding: '10px' }}>

72

<h3>Table of Contents</h3>

73

<Outline />

74

</div>

75

<div style={{ flex: 1 }}>

76

<Page pageNumber={1} />

77

</div>

78

</Document>

79

</div>

80

);

81

}

82

83

// Outline with custom navigation handling

84

function CustomOutlineNavigation() {

85

const [currentPage, setCurrentPage] = useState(1);

86

87

const handleOutlineClick = ({ pageNumber }) => {

88

setCurrentPage(pageNumber);

89

console.log('Navigating to page', pageNumber);

90

};

91

92

return (

93

<Document file="sample.pdf">

94

<Outline

95

onItemClick={handleOutlineClick}

96

onLoadSuccess={(outline) => {

97

if (outline) {

98

console.log('Outline loaded with', outline.length, 'items');

99

} else {

100

console.log('No outline available');

101

}

102

}}

103

/>

104

<Page pageNumber={currentPage} />

105

</Document>

106

);

107

}

108

109

// Styled outline

110

function StyledOutline() {

111

return (

112

<Document file="sample.pdf">

113

<Outline

114

className="custom-outline border p-4"

115

onLoadError={(error) => {

116

console.error('Failed to load outline:', error);

117

}}

118

/>

119

<Page pageNumber={1} />

120

</Document>

121

);

122

}

123

```

124

125

### Thumbnail Component

126

127

Displays page thumbnails for quick navigation and document overview.

128

129

```typescript { .api }

130

/**

131

* Displays a thumbnail of a page for navigation purposes

132

* Does not render text or annotation layers for performance

133

* Should be placed inside <Document /> or passed explicit pdf prop

134

* @param props - Thumbnail configuration extending Page props

135

* @returns React element rendering the page thumbnail

136

*/

137

function Thumbnail(props: ThumbnailProps): React.ReactElement;

138

139

interface ThumbnailProps extends Omit<PageProps,

140

| 'className'

141

| 'customTextRenderer'

142

| 'onGetAnnotationsError'

143

| 'onGetAnnotationsSuccess'

144

| 'onGetTextError'

145

| 'onGetTextSuccess'

146

| 'onRenderAnnotationLayerError'

147

| 'onRenderAnnotationLayerSuccess'

148

| 'onRenderTextLayerError'

149

| 'onRenderTextLayerSuccess'

150

| 'renderAnnotationLayer'

151

| 'renderForms'

152

| 'renderTextLayer'

153

> {

154

/** Class names for styling the thumbnail */

155

className?: ClassName;

156

/** Function called when thumbnail is clicked */

157

onItemClick?: (args: OnItemClickArgs) => void;

158

}

159

```

160

161

**Usage Examples:**

162

163

```typescript

164

import { Document, Page, Thumbnail } from "react-pdf";

165

166

// Basic thumbnail grid

167

function ThumbnailGrid() {

168

const [numPages, setNumPages] = useState(null);

169

const [currentPage, setCurrentPage] = useState(1);

170

171

return (

172

<Document

173

file="sample.pdf"

174

onLoadSuccess={({ numPages }) => setNumPages(numPages)}

175

>

176

<div style={{ display: 'flex' }}>

177

<div style={{ width: '200px', maxHeight: '600px', overflow: 'auto' }}>

178

{Array.from({ length: numPages }, (_, index) => (

179

<Thumbnail

180

key={`thumb_${index + 1}`}

181

pageNumber={index + 1}

182

width={150}

183

className={`thumbnail ${currentPage === index + 1 ? 'active' : ''}`}

184

onItemClick={({ pageNumber }) => setCurrentPage(pageNumber)}

185

/>

186

))}

187

</div>

188

<div style={{ flex: 1 }}>

189

<Page pageNumber={currentPage} />

190

</div>

191

</div>

192

</Document>

193

);

194

}

195

196

// Thumbnail carousel

197

function ThumbnailCarousel() {

198

const [numPages, setNumPages] = useState(null);

199

const [currentPage, setCurrentPage] = useState(1);

200

201

return (

202

<Document

203

file="sample.pdf"

204

onLoadSuccess={({ numPages }) => setNumPages(numPages)}

205

>

206

<div>

207

<Page pageNumber={currentPage} scale={1.2} />

208

<div style={{

209

display: 'flex',

210

overflowX: 'auto',

211

padding: '10px',

212

gap: '10px'

213

}}>

214

{Array.from({ length: numPages }, (_, index) => (

215

<Thumbnail

216

key={`thumb_${index + 1}`}

217

pageNumber={index + 1}

218

height={100}

219

className={`thumbnail-small ${

220

currentPage === index + 1 ? 'active' : ''

221

}`}

222

onItemClick={({ pageNumber }) => setCurrentPage(pageNumber)}

223

/>

224

))}

225

</div>

226

</div>

227

</Document>

228

);

229

}

230

231

// Thumbnail with page numbers

232

function ThumbnailWithNumbers() {

233

return (

234

<Document file="sample.pdf">

235

{Array.from({ length: 5 }, (_, index) => (

236

<div key={index} className="thumbnail-container">

237

<Thumbnail

238

pageNumber={index + 1}

239

width={120}

240

scale={0.3}

241

/>

242

<div className="page-number">Page {index + 1}</div>

243

</div>

244

))}

245

</Document>

246

);

247

}

248

```

249

250

### Navigation Event Handling

251

252

Centralized navigation handling through Document component for consistent behavior.

253

254

```typescript { .api }

255

interface NavigationHandling {

256

/** Central click handler for all navigation components */

257

onItemClick?: (args: OnItemClickArgs) => void;

258

}

259

260

interface OnItemClickArgs {

261

/** Destination object for internal links */

262

dest?: Dest;

263

/** Zero-based page index */

264

pageIndex: number;

265

/** One-based page number */

266

pageNumber: number;

267

}

268

269

type Dest = Promise<ResolvedDest> | ResolvedDest | string | null;

270

type ResolvedDest = (RefProxy | number)[];

271

```

272

273

**Usage Examples:**

274

275

```typescript

276

// Centralized navigation handling

277

function PDFNavigator() {

278

const [currentPage, setCurrentPage] = useState(1);

279

const [numPages, setNumPages] = useState(null);

280

281

const handleNavigation = ({ pageNumber, dest }) => {

282

setCurrentPage(pageNumber);

283

284

// Handle different destination types

285

if (dest && typeof dest === 'string') {

286

// Named destination

287

console.log('Navigating to named destination:', dest);

288

} else if (dest && Array.isArray(dest)) {

289

// Explicit destination with coordinates

290

console.log('Navigating to coordinates:', dest);

291

}

292

};

293

294

return (

295

<Document

296

file="sample.pdf"

297

onLoadSuccess={({ numPages }) => setNumPages(numPages)}

298

onItemClick={handleNavigation}

299

>

300

<div style={{ display: 'flex' }}>

301

{/* Outline navigation */}

302

<div style={{ width: '250px' }}>

303

<h3>Contents</h3>

304

<Outline />

305

306

<h3>Pages</h3>

307

<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr' }}>

308

{Array.from({ length: numPages }, (_, index) => (

309

<Thumbnail

310

key={index}

311

pageNumber={index + 1}

312

width={80}

313

className={currentPage === index + 1 ? 'active' : ''}

314

/>

315

))}

316

</div>

317

</div>

318

319

{/* Main page view */}

320

<div style={{ flex: 1 }}>

321

<Page pageNumber={currentPage} />

322

</div>

323

</div>

324

</Document>

325

);

326

}

327

328

// Navigation with history

329

function PDFWithHistory() {

330

const [pageHistory, setPageHistory] = useState([1]);

331

const [historyIndex, setHistoryIndex] = useState(0);

332

333

const navigateToPage = ({ pageNumber }) => {

334

const newHistory = pageHistory.slice(0, historyIndex + 1);

335

newHistory.push(pageNumber);

336

setPageHistory(newHistory);

337

setHistoryIndex(newHistory.length - 1);

338

};

339

340

const goBack = () => {

341

if (historyIndex > 0) {

342

setHistoryIndex(historyIndex - 1);

343

}

344

};

345

346

const goForward = () => {

347

if (historyIndex < pageHistory.length - 1) {

348

setHistoryIndex(historyIndex + 1);

349

}

350

};

351

352

const currentPage = pageHistory[historyIndex];

353

354

return (

355

<Document file="sample.pdf" onItemClick={navigateToPage}>

356

<div>

357

<div className="navigation-controls">

358

<button onClick={goBack} disabled={historyIndex === 0}>

359

Back

360

</button>

361

<button onClick={goForward} disabled={historyIndex === pageHistory.length - 1}>

362

Forward

363

</button>

364

<span>Page {currentPage}</span>

365

</div>

366

367

<div style={{ display: 'flex' }}>

368

<Outline />

369

<Page pageNumber={currentPage} />

370

</div>

371

</div>

372

</Document>

373

);

374

}

375

```

376

377

### Outline Structure

378

379

Understanding and working with hierarchical outline data.

380

381

```typescript { .api }

382

interface OutlineStructure {

383

/** Process nested outline items */

384

processOutline?: (outline: PDFOutline) => void;

385

/** Extract all destinations from outline */

386

extractDestinations?: (outline: PDFOutline) => Dest[];

387

/** Find outline item by destination */

388

findOutlineItem?: (outline: PDFOutline, dest: Dest) => OutlineNode | null;

389

}

390

```

391

392

**Usage Examples:**

393

394

```typescript

395

// Custom outline processor

396

function CustomOutlineProcessor() {

397

const [outlineData, setOutlineData] = useState(null);

398

399

const processOutline = (outline) => {

400

if (!outline) return;

401

402

// Flatten outline for search

403

const flatOutline = [];

404

405

const flatten = (items, level = 0) => {

406

items.forEach(item => {

407

flatOutline.push({ ...item, level });

408

if (item.items && item.items.length > 0) {

409

flatten(item.items, level + 1);

410

}

411

});

412

};

413

414

flatten(outline);

415

setOutlineData(flatOutline);

416

};

417

418

return (

419

<Document file="sample.pdf">

420

<Outline

421

onLoadSuccess={processOutline}

422

onItemClick={({ pageNumber, dest }) => {

423

const item = outlineData?.find(item => item.dest === dest);

424

console.log('Clicked outline item:', item?.title);

425

}}

426

/>

427

</Document>

428

);

429

}

430

431

// Outline search functionality

432

function SearchableOutline() {

433

const [outline, setOutline] = useState(null);

434

const [searchTerm, setSearchTerm] = useState('');

435

const [filteredOutline, setFilteredOutline] = useState(null);

436

437

useEffect(() => {

438

if (!outline || !searchTerm) {

439

setFilteredOutline(outline);

440

return;

441

}

442

443

const filterOutline = (items) => {

444

return items.filter(item => {

445

const matchesSearch = item.title.toLowerCase().includes(searchTerm.toLowerCase());

446

const hasMatchingChildren = item.items && filterOutline(item.items).length > 0;

447

448

if (matchesSearch || hasMatchingChildren) {

449

return {

450

...item,

451

items: hasMatchingChildren ? filterOutline(item.items) : item.items

452

};

453

}

454

return false;

455

}).filter(Boolean);

456

};

457

458

setFilteredOutline(filterOutline(outline));

459

}, [outline, searchTerm]);

460

461

return (

462

<Document file="sample.pdf">

463

<div>

464

<input

465

type="text"

466

placeholder="Search outline..."

467

value={searchTerm}

468

onChange={(e) => setSearchTerm(e.target.value)}

469

/>

470

<Outline

471

onLoadSuccess={setOutline}

472

// Note: This is conceptual - actual filtering would need custom rendering

473

/>

474

</div>

475

</Document>

476

);

477

}

478

```