npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

jagran-react-doc-viewer

v1.0.4

Published

A highly customizable React document viewer with support for PDF, DOCX, PPTX, images, and more

Readme

React Document Viewer

A highly customizable React document viewer with support for PDF, DOCX, PPTX, images, and more. Built with TypeScript and designed for flexibility and performance.

Features

  • 📄 Multi-format support: PDF, DOCX, PPTX, images, and text files
  • 🎨 Fully customizable: Themes, renderers, and UI components
  • 🔧 TypeScript: Full type safety and IntelliSense support
  • 🚀 Performance: Optimized rendering with lazy loading and prerendering
  • 📱 Responsive: Works seamlessly across devices with device-specific configurations
  • 🎯 Extensible: Custom renderers and plugins
  • 🌙 Theme support: Built-in light/dark themes with full customization
  • 🖼️ Thumbnail support: Document thumbnails and navigation
  • 📄 Page-specific rendering: Start from specific pages
  • 🔍 Advanced zoom controls: Device-specific zoom levels and smooth animations

Installation

npm install jagran-react-doc-viewer
yarn add jagran-react-doc-viewer

Quick Start

import React from 'react';
import { DocViewer } from 'jagran-react-doc-viewer';

function App() {
  const documents = [
    {
      uri: 'https://example.com/document.pdf',
      fileName: 'Sample Document.pdf',
    },
    {
      uri: 'https://example.com/presentation.pptx',
      fileName: 'Presentation.pptx',
    },
  ];

  return (
    <div style={{ height: '100vh' }}>
      <DocViewer documents={documents} />
    </div>
  );
}

export default App;

Comprehensive Examples

Basic Document Viewer with Multiple Files

import React, { useState } from 'react';
import { DocViewer, DocumentSource } from 'jagran-react-doc-viewer';

function BasicViewer() {
  const [documents] = useState<DocumentSource[]>([
    {
      uri: '/documents/annual-report.pdf',
      fileName: 'Annual Report 2024.pdf',
      fileType: 'pdf',
      startPage: 3, // Start from page 3
      thumbnail: '/thumbnails/annual-report-thumb.jpg'
    },
    {
      uri: '/documents/presentation.pptx',
      fileName: 'Q4 Presentation.pptx',
      fileType: 'pptx'
    },
    {
      uri: '/documents/contract.docx',
      fileName: 'Service Contract.docx',
      fileType: 'docx'
    },
    {
      uri: '/images/diagram.png',
      fileName: 'System Architecture.png',
      fileType: 'png'
    }
  ]);

  return (
    <div style={{ height: '100vh', width: '100%' }}>
      <DocViewer 
        documents={documents}
        activeDocument={0}
        showThumbnail={true}
      />
    </div>
  );
}

Advanced Configuration with Custom Styling

import React from 'react';
import { DocViewer, ViewerConfig, ViewerEvents } from 'jagran-react-doc-viewer';

