Skip to content

Commit f9668cb

Browse files
authored
Adds InputWithSlider component (#37)
1 parent 36bc7ba commit f9668cb

File tree

1 file changed

+72
-0
lines changed

1 file changed

+72
-0
lines changed

components/InputWithSlider.tsx

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
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\.\-\+ArrowLeftArrowRight]/.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

Comments
 (0)