1
+ import { useState , useEffect } from "react" ;
2
+
3
+ type InputWithSliderProps = {
4
+ label : string ;
5
+ value : number ;
6
+ min : number ;
7
+ max : number ;
8
+ step : number ;
9
+ onChange : ( newValue : number ) => void ;
10
+ disabled ?: boolean ;
11
+ }
12
+
13
+ export function InputWithSlider ( props : InputWithSliderProps ) {
14
+
15
+ const [ input , setInput ] = useState ( props . value . toString ( ) ) ;
16
+
17
+ function onKeyDown ( e : React . KeyboardEvent < HTMLInputElement > ) {
18
+ if ( ! / [ \d \b \. \- \+ A r r o w L e f t A r r o w R i g h t ] / . test ( e . key ) && ! e . ctrlKey && ! e . metaKey ) {
19
+ e . preventDefault ( ) ;
20
+ }
21
+ }
22
+
23
+ function onBlur ( e : React . FocusEvent < HTMLInputElement > ) {
24
+ let value = e . target . value ;
25
+ if ( value === '' ) {
26
+ props . onChange ( props . min ) ;
27
+ } else {
28
+ const numericValue = parseFloat ( value ) ;
29
+ const minMaxValue = Math . min ( Math . max ( numericValue , props . min ) , props . max ) ;
30
+ const roundedValue = Math . round ( minMaxValue / props . step ) * props . step ;
31
+ props . onChange ( roundedValue ) ;
32
+ setInput ( roundedValue . toString ( ) ) ;
33
+ }
34
+ }
35
+
36
+ useEffect ( ( ) => {
37
+ setInput ( props . value . toString ( ) ) ;
38
+ } , [ props . value ] ) ;
39
+
40
+ return (
41
+ < div className = "h-full flex items-center" >
42
+ < div className = "w-full flex flex-col gap-y-2" >
43
+ < div className = 'flex items-center justify-between' >
44
+ < label className = "block text-sm font-medium text-gray-900" > { props . label } </ label >
45
+ < input
46
+ type = "text"
47
+ value = { input }
48
+ min = { props . min }
49
+ max = { props . max }
50
+ step = { props . step }
51
+ onKeyDown = { onKeyDown }
52
+ onChange = { ( e ) => setInput ( e . target . value ) }
53
+ onBlur = { onBlur }
54
+ disabled = { props . disabled }
55
+ className = "w-20 h-6 text-right text-sm text-gray-900 border border-transparent hover:border-gray-200 rounded-lg align-top"
56
+ />
57
+ </ div >
58
+ < input
59
+ type = "range"
60
+ value = { props . value * 100 }
61
+ min = { props . min * 100 }
62
+ max = { props . max * 100 }
63
+ step = { props . step * 100 }
64
+ onChange = { ( e ) => props . onChange ( parseInt ( e . target . value ) / 100 ) }
65
+ disabled = { props . disabled }
66
+ className = "w-full h-1 bg-gray-200 rounded-lg appearance-none cursor-pointer"
67
+ />
68
+ </ div >
69
+ </ div >
70
+ ) ;
71
+
72
+ }
0 commit comments