Files
ohif-viewer/platform/ui/src/components/InputDoubleRange/InputDoubleRange.tsx
2025-05-27 11:05:07 +07:00

239 lines
7.6 KiB
TypeScript

import React, { useState, useEffect, useRef } from 'react';
import classNames from 'classnames';
import { InputNumber } from '../../components'; // Import InputNumber component
import './InputDoubleRange.css';
type InputDoubleRangeProps = {
values: [number, number];
onChange: (values: [number, number]) => void;
minValue?: number;
maxValue?: number;
step?: number;
unit?: string;
containerClassName?: string;
inputClassName?: string;
labelClassName?: string;
labelVariant?: string;
showLabel?: boolean;
labelPosition?: 'left' | 'right';
trackColor?: string;
allowNumberEdit?: boolean;
showAdjustmentArrows?: boolean;
allowOutOfRange?: boolean;
};
const InputDoubleRange: React.FC<InputDoubleRangeProps> = ({
values,
onChange,
minValue = 0,
maxValue = 100,
step = 1,
unit = '',
containerClassName = '',
inputClassName = '',
labelClassName = '',
labelVariant = 'body1',
showLabel = false,
labelPosition = 'left',
trackColor = 'primary',
allowNumberEdit = false,
allowOutOfRange = false,
showAdjustmentArrows = false,
}) => {
// Set initial thumb positions as percentages
const initialPercentageStart = Math.round(((values[0] - minValue) / (maxValue - minValue)) * 100);
const initialPercentageEnd = Math.round(((values[1] - minValue) / (maxValue - minValue)) * 100);
const [percentageStart, setPercentageStart] = useState(initialPercentageStart);
const [percentageEnd, setPercentageEnd] = useState(initialPercentageEnd);
const [rangeValue, setRangeValue] = useState(values);
const selectedThumbRef = useRef(null);
const sliderRef = useRef(null);
const updateRangeValues = (newValues, index = null) => {
const updatedRangeValue = Array.isArray(newValues) ? [...newValues] : [...rangeValue];
if (index !== null) {
updatedRangeValue[index] = newValues;
}
const calculatePercentage = value => {
if (value < minValue) {
return 0;
}
if (value > maxValue) {
return 100;
}
return ((value - minValue) / (maxValue - minValue)) * 100;
};
const newPercentageStart = calculatePercentage(updatedRangeValue[0]);
const newPercentageEnd = calculatePercentage(updatedRangeValue[1]);
setRangeValue(updatedRangeValue);
onChange(updatedRangeValue);
setPercentageStart(newPercentageStart);
setPercentageEnd(newPercentageEnd);
};
useEffect(() => {
updateRangeValues(values);
}, [values, minValue, maxValue]);
const LabelOrEditableNumber = (val, index) => {
return allowNumberEdit ? (
// the pl-[2px] class is used to align the thumb so that it doesn't
// go over the label when the value is full, not sure what is wrong
// with the implementation, we need to fix it properly
<div className={index === 1 && 'pl-[2px]'}>
<InputNumber
minValue={minValue}
maxValue={maxValue}
value={val}
onChange={newValue => {
updateRangeValues(newValue, index);
}}
step={step}
labelClassName={classNames(labelClassName ?? 'text-white')}
showAdjustmentArrows={showAdjustmentArrows}
/>
</div>
) : (
<span className={classNames(labelClassName ?? 'text-white')}>
{val}
{unit}
</span>
);
};
useEffect(() => {
return () => {
window.removeEventListener('mousemove', handleMouseMove);
window.removeEventListener('mouseup', handleGlobalMouseUp);
};
}, []);
const handleGlobalMouseUp = () => {
// Remove global mouse event listeners
window.removeEventListener('mousemove', handleMouseMove);
window.removeEventListener('mouseup', handleGlobalMouseUp);
selectedThumbRef.current = null;
};
const handleMouseDown = e => {
const rect = sliderRef.current.getBoundingClientRect();
const x = e.clientX - rect.left;
const percentageClicked = (x / rect.width) * 100;
// Calculate the distances from the clicked point to both thumbs' positions
const distanceToStartThumb = Math.abs(percentageClicked - percentageStart);
const distanceToEndThumb = Math.abs(percentageClicked - percentageEnd);
// Check if the clicked point is within a threshold distance to either thumb
if (distanceToStartThumb < 10) {
selectedThumbRef.current = 0;
} else if (distanceToEndThumb < 10) {
selectedThumbRef.current = 1;
}
window.addEventListener('mousemove', handleMouseMove);
window.addEventListener('mouseup', handleGlobalMouseUp);
};
const handleMouseMove = e => {
const selectedThumbValue = selectedThumbRef.current;
if (selectedThumbValue === null) {
return;
}
const rect = sliderRef.current.getBoundingClientRect();
const x = e.clientX - rect.left;
const newValue =
Math.round(((x / rect.width) * (maxValue - minValue) + minValue) / step) * step;
if (!allowOutOfRange) {
const clampedValue = Math.min(Math.max(newValue, minValue), maxValue);
const updatedRangeValue = [...rangeValue];
updatedRangeValue[selectedThumbValue] = clampedValue;
setRangeValue(updatedRangeValue);
onChange(updatedRangeValue);
const percentage = Math.round(((clampedValue - minValue) / (maxValue - minValue)) * 100);
if (selectedThumbValue === 0) {
setPercentageStart(percentage);
} else {
setPercentageEnd(percentage);
}
} else {
const updatedRangeValue = [...rangeValue];
updatedRangeValue[selectedThumbValue] = newValue;
setRangeValue(updatedRangeValue);
onChange(updatedRangeValue);
// Update the thumb position
const percentage = Math.round(((newValue - minValue) / (maxValue - minValue)) * 100);
if (percentage < 0) {
if (selectedThumbValue === 0) {
setPercentageStart(0);
} else {
setPercentageEnd(0);
}
} else if (percentage > 100) {
if (selectedThumbValue === 0) {
setPercentageStart(100);
} else {
setPercentageEnd(100);
}
} else {
if (selectedThumbValue === 0) {
setPercentageStart(percentage);
} else {
setPercentageEnd(percentage);
}
}
}
// Update the correct values in the rangeValue array
};
// Calculate the range values percentages for gradient background
const rangeValuePercentageStart = ((rangeValue[0] - minValue) / (maxValue - minValue)) * 100;
const rangeValuePercentageEnd = ((rangeValue[1] - minValue) / (maxValue - minValue)) * 100;
return (
<div className={`flex select-none items-center space-x-2 ${containerClassName ?? ''}`}>
{showLabel && LabelOrEditableNumber(rangeValue[0], 0)}
<div
className="relative flex h-10 w-full items-center"
onMouseDown={handleMouseDown}
ref={sliderRef}
>
<div
className="h-[3px] w-full rounded-lg"
style={{
background: `linear-gradient(to right, #3a3f99 0%, #3a3f99 ${rangeValuePercentageStart}%, #5acce6 ${rangeValuePercentageStart}%, #5acce6 ${rangeValuePercentageEnd}%, #3a3f99 ${rangeValuePercentageEnd}%, #3a3f99 100%)`,
}}
></div>
<div
className="input-range-thumb-design absolute h-3 w-3 cursor-pointer"
style={{
left: `calc(${percentageStart}% - 3px)`,
}}
></div>
<div
className="input-range-thumb-design absolute h-3 w-3 cursor-pointer rounded-full"
style={{ left: `calc(${percentageEnd}% - 3px)` }}
></div>
</div>
{showLabel && LabelOrEditableNumber(rangeValue[1], 1)}
</div>
);
};
export default InputDoubleRange;