File size: 3,482 Bytes
7362797
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
/*
* 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
  );
}