Bin
2025-12-16 9e0b2ba2c317b1a86212f24cbae3195ad1f3dbfa
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
import type React from "react";
import { type FC, useEffect, useRef, useState } from "react";
import { Tooltip } from "@humansignal/ui";
import { IconInfoConfig } from "@humansignal/icons";
import { cn } from "../../../utils/bem";
 
import "./Slider.scss";
 
export interface SliderProps {
  description?: string;
  showInput?: boolean;
  info?: string;
  max: number;
  min: number;
  value: number;
  step?: number;
  onChange: (e: React.FormEvent<HTMLInputElement>) => void;
}
 
export const Slider: FC<SliderProps> = ({
  description,
  info,
  max,
  min,
  value,
  step = 1,
  showInput = true,
  onChange,
}) => {
  const sliderRef = useRef<HTMLDivElement>();
  const [valueError, setValueError] = useState<number | string | undefined>();
 
  useEffect(() => {
    changeBackgroundSize();
  }, [value]);
 
  const changeBackgroundSize = () => {
    if (sliderRef.current) sliderRef.current.style.backgroundSize = `${((value - min) * 100) / (max - min)}% 100%`;
  };
 
  const handleChangeInputValue = (e: React.FormEvent<HTMLInputElement>) => {
    setValueError(undefined);
 
    // match only numbers and dot
    const partialFloat = e.currentTarget.value.match(/^[0-9]*\.$/);
 
    if (partialFloat) {
      setValueError(e.currentTarget.value);
      return;
    }
 
    const noZero = e.currentTarget.value.match(/^\.[0-9]*$/);
    const normalizedValue = noZero ? `0${e.currentTarget.value}` : e.currentTarget.value;
 
    const newValue = Number.parseFloat(normalizedValue);
 
    if (isNaN(newValue)) {
      setValueError(e.currentTarget.value);
      return;
    }
    if (newValue > max || newValue < min) {
      setValueError(newValue);
    } else {
      onChange(e);
    }
  };
 
  const renderInput = () => {
    return (
      <div className={cn("audio-slider").elem("control").toClassName()}>
        <div className={cn("audio-slider").elem("info").toClassName()}>
          {description}
          {info && (
            <Tooltip title={info}>
              <IconInfoConfig />
            </Tooltip>
          )}
        </div>
        {showInput && (
          <input
            className={cn("audio-slider")
              .elem("input")
              .mod(
                valueError !== undefined &&
                  (typeof valueError === "string" || valueError > max || valueError < min) && { error: "control" },
              )
              .toClassName()}
            type="text"
            min={min}
            max={max}
            value={valueError === undefined ? value : valueError}
            onChange={handleChangeInputValue}
          />
        )}
      </div>
    );
  };
 
  return (
    <div className={cn("audio-slider").toClassName()}>
      <input
        ref={sliderRef as any}
        className={cn("audio-slider").elem("range").toClassName()}
        type="range"
        min={min}
        max={max}
        step={step}
        value={value}
        onChange={handleChangeInputValue}
      />
      {renderInput()}
    </div>
  );
};