jagran-react-doc-viewer
Version:
A highly customizable React document viewer with support for PDF, DOCX, PPTX, images, and more
1,070 lines (922 loc) ⢠29.1 kB
Markdown
# 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
```bash
npm install jagran-react-doc-viewer
```
```bash
yarn add jagran-react-doc-viewer
```
## Quick Start
```tsx
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
```tsx
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
```tsx
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
```tsx
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
```tsx
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
```tsx
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
```tsx
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
```tsx
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:
```tsx
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
```tsx
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
```tsx
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
```tsx
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
```tsx
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
```bash
# 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
```tsx
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](LICENSE) file for details.
## Support
- š§ **Issues**: [GitHub Issues](https://github.com/jnmamp/jagran-pdf-viewer/issues)
- š **Documentation**: [GitHub Wiki](https://github.com/jnmamp/jagran-pdf-viewer/wiki)
## Acknowledgments
- Built with [PDF.js](https://mozilla.github.io/pdf.js/) for PDF rendering
- Uses [Mammoth.js](https://github.com/mwilliamson/mammoth.js) for DOCX support
- Inspired by modern document viewers and React best practices