All checks were successful
Automated Container Build / build-and-push (push) Successful in 28s
95 lines
3.7 KiB
TypeScript
95 lines
3.7 KiB
TypeScript
'use client';
|
|
|
|
import { useState } from 'react';
|
|
import { Document, Page, pdfjs } from 'react-pdf';
|
|
import { ChevronLeft, ChevronRight, ZoomIn, ZoomOut, Loader2 } from 'lucide-react';
|
|
import 'react-pdf/dist/Page/AnnotationLayer.css';
|
|
import 'react-pdf/dist/Page/TextLayer.css';
|
|
|
|
if (typeof window !== 'undefined') {
|
|
pdfjs.GlobalWorkerOptions.workerSrc = `//unpkg.com/pdfjs-dist@${pdfjs.version}/build/pdf.worker.min.mjs`;
|
|
}
|
|
|
|
export default function PdfViewer({ url }: { url: string }) {
|
|
const [numPages, setNumPages] = useState<number>();
|
|
const [pageNumber, setPageNumber] = useState<number>(1);
|
|
const [scale, setScale] = useState<number>(1.2);
|
|
|
|
function onDocumentLoadSuccess({ numPages }: { numPages: number }): void {
|
|
setNumPages(numPages);
|
|
}
|
|
|
|
return (
|
|
<div className="flex flex-col w-full h-full">
|
|
{/* PDF Toolbar */}
|
|
<div className="flex items-center justify-between p-2 border-b" style={{ borderColor: 'var(--color-border-subtle)', backgroundColor: 'var(--color-bg-elevated)' }}>
|
|
<div className="flex items-center gap-2">
|
|
<button
|
|
onClick={() => setPageNumber(p => Math.max(1, p - 1))}
|
|
disabled={pageNumber <= 1}
|
|
className="p-1 rounded hover:bg-white/10 disabled:opacity-50"
|
|
style={{ color: 'var(--color-text-primary)' }}
|
|
>
|
|
<ChevronLeft className="w-5 h-5" />
|
|
</button>
|
|
<span className="text-sm font-medium min-w-[80px] text-center" style={{ color: 'var(--color-text-secondary)' }}>
|
|
{numPages ? `${pageNumber} / ${numPages}` : 'Loading...'}
|
|
</span>
|
|
<button
|
|
onClick={() => setPageNumber(p => (numPages ? Math.min(numPages, p + 1) : p))}
|
|
disabled={pageNumber >= (numPages || 1)}
|
|
className="p-1 rounded hover:bg-white/10 disabled:opacity-50"
|
|
style={{ color: 'var(--color-text-primary)' }}
|
|
>
|
|
<ChevronRight className="w-5 h-5" />
|
|
</button>
|
|
</div>
|
|
<div className="flex items-center gap-2">
|
|
<button
|
|
onClick={() => setScale(s => Math.max(0.5, s - 0.25))}
|
|
className="p-1 rounded hover:bg-white/10"
|
|
style={{ color: 'var(--color-text-primary)' }}
|
|
>
|
|
<ZoomOut className="w-5 h-5" />
|
|
</button>
|
|
<span className="text-sm font-medium w-12 text-center" style={{ color: 'var(--color-text-secondary)' }}>
|
|
{Math.round(scale * 100)}%
|
|
</span>
|
|
<button
|
|
onClick={() => setScale(s => Math.min(3, s + 0.25))}
|
|
className="p-1 rounded hover:bg-white/10"
|
|
style={{ color: 'var(--color-text-primary)' }}
|
|
>
|
|
<ZoomIn className="w-5 h-5" />
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
{/* PDF Content */}
|
|
<div className="flex-1 overflow-auto bg-neutral-100 dark:bg-neutral-900 py-4 flex flex-col items-center">
|
|
<Document
|
|
file={url}
|
|
onLoadSuccess={onDocumentLoadSuccess}
|
|
onLoadError={(error) => console.error('Failed to load PDF:', error)}
|
|
loading={
|
|
<div className="flex items-center justify-center h-full mt-20">
|
|
<Loader2 className="w-8 h-8 animate-spin" style={{ color: 'var(--color-accent)' }} />
|
|
</div>
|
|
}
|
|
className="flex flex-col items-center gap-6"
|
|
>
|
|
{numPages && (
|
|
<Page
|
|
key={`page_${pageNumber}`}
|
|
pageNumber={pageNumber}
|
|
scale={scale}
|
|
renderTextLayer={true}
|
|
renderAnnotationLayer={true}
|
|
className="bg-white shadow-xl"
|
|
/>
|
|
)}
|
|
</Document>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|