Repository: motion-canvas/motion-canvas
File: packages/ui/src/components/controls/Slider.tsx (TypeScript)

Code before:

export interface SliderProps {
  value?: number;
  onChange?: (value: number) => void;
}

export function Slider({value, onChange}: SliderProps) {
  const [internalValue, setInternalValue] = useState(value);

  useEffect(() => {
    setInternalValue(value);
  }, [value]);

  return (
    <div
      className={styles.slider}
      onPointerDown={event => {
        if (event.button === MouseButton.Left) {
          event.preventDefault();
          event.stopPropagation();
          event.currentTarget.setPointerCapture(event.pointerId);

          const rect = event.currentTarget.getBoundingClientRect();
          // +2 & +4 to account for thumb offset
          const y = event.clientY - rect.y + 2;
          const newInternalValue = 1 - y / (rect.height + 4);
          setInternalValue(clamp(0, 1, newInternalValue));
        }
      }}
      onPointerMove={event => {
        if (event.currentTarget.hasPointerCapture(event.pointerId)) {
          event.stopPropagation();

          const rect = event.currentTarget.getBoundingClientRect();
          // +2 & +4 to account for thumb offset
          const y = event.clientY - rect.y + 2;
          const newInternalValue = 1 - y / (rect.height + 4);
          setInternalValue(clamp(0, 1, newInternalValue));
        }
      }}
      onPointerUp={event => {
        if (event.button === MouseButton.Left) {
          event.stopPropagation();
          event.currentTarget.releasePointerCapture(event.pointerId);

          onChange?.(internalValue);
        }
      }}
    >
      <div className={styles.sliderTrack}>
        <div

Review comment (bug):
The thumb offset is not necessary because the thumb should be able to overflow the slider in extreme positions:
![image](https://github.com/motion-canvas/motion-canvas/assets/64662184/d64e4798-88b1-4f76-be76-30b341fe3dea)


Code after:

export interface SliderProps {
  value?: number;
  onChange?: (value: number) => void;
}

export function Slider({value, onChange}: SliderProps) {
  const [internalValue, setInternalValue] = useState(value);

  useEffect(() => {
    setInternalValue(value);
  }, [value]);

  return (
    <div
      className={styles.slider}
      onPointerDown={event => {
        if (event.button === MouseButton.Left) {
          event.preventDefault();
          event.stopPropagation();
          event.currentTarget.setPointerCapture(event.pointerId);

          const rect = event.currentTarget.getBoundingClientRect();
          const y = clamp(0, rect.height, event.clientY - rect.y);
          const newInternalValue = 1 - y / rect.height;
          setInternalValue(clamp(0, 1, newInternalValue));
        }
      }}
      onPointerMove={event => {
        if (event.currentTarget.hasPointerCapture(event.pointerId)) {
          event.stopPropagation();

          const rect = event.currentTarget.getBoundingClientRect();
          const y = clamp(0, rect.height, event.clientY - rect.y);
          const newInternalValue = 1 - y / rect.height;
          setInternalValue(clamp(0, 1, newInternalValue));
        }
      }}
      onPointerUp={event => {
        if (event.button === MouseButton.Left) {
          event.stopPropagation();
          event.currentTarget.releasePointerCapture(event.pointerId);

          onChange?.(internalValue);
        }
      }}
    >
      <div className={styles.sliderTrack}>
        <div
          className={styles.sliderProgress}
          style={`height: ${internalValue * 100}%`}