Spaces:
Runtime error
Runtime error
/* | |
* Copyright (c) Meta Platforms, Inc. and affiliates. | |
* | |
* This source code is licensed under the Chameleon License found in the | |
* LICENSE file in the root directory of this source tree. | |
*/ | |
import { ChangeEvent, useEffect, useState } from "react"; | |
export type InputRangeProps = { | |
value: number; | |
step?: number; | |
min?: number; | |
max?: number; | |
integerOnly?: boolean; | |
slider?: boolean; | |
optional?: boolean; | |
placeholder?: string; | |
label?: string; | |
className?: string; | |
onValueChange: (n: any) => void; | |
}; | |
export function InputRange({ | |
value, | |
onValueChange, | |
step = 0.1, | |
min = 0, | |
max = 1, | |
integerOnly = false, | |
slider = true, | |
optional = false, | |
placeholder, | |
label, | |
className, | |
}: InputRangeProps) { | |
const [tempValue, setTempValue] = useState<string>(""); | |
const [tempOptionalValue, setTempOptionalValue] = useState<number>(0); | |
const [valid, setValid] = useState<boolean>(true); | |
const [skipped, setSkipped] = useState<boolean>(optional); | |
/* | |
* Here we first validate the value and then run the update callback if the value is valid. | |
*/ | |
function validate(valueString: string, updateFn: (value: any) => void) { | |
setTempValue(valueString); | |
const parseFn = integerOnly ? parseInt : parseFloat; | |
const n = parseFn(valueString) || NaN; | |
const integerCheck = integerOnly ? Math.floor(n) === n : true; | |
if (skipped) { | |
updateFn(null); | |
setValid(true); | |
} else if (n && n >= min && n <= max && integerCheck) { | |
updateFn(n); | |
setValid(true); | |
} else { | |
setValid(false); | |
} | |
} | |
function handleOptional(evt: ChangeEvent<HTMLInputElement>) { | |
// if checked, skip should be false | |
if (evt.currentTarget.checked === skipped) { | |
setSkipped(!skipped); | |
} | |
} | |
useEffect(() => { | |
setTempValue(`${value}`); | |
}, [value, setTempValue]); | |
useEffect(() => { | |
if (skipped) { | |
setTempOptionalValue(value); | |
validate("", onValueChange); | |
} else if (optional && !value) { | |
validate(`${tempOptionalValue || min}`, onValueChange); | |
} | |
}, [skipped, tempOptionalValue, setTempOptionalValue]); | |
const input = ( | |
<div className={`flex flex-row items-center gap-4 ${className}`}> | |
{slider && ( | |
<input | |
type="range" | |
min={min} | |
max={max} | |
value={tempValue} | |
step={step} | |
disabled={skipped} | |
className={`range flex-1`} | |
onChange={(evt) => validate(evt.currentTarget.value, onValueChange)} | |
/> | |
)} | |
<input | |
type="text" | |
placeholder={placeholder || ""} | |
value={tempValue} | |
disabled={skipped} | |
onChange={(evt) => validate(evt.currentTarget.value, onValueChange)} | |
className={`input ${valid ? "border-gray-200" : "border-red-300"} w-24`} | |
/> | |
</div> | |
); | |
return label ? ( | |
<div className="form-control mt-6 flex flex-row gap-2 items-center"> | |
<label className="label font-semibold flex flex-1 leading-5 gap-2"> | |
{optional && ( | |
<input | |
type="checkbox" | |
checked={!skipped} | |
className="checkbox checkbox-primary" | |
onChange={handleOptional} | |
/> | |
)} | |
<span className={`flex-1 `}> | |
{label}{" "} | |
{skipped && <div className="text-xs text-gray-400">(skipped)</div>} | |
</span> | |
</label> | |
<div className={`flex-1 flex-grow-3 ${skipped && "hidden"}`}>{input}</div> | |
</div> | |
) : ( | |
input | |
); | |
} | |