feat(Slider): add hover functionality with tooltip and indicator

This commit is contained in:
alexsparkes
2026-01-27 21:40:12 +00:00
parent 7f0b37c713
commit 9e692b97ce
2 changed files with 107 additions and 5 deletions

View File

@@ -9,7 +9,10 @@ import './Slider.scss';
const SliderComponent = memo((props) => {
const [value, setValue] = useState(localStorage.getItem(props.name) || props.default);
const [hoverValue, setHoverValue] = useState(null);
const [hoverPosition, setHoverPosition] = useState(0);
const animationRef = useRef(null);
const sliderRef = useRef(null);
const handleChange = useCallback(
(e) => {
@@ -57,7 +60,8 @@ const SliderComponent = memo((props) => {
const easeOutCubic = 1 - Math.pow(1 - progress, 3);
const currentValue = startValue + (endValue - startValue) * easeOutCubic;
const roundedValue = Math.round(currentValue / (Number(props.step) || 1)) * (Number(props.step) || 1);
const roundedValue =
Math.round(currentValue / (Number(props.step) || 1)) * (Number(props.step) || 1);
localStorage.setItem(props.name, roundedValue);
setValue(roundedValue);
@@ -76,6 +80,30 @@ const SliderComponent = memo((props) => {
toast(variables.getMessage('toasts.reset'));
}, [value, props]);
const handleMouseMove = useCallback(
(e) => {
if (!sliderRef.current || props.disabled) return;
const rect = sliderRef.current.getBoundingClientRect();
const x = e.clientX - rect.left;
const percentage = Math.max(0, Math.min(100, (x / rect.width) * 100));
const range = Number(props.max) - Number(props.min);
const rawValue = (percentage / 100) * range + Number(props.min);
const step = Number(props.step) || 1;
const snappedValue = Math.round(rawValue / step) * step;
const clampedValue = Math.max(Number(props.min), Math.min(Number(props.max), snappedValue));
setHoverPosition(percentage);
setHoverValue(clampedValue);
},
[props],
);
const handleMouseLeave = useCallback(() => {
setHoverValue(null);
}, []);
const percentage =
((Number(value) - Number(props.min)) / (Number(props.max) - Number(props.min))) * 100;
@@ -88,8 +116,9 @@ const SliderComponent = memo((props) => {
{variables.getMessage('modals.main.settings.buttons.reset')}
</span>
</div>
<div className="slider-wrapper">
<div className="slider-wrapper" onMouseMove={handleMouseMove} onMouseLeave={handleMouseLeave}>
<input
ref={sliderRef}
type="range"
className="slider-input"
value={Number(value)}
@@ -104,6 +133,14 @@ const SliderComponent = memo((props) => {
aria-valuenow={Number(value)}
disabled={props.disabled || false}
/>
{hoverValue !== null && !props.disabled && (
<>
<div className="slider-hover-indicator" style={{ left: `${hoverPosition}%` }} />
<div className="slider-hover-tooltip" style={{ left: `${hoverPosition}%` }}>
{hoverValue}
</div>
</>
)}
{props.marks && props.marks.length > 0 && (
<div className="slider-marks">
{props.marks.map((mark) => (

View File

@@ -29,6 +29,7 @@
font-weight: 500;
text-transform: uppercase;
letter-spacing: 0.5px;
transition: all 0.2s ease;
@include themed {
color: t($link);
@@ -36,10 +37,16 @@
&:hover {
opacity: 0.8;
transform: scale(1.05);
}
svg {
font-size: 12px;
transition: transform 0.2s ease;
}
&:hover svg {
transform: rotate(180deg);
}
}
@@ -56,7 +63,7 @@
border-radius: 3px;
outline: none;
cursor: pointer;
transition: background 0.3s ease;
transition: all 0.2s ease;
@include themed {
background: linear-gradient(
@@ -68,6 +75,11 @@
);
}
&:hover:not(:disabled) {
height: 8px;
filter: brightness(1.1);
}
&::-webkit-slider-thumb {
-webkit-appearance: none;
appearance: none;
@@ -114,7 +126,9 @@
&::-webkit-slider-thumb {
@include themed {
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.2), 0 0 0 3px t($link);
box-shadow:
0 2px 6px rgba(0, 0, 0, 0.2),
0 0 0 3px t($link);
}
transform: scale(1.15);
@@ -122,7 +136,9 @@
&::-moz-range-thumb {
@include themed {
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.2), 0 0 0 3px t($link);
box-shadow:
0 2px 6px rgba(0, 0, 0, 0.2),
0 0 0 3px t($link);
}
transform: scale(1.15);
@@ -171,4 +187,53 @@
}
}
}
.slider-hover-indicator {
position: absolute;
top: 50%;
transform: translate(-50%, -50%);
width: 12px;
height: 12px;
border-radius: 50%;
pointer-events: none;
z-index: 1;
opacity: 0.6;
@include themed {
background: t($link);
box-shadow: 0 0 8px rgba(t($link), 0.5);
}
}
.slider-hover-tooltip {
position: absolute;
top: -35px;
transform: translateX(-50%);
padding: 4px 8px;
border-radius: 4px;
font-size: 12px;
font-weight: 600;
white-space: nowrap;
pointer-events: none;
z-index: 2;
@include themed {
background: t($link);
color: t($background);
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
}
&::after {
content: '';
position: absolute;
top: 100%;
left: 50%;
transform: translateX(-50%);
border: 5px solid transparent;
@include themed {
border-top-color: t($link);
}
}
}
}