1- import React , { useRef } from 'react'
1+ import React , { useRef , useMemo , useState , useEffect } from 'react'
22import { RangeWrapper , Range , Scrubber , Indicator } from './StyledRange'
33import { sanitizeStep } from './number-plugin'
44import { useDrag } from '../../hooks'
55import { invertedRange , range } from '../../utils'
66import { useTh } from '../../styles'
77import type { RangeSliderProps } from './number-types'
88
9+ // ===========================================
10+ // STEP VISUALIZATION CONFIGURATION
11+ // ===========================================
12+
13+ // Minimum spacing between step indicators in pixels
14+ // - Set to 0 to always show step visualization
15+ // - Increase (e.g., 5-10) to reduce visual clutter when steps are dense
16+ const MIN_STEP_SPACING_PX = 3
17+
18+ // Visualization mode - CHANGE THIS TO SWITCH MODES:
19+ // - 'lines': Vertical lines inside the range bar (subtle, integrated)
20+ // - 'dots': Circles below the range bar (prominent, separated)
21+ type StepVisualizationMode = 'lines' | 'dots'
22+ const STEP_VISUALIZATION_MODE : StepVisualizationMode = 'dots'
23+
924export function RangeSlider ( { value, min, max, onDrag, step, initialValue } : RangeSliderProps ) {
1025 const ref = useRef < HTMLDivElement > ( null )
1126 const scrubberRef = useRef < HTMLDivElement > ( null )
1227 const rangeWidth = useRef < number > ( 0 )
1328 const scrubberWidth = useTh ( 'sizes' , 'scrubberWidth' )
29+ const [ elementWidth , setElementWidth ] = useState ( 0 )
30+
31+ useEffect ( ( ) => {
32+ if ( ref . current ) {
33+ const updateWidth = ( ) => {
34+ const { width } = ref . current ! . getBoundingClientRect ( )
35+ setElementWidth ( width )
36+ }
37+ updateWidth ( )
38+
39+ // Update width on resize
40+ const resizeObserver = new ResizeObserver ( updateWidth )
41+ resizeObserver . observe ( ref . current )
42+
43+ return ( ) => resizeObserver . disconnect ( )
44+ }
45+ } , [ ] )
1446
1547 const bind = useDrag ( ( { event, first, xy : [ x ] , movement : [ mx ] , memo } ) => {
1648 if ( first ) {
@@ -29,12 +61,76 @@ export function RangeSlider({ value, min, max, onDrag, step, initialValue }: Ran
2961
3062 const pos = range ( value , min , max )
3163
64+ // Calculate step lines for visualization
65+ const stepLines = useMemo ( ( ) => {
66+ if ( ! step || ! Number . isFinite ( min ) || ! Number . isFinite ( max ) || elementWidth === 0 ) return [ ]
67+
68+ const rangeSpan = max - min
69+ const stepCount = Math . floor ( rangeSpan / step )
70+
71+ if ( stepCount <= 1 ) return [ ]
72+
73+ // Calculate step spacing in pixels
74+ const stepSpacingPx = ( elementWidth * step ) / rangeSpan
75+
76+ // Don't show step lines if they would be too close together
77+ if ( stepSpacingPx < MIN_STEP_SPACING_PX ) return [ ]
78+
79+ const lines = [ ]
80+ for ( let i = 1 ; i < stepCount ; i ++ ) {
81+ const stepValue = min + i * step
82+ const stepPos = range ( stepValue , min , max )
83+ lines . push ( stepPos * 100 ) // Convert to percentage
84+ }
85+
86+ return lines
87+ } , [ step , min , max , elementWidth ] )
88+
3289 return (
3390 < RangeWrapper ref = { ref } { ...bind ( ) } >
3491 < Range >
92+ { stepLines . length > 0 && STEP_VISUALIZATION_MODE === 'lines' && (
93+ < svg
94+ style = { {
95+ position : 'absolute' ,
96+ top : 0 ,
97+ left : 0 ,
98+ width : '100%' ,
99+ height : '100%' ,
100+ pointerEvents : 'none' ,
101+ } } >
102+ { stepLines . map ( ( linePos , index ) => (
103+ < line
104+ key = { index }
105+ x1 = { `${ linePos } %` }
106+ y1 = "0"
107+ x2 = { `${ linePos } %` }
108+ y2 = "100%"
109+ stroke = "currentColor"
110+ strokeWidth = "1"
111+ opacity = "0.2"
112+ />
113+ ) ) }
114+ </ svg >
115+ ) }
116+ { stepLines . length > 0 && STEP_VISUALIZATION_MODE === 'dots' && (
117+ < svg
118+ style = { {
119+ position : 'absolute' ,
120+ top : '100%' ,
121+ left : 0 ,
122+ width : '100%' ,
123+ height : '8px' ,
124+ pointerEvents : 'none' ,
125+ } } >
126+ { stepLines . map ( ( dotPos , index ) => (
127+ < circle key = { index } cx = { `${ dotPos } %` } cy = "4" r = "1.25" fill = "currentColor" opacity = "0.4" />
128+ ) ) }
129+ </ svg >
130+ ) }
35131 < Indicator style = { { left : 0 , right : `${ ( 1 - pos ) * 100 } %` } } />
36132 </ Range >
37- < Scrubber ref = { scrubberRef } style = { { left : `calc(${ pos } * ( 100% - ${ scrubberWidth } ))` } } />
133+ < Scrubber ref = { scrubberRef } style = { { left : `calc(( ${ pos } * 100%) - (var(--leva-sizes- scrubberWidth) / 2 ))` } } />
38134 </ RangeWrapper >
39135 )
40136}
0 commit comments