Skip to content

Commit 0c56e46

Browse files
committed
fix: pdf viewer shows all pages in 1 scroll
1 parent cbbce91 commit 0c56e46

File tree

1 file changed

+82
-30
lines changed

1 file changed

+82
-30
lines changed

src/components/pdfViewer.tsx

Lines changed: 82 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
import "react-pdf/dist/Page/AnnotationLayer.css";
44
import "react-pdf/dist/Page/TextLayer.css";
5-
import { useState } from "react";
5+
import { useState, useRef, useCallback, useEffect } from "react";
66
import { Document, Page, pdfjs } from "react-pdf";
77
import { Download, ZoomIn, ZoomOut } from "lucide-react";
88
import { Button } from "./ui/button";
@@ -24,51 +24,98 @@ interface PdfViewerProps {
2424
export default function PdfViewer({ url, name }: PdfViewerProps) {
2525
const [numPages, setNumPages] = useState<number>();
2626
const [pageNumber, setPageNumber] = useState<number>(1);
27-
const [scale, setScale] = useState<number>(1); // Default zoom level (100%)
27+
const [scale, setScale] = useState<number>(1);
28+
const pageRefs = useRef<(HTMLDivElement | null)[]>([]);
29+
const containerRef = useRef<HTMLDivElement>(null);
2830

29-
// Handle document load success
3031
function onDocumentLoadSuccess({ numPages }: { numPages: number }): void {
3132
setNumPages(numPages);
32-
setPageNumber(1); // Reset to page 1 when new document loads
33+
setPageNumber(1);
34+
pageRefs.current = Array(numPages).fill(null) as (HTMLDivElement | null)[];
3335
}
3436

35-
// Navigate to previous page
37+
const scrollToPage = useCallback((page: number) => {
38+
if (pageRefs.current[page - 1] && containerRef.current) {
39+
const pageElement = pageRefs.current[page - 1];
40+
const container = containerRef.current;
41+
if (pageElement) {
42+
const offset = pageElement.offsetTop - container.offsetTop;
43+
container.scrollTo({ top: offset, behavior: "smooth" });
44+
setPageNumber(page);
45+
}
46+
}
47+
}, []);
48+
49+
const handleScroll = useCallback(() => {
50+
if (!containerRef.current || !pageRefs.current) return;
51+
const container = containerRef.current;
52+
const scrollTop = container.scrollTop + container.offsetTop;
53+
54+
for (let i = 0; i < pageRefs.current.length; i++) {
55+
const pageEl = pageRefs.current[i];
56+
if (pageEl) {
57+
const pageTop = pageEl.offsetTop;
58+
const pageBottom = pageTop + pageEl.offsetHeight;
59+
if (scrollTop >= pageTop && scrollTop < pageBottom) {
60+
setPageNumber(i + 1);
61+
break;
62+
}
63+
}
64+
}
65+
}, []);
66+
67+
useEffect(() => {
68+
const container = containerRef.current;
69+
if (container) {
70+
container.addEventListener("scroll", handleScroll);
71+
return () => container.removeEventListener("scroll", handleScroll);
72+
}
73+
}, [handleScroll]);
74+
3675
const goToPreviousPage = () => {
37-
setPageNumber((prev) => Math.max(1, prev - 1));
76+
setPageNumber((prev) => {
77+
const newPage = Math.max(1, prev - 1);
78+
scrollToPage(newPage);
79+
return newPage;
80+
});
3881
};
3982

40-
// Navigate to next page
4183
const goToNextPage = () => {
42-
setPageNumber((prev) => Math.min(numPages ?? 1, prev + 1));
84+
setPageNumber((prev) => {
85+
const newPage = Math.min(numPages ?? 1, prev + 1);
86+
scrollToPage(newPage);
87+
return newPage;
88+
});
4389
};
4490

45-
// Handle page number input change
4691
const handlePageChange = (e: React.ChangeEvent<HTMLInputElement>) => {
4792
const value = parseInt(e.target.value, 10);
4893
if (!isNaN(value) && value >= 1 && value <= (numPages ?? 1)) {
4994
setPageNumber(value);
95+
scrollToPage(value);
5096
}
5197
};
5298

53-
// Zoom in (increase scale)
5499
const zoomIn = () => {
55-
setScale((prev) => Math.min(prev + 0.25, 3)); // Max scale: 300%
100+
setScale((prev) => Math.min(prev + 0.25, 3));
56101
};
57102

58-
// Zoom out (decrease scale)
59103
const zoomOut = () => {
60-
setScale((prev) => Math.max(prev - 0.25, 0.25)); // Min scale: 25%
104+
setScale((prev) => Math.max(prev - 0.25, 0.25));
61105
};
106+
62107
const downloadPDF = async () => {
63108
const fileName = `${name}.pdf`;
64109
await downloadFile(url, fileName);
65110
};
111+
66112
return (
67113
<div className="flex flex-col items-center">
68-
{/* PDF Document */}
69-
<div className="max-h-[70vh] overflow-auto border border-gray-300 shadow-lg">
114+
<div
115+
ref={containerRef}
116+
className="max-h-[70vh] overflow-auto border border-gray-300 shadow-lg"
117+
>
70118
<Document
71-
className="flex justify-center"
72119
file={url}
73120
onLoadSuccess={onDocumentLoadSuccess}
74121
error={
@@ -83,20 +130,27 @@ export default function PdfViewer({ url, name }: PdfViewerProps) {
83130
<div className="p-4 text-gray-500">No PDF file specified.</div>
84131
}
85132
>
86-
<Page
87-
pageNumber={pageNumber}
88-
scale={scale}
89-
renderAnnotationLayer={true}
90-
renderTextLayer={true}
91-
className="w-max-[75vw] shadow-md"
92-
/>
133+
{numPages &&
134+
Array.from({ length: numPages }, (_, index) => (
135+
<div
136+
key={`page_${index + 1}`}
137+
ref={(el) => {
138+
pageRefs.current[index] = el;
139+
}}
140+
>
141+
<Page
142+
pageNumber={index + 1}
143+
scale={scale}
144+
renderAnnotationLayer={true}
145+
renderTextLayer={true}
146+
className="w-max-[75vw] mb-4 shadow-md"
147+
/>
148+
</div>
149+
))}
93150
</Document>
94151
</div>
95152

96-
{/* Controls */}
97153
<div className="mt-4 flex flex-col items-center gap-4 rounded-lg bg-[#262635] p-4 shadow sm:flex-row">
98-
{/* Page Navigation */}
99-
100154
<div className="flex items-center gap-2">
101155
<Button
102156
onClick={goToPreviousPage}
@@ -111,7 +165,7 @@ export default function PdfViewer({ url, name }: PdfViewerProps) {
111165
onChange={handlePageChange}
112166
min={1}
113167
max={numPages}
114-
className="h-10 w-16 rounded border p-1 text-center"
168+
className="h-10 w-16 rounded border p-1 text-center [appearance:textfield] [&::-webkit-inner-spin-button]:appearance-none [&::-webkit-outer-spin-button]:appearance-none"
115169
/>
116170
<span>of {numPages ?? 1}</span>
117171
<Button
@@ -123,9 +177,7 @@ export default function PdfViewer({ url, name }: PdfViewerProps) {
123177
</Button>
124178
</div>
125179

126-
{/* Zoom Controls */}
127180
<div className="flex items-center gap-2">
128-
{" "}
129181
<Button
130182
onClick={zoomOut}
131183
disabled={scale <= 0.25}
@@ -139,7 +191,7 @@ export default function PdfViewer({ url, name }: PdfViewerProps) {
139191
disabled={scale >= 3}
140192
className="h-10 w-10 rounded p-0 text-white transition hover:bg-[#6536c1] disabled:bg-gray-300"
141193
>
142-
{<ZoomIn />}
194+
<ZoomIn />
143195
</Button>
144196
<ShareButton />
145197
<Button onClick={downloadPDF} className="aspect-square h-10 w-10 p-0">

0 commit comments

Comments
 (0)