All files / webview-app/src/components/PropertiesPanel NumberField.tsx

73.91% Statements 17/23
75% Branches 6/8
80% Functions 4/5
73.91% Lines 17/23

Press n or j to go to the next uncovered block, b, p or k for the previous block.

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                            2x     34x 34x   34x 12x 12x     34x 5x   5x 5x 1x 1x     4x 4x     34x                       34x               5x            
import { useEffect, useState } from "react";
 
import { FormField } from "@/components/PropertiesPanel/FormField";
import { cn } from "@/lib/utils";
import { type NumericInputOptions, parseClampedNumericInput } from "@/schemas/parsers";
 
interface NumberFieldProps {
  label: string;
  value: number;
  range: NumericInputOptions;
  onChange: (value: number) => void;
}
 
const INPUT_CLASS_NAME =
  "flex h-8 w-full rounded border border-vscode-panel-border bg-vscode-input-background px-2 py-1 text-sm text-vscode-input-foreground outline-none transition-colors placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50";
 
export function NumberField({ label, value, range, onChange }: NumberFieldProps) {
  const [inputValue, setInputValue] = useState(String(value));
  const [error, setError] = useState<string | null>(null);
 
  useEffect(() => {
    setInputValue(String(value));
    setError(null);
  }, [value]);
 
  const handleChange = (nextValue: string) => {
    setInputValue(nextValue);
 
    const parsed = parseClampedNumericInput(nextValue, range);
    if (parsed === null) {
      setError("Enter a valid number");
      return;
    }
 
    setError(null);
    onChange(parsed);
  };
 
  const handleBlur = () => {
    const parsed = parseClampedNumericInput(inputValue, range);
 
    if (parsed === null) {
      setInputValue(String(value));
      setError(null);
      return;
    }
 
    setInputValue(String(parsed));
  };
 
  return (
    <FormField label={label} error={error}>
      <input
        type="text"
        inputMode="numeric"
        className={cn(INPUT_CLASS_NAME, error && "border-destructive")}
        value={inputValue}
        aria-invalid={error ? "true" : "false"}
        onChange={(event) => handleChange(event.target.value)}
        onBlur={handleBlur}
      />
    </FormField>
  );
}