/** * VolumeSlider - Reusable volume slider component with popup and snap-to-100% */ import React, { useState, useRef, useCallback } from "react"; interface VolumeSliderProps { /** Current value (0-2 for 0-200%) */ value: number; /** Callback when value changes */ onChange: (value: number) => void; /** Min value (default 0) */ min?: number; /** Max value (default 2 for 200%) */ max?: number; /** Snap threshold around 100% (default 0.05 = 5%) */ snapThreshold?: number; /** Optional className for the container */ className?: string; /** Compact mode for inline use */ compact?: boolean; } export const VolumeSlider: React.FC = ({ value, onChange, min = 0, max = 2, snapThreshold = 0.05, className, compact = false, }) => { const [isDragging, setIsDragging] = useState(false); const [popupPosition, setPopupPosition] = useState(0); const sliderRef = useRef(null); const handleChange = useCallback( (e: React.ChangeEvent) => { let newValue = parseInt(e.target.value, 10) / 100; // Snap to 100% if within threshold if (Math.abs(newValue - 1) <= snapThreshold) { newValue = 1; } onChange(newValue); // Update popup position if (sliderRef.current) { const rect = sliderRef.current.getBoundingClientRect(); const percent = (newValue - min) / (max - min); setPopupPosition(percent * rect.width); } }, [onChange, min, max, snapThreshold] ); const handleMouseDown = useCallback(() => { setIsDragging(true); // Update initial position if (sliderRef.current) { const rect = sliderRef.current.getBoundingClientRect(); const percent = (value - min) / (max - min); setPopupPosition(percent * rect.width); } }, [value, min, max]); const handleMouseUp = useCallback(() => { setIsDragging(false); }, []); const displayValue = Math.round(value * 100); const isBoost = value > 1; const isDefault = value === 1; return (
{/* Popup tooltip */} {isDragging && (
{displayValue}%{isDefault && " (default)"} {/* Arrow */}
)} {/* Slider track with 100% marker */}
{/* 100% marker line */}
); }; export default VolumeSlider;