react-pdf-flipbook-viewer
Version:
A customizable React component to render PDF documents in a flipbook-style viewer — perfect for brochures, magazines, and interactive documents. ## Features
95 lines (94 loc) • 6.42 kB
JavaScript
import { useEffect, useCallback } from 'react';
import { Button } from '../../button'; // Assuming this path is correct
import { ChevronLeft, ChevronRight, Maximize, Minimize } from 'lucide-react';
import Zoom from './zoom'; // Assuming this component is compact
import SliderNav from './slider-nav/slider-nav'; // Assuming this component is compact or its height is managed
import useScreenSize from './../../../hooks/use-screensize';
import Share from '../../share'; // Uses SimpleDropdown with z-index: 50
import React from 'react';
const Toolbar = ({ flipbookRef, containerRef, screenfull, pdfDetails, viewerStates, shareUrl, disableShare, setViewerStates }) => {
const { width: screenWidth } = useScreenSize();
// Determine current page display string
const isTwoPageSpread = viewerStates.currentPageIndex > 0 && // Not the very first page (which is often a single cover)
(viewerStates.currentPageIndex + 1) < pdfDetails.totalPages && // Not the very last page if it's single
screenWidth >= 768; // Example: Consider two-page spread only on larger screens
let pagesInFlipViewDisplay;
if (isTwoPageSpread && viewerStates.currentPageIndex % 2 !== 0) { // If current page index is odd, it's the left page of a spread
pagesInFlipViewDisplay = `${viewerStates.currentPageIndex + 1}-${viewerStates.currentPageIndex + 2}`;
}
else {
pagesInFlipViewDisplay = `${viewerStates.currentPageIndex + 1}`;
}
// Full screen toggle
const fullScreen = useCallback(() => {
if (screenfull && screenfull.isEnabled && containerRef.current) {
screenfull.toggle(containerRef.current, { navigationUI: "hide" }).catch((err) => {
// More user-friendly error handling could be implemented here
console.error('Failed to toggle fullscreen:', err);
alert('Failed to enable fullscreen. Your browser might not support it or it was blocked.');
});
}
else if (screenfull && !screenfull.isEnabled) {
alert('Fullscreen is not available in this browser or context.');
}
}, [screenfull, containerRef]);
// Keyboard shortcuts for navigation
useEffect(() => {
const handleKeyDown = (event) => {
if (!flipbookRef.current || !flipbookRef.current.pageFlip)
return;
if (event.key === 'ArrowRight') {
event.preventDefault();
flipbookRef.current.pageFlip().flipNext();
}
else if (event.key === 'ArrowLeft') {
event.preventDefault();
flipbookRef.current.pageFlip().flipPrev();
}
};
document.addEventListener('keydown', handleKeyDown);
return () => {
document.removeEventListener('keydown', handleKeyDown);
};
}, [flipbookRef]); // Dependency: flipbookRef
const canFlipPrev = viewerStates.currentPageIndex > 0;
const canFlipNext = viewerStates.currentPageIndex < pdfDetails.totalPages - 1;
return (React.createElement("div", { className: "fixed bottom-0 left-0 right-0 w-full bg-background shadow-lg z-40 p-2 print:hidden" },
React.createElement("div", { className: "mb-1" },
React.createElement(SliderNav, { flipbookRef: flipbookRef, pdfDetails: pdfDetails, viewerStates: viewerStates, setViewerStates: setViewerStates })),
React.createElement("div", { className: "flex items-center justify-between gap-x-1 sm:gap-x-2" },
React.createElement("div", { className: "hidden md:flex items-center flex-shrink-0 min-w-[100px]" },
" ",
pdfDetails?.totalPages > 0 && (React.createElement("p", { className: 'text-xs font-medium text-muted-foreground whitespace-nowrap' },
"Page ",
pagesInFlipViewDisplay,
" of ",
pdfDetails?.totalPages))),
React.createElement("div", { className: "md:hidden flex-shrink-0 min-w-[100px]" }),
React.createElement("div", { className: "flex-grow flex items-center justify-center gap-1 sm:gap-2" },
React.createElement(Button, { onClick: () => {
if (flipbookRef.current?.pageFlip) {
screenWidth < 768 ? flipbookRef.current.pageFlip().turnToPrevPage() : flipbookRef.current.pageFlip().flipPrev();
}
}, disabled: !canFlipPrev, variant: 'secondary', size: 'icon', className: 'size-8 min-w-8', "aria-label": "Previous page" },
React.createElement(ChevronLeft, { className: "size-4 min-w-4" })),
React.createElement("div", { className: "md:hidden" }, pdfDetails?.totalPages > 0 && (React.createElement("p", { className: 'text-xs font-medium text-muted-foreground whitespace-nowrap' },
pagesInFlipViewDisplay,
"/",
pdfDetails?.totalPages))),
React.createElement(Button, { onClick: () => {
if (flipbookRef.current?.pageFlip) {
screenWidth < 768 ? flipbookRef.current.pageFlip().turnToNextPage() : flipbookRef.current.pageFlip().flipNext();
}
}, disabled: !canFlipNext, variant: 'secondary', size: 'icon', className: 'size-8 min-w-8', "aria-label": "Next page" },
React.createElement(ChevronRight, { className: "size-4 min-w-4" }))),
React.createElement("div", { className: "flex items-center flex-shrink-0 justify-end gap-1 sm:gap-2 min-w-[100px]" },
" ",
React.createElement(Zoom, { zoomScale: viewerStates.zoomScale, screenWidth: screenWidth }),
!disableShare && (React.createElement(Share, { shareUrl: shareUrl })),
screenfull && screenfull.isEnabled && ( // Only show button if screenfull is available
React.createElement(Button, { onClick: fullScreen, variant: 'secondary', size: 'icon', className: 'size-8 min-w-8', "aria-label": screenfull.isFullscreen ? "Exit fullscreen" : "Enter fullscreen" }, screenfull.isFullscreen ?
React.createElement(Minimize, { className: "size-4 min-w-4" }) :
React.createElement(Maximize, { className: "size-4 min-w-4" })))))));
};
export default Toolbar;