function AdvancedViewer() {
  const documents = [
    {
      uri: '/documents/technical-manual.pdf',
      fileName: 'Technical Manual.pdf',
      startPage: 1
    }
  ];

  const config: ViewerConfig = {
    theme: 'dark',
    showToolbar: true,
    showNavigation: true,
    allowDownload: true,
    allowPrint: true,
    allowFullscreen: true,
    loadingTimeout: 5000,
    errorRetryAttempts: 3,
    
    // Header configuration
    header: {
      visible: true,
      height: '60px',
      style: { backgroundColor: '#2c3e50', color: 'white' },
      className: 'custom-header'
    },
    
    // Navigation configuration
    navigation: {
      visible: true,
      position: 'bottom',
      showThumbnails: true,
      thumbnailSize: { width: 120, height: 160 },
      icons: {
        prev: '⬅️',
        next: '➡️',
        download: '💾',
        print: '🖨️',
        fullscreen: '🔍',
        zoomIn: '🔍+',
        zoomOut: '🔍-'
      }
    },
    
    // Prerender configuration for better performance
    prerender: {
      enabled: true,
      threshold: 10, // 10MB threshold
      strategy: 'next' // Prerender next document
    },
    
    // Zoom configuration
    initialZoom: 1.2,
    initialZoomByDevice: {
      mobile: 0.8,
      tablet: 1.0,
      desktop: 1.2,
      tv: 1.5
    },
    zoomStep: 0.25,
    minZoom: 0.5,
    maxZoom: 3.0,
    
    // Responsive breakpoints
    mobileBreakpoint: 768,
    tabletBreakpoint: 1024,
    
    // Mobile-specific behavior
    mobileBehavior: {
      collapseToolbar: true,
      hideNavigation: false,
      simplifiedControls: true
    },
    
    // Animation settings
    animatePageTransition: true,
    transitionDuration: 300,
    zoomAnimationDuration: 200,
    
    // Spacing and layout
    pageGap: 20,
    pagePadding: 15,
    
    // Tooltips
    tooltips: {
      download: 'Download document',
      zoom: {
        in: 'Zoom in',
        out: 'Zoom out',
        reset: 'Reset zoom',
        current: 'Current zoom: {zoom}%'
      },
      navigation: {
        previous: 'Previous document',
        next: 'Next document',
        pageInfo: 'Page {current} of {total}'
      },
      toolbar: {
        download: 'Download current document',
        print: 'Print document',
        fullscreen: 'Toggle fullscreen'
      }
    }
  };

  const events: ViewerEvents = {
    onDocumentLoad: (document) => {
      console.log('Document loaded:', document.fileName);
    },
    onDocumentError: (error, document) => {
      console.error('Error loading document:', document.fileName, error);
    },
    onPageChange: (page, totalPages) => {
      console.log(`Page ${page} of ${totalPages}`);
    },
    onZoomChange: (zoom) => {
      console.log('Zoom level:', zoom);
    },
    onFullscreenToggle: (isFullscreen) => {
      console.log('Fullscreen:', isFullscreen);
    }
  };

  return (
    <DocViewer 
      documents={documents}
      config={config}
      events={events}
      className="advanced-doc-viewer"
      style={{ height: '100vh', border: '1px solid #ddd' }}
    />
  );
}

Custom Theme Example

import React from 'react';
import { DocViewer, ViewerTheme } from 'jagran-react-doc-viewer';

function ThemedViewer() {
  const customTheme: ViewerTheme = {
    primary: '#3498db',
    secondary: '#2ecc71',
    background: '#ecf0f1',
    surface: '#ffffff',
    text: '#2c3e50',
    textSecondary: '#7f8c8d',
    border: '#bdc3c7',
    error: '#e74c3c',
    success: '#27ae60'
  };

  const documents = [
    { uri: '/docs/styled-document.pdf', fileName: 'Styled Document.pdf' }
  ];

  return (
    <DocViewer 
      documents={documents}
      config={{ theme: customTheme }}
      style={{ 
        height: '100vh',
        '--doc-viewer-primary': customTheme.primary,
        '--doc-viewer-background': customTheme.background
      } as React.CSSProperties}
    />
  );
}

Using Refs for Programmatic Control

import React, { useRef, useCallback } from 'react';
import { DocViewer, DocViewerRef } from 'jagran-react-doc-viewer';

