mirror of
https://github.com/mue/mue.git
synced 2026-07-16 05:23:49 +02:00
feat(Slider): add hover functionality with tooltip and indicator
This commit is contained in:
@@ -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) => (
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user