Files
irth-new-client-120/src/components/ui/MultiRangeSlider.tsx
T
2026-01-20 13:34:23 +05:00

150 lines
4.9 KiB
TypeScript

/* eslint-disable react-hooks/exhaustive-deps */
import clsx from "clsx";
import {
useEffect,
useRef,
useState,
MouseEvent as ReactMouseEvent,
TouchEvent as ReactTouchEvent,
} from "react";
interface IMultiRangeSlider {
min: number;
max: number;
currentMin: number;
currentMax: number;
offset: number;
disabled?: boolean;
label: string;
onChange: (value: [number, number]) => void;
}
function MultiRangeSlider({
currentMax,
currentMin,
max,
min,
onChange,
offset,
label,
disabled = false,
}: IMultiRangeSlider) {
const [current, setCurrent] = useState<"min" | "max" | null>(null);
const rangeRef = useRef<HTMLDivElement>(null);
function calculateValue(clientX: number, isMin: boolean) {
if (!rangeRef.current) return;
const rect = rangeRef.current.getBoundingClientRect();
const percentage = (clientX - rect.x) / rect.width;
const newValue = min + percentage * (max - min);
return isMin
? Math.max(Math.min(newValue, currentMax - offset), min)
: Math.min(Math.max(newValue, currentMin + offset), max);
}
const [value, setValue] = useState<[number, number]>([
currentMin,
currentMax,
]);
useEffect(() => {
setValue([currentMin, currentMax]);
}, [currentMin, currentMax]);
function handleChange(
e: MouseEvent | TouchEvent | ReactMouseEvent | ReactTouchEvent
) {
if (!current || disabled) return;
const { clientX } = "touches" in e ? e.touches[0] : e;
const newValue = calculateValue(clientX, current === "min");
if (newValue !== undefined) {
if (current === "min") setValue([newValue, currentMax]);
else setValue([currentMin, newValue]);
}
}
function handleMouseUp() {
setCurrent(null);
onChange(value.map(Math.ceil) as [number, number]);
}
useEffect(() => {
if (!current) return;
document.addEventListener("mousemove", handleChange as EventListener);
document.addEventListener("mouseup", handleMouseUp);
document.addEventListener("mouseleave", handleMouseUp);
document.addEventListener("touchmove", handleChange as EventListener);
document.addEventListener("touchend", handleMouseUp);
document.addEventListener("touchcancel", handleMouseUp);
return () => {
document.removeEventListener("mousemove", handleChange as EventListener);
document.removeEventListener("mouseup", handleMouseUp);
document.removeEventListener("mouseleave", handleMouseUp);
document.removeEventListener("touchmove", handleChange as EventListener);
document.removeEventListener("touchend", handleMouseUp);
document.removeEventListener("touchcancel", handleMouseUp);
};
}, [current, value]);
const getThumbStyle = (newValue: number) => ({
left: `${((newValue - min) / (max - min)) * 100}%`,
});
return (
<div className="2xl:space-y-[0.556vw] space-y-2">
<p className="text-s text-[#0D1922]/70">{label}</p>
<div className="bg-white/80 2xl:rounded-[0.833vw] rounded-xl relative 2xl:px-[1.111vw] 2xl:py-[0.972vw] px-4 py-3.5 flex justify-between 2xl:ring-[0.069vw] ring-1 ring-[#E2E2DC]">
<p className={clsx("text-s", disabled && "text-[#0D1922]/40")}>
{Intl.NumberFormat("en").format(Math.ceil(value[0]))}
</p>
<p className={clsx("text-s", disabled && "text-[#0D1922]/40")}>
{Intl.NumberFormat("en").format(Math.ceil(value[1]))}
</p>
<div className="absolute bottom-0 left-0 w-full 2xl:px-[1.111vw] px-4 translate-y-1/2">
<div
className="relative flex 2xl:h-[1.111vw] h-4"
ref={rangeRef}
onMouseMove={
handleChange as React.MouseEventHandler<HTMLDivElement>
}
onTouchMove={
handleChange as React.TouchEventHandler<HTMLDivElement>
}
>
<div
style={{
width: `${((value[1] - value[0]) / (max - min)) * 100}%`,
left: `${((value[0] - min) / (max - min)) * 100}%`,
}}
className={clsx(
"2xl:h-[0.139vw] h-0.5 self-center relative",
disabled ? "bg-[#D2D2D2]" : "bg-[#00BED7]"
)}
/>
{["min", "max"].map((type) => (
<div
key={type}
onMouseDown={() => setCurrent(type as "min" | "max")}
onTouchStart={() => setCurrent(type as "min" | "max")}
style={getThumbStyle(type === "min" ? value[0] : value[1])}
className={clsx(
"rounded-full 2xl:w-[1.111vw] 2xl:h-[1.111vw] w-4 h-4 absolute bottom-0 -translate-x-1/2",
current === type ? "cursor-grabbing" : "cursor-grab",
disabled ? "bg-[#D2D2D2] !cursor-default" : "bg-[#00BED7]"
)}
/>
))}
</div>
</div>
</div>
</div>
);
}
export default MultiRangeSlider;