22
33import "react-pdf/dist/Page/AnnotationLayer.css" ;
44import "react-pdf/dist/Page/TextLayer.css" ;
5- import { useState } from "react" ;
5+ import { useState , useRef , useCallback , useEffect } from "react" ;
66import { Document , Page , pdfjs } from "react-pdf" ;
77import { Download , ZoomIn , ZoomOut } from "lucide-react" ;
88import { Button } from "./ui/button" ;
@@ -24,51 +24,98 @@ interface PdfViewerProps {
2424export 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