function ControlledViewer() {
  const viewerRef = useRef<DocViewerRef>(null);
  
  const documents = [
    { uri: '/docs/manual.pdf', fileName: 'User Manual.pdf' },
    { uri: '/docs/guide.pdf', fileName: 'Quick Start Guide.pdf' }
  ];

  const handleNextDocument = useCallback(() => {
    viewerRef.current?.nextDocument();
  }, []);

  const handlePreviousDocument = useCallback(() => {
    viewerRef.current?.previousDocument();
  }, []);

  const handleZoomIn = useCallback(() => {
    viewerRef.current?.incrementZoom();
  }, []);

  const handleZoomOut = useCallback(() => {
    viewerRef.current?.decrementZoom();
  }, []);

  const handleDownload = useCallback(() => {
    viewerRef.current?.downloadCurrent();
  }, []);

  const handlePrint = useCallback(() => {
    viewerRef.current?.printCurrent();
  }, []);

  const goToSpecificDocument = useCallback((index: number) => {
    viewerRef.current?.setActiveDocument(index);
  }, []);

  return (
    <div>
      {/* Custom toolbar */}
      <div style={{ padding: '10px', borderBottom: '1px solid #ddd' }}>
        <button onClick={handlePreviousDocument}>← Previous</button>
        <button onClick={handleNextDocument}>Next →</button>
        <button onClick={handleZoomIn}>Zoom In</button>
        <button onClick={handleZoomOut}>Zoom Out</button>
        <button onClick={handleDownload}>Download</button>
        <button onClick={handlePrint}>Print</button>
        <button onClick={() => goToSpecificDocument(0)}>Go to First</button>
        <button onClick={() => goToSpecificDocument(1)}>Go to Second</button>
      </div>
      
      <DocViewer 
        ref={viewerRef}
        documents={documents}
        config={{ showToolbar: false }} // Hide default toolbar
        style={{ height: 'calc(100vh - 60px)' }}
      />
    </div>
  );
}

Custom Loading and Error Components

import React from 'react';
import { DocViewer, LoadingRendererProps, ErrorRendererProps } from 'jagran-react-doc-viewer';

// Custom loading component
const CustomLoadingComponent: React.FC<LoadingRendererProps> = ({ 
  document, 
  fileName, 
  progress 
}) => (
  <div style={{ 
    display: 'flex', 
    flexDirection: 'column', 
    alignItems: 'center', 
    justifyContent: 'center', 
    height: '100%',
    backgroundColor: '#f8f9fa'
  }}>
    <div className="spinner" style={{ 
      width: '40px', 
      height: '40px', 
      border: '4px solid #e3e3e3',
      borderTop: '4px solid #3498db',
      borderRadius: '50%',
      animation: 'spin 1s linear infinite'
    }} />
    <h3 style={{ marginTop: '20px', color: '#2c3e50' }}>
      Loading {fileName || 'document'}...
    </h3>
    {progress && (
      <div style={{ width: '200px', backgroundColor: '#e0e0e0', borderRadius: '10px', marginTop: '10px' }}>
        <div style={{ 
          width: `${progress}%`, 
          height: '8px', 
          backgroundColor: '#3498db', 
          borderRadius: '10px',
          transition: 'width 0.3s ease'
        }} />
      </div>
    )}
  </div>
);

// Custom error component
const CustomErrorComponent: React.FC<ErrorRendererProps> = ({ 
  error, 
  document, 
  onRetry 
}) => (
  <div style={{ 
    display: 'flex', 
    flexDirection: 'column', 
    alignItems: 'center', 
    justifyContent: 'center', 
    height: '100%',
    backgroundColor: '#fff5f5',
    color: '#e53e3e'
  }}>
    <div style={{ fontSize: '48px', marginBottom: '20px' }}>⚠️</div>
    <h3>Failed to load document</h3>
    <p style={{ textAlign: 'center', maxWidth: '400px', margin: '10px 0' }}>
      {document.fileName || 'Unknown document'} could not be loaded.
    </p>
    <p style={{ fontSize: '14px', color: '#666', marginBottom: '20px' }}>
      Error: {error.message}
    </p>
    {onRetry && (
      <button 
        onClick={onRetry}
        style={{
          padding: '10px 20px',
          backgroundColor: '#3498db',
          color: 'white',
          border: 'none',
          borderRadius: '5px',
          cursor: 'pointer'
        }}
      >
        Try Again
      </button>
    )}
  </div>
);

function ViewerWithCustomComponents() {
  const documents = [
    { uri: '/docs/document.pdf', fileName: 'Important Document.pdf' }
  ];

  return (
    <DocViewer 
      documents={documents}
      loadingRenderer={CustomLoadingComponent}
      errorRenderer={CustomErrorComponent}
      config={{
        loadingTimeout: 10000,
        errorRetryAttempts: 5
      }}
    />
  );
}

