/** * RangeSlider component for settings UI. */ import React, { useRef } from "react"; export type RangeSliderProps = { /** Label displayed above the slider */ label?: string; /** Description text below the label */ description?: string; /** Current value */ value: number; /** Minimum value */ min: number; /** Maximum value */ max: number; /** Step increment (default: 1) */ step?: number; /** Unit suffix (e.g., 'px', '%') */ unit?: string; /** Change handler */ onChange: (value: number) => void; /** Additional class names for the container */ className?: string; /** Whether to span full width in grid (col-span-2) */ fullWidth?: boolean; }; export function RangeSlider({ label, description, value, min, max, step = 1, unit = "px", onChange, className = "", fullWidth = false, }: RangeSliderProps): React.ReactElement { const containerClass = `b3-wvs-relative${fullWidth ? " b3-wvs-col-span-2" : ""}${className ? ` ${className}` : ""}`; const tooltipRef = useRef(null); const precision = (() => { const stepText = String(step); const dotIndex = stepText.indexOf("."); return dotIndex >= 0 ? stepText.length - dotIndex - 1 : 0; })(); const parseNumeric = (rawValue: string, fallback: number): number => { const parsed = Number(rawValue); return Number.isFinite(parsed) ? parsed : fallback; }; const formatValue = (rawValue: number): string => { if (!Number.isFinite(rawValue)) return String(min); if (precision <= 0) return String(Math.round(rawValue)); return rawValue.toFixed(precision).replace(/\.?0+$/, ""); }; const rangePercent = (() => { if (max <= min) return 0; const pct = ((value - min) / (max - min)) * 100; return Math.min(100, Math.max(0, pct)); })(); const updateTooltipPosition = (inputEl: HTMLInputElement) => { const tooltip = tooltipRef.current; if (!tooltip) return; const val = parseNumeric(inputEl.value, value); const percentage = ((val - min) / (max - min)) * 100; tooltip.style.left = `${percentage}%`; tooltip.textContent = `${formatValue(val)}${unit}`; }; return (
{label && ( )} {description && (

{description}

)}
onChange(parseNumeric(e.target.value, value))} onMouseDown={(e) => { const tooltip = tooltipRef.current; if (tooltip) { tooltip.style.opacity = "1"; updateTooltipPosition(e.currentTarget); } }} onMouseUp={() => { const tooltip = tooltipRef.current; if (tooltip) tooltip.style.opacity = "0"; }} onMouseLeave={() => { const tooltip = tooltipRef.current; if (tooltip) tooltip.style.opacity = "0"; }} onInput={(e) => updateTooltipPosition(e.currentTarget as HTMLInputElement) } className="b3-wvs-range-input" style={ { "--b3-range-pct": `${rangePercent}%` } as React.CSSProperties } />
{formatValue(value)} {unit}
{formatValue(min)} {unit} Currently {formatValue(value)} {unit} {formatValue(max)} {unit}
); } export default RangeSlider;