@@ -15,137 +15,71 @@ limitations under the License.
1515*/
1616
1717import * as React from "react" ;
18+ import { ChangeEvent } from "react" ;
1819
1920interface IProps {
2021 // A callback for the selected value
21- onSelectionChange : ( value : number ) => void ;
22+ onChange : ( value : number ) => void ;
2223
2324 // The current value of the slider
2425 value : number ;
2526
26- // The range and values of the slider
27- // Currently only supports an ascending, constant interval range
28- values : number [ ] ;
27+ // The min and max of the slider
28+ min : number ;
29+ max : number ;
30+ // The step size of the slider, can be a number or "any"
31+ step : number | "any" ;
2932
30- // A function for formatting the the values
33+ // A function for formatting the values
3134 displayFunc : ( value : number ) => string ;
3235
3336 // Whether the slider is disabled
3437 disabled : boolean ;
3538}
3639
37- export default class Slider extends React . Component < IProps > {
38- // offset is a terrible inverse approximation.
39- // if the values represents some function f(x) = y where x is the
40- // index of the array and y = values[x] then offset(f, y) = x
41- // s.t f(x) = y.
42- // it assumes a monotonic function and interpolates linearly between
43- // y values.
44- // Offset is used for finding the location of a value on a
45- // non linear slider.
46- private offset ( values : number [ ] , value : number ) : number {
47- // the index of the first number greater than value.
48- const closest = values . reduce ( ( prev , curr ) => {
49- return value > curr ? prev + 1 : prev ;
50- } , 0 ) ;
51-
52- // Off the left
53- if ( closest === 0 ) {
54- return 0 ;
55- }
56-
57- // Off the right
58- if ( closest === values . length ) {
59- return 100 ;
60- }
61-
62- // Now
63- const closestLessValue = values [ closest - 1 ] ;
64- const closestGreaterValue = values [ closest ] ;
65-
66- const intervalWidth = 1 / ( values . length - 1 ) ;
40+ const THUMB_SIZE = 2.4 ; // em
6741
68- const linearInterpolation = ( value - closestLessValue ) / ( closestGreaterValue - closestLessValue ) ;
69-
70- return 100 * ( closest - 1 + linearInterpolation ) * intervalWidth ;
42+ export default class Slider extends React . Component < IProps > {
43+ private get position ( ) : number {
44+ const { min, max, value } = this . props ;
45+ return Number ( ( ( value - min ) * 100 ) / ( max - min ) ) ;
7146 }
7247
73- public render ( ) : React . ReactNode {
74- const dots = this . props . values . map ( ( v ) => (
75- < Dot
76- active = { v <= this . props . value }
77- label = { this . props . displayFunc ( v ) }
78- onClick = { this . props . disabled ? ( ) => { } : ( ) => this . props . onSelectionChange ( v ) }
79- key = { v }
80- disabled = { this . props . disabled }
81- />
82- ) ) ;
48+ private onChange = ( ev : ChangeEvent < HTMLInputElement > ) : void => {
49+ this . props . onChange ( parseInt ( ev . target . value , 10 ) ) ;
50+ } ;
8351
52+ public render ( ) : React . ReactNode {
8453 let selection : JSX . Element | undefined ;
8554
8655 if ( ! this . props . disabled ) {
87- const offset = this . offset ( this . props . values , this . props . value ) ;
56+ const position = this . position ;
8857 selection = (
89- < div className = "mx_Slider_selection" >
90- < div className = "mx_Slider_selectionDot" style = { { left : "calc(-1.195em + " + offset + "%)" } } >
91- < div className = "mx_Slider_selectionText" > { this . props . value } </ div >
92- </ div >
93- < hr style = { { width : offset + "%" } } />
94- </ div >
58+ < output
59+ className = "mx_Slider_selection"
60+ style = { {
61+ left : `calc(2px + ${ position } % + ${ THUMB_SIZE / 2 } em - ${ ( position / 100 ) * THUMB_SIZE } em)` ,
62+ } }
63+ >
64+ < span className = "mx_Slider_selection_label" > { this . props . value } </ span >
65+ </ output >
9566 ) ;
9667 }
9768
9869 return (
9970 < div className = "mx_Slider" >
100- < div >
101- < div className = "mx_Slider_bar" >
102- < hr onClick = { this . props . disabled ? ( ) => { } : this . onClick . bind ( this ) } />
103- { selection }
104- </ div >
105- < div className = "mx_Slider_dotContainer" > { dots } </ div >
106- </ div >
71+ < input
72+ type = "range"
73+ min = { this . props . min }
74+ max = { this . props . max }
75+ value = { this . props . value }
76+ onChange = { this . onChange }
77+ disabled = { this . props . disabled }
78+ step = { this . props . step }
79+ autoComplete = "off"
80+ />
81+ { selection }
10782 </ div >
10883 ) ;
10984 }
110-
111- public onClick ( event : React . MouseEvent ) : void {
112- const width = ( event . target as HTMLElement ) . clientWidth ;
113- // nativeEvent is safe to use because https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/offsetX
114- // is supported by all modern browsers
115- const relativeClick = event . nativeEvent . offsetX / width ;
116- const nearestValue = this . props . values [ Math . round ( relativeClick * ( this . props . values . length - 1 ) ) ] ;
117- this . props . onSelectionChange ( nearestValue ) ;
118- }
119- }
120-
121- interface IDotProps {
122- // Callback for behavior onclick
123- onClick : ( ) => void ;
124-
125- // Whether the dot should appear active
126- active : boolean ;
127-
128- // The label on the dot
129- label : string ;
130-
131- // Whether the slider is disabled
132- disabled : boolean ;
133- }
134-
135- class Dot extends React . PureComponent < IDotProps > {
136- public render ( ) : React . ReactNode {
137- let className = "mx_Slider_dot" ;
138- if ( ! this . props . disabled && this . props . active ) {
139- className += " mx_Slider_dotActive" ;
140- }
141-
142- return (
143- < span onClick = { this . props . onClick } className = "mx_Slider_dotValue" >
144- < div className = { className } />
145- < div className = "mx_Slider_labelContainer" >
146- < div className = "mx_Slider_label" > { this . props . label } </ div >
147- </ div >
148- </ span >
149- ) ;
150- }
15185}
0 commit comments