API Reference

DocViewer Props

| Prop | Type | Default | Description | |------|------|---------|-------------| | documents | DocumentSource[] | [] | Array of documents to display | | activeDocument | number | 0 | Index of initially active document | | startIndex | number | 0 | Index to start displaying documents from | | config | ViewerConfig | {} | Viewer configuration options | | events | ViewerEvents | {} | Event handlers | | customRenderers | CustomRenderer[] | [] | Custom renderers for specific file types | | loadingRenderer | ComponentType<LoadingRendererProps> | - | Custom loading component | | errorRenderer | ComponentType<ErrorRendererProps> | - | Custom error component | | className | string | - | CSS class name | | style | React.CSSProperties | - | Inline styles | | zoomLevel | number | - | External zoom control | | onZoomChange | (zoomLevel: number) => number | - | Zoom change handler | | allowScreenshot | boolean | false | Allow screenshot functionality | | loading | boolean | - | External loading state | | showThumbnail | boolean | false | Show document thumbnails | | onLoadingChange | (loading: boolean) => void | - | Loading state change handler |

DocumentSource Interface

interface DocumentSource {
  uri: string;                    // Document URL or path
  fileName?: string;              // Display name for the document
  fileType?: string;              // File type (pdf, docx, pptx, etc.)
  startPage?: number;             // Page to start rendering from (1-based)
  thumbnail?: string;             // Thumbnail image URL
}

ViewerConfig Interface

interface ViewerConfig {
  theme?: 'light' | 'dark' | ViewerTheme;
  showToolbar?: boolean;
  showNavigation?: boolean;
  allowDownload?: boolean;
  allowPrint?: boolean;
  allowFullscreen?: boolean;
  loadingTimeout?: number;
  errorRetryAttempts?: number;
  
  // Layout configuration
  header?: HeaderConfig;
  navigation?: NavigationConfig;
  prerender?: PrerenderConfig;
  
  // Zoom configuration
  initialZoom?: number;
  initialZoomByDevice?: {
    mobile?: number;
    tablet?: number;
    ipad?: number;
    desktop?: number;
    tv?: number;
  };
  zoomStep?: number;
  minZoom?: number;
  maxZoom?: number;
  
  // Responsive configuration
  mobileBreakpoint?: number;
  tabletBreakpoint?: number;
  mobileBehavior?: {
    collapseToolbar?: boolean;
    hideNavigation?: boolean;
    simplifiedControls?: boolean;
  };
  
  // Animation configuration
  animatePageTransition?: boolean;
  transitionDuration?: number;
  zoomAnimationDuration?: number;
  
  // Spacing configuration
  pageGap?: number;
  pagePadding?: number;
  
  // Tooltip configuration
  tooltips?: {
    download?: string;
    zoom?: {
      in?: string;
      out?: string;
      reset?: string;
      current?: string;
    };
    navigation?: {
      previous?: string;
      next?: string;
      pageInfo?: string;
    };
    toolbar?: {
      download?: string;
      print?: string;
      fullscreen?: string;
      customActions?: Record<string, string>;
    };
    error?: {
      retry?: string;
      generic?: string;
    };
  };
}

Custom Renderers

Create custom renderers for specific file types:

import React from 'react';
import { DocViewer, CustomRenderer, RendererProps } from 'jagran-react-doc-viewer';

// Custom PDF renderer with enhanced features
const EnhancedPDFRenderer: React.FC<RendererProps> = ({ 
  document, 
  config, 
  events,
  externalZoom,
  onZoomUpdate 
}) => {
  const [currentPage, setCurrentPage] = React.useState(1);
  const [totalPages, setTotalPages] = React.useState(0);

  return (
    <div className="enhanced-pdf-renderer">
      <div className="pdf-header">
        <h3>Enhanced PDF: {document.fileName}</h3>
        <div className="page-info">
          Page {currentPage} of {totalPages}
        </div>
      </div>
      <div className="pdf-content">
        {/* Your custom PDF rendering logic here */}
        <iframe 
          src={`${document.uri}#page=${document.startPage || 1}`}
          style={{ width: '100%', height: '100%', border: 'none' }}
          title={document.fileName}
        />
      </div>
    </div>
  );
};

