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