126 lines
4.8 KiB
TypeScript
126 lines
4.8 KiB
TypeScript
import { type FC, useRef, useCallback, useState } from 'react';
|
||
import HTMLFlipBook from 'react-pageflip';
|
||
import { type PageData } from '../types';
|
||
import BookPage from './BookPage';
|
||
import { ArrowLeft2, ArrowRight2 } from 'iconsax-react';
|
||
|
||
type BookViewerProps = {
|
||
pages: PageData[];
|
||
};
|
||
|
||
interface PageFlipAPI {
|
||
flipNext: () => void;
|
||
flipPrev: () => void;
|
||
}
|
||
|
||
interface FlipBookInstance {
|
||
pageFlip: () => PageFlipAPI | undefined;
|
||
}
|
||
|
||
interface FlipEvent {
|
||
data: number;
|
||
}
|
||
|
||
/**
|
||
* کامپوننت اصلی کتابخوان با استفاده از react-pageflip
|
||
* این کامپوننت از HTMLFlipBook استفاده میکند که یک wrapper برای PageFlip است
|
||
* از HTML برای رندر صفحات استفاده میشود (نه Canvas) برای سازگاری با Konva در آینده
|
||
*/
|
||
const BookViewer: FC<BookViewerProps> = ({ pages }) => {
|
||
const bookRef = useRef<FlipBookInstance | null>(null);
|
||
const [currentPage, setCurrentPage] = useState(0);
|
||
|
||
const handlePageFlip = useCallback((e: FlipEvent) => {
|
||
const pageNum = e.data;
|
||
setCurrentPage(pageNum);
|
||
}, []);
|
||
|
||
const goToNextPage = useCallback(() => {
|
||
bookRef.current?.pageFlip()?.flipNext();
|
||
}, []);
|
||
|
||
const goToPrevPage = useCallback(() => {
|
||
bookRef.current?.pageFlip()?.flipPrev();
|
||
}, []);
|
||
|
||
return (
|
||
<div className="flex flex-col items-center gap-4 md:gap-6 w-full h-full" dir="rtl">
|
||
{/* نمایش کتاب */}
|
||
<div className="relative w-full flex justify-center flex-1 items-center">
|
||
{/* Border و Shadow عمودی در وسط کتاب */}
|
||
<div
|
||
className="absolute inset-y-0 left-1/2 -translate-x-1/2 w-px border-r border-gray-300 pointer-events-none z-50"
|
||
style={{
|
||
boxShadow: '-5px 0 15px rgba(0, 0, 0, 0.15), 5px 0 15px rgba(0, 0, 0, 0.15)'
|
||
}}
|
||
/>
|
||
<HTMLFlipBook
|
||
ref={bookRef}
|
||
width={794}
|
||
height={1123}
|
||
size="stretch"
|
||
minWidth={315}
|
||
minHeight={400}
|
||
maxWidth={1000}
|
||
maxHeight={1533}
|
||
drawShadow={true}
|
||
flippingTime={800}
|
||
usePortrait={true}
|
||
startPage={0}
|
||
autoSize={true}
|
||
maxShadowOpacity={0.5}
|
||
showCover={true}
|
||
mobileScrollSupport={true}
|
||
clickEventForward={true}
|
||
useMouseEvents={true}
|
||
swipeDistance={30}
|
||
startZIndex={1}
|
||
showPageCorners={true}
|
||
disableFlipByClick={false}
|
||
className="flipbook-container mx-auto"
|
||
style={{
|
||
direction: 'rtl',
|
||
}}
|
||
onFlip={handlePageFlip}
|
||
>
|
||
{pages.map((page) => (
|
||
<BookPage key={page.id} page={page} />
|
||
))}
|
||
</HTMLFlipBook>
|
||
</div>
|
||
|
||
{/* کنترلهای ناوبری */}
|
||
<div className="flex items-center gap-3 md:gap-6 bg-white rounded-full px-4 md:px-6 py-2 md:py-3 shadow-lg">
|
||
{/* دکمه صفحه بعد (در RTL، صفحه سمت چپ) */}
|
||
<button
|
||
onClick={goToNextPage}
|
||
disabled={currentPage <= 0}
|
||
className="p-2 md:p-2 rounded-full hover:bg-gray-100 disabled:opacity-30 disabled:cursor-not-allowed transition-all"
|
||
aria-label="صفحه بعد"
|
||
>
|
||
<ArrowLeft2 size={20} className="text-gray-700 md:w-6 md:h-6" />
|
||
</button>
|
||
|
||
{/* شماره صفحه */}
|
||
<div className="flex items-center gap-2 min-w-[100px] md:min-w-[120px] justify-center">
|
||
<span className="text-xs md:text-sm text-gray-600">
|
||
صفحه {currentPage + 1} از {pages.length}
|
||
</span>
|
||
</div>
|
||
|
||
{/* دکمه صفحه قبل (در RTL، صفحه سمت راست) */}
|
||
<button
|
||
onClick={goToPrevPage}
|
||
disabled={currentPage >= pages.length - 1}
|
||
className="p-2 md:p-2 rounded-full hover:bg-gray-100 disabled:opacity-30 disabled:cursor-not-allowed transition-all"
|
||
aria-label="صفحه قبل"
|
||
>
|
||
<ArrowRight2 size={20} className="text-gray-700 md:w-6 md:h-6" />
|
||
</button>
|
||
</div>
|
||
</div>
|
||
);
|
||
};
|
||
|
||
export default BookViewer;
|