const customRenderers: CustomRenderer[] = [
  {
    fileTypes: ['.pdf'],
    component: EnhancedPDFRenderer,
    priority: 1,
    handlesOwnZoom: true
  }
];

<DocViewer 
  documents={documents} 
  customRenderers={customRenderers}
/>

Supported File Types

  • PDF: .pdf - Full-featured PDF rendering with zoom, navigation, and text selection
  • Microsoft Word: .docx - Document rendering with formatting preservation
  • Microsoft PowerPoint: .pptx - Presentation slides with navigation
  • Images: .jpg, .jpeg, .png, .gif, .bmp, .webp - High-quality image display with zoom
  • Text Files: .txt, .md, .json, .xml, .csv, .js, .ts, .css, .html - Syntax-highlighted text rendering

Real-World Integration Examples

Enterprise Document Management System

import React, { useState, useCallback } from 'react';
import { DocViewer, DocumentSource, ViewerEvents } from 'jagran-react-doc-viewer';

function EnterpriseDocumentViewer() {
  const [documents] = useState<DocumentSource[]>([
    {
      uri: '/api/documents/contracts/service-agreement-2024.pdf',
      fileName: 'Service Agreement 2024.pdf',
      fileType: 'pdf',
      startPage: 1
    },
    {
      uri: '/api/documents/reports/quarterly-report-q4.docx',
      fileName: 'Q4 Financial Report.docx',
      fileType: 'docx'
    }
  ]);

  const [auditLog, setAuditLog] = useState<string[]>([]);

  const handleDocumentAccess = useCallback((document: DocumentSource) => {
    const logEntry = `${new Date().toISOString()}: User accessed ${document.fileName}`;
    setAuditLog(prev => [...prev, logEntry]);
    
    // Send to analytics service
    fetch('/api/analytics/document-access', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        documentUri: document.uri,
        fileName: document.fileName,
        timestamp: new Date().toISOString()
      })
    });
  }, []);

  const events: ViewerEvents = {
    onDocumentLoad: handleDocumentAccess,
    onDocumentError: (error, document) => {
      console.error(`Failed to load ${document.fileName}:`, error);
    },
    onPageChange: (page, totalPages) => {
      const progress = (page / totalPages) * 100;
      if (progress > 50) {
        console.log('Document substantially read');
      }
    }
  };

  const enterpriseConfig = {
    theme: 'light' as const,
    showToolbar: true,
    allowDownload: true,
    allowPrint: true,
    
    header: {
      visible: true,
      customContent: (
        <div style={{ display: 'flex', alignItems: 'center', gap: '10px' }}>
          <img src="/company-logo.png" alt="Company" height="30" />
          <span style={{ fontWeight: 'bold' }}>Enterprise Document Viewer</span>
        </div>
      ),
      style: { backgroundColor: '#1e3a8a', color: 'white' }
    },
    
    initialZoomByDevice: {
      mobile: 0.7,
      tablet: 0.9,
      desktop: 1.0
    }
  };

  return (
    <div style={{ height: '100vh' }}>
      <DocViewer 
        documents={documents}
        config={enterpriseConfig}
        events={events}
        className="enterprise-doc-viewer"
      />
    </div>
  );
}

E-Learning Platform Integration

import React, { useState, useEffect } from 'react';
import { DocViewer, DocumentSource } from 'jagran-react-doc-viewer';

function ELearningDocumentViewer() {
  const [lessonDocuments, setLessonDocuments] = useState<DocumentSource[]>([]);
  const [readingProgress, setReadingProgress] = useState<Record<string, number>>({});
  const [completedDocuments, setCompletedDocuments] = useState<Set<string>>(new Set());

  const handlePageChange = (page: number, totalPages: number, documentIndex: number) => {
    const progress = (page / totalPages) * 100;
    const docId = lessonDocuments[documentIndex]?.fileName || '';
    
    setReadingProgress(prev => ({
      ...prev,
      [docId]: progress
    }));

    if (progress > 90) {
      setCompletedDocuments(prev => new Set([...prev, docId]));
    }
  };

  const learningConfig = {
    theme: 'light' as const,
    showToolbar: true,
    allowDownload: true,
    allowPrint: false,
    
    initialZoom: 1.1,
    pageGap: 30,
    pagePadding: 20,
    
    navigation: {
      visible: true,
      position: 'bottom' as const,
      showThumbnails: true,
      thumbnailSize: { width: 100, height: 140 }
    },
    
    animatePageTransition: true,
    transitionDuration: 400
  };

  return (
    <div style={{ height: '100vh', display: 'flex' }}>
      <div style={{ width: '300px', backgroundColor: '#f8fafc', padding: '20px' }}>
        <h3>Lesson Documents</h3>
        {lessonDocuments.map((doc, index) => {
          const progress = readingProgress[doc.fileName || ''] || 0;
          const isCompleted = completedDocuments.has(doc.fileName || '');
          
          return (
            <div key={index} style={{ 
              marginBottom: '15px',
              padding: '10px',
              backgroundColor: 'white',
              borderRadius: '8px',
              border: isCompleted ? '2px solid #10b981' : '1px solid #e2e8f0'
            }}>
              <span style={{ fontWeight: '500' }}>{doc.fileName}</span>
              <div style={{ 
                width: '100%', 
                height: '4px', 
                backgroundColor: '#e5e7eb',
                borderRadius: '2px',
                marginTop: '5px'
              }}>
                <div style={{
                  width: `${progress}%`,
                  height: '100%',
                  backgroundColor: isCompleted ? '#10b981' : '#3b82f6'
                }} />
              </div>
            </div>
          );
        })}
      </div>
      
      <div style={{ flex: 1 }}>
        <DocViewer 
          documents={lessonDocuments}
          config={learningConfig}
          events={{ onPageChange: handlePageChange }}
        />
      </div>
    </div>
  );
}

Performance Optimization

Lazy Loading and Prerendering

import React from 'react';
import { DocViewer } from 'jagran-react-doc-viewer';

function OptimizedViewer() {
  const documents = [
    { uri: '/docs/large-document.pdf', fileName: 'Large Document.pdf' },
    { uri: '/docs/presentation.pptx', fileName: 'Presentation.pptx' }
  ];

  const performanceConfig = {
    // Enable prerendering for better performance
    prerender: {
      enabled: true,
      threshold: 5, // Only prerender files smaller than 5MB
      strategy: 'next' as const // Prerender next document in queue
    },
    
    // Optimize loading
    loadingTimeout: 15000,
    errorRetryAttempts: 2,
    
    // Smooth animations
    animatePageTransition: true,
    transitionDuration: 250,
    zoomAnimationDuration: 150
  };

  return (
    <DocViewer 
      documents={documents}
      config={performanceConfig}
      loading={false} // Control external loading state
      onLoadingChange={(loading) => {
        console.log('Loading state changed:', loading);
      }}
    />
  );
}

Mobile-First Responsive Design

import React from 'react';
import { DocViewer } from 'jagran-react-doc-viewer';

function ResponsiveViewer() {
  const documents = [
    { uri: '/docs/mobile-optimized.pdf', fileName: 'Mobile Document.pdf' }
  ];

  const responsiveConfig = {
    // Device-specific zoom levels
    initialZoomByDevice: {
      mobile: 0.6,    // Smaller zoom for mobile screens
      tablet: 0.8,    // Medium zoom for tablets
      ipad: 0.9,      // Slightly larger for iPads
      desktop: 1.0,   // Standard zoom for desktop
      tv: 1.4         // Larger zoom for TV displays
    },
    
    // Responsive breakpoints
    mobileBreakpoint: 768,
    tabletBreakpoint: 1024,
    
    // Mobile-specific behavior
    mobileBehavior: {
      collapseToolbar: true,      // Collapse toolbar on mobile
      hideNavigation: false,      // Keep navigation visible
      simplifiedControls: true    // Use simplified control set
    },
    
    // Touch-friendly navigation
    navigation: {
      visible: true,
      position: 'bottom' as const,
      style: { 
        padding: '15px',           // Larger touch targets
        fontSize: '16px'           // Readable text size
      }
    }
  };

  return (
    <DocViewer 
      documents={documents}
      config={responsiveConfig}
      style={{ 
        height: '100vh',
        // CSS custom properties for responsive design
        '--mobile-padding': '10px',
        '--desktop-padding': '20px'
      } as React.CSSProperties}
    />
  );
}

Development

Prerequisites

  • Node.js 16+
  • npm or yarn

Setup

# Clone the repository
git clone https://github.com/jnmamp/jagran-pdf-viewer.git
cd jagran-pdf-viewer

# Install dependencies
npm install

# Start development server
npm run dev

# Build for production
npm run build

# Run tests
npm test

# Lint code
npm run lint

# Type checking
npm run type-check

Project Structure

src/
├── components/          # React components
│   ├── DocViewer.tsx   # Main viewer component
│   └── ...             # Other UI components
├── hooks/              # Custom React hooks
├── renderers/          # File type renderers
│   ├── PDFRenderer.tsx
│   ├── ImageRenderer.tsx
│   └── ...
├── styles/             # CSS styles
├── theme/              # Theme definitions
├── types/              # TypeScript type definitions
├── utils/              # Utility functions
└── index.ts            # Main entry point

Building Custom Renderers

import React from 'react';
import { RendererProps } from 'jagran-react-doc-viewer';

// Example: Custom CSV renderer
export const CSVRenderer: React.FC<RendererProps> = ({ 
  document, 
  config,
  loading,
  onLoadingChange 
}) => {
  const [csvData, setCsvData] = React.useState<string[][]>([]);
  const [headers, setHeaders] = React.useState<string[]>([]);

  React.useEffect(() => {
    onLoadingChange?.(true);
    
    fetch(document.uri)
      .then(response => response.text())
      .then(text => {
        const lines = text.split('\n');
        const headers = lines[0].split(',');
        const data = lines.slice(1).map(line => line.split(','));
        
        setHeaders(headers);
        setCsvData(data);
      })
      .finally(() => {
        onLoadingChange?.(false);
      });
  }, [document.uri, onLoadingChange]);

  if (loading) {
    return <div>Loading CSV data...</div>;
  }

  return (
    <div style={{ padding: '20px', overflow: 'auto' }}>
      <h3>{document.fileName}</h3>
      <table style={{ width: '100%', borderCollapse: 'collapse' }}>
        <thead>
          <tr>
            {headers.map((header, index) => (
              <th key={index} style={{ 
                border: '1px solid #ddd', 
                padding: '8px',
                backgroundColor: '#f5f5f5'
              }}>
                {header}
              </th>
            ))}
          </tr>
        </thead>
        <tbody>
          {csvData.map((row, rowIndex) => (
            <tr key={rowIndex}>
              {row.map((cell, cellIndex) => (
                <td key={cellIndex} style={{ 
                  border: '1px solid #ddd', 
                  padding: '8px' 
                }}>
                  {cell}
                </td>
              ))}
            </tr>
          ))}
        </tbody>
      </table>
    </div>
  );
};

// Register the custom renderer
const customRenderers = [
  {
    fileTypes: ['.csv'],
    component: CSVRenderer,
    priority: 1
  }
];

Contributing

  1. Fork the repository
  2. Create your feature branch (git checkout -b feature/amazing-feature)
  3. Commit your changes (git commit -m 'Add some amazing feature')
  4. Push to the branch (git push origin feature/amazing-feature)
  5. Open a Pull Request

License

This project is licensed under the MIT License - see the LICENSE file for details.

Support

Acknowledgments

  • Built with PDF.js for PDF rendering
  • Uses Mammoth.js for DOCX support
  • Inspired by modern document viewers and React best practices