CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-react-pdf

Display PDFs in your React app as easily as if they were images.

Pending
Overview
Eval results
Files

navigation-components.mddocs/

Navigation Components

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

Capabilities

Outline Component

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

/**
 * Displays an outline (table of contents) for the PDF document
 * Should be placed inside <Document /> or passed explicit pdf prop
 * @param props - Outline configuration and event handlers
 * @returns React element rendering the outline or null if no outline exists
 */
function Outline(props: OutlineProps): React.ReactElement | null;

interface OutlineProps {
  /** Function called when outline item is clicked */
  onItemClick?: (args: OnItemClickArgs) => void;
  /** Function called when outline loads successfully */
  onLoadSuccess?: (outline: PDFOutline | null) => void;
  /** Function called when outline fails to load */
  onLoadError?: (error: Error) => void;
  /** Class names for styling */
  className?: ClassName;
  /** React ref for the outline container div */
  inputRef?: React.Ref<HTMLDivElement>;
  /** PDF document proxy (usually provided by Document context) */
  pdf?: PDFDocumentProxy | false;
}

type PDFOutline = OutlineNode[] | null;

interface OutlineNode {
  /** Title of the outline item */
  title: string;
  /** Destination reference */
  dest: Dest | null;
  /** Child outline items */
  items: OutlineNode[];
  /** Whether the item is bold */
  bold?: boolean;
  /** Whether the item is italic */
  italic?: boolean;
  /** Item color in RGB */
  color?: number[];
}

interface OnItemClickArgs {
  /** Destination reference for navigation */
  dest?: Dest;
  /** Zero-based page index */
  pageIndex: number;
  /** One-based page number */
  pageNumber: number;
}

Usage Examples:

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

// Basic outline display
function PDFWithOutline() {
  return (
    <div style={{ display: 'flex' }}>
      <Document file="document-with-toc.pdf">
        <div style={{ width: '200px', padding: '10px' }}>
          <h3>Table of Contents</h3>
          <Outline />
        </div>
        <div style={{ flex: 1 }}>
          <Page pageNumber={1} />
        </div>
      </Document>
    </div>
  );
}

// Outline with custom navigation handling
function CustomOutlineNavigation() {
  const [currentPage, setCurrentPage] = useState(1);
  
  const handleOutlineClick = ({ pageNumber }) => {
    setCurrentPage(pageNumber);
    console.log('Navigating to page', pageNumber);
  };
  
  return (
    <Document file="sample.pdf">
      <Outline 
        onItemClick={handleOutlineClick}
        onLoadSuccess={(outline) => {
          if (outline) {
            console.log('Outline loaded with', outline.length, 'items');
          } else {
            console.log('No outline available');
          }
        }}
      />
      <Page pageNumber={currentPage} />
    </Document>
  );
}

// Styled outline
function StyledOutline() {
  return (
    <Document file="sample.pdf">
      <Outline 
        className="custom-outline border p-4"
        onLoadError={(error) => {
          console.error('Failed to load outline:', error);
        }}
      />
      <Page pageNumber={1} />
    </Document>
  );
}

Thumbnail Component

Displays page thumbnails for quick navigation and document overview.

/**
 * Displays a thumbnail of a page for navigation purposes
 * Does not render text or annotation layers for performance
 * Should be placed inside <Document /> or passed explicit pdf prop
 * @param props - Thumbnail configuration extending Page props
 * @returns React element rendering the page thumbnail
 */
function Thumbnail(props: ThumbnailProps): React.ReactElement;

interface ThumbnailProps extends Omit<PageProps, 
  | 'className'
  | 'customTextRenderer' 
  | 'onGetAnnotationsError'
  | 'onGetAnnotationsSuccess'
  | 'onGetTextError'
  | 'onGetTextSuccess'
  | 'onRenderAnnotationLayerError'
  | 'onRenderAnnotationLayerSuccess'
  | 'onRenderTextLayerError'
  | 'onRenderTextLayerSuccess'
  | 'renderAnnotationLayer'
  | 'renderForms'
  | 'renderTextLayer'
> {
  /** Class names for styling the thumbnail */
  className?: ClassName;
  /** Function called when thumbnail is clicked */
  onItemClick?: (args: OnItemClickArgs) => void;
}

