UNPKG

@ducor/react

Version:

admin template ui interface

135 lines (134 loc) 6.37 kB
import { jsx as _jsx } from "react/jsx-runtime"; import { memo, useEffect, useRef, useState } from "react"; import { twMerge } from "tailwind-merge"; import { useScrollArea } from "./provider"; import { useElementSize } from "@ducor/hooks"; // Utility function to constrain a value between min and max function clamp(value, min, max) { return Math.min(Math.max(value, min), max); } const Slider = memo(({ position }) => { const isVertical = position === "vertical"; const { direction, size, scrollOffset, setScrollOffset, scrollPercent } = useScrollArea(); const widthOrHeight = isVertical ? size.height : size.width; const [max, setMax] = useState(0); const [thumbSize, setThumbSize] = useState(0); const { ref: sliderRef, width, height } = useElementSize(); const thumbRef = useRef(null); const initialMousePosition = useRef(0); const initialPercentPosition = useRef(0); useEffect(() => { if (!sliderRef.current || !thumbRef.current || width === 0 || height === 0) return; const trackMinSize = 8; const trackSize = isVertical ? height : width; const contentSize = widthOrHeight; const calculatedMax = Math.max(contentSize - trackSize, 0); setMax(calculatedMax); if (contentSize <= trackSize) { setThumbSize(0); } else { const ratio = trackSize / contentSize; const interpolatedThumbSize = Math.max(trackMinSize, Math.floor(ratio * trackSize)); setThumbSize(interpolatedThumbSize); } return () => { setThumbSize(0); setMax(0); }; }, [widthOrHeight, isVertical, width, height]); const handleMouseMove = (event) => { if (!sliderRef.current || !thumbRef.current || max <= 0) return; const trackRect = sliderRef.current.getBoundingClientRect(); if (isVertical) { const deltaY = event.clientY - initialMousePosition.current; const availableSpace = trackRect.height - thumbSize; // Calculate the delta as a percentage const deltaPercent = deltaY / availableSpace; // Add delta to the initial percentage and clamp between 0 and 1 const newPercent = clamp(initialPercentPosition.current + deltaPercent, 0, 1); setScrollOffset({ x: scrollOffset.x, y: newPercent * max, }); } else { const deltaX = event.clientX - initialMousePosition.current; const availableSpace = trackRect.width - thumbSize; // Calculate the delta as a percentage const deltaPercent = deltaX / availableSpace; // Add delta to the initial percentage and clamp between 0 and 1 const newPercent = clamp(initialPercentPosition.current + deltaPercent, 0, 1); setScrollOffset({ x: newPercent * max, y: scrollOffset.y, }); } }; const handleMouseDown = (e) => { e.preventDefault(); if (!sliderRef.current || !thumbRef.current) return; const isThumb = e.target === thumbRef.current; const sliderRect = sliderRef.current.getBoundingClientRect(); if (!isThumb) { // Click on the track, not on the thumb let newPercent; if (isVertical) { const clickY = e.clientY - sliderRect.top; const trackSize = sliderRect.height; const thumbCenter = clickY - thumbSize / 2; const availableSpace = trackSize - thumbSize; // Calculate percentage and clamp newPercent = clamp(thumbCenter / availableSpace, 0, 1); setScrollOffset({ x: scrollOffset.x, y: newPercent * max, }); } else { const clickX = e.clientX - sliderRect.left; const trackSize = sliderRect.width; const thumbCenter = clickX - thumbSize / 2; const availableSpace = trackSize - thumbSize; // Calculate percentage and clamp newPercent = clamp(thumbCenter / availableSpace, 0, 1); setScrollOffset({ x: newPercent * max, y: scrollOffset.y, }); } // Store initial positions for drag handling initialMousePosition.current = isVertical ? e.clientY : e.clientX; initialPercentPosition.current = newPercent; } else { // Click on the thumb initialMousePosition.current = isVertical ? e.clientY : e.clientX; initialPercentPosition.current = isVertical ? scrollPercent.y : scrollPercent.x; } document.addEventListener("mousemove", handleMouseMove); document.addEventListener("mouseup", handleMouseUp); }; const handleMouseUp = () => { document.removeEventListener("mousemove", handleMouseMove); document.removeEventListener("mouseup", handleMouseUp); }; // Calculate thumb position as percentage const thumbPositionPercent = isVertical ? scrollPercent.y * 100 : scrollPercent.x * 100; return (_jsx("div", { ref: sliderRef, className: twMerge("relative cursor-pointer bg-red-800 size-full", thumbSize === 0 && "opacity-0 pointer-events-none", isVertical ? "z-0" : "z-10"), onMouseDown: handleMouseDown, role: 'scrollbar', "aria-orientation": position, "aria-valuenow": isVertical ? scrollOffset.y : scrollOffset.x, "aria-valuemin": 0, "aria-valuemax": max, "data-scroll-aria": `${position}-slider`, children: _jsx("div", { ref: thumbRef, className: 'absolute bg-blue-500 transition-all duration-100', style: { [isVertical ? "height" : "width"]: `${thumbSize}px`, [!isVertical ? "height" : "width"]: "100%", [isVertical ? "top" : "left"]: `${thumbPositionPercent}%`, transform: isVertical ? `translateY(-${(thumbPositionPercent * thumbSize) / 100}px)` : `translateX(-${(thumbPositionPercent * thumbSize) / 100}px)`, } }) })); }); export default Slider;