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
100 lines (99 loc) • 5.94 kB
JavaScript
import { cn } from "./../../../../lib/utils";
import { useState, useEffect, useRef } from 'react';
import Draggable from 'react-draggable';
import HoverItem from './hover-item';
import { useDebounce } from './../../../../hooks/use-debounce';
import React from "react";
const Slider = ({ maxSlide = 10, currentSlide, onSlideChange, totalPages }) => {
const [value, setValue] = useState(1);
const [hoverValue, setHoverValue] = useState(0);
const [thumbPosition, setThumbPosition] = useState(0);
const [dragging, setDragging] = useState(false);
const [tooltipPosition, setTooltipPosition] = useState({ left: 0, top: 0 });
const sliderRef = useRef(null);
const tooltipRef = useRef(null);
const nodeRef = useRef(null);
// Update thumb position on value & screen size change >>>>>>>>>
useEffect(() => {
const updateThumbPosition = () => {
if (sliderRef.current && !dragging) {
const sliderRect = sliderRef.current.getBoundingClientRect();
const sliderWidth = sliderRect.width;
const newPosition = ((value - 1) / (maxSlide - 1)) * sliderWidth;
setThumbPosition(newPosition);
}
};
updateThumbPosition();
window.addEventListener('resize', updateThumbPosition);
return () => window.removeEventListener('resize', updateThumbPosition);
}, [value, maxSlide, dragging]);
// Handle dragging the thumb >>>>>>>>>
const handleDrag = (e, data) => {
if (sliderRef.current) {
const sliderRect = sliderRef.current.getBoundingClientRect();
const sliderWidth = sliderRect.width;
const offsetX = data.x;
const thumbWidth = data.node.clientWidth;
const newValue = Math.min(Math.max(1, Math.round((offsetX / (sliderWidth - thumbWidth)) * (maxSlide - 1) + 1)), maxSlide);
setValue(newValue);
setHoverValue(newValue);
if (tooltipRef?.current !== null) {
const tooltipWidth = tooltipRef.current.getBoundingClientRect().width;
const tooltipHeight = tooltipRef.current.getBoundingClientRect().height;
const tooltipLeft = Math.max(0, Math.min(offsetX - tooltipWidth / 2, sliderWidth - tooltipWidth));
const tooltipTop = Math.max(0, Math.min(-20 - tooltipHeight / 2, sliderRect.height - tooltipHeight));
setTooltipPosition({ left: tooltipLeft, top: tooltipTop });
}
}
};
// Handle onClick to change slide >>>>>>>>>
const handleSlideChange = (e) => {
if (sliderRef.current) {
const rect = sliderRef.current.getBoundingClientRect();
const clickedValue = Math.min(Math.max(1, Math.round(((e.clientX - rect.left) / rect.width) * (maxSlide - 1) + 1)), maxSlide);
setValue(clickedValue);
}
};
// Handle hover value tooltip >>>>>>>>>
const handlePointerMove = (e) => {
if (sliderRef.current && tooltipRef.current && !dragging) {
const rect = sliderRef.current.getBoundingClientRect();
const hoverValue = Math.min(Math.max(1, Math.round(((e.clientX - rect.left) / rect.width) * (maxSlide - 1) + 1)), maxSlide);
setHoverValue(hoverValue);
const tooltipWidth = tooltipRef.current.getBoundingClientRect().width;
const tooltipHeight = tooltipRef.current.getBoundingClientRect().height;
const tooltipLeft = Math.max(0, Math.min(e.clientX - rect.left - tooltipWidth / 2, rect.width - tooltipWidth));
const tooltipTop = Math.max(0, Math.min(e.clientY - rect.top - 20 - tooltipHeight / 2, rect.height - tooltipHeight));
setTooltipPosition({ left: tooltipLeft, top: tooltipTop });
}
};
// Hide hover value tooltip >>>>>>>>>
const handlePointerLeave = () => {
setHoverValue(0);
};
// Hide hover value tooltip on drag end >>>>>>>>>
useEffect(() => {
if (!dragging) {
handlePointerLeave();
}
}, [dragging]);
// Update value on slide change >>>>>>>>>
useEffect(() => {
setValue(currentSlide);
}, [currentSlide]);
// Update debounced value on slide change >>>>>>>>>
const debouncedValue = useDebounce(value, 500);
useEffect(() => {
onSlideChange(debouncedValue);
}, [debouncedValue, onSlideChange]);
return (React.createElement("div", { className: "py-4" },
React.createElement("div", { ref: sliderRef, className: "relative w-full h-1 bg-foreground rounded-full", onPointerMove: handlePointerMove, onPointerLeave: handlePointerLeave, onPointerCancel: handlePointerLeave },
React.createElement(Draggable //@ts-ignore
, { nodeRef: nodeRef, axis: "x", bounds: "parent", position: { x: thumbPosition, y: 0 }, onStart: () => setDragging(true), onDrag: handleDrag, onStop: () => setDragging(false) },
React.createElement("div", { ref: nodeRef, className: "absolute z-20 size-1 bg-primary rounded-full cursor-pointer" },
React.createElement("div", { className: cn("size-3 hover:size-4 bg-primary absolute -top-1 hover:-top-1.5 -left-1 hover:-left-1.5 rounded-full transition-all", dragging ? 'w-3 h-3 -left-1.5 -top-1.5 rounded-full' : '') }))),
React.createElement("div", { className: "absolute inset-0 cursor-pointer w-full h-3 top-1/2 -translate-y-1/2 bg-transparent", onClick: handleSlideChange }),
React.createElement("div", { ref: tooltipRef, className: cn('bg-primary/20 backdrop-blur-sm text-white rounded p-2 text-xs w-fit h-fit', hoverValue === 0 ? 'opacity-0 w-0 h-0 select-none' : ''), style: { position: 'absolute', left: tooltipPosition.left, bottom: '20px' } },
React.createElement(HoverItem, { slide: hoverValue, totalPages: totalPages, totalSlides: maxSlide })))));
};
export default Slider;