Usage Examples:

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

// Basic thumbnail grid
function ThumbnailGrid() {
  const [numPages, setNumPages] = useState(null);
  const [currentPage, setCurrentPage] = useState(1);
  
  return (
    <Document 
      file="sample.pdf"
      onLoadSuccess={({ numPages }) => setNumPages(numPages)}
    >
      <div style={{ display: 'flex' }}>
        <div style={{ width: '200px', maxHeight: '600px', overflow: 'auto' }}>
          {Array.from({ length: numPages }, (_, index) => (
            <Thumbnail
              key={`thumb_${index + 1}`}
              pageNumber={index + 1}
              width={150}
              className={`thumbnail ${currentPage === index + 1 ? 'active' : ''}`}
              onItemClick={({ pageNumber }) => setCurrentPage(pageNumber)}
            />
          ))}
        </div>
        <div style={{ flex: 1 }}>
          <Page pageNumber={currentPage} />
        </div>
      </div>
    </Document>
  );
}

// Thumbnail carousel
function ThumbnailCarousel() {
  const [numPages, setNumPages] = useState(null);
  const [currentPage, setCurrentPage] = useState(1);
  
  return (
    <Document 
      file="sample.pdf"
      onLoadSuccess={({ numPages }) => setNumPages(numPages)}
    >
      <div>
        <Page pageNumber={currentPage} scale={1.2} />
        <div style={{ 
          display: 'flex', 
          overflowX: 'auto', 
          padding: '10px',
          gap: '10px'
        }}>
          {Array.from({ length: numPages }, (_, index) => (
            <Thumbnail
              key={`thumb_${index + 1}`}
              pageNumber={index + 1}
              height={100}
              className={`thumbnail-small ${
                currentPage === index + 1 ? 'active' : ''
              }`}
              onItemClick={({ pageNumber }) => setCurrentPage(pageNumber)}
            />
          ))}
        </div>
      </div>
    </Document>
  );
}

// Thumbnail with page numbers
function ThumbnailWithNumbers() {
  return (
    <Document file="sample.pdf">
      {Array.from({ length: 5 }, (_, index) => (
        <div key={index} className="thumbnail-container">
          <Thumbnail
            pageNumber={index + 1}
            width={120}
            scale={0.3}
          />
          <div className="page-number">Page {index + 1}</div>
        </div>
      ))}
    </Document>
  );
}

Navigation Event Handling

Centralized navigation handling through Document component for consistent behavior.

interface NavigationHandling {
  /** Central click handler for all navigation components */
  onItemClick?: (args: OnItemClickArgs) => void;
}

interface OnItemClickArgs {
  /** Destination object for internal links */
  dest?: Dest;
  /** Zero-based page index */
  pageIndex: number;
  /** One-based page number */
  pageNumber: number;
}

type Dest = Promise<ResolvedDest> | ResolvedDest | string | null;
type ResolvedDest = (RefProxy | number)[];

Usage Examples:

// Centralized navigation handling
function PDFNavigator() {
  const [currentPage, setCurrentPage] = useState(1);
  const [numPages, setNumPages] = useState(null);
  
  const handleNavigation = ({ pageNumber, dest }) => {
    setCurrentPage(pageNumber);
    
    // Handle different destination types
    if (dest && typeof dest === 'string') {
      // Named destination
      console.log('Navigating to named destination:', dest);
    } else if (dest && Array.isArray(dest)) {
      // Explicit destination with coordinates
      console.log('Navigating to coordinates:', dest);
    }
  };
  
  return (
    <Document 
      file="sample.pdf"
      onLoadSuccess={({ numPages }) => setNumPages(numPages)}
      onItemClick={handleNavigation}
    >
      <div style={{ display: 'flex' }}>
        {/* Outline navigation */}
        <div style={{ width: '250px' }}>
          <h3>Contents</h3>
          <Outline />
          
          <h3>Pages</h3>
          <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr' }}>
            {Array.from({ length: numPages }, (_, index) => (
              <Thumbnail
                key={index}
                pageNumber={index + 1}
                width={80}
                className={currentPage === index + 1 ? 'active' : ''}
              />
            ))}
          </div>
        </div>
        
        {/* Main page view */}
        <div style={{ flex: 1 }}>
          <Page pageNumber={currentPage} />
        </div>
      </div>
    </Document>
  );
}

// Navigation with history
function PDFWithHistory() {
  const [pageHistory, setPageHistory] = useState([1]);
  const [historyIndex, setHistoryIndex] = useState(0);
  
  const navigateToPage = ({ pageNumber }) => {
    const newHistory = pageHistory.slice(0, historyIndex + 1);
    newHistory.push(pageNumber);
    setPageHistory(newHistory);
    setHistoryIndex(newHistory.length - 1);
  };
  
  const goBack = () => {
    if (historyIndex > 0) {
      setHistoryIndex(historyIndex - 1);
    }
  };
  
  const goForward = () => {
    if (historyIndex < pageHistory.length - 1) {
      setHistoryIndex(historyIndex + 1);
    }
  };
  
  const currentPage = pageHistory[historyIndex];
  
  return (
    <Document file="sample.pdf" onItemClick={navigateToPage}>
      <div>
        <div className="navigation-controls">
          <button onClick={goBack} disabled={historyIndex === 0}>
            Back
          </button>
          <button onClick={goForward} disabled={historyIndex === pageHistory.length - 1}>
            Forward
          </button>
          <span>Page {currentPage}</span>
        </div>
        
        <div style={{ display: 'flex' }}>
          <Outline />
          <Page pageNumber={currentPage} />
        </div>
      </div>
    </Document>
  );
}

Outline Structure

Understanding and working with hierarchical outline data.

interface OutlineStructure {
  /** Process nested outline items */
  processOutline?: (outline: PDFOutline) => void;
  /** Extract all destinations from outline */
  extractDestinations?: (outline: PDFOutline) => Dest[];
  /** Find outline item by destination */
  findOutlineItem?: (outline: PDFOutline, dest: Dest) => OutlineNode | null;
}

Usage Examples:

// Custom outline processor
function CustomOutlineProcessor() {
  const [outlineData, setOutlineData] = useState(null);
  
  const processOutline = (outline) => {
    if (!outline) return;
    
    // Flatten outline for search
    const flatOutline = [];
    
    const flatten = (items, level = 0) => {
      items.forEach(item => {
        flatOutline.push({ ...item, level });
        if (item.items && item.items.length > 0) {
          flatten(item.items, level + 1);
        }
      });
    };
    
    flatten(outline);
    setOutlineData(flatOutline);
  };
  
  return (
    <Document file="sample.pdf">
      <Outline 
        onLoadSuccess={processOutline}
        onItemClick={({ pageNumber, dest }) => {
          const item = outlineData?.find(item => item.dest === dest);
          console.log('Clicked outline item:', item?.title);
        }}
      />
    </Document>
  );
}

// Outline search functionality
function SearchableOutline() {
  const [outline, setOutline] = useState(null);
  const [searchTerm, setSearchTerm] = useState('');
  const [filteredOutline, setFilteredOutline] = useState(null);
  
  useEffect(() => {
    if (!outline || !searchTerm) {
      setFilteredOutline(outline);
      return;
    }
    
    const filterOutline = (items) => {
      return items.filter(item => {
        const matchesSearch = item.title.toLowerCase().includes(searchTerm.toLowerCase());
        const hasMatchingChildren = item.items && filterOutline(item.items).length > 0;
        
        if (matchesSearch || hasMatchingChildren) {
          return {
            ...item,
            items: hasMatchingChildren ? filterOutline(item.items) : item.items
          };
        }
        return false;
      }).filter(Boolean);
    };
    
    setFilteredOutline(filterOutline(outline));
  }, [outline, searchTerm]);
  
  return (
    <Document file="sample.pdf">
      <div>
        <input
          type="text"
          placeholder="Search outline..."
          value={searchTerm}
          onChange={(e) => setSearchTerm(e.target.value)}
        />
        <Outline 
          onLoadSuccess={setOutline}
          // Note: This is conceptual - actual filtering would need custom rendering
        />
      </div>
    </Document>
  );
}

Install with Tessl CLI

npx tessl i tessl/npm-react-pdf

docs

context-hooks.md

document-management.md

index.md

navigation-components.md

page-rendering.md

tile.json