@ducor/react
Version:
admin template ui interface
135 lines (134 loc) • 6.37 kB
JavaScript
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;