UNPKG

@vvelediaz/react-pdf-viewer

Version:

A modern, lightweight React PDF viewer component featuring zoom controls, page navigation, and responsive design

538 lines (418 loc) 14.7 kB
# @vvelediaz/react-pdf-viewer A modern, feature-rich React PDF viewer component with a classic Mac OS X Aqua interface. Perfect for desktop and web applications requiring elegant PDF display capabilities with zero configuration hassles. [![npm](https://img.shields.io/npm/v/@vvelediaz/react-pdf-viewer)](https://www.npmjs.com/package/@vvelediaz/react-pdf-viewer) ## ✨ Features - 📄 **Universal PDF Support** - Display PDFs from URLs, File objects, or ArrayBuffers - 🔍 **Advanced Zoom Controls** - Zoom from 50% to 300% with smooth scaling - 🖥️ **Classic Mac OS X Aqua Design** - Authentic Aqua interface with brushed metal toolbars - ⬅️➡️ **Page Navigation** - Intuitive page controls with jump-to-page functionality - 📜 **Multiple View Modes** - Single page or continuous scroll viewing -**High Performance** - Efficient rendering with react-pdf - 🎨 **Beautiful UI** - Classic Mac interface with proper gradients, shadows, and typography - 🔧 **TypeScript Support** - Fully typed for better development experience - 🌐 **Cross Platform** - Works on all modern browsers and desktop applications - ⚛️ **React 19 Ready** - Fully compatible with React 18 and 19 - 🎯 **Minimal Dependencies** - Only essential dependencies for maximum compatibility - 🚫 **Zero CORS Issues** - Local worker files eliminate cross-origin problems - 🔄 **Auto-Configuration** - Automatic PDF.js worker setup with version matching ## 📦 Installation ```bash # npm npm install @vvelediaz/react-pdf-viewer # Yarn yarn add @vvelediaz/react-pdf-viewer # pnpm pnpm add @vvelediaz/react-pdf-viewer # Bun bun add @vvelediaz/react-pdf-viewer ``` ## ⚡ Quick Setup After installation, you need to set up the PDF.js worker file. Choose one of these methods: ### Method 1: Copy Worker File (Recommended) ```bash # Copy the PDF worker to your public directory cp node_modules/@vvelediaz/react-pdf-viewer/public/pdf.worker.min.js public/ ``` ### Method 2: Use setupPDFJS() Function ```tsx import { setupPDFJS } from '@vvelediaz/react-pdf-viewer' // Call once in your app's entry point (main.tsx, index.tsx, or App.tsx) setupPDFJS() // Uses CDN fallback automatically ``` ### Method 3: Custom Worker Path ```tsx import { setupPDFJS } from '@vvelediaz/react-pdf-viewer' // Use a custom worker path setupPDFJS('https://your-cdn.com/pdf.worker.min.js') ``` ## 🚀 Quick Start ```tsx import React from 'react' import { PDFViewer } from '@vvelediaz/react-pdf-viewer' // Required CSS imports import 'react-pdf/dist/Page/AnnotationLayer.css' import 'react-pdf/dist/Page/TextLayer.css' // Copy PDFViewer.css from the GitHub repo to your project function MyApp() { const handleLoadSuccess = (pdf) => { console.log('PDF loaded with', pdf.numPages, 'pages') } const handlePageChange = (pageNumber) => { console.log('Current page:', pageNumber) } return ( <PDFViewer file="/path/to/document.pdf" width="100%" height="600px" onLoadSuccess={handleLoadSuccess} onPageChange={handlePageChange} scrollMode="continuous" initialZoom={1.2} /> ) } ``` ## 🛠️ Setup & Configuration ### Automatic PDF.js Configuration The component automatically handles PDF.js worker setup with **zero configuration required**: -**Auto-detects** correct worker version -**Eliminates CORS issues** with local worker files -**Matches API versions** automatically -**No manual setup** needed ### Manual Configuration (Optional) If you need custom worker configuration: ```tsx import { setupPDFJS } from '@vvelediaz/react-pdf-viewer' // Call once in your app's entry point setupPDFJS() ``` ### Bundler Configuration #### Vite (Recommended) Add to your `vite.config.ts`: ```typescript import { defineConfig } from 'vite' import react from '@vitejs/plugin-react' export default defineConfig({ plugins: [react()], optimizeDeps: { include: ['react-pdf', 'pdfjs-dist'], }, }) ``` #### Next.js Add to your `next.config.js`: ```javascript /** @type {import('next').NextConfig} */ const nextConfig = { transpilePackages: ['react-pdf'], webpack: (config) => { config.resolve.alias.canvas = false return config }, } module.exports = nextConfig ``` #### Webpack ```javascript module.exports = { resolve: { alias: { canvas: false, }, }, module: { rules: [ { test: /\.mjs$/, include: /node_modules/, type: 'javascript/auto', }, ], }, } ``` ## 🔧 Troubleshooting ### Common Issues & Solutions #### ✅ "Worker File Not Found (404)" **Solution**: Copy the worker file to your public directory: ```bash cp node_modules/@vvelediaz/react-pdf-viewer/public/pdf.worker.min.js public/ ``` #### ✅ "Failed to fetch dynamically imported module" **Solution**: Use the setupPDFJS() function with CDN fallback: ```tsx import { setupPDFJS } from '@vvelediaz/react-pdf-viewer' setupPDFJS() // Automatically uses CDN if local file not found ``` #### ✅ "MIME type errors" **Solution**: Ensure your server serves .js files with correct MIME type, or use CDN: ```tsx setupPDFJS('https://unpkg.com/pdfjs-dist@4.8.69/build/pdf.worker.min.js') ``` #### ✅ "No CORS Errors" **Fixed**: Component uses local worker files with CDN fallback automatically. #### ✅ "No Version Mismatches" **Fixed**: Automatic version matching between react-pdf and PDF.js worker. #### ✅ "No Configuration Required" **Fixed**: Zero-config setup works out of the box with automatic fallbacks. #### ✅ "No React Externalization Errors" **Fixed**: Package now ships as compiled JavaScript instead of raw TypeScript, eliminating Vite externalization issues. **Previous Error**: `Module "npm:react@^18.2.0" has been externalized for browser compatibility` **Solution**: The package is now properly built as a library with React as an external dependency. #### PDF Not Loading **Solutions**: 1. Check file path/URL is correct 2. Verify file is a valid PDF 3. Check browser console for specific errors 4. Ensure required CSS files are imported #### Memory Issues with Large PDFs **Solutions**: - Use `scrollMode="page"` for very large documents - Implement lazy loading for multiple PDFs - Consider using lower `initialZoom` values #### TypeScript Errors **Solutions**: - Ensure `@types/react` and `@types/react-dom` are installed - Import types: `import type { PDFViewerProps } from '@vvelediaz/react-pdf-viewer/types'` ### Browser Compatibility Works in all modern browsers that support: - ✅ ES2018+ features - ✅ Web Workers - ✅ ArrayBuffer and Blob APIs - ✅ CSS Grid and Flexbox **Tested Browsers**: - Chrome 90+ - Firefox 88+ - Safari 14+ - Edge 90+ ## 📚 API Reference ### PDFViewer Props | Prop | Type | Default | Description | |------|------|---------|-------------| | `file` | `string \| File \| ArrayBuffer` | **Required** | PDF source - URL, File object, or ArrayBuffer | | `width` | `string \| number` | `'100%'` | Component width | | `height` | `string \| number` | `'600px'` | Component height | | `onLoadSuccess` | `(pdf: PDFDocumentProxy) => void` | - | Called when PDF loads successfully | | `onLoadError` | `(error: Error) => void` | - | Called when PDF fails to load | | `onPageChange` | `(pageNumber: number) => void` | - | Called when current page changes | | `onZoomChange` | `(scale: number) => void` | - | Called when zoom level changes | | `className` | `string` | `''` | Additional CSS classes | | `initialPage` | `number` | `1` | Starting page number | | `initialZoom` | `number` | `1.0` | Starting zoom level (0.5 - 3.0) | | `scrollMode` | `'page' \| 'continuous'` | `'page'` | View mode for PDF display | ### Utility Functions ```tsx import { setupPDFJS } from '@vvelediaz/react-pdf-viewer' // Configure PDF.js worker (call once in your app) setupPDFJS() ``` ### Types ```tsx import type { PDFViewerProps, PDFDocumentProxy, PDFPageProxy, PDFLoadSuccess, PDFError } from '@vvelediaz/react-pdf-viewer/types' ``` ## 🛠️ Advanced Usage ### File Upload with Validation ```tsx import React, { useState } from 'react' import { PDFViewer } from '@vvelediaz/react-pdf-viewer' function PDFUploader() { const [pdfFile, setPdfFile] = useState<File | null>(null) const [error, setError] = useState<string | null>(null) const handleFileChange = (event: React.ChangeEvent<HTMLInputElement>) => { const file = event.target.files?.[0] if (!file) return // Validate file type if (!file.type.includes('pdf') && !file.name.toLowerCase().endsWith('.pdf')) { setError('Please select a valid PDF file') return } // Validate file size (10MB limit) if (file.size > 10 * 1024 * 1024) { setError('File size must be less than 10MB') return } setError(null) setPdfFile(file) } return ( <div> <input type="file" accept=".pdf,application/pdf" onChange={handleFileChange} /> {error && <div className="error">{error}</div>} {pdfFile && ( <PDFViewer file={pdfFile} height="500px" scrollMode="continuous" onLoadError={(err) => setError(err.message)} /> )} </div> ) } ``` ### Custom Error Handling & Loading States ```tsx import React, { useState } from 'react' import { PDFViewer } from '@vvelediaz/react-pdf-viewer' function PDFWithStates() { const [loading, setLoading] = useState(true) const [error, setError] = useState<string | null>(null) const [pageCount, setPageCount] = useState(0) const handleLoadSuccess = (pdf) => { setPageCount(pdf.numPages) setLoading(false) setError(null) } const handleLoadError = (err: Error) => { setError(`Failed to load PDF: ${err.message}`) setLoading(false) } return ( <div> {loading && <div className="loading">Loading PDF...</div>} {error && ( <div className="error"> <h3>Error</h3> <p>{error}</p> <button onClick={() => { setError(null) setLoading(true) }}> Retry </button> </div> )} {!loading && !error && ( <div className="pdf-info"> Document loaded successfully ({pageCount} pages) </div> )} <PDFViewer file="/path/to/document.pdf" onLoadSuccess={handleLoadSuccess} onLoadError={handleLoadError} /> </div> ) } ``` ## 🎨 Styling ### Classic Mac OS X Aqua Interface The component features an authentic classic Mac OS X Aqua design: - **🖥️ Brushed Metal Toolbars** - Authentic metal texture with subtle striped patterns - **🔵 Classic Aqua Buttons** - Proper gradients, shadows, and hover effects - **📜 Blue Aqua Scrollbars** - Traditional Mac scrollbar styling - **📝 Lucida Grande Typography** - System font matching the classic Mac experience ### Required CSS Imports ```tsx // Essential react-pdf styles import 'react-pdf/dist/Page/AnnotationLayer.css' import 'react-pdf/dist/Page/TextLayer.css' // Component styles (copy from GitHub repo) import './PDFViewer.css' ``` ### Custom Styling ```css /* Override default styles */ .pdf-viewer { --aqua-blue: #007AFF; --metal-bg: linear-gradient(to bottom, #E8E8E8, #D0D0D0); --button-gradient: linear-gradient(to bottom, #F8F8F8, #E0E0E0); } /* Custom scrollbar colors */ .pdf-content::-webkit-scrollbar-thumb { background: linear-gradient(to bottom, #FF6B6B, #FF5252) !important; } ``` ### Multiple PDF Viewer with Tabs ```tsx import React, { useState } from 'react' import { PDFViewer } from '@vvelediaz/react-pdf-viewer' function MultiPDFViewer() { const documents = [ { id: 1, name: 'Document 1', url: '/doc1.pdf' }, { id: 2, name: 'Document 2', url: '/doc2.pdf' }, { id: 3, name: 'Document 3', url: '/doc3.pdf' }, ] const [activeDoc, setActiveDoc] = useState(documents[0]) return ( <div> <div className="tabs"> {documents.map(doc => ( <button key={doc.id} className={activeDoc.id === doc.id ? 'active' : ''} onClick={() => setActiveDoc(doc)} > {doc.name} </button> ))} </div> <PDFViewer key={activeDoc.id} // Force re-render when switching docs file={activeDoc.url} height="600px" scrollMode="page" /> </div> ) } ``` ## 📱 Platform Support - **✅ Web Browsers** - Chrome, Firefox, Safari, Edge (latest versions) - **✅ Desktop Apps** - Electron, Tauri, and other desktop frameworks - **✅ Mobile Web** - iOS Safari, Android Chrome (responsive design) - **✅ PWA** - Progressive Web Applications - **✅ Server-Side** - Next.js, Remix (with proper configuration) ## 🔧 Requirements - **React** 18+ or 19+ - **Modern Browser** with PDF.js support - **No additional UI frameworks** required - **TypeScript** 5+ (optional but recommended) ## 📈 Performance Tips 1. **Use `scrollMode="page"`** for large documents (>50 pages) 2. **Implement lazy loading** when displaying multiple PDFs 3. **Set appropriate `initialZoom`** based on your use case 4. **Use `React.memo`** for wrapper components to avoid unnecessary re-renders 5. **Preload critical PDFs** using `<link rel="preload">` in your HTML ## 🤝 Contributing We welcome contributions! Please see our [Contributing Guide](CONTRIBUTING.md) for details. ### Development Setup ```bash # Clone the repository git clone https://github.com/yourusername/react-pdf-viewer.git # Install dependencies bun install # Start development server (auto-copies worker files) bun run dev # Run type checking bun run type-check # Build for production bun run build ``` ## 📄 License MIT License - see the [LICENSE](LICENSE) file for details. ## 🔗 Links - **[npm Package](https://www.npmjs.com/package/@vvelediaz/react-pdf-viewer)** - Official package registry - **[GitHub Repository](https://github.com/yourusername/react-pdf-viewer)** - Source code and issues - **[React PDF](https://github.com/wojtekmaj/react-pdf)** - Underlying PDF rendering library - **[PDF.js](https://mozilla.github.io/pdf.js/)** - Mozilla's PDF rendering engine ## 🎯 Roadmap - [ ] **Dark mode support** for Aqua interface - [ ] **Annotation tools** (highlight, comments) - [ ] **Search functionality** within PDFs - [ ] **Thumbnail navigation** sidebar - [ ] **Print support** with custom styling - [ ] **Accessibility improvements** (ARIA labels, keyboard navigation) - [ ] **Mobile gestures** (pinch to zoom, swipe navigation)