File size: 3,668 Bytes
36bc1d5
89493ee
 
 
 
 
 
 
36bc1d5
 
89493ee
 
36bc1d5
89493ee
 
36bc1d5
89493ee
 
 
 
 
36bc1d5
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
89493ee
 
36bc1d5
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
89493ee
 
 
 
 
36bc1d5
 
 
 
89493ee
 
36bc1d5
 
 
 
 
89493ee
 
 
 
 
 
 
f3f5382
89493ee
 
 
 
 
 
 
 
 
 
 
 
36bc1d5
 
f3f5382
89493ee
 
 
 
 
 
 
 
 
 
 
f3f5382
89493ee
 
 
 
 
 
 
 
 
 
 
 
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
129
130
131
132
133
import { useState, useEffect, useRef } from "react";

const NumberInput = ({
  label,
  value,
  onChange,
  min = 1,
  max = 48,
  step = 1,
  allowDecimals = false,
  className = "",
  compact = false,
  disabled = false,
}) => {
  const [inputValue, setInputValue] = useState(value?.toString() || "");
  const prevDisabledRef = useRef(disabled);

  useEffect(() => {
    setInputValue(value?.toString() || "");
  }, [value]);

  useEffect(() => {
    if (disabled && !prevDisabledRef.current && allowDecimals) {
      const numValue = parseFloat(inputValue);
      if (
        inputValue === "" ||
        inputValue === "." ||
        isNaN(numValue) ||
        numValue < min ||
        numValue > max
      ) {
        const clampedValue = isNaN(numValue)
          ? min
          : Math.max(min, Math.min(max, numValue));
        setInputValue(clampedValue.toString());
        onChange(clampedValue);
      }
    }
    prevDisabledRef.current = disabled;
  }, [disabled, allowDecimals, inputValue, min, max, onChange]);

  const handleChange = (e) => {
    const inputVal = e.target.value;

    if (allowDecimals) {
      if (inputVal === ".") return;

      setInputValue(inputVal);

      if (inputVal !== "" && !inputVal.endsWith(".")) {
        const numValue = parseFloat(inputVal);
        if (!isNaN(numValue)) {
          onChange(numValue);
        }
      }
    } else {
      const cleanedInput = inputVal.includes(".")
        ? inputVal.split(".")[0]
        : inputVal;
      setInputValue(cleanedInput);

      if (cleanedInput === "") return;
      const numValue = parseInt(cleanedInput);
      if (!isNaN(numValue)) {
        const clampedValue = Math.max(min, Math.min(max, numValue));
        onChange(clampedValue);
        if (clampedValue !== numValue) {
          setInputValue(clampedValue.toString());
        }
      }
    }
  };

  const handleBlur = () => {
    if (
      inputValue === "" ||
      isNaN(allowDecimals ? parseFloat(inputValue) : parseInt(inputValue))
    ) {
      onChange(min);
      setInputValue(min.toString());
    } else if (allowDecimals) {
      const numValue = parseFloat(inputValue);
      const clampedValue = Math.max(min, Math.min(max, numValue));
      onChange(clampedValue);
      setInputValue(clampedValue.toString());
    }
  };

  return (
    <div className={className}>
      {label && (
        <label
          className={`block font-medium text-foreground ${
            compact ? "text-xs mb-1" : "text-sm mb-2"
          }`}
        >
          {label}
        </label>
      )}
      <div className="relative">
        <input
          type="number"
          value={inputValue}
          onChange={handleChange}
          onBlur={handleBlur}
          step={step}
          disabled={disabled}
          className={`w-full bg-background border-2 border-secondary-300 hover:bg-primary-50 hover:shadow-lg focus:outline-none focus:ring-2 focus:ring-primary-500 transition-all duration-200 text-foreground font-medium disabled:opacity-50 disabled:cursor-not-allowed ${
            compact ? "p-2 rounded-lg text-sm" : "p-4 rounded-xl"
          }`}
          min={min}
          max={max}
        />
        <div
          className={`absolute right-2 top-1/2 transform -translate-y-1/2 ${
            compact ? "right-1" : "right-3"
          }`}
        >
          <div
            className={`text-foreground bg-background px-1 py-0.5 rounded border border-secondary-200 ${
              compact ? "text-xs px-1" : "text-xs px-2 py-1"
            }`}
          >
            {min}-{max}
          </div>
        </div>
      </div>
    </div>
  );
};

export default NumberInput;