Files
irth-new-client/src/components/ui/MultiRangeSlider.tsx
T

136 lines
4.2 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;
onChangeMin: (min: number) => void;
onChangeMax: (max: number) => void;
}
function MultiRangeSlider({
currentMax,
currentMin,
max,
min,
onChangeMin,
onChangeMax,
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 value = min + percentage * (max - min);
return isMin
? Math.max(Math.min(value, currentMax - offset), min)
: Math.min(Math.max(value, currentMin + offset), max);
}
function handleChange(
e: MouseEvent | TouchEvent | ReactMouseEvent | ReactTouchEvent
) {
if (!current || disabled) return;
const { clientX } = "touches" in e ? e.touches[0] : e;
const value = calculateValue(clientX, current === "min");
if (value !== undefined) {
if (current === "min") onChangeMin(value);
else onChangeMax(value);
}
}
function handleMouseUp() {
setCurrent(null);
}
useEffect(() => {
if (current) {
document.addEventListener("mousemove", handleChange as EventListener);
document.addEventListener("mouseup", handleMouseUp);
document.addEventListener("mouseleave", handleMouseUp);
}
return () => {
document.removeEventListener("mousemove", handleChange as EventListener);
document.removeEventListener("mouseup", handleMouseUp);
document.removeEventListener("mouseleave", handleMouseUp);
};
}, [current]);
const getThumbStyle = (value: number) => ({
left: `${((value - 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.round(currentMin))}
</p>
<p className={clsx("text-s", disabled && "text-[#0D1922]/40")}>
{Intl.NumberFormat("en").format(Math.round(currentMax))}
</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: `${((currentMax - currentMin) / (max - min)) * 100}%`,
left: `${((currentMin - 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" ? currentMin : currentMax)}
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;