logo

Sample Code: Create a volume control bar with React Hooks, RxJS and Styled Components

import React, { useEffect, useRef, useState } from "react";
import ReactDOM from "react-dom";
import styled from "styled-components";
import { fromEvent, merge } from "rxjs";
import { switchMap, takeUntil, map } from "rxjs/operators";

const VolumeWrapper = styled.div`
  position: relative;
  background: lightgray;
  height: 8px;
  width: 100px;
  border-radius: 5px;
`;

const Padding = styled.div`
  height: 20px;
  top: -5px;
  width: 100%;
  position: absolute;
`;

const Progress = styled.div`
  position: absolute;
  width: 100%;
  background-color: #0000cd;
  transform: scaleX(${props => props.value});
  transform-origin: 0 0;
  direction: ltr;
  left: 0;
  top: 0;
  height: 8px;
  border-radius: 5px;
`;

const Thumb = styled.span`
  position: absolute;
  left: -10px;
  width: 20px;
  height: 20px;
  background-color: white;
  top: -6px;
  box-shadow: 0 1px 2px 1px gray;
  border-radius: 50%;
  transform: translateX(${props => props.thumbPosition}px);
`;

const Volume = () => {
  const volumeRef = useRef(null);
  const [volume, setVolume] = useState(0);
  const [thumbPos, setThumPos] = useState(0);

  useEffect(() => {
    const { current: ref } = volumeRef;
    const end$ = merge(fromEvent(ref, "mouseup"), fromEvent(window, "mouseup"));
    const mousemove$ = fromEvent(ref, "mousemove").pipe(takeUntil(end$));
    const mouseDown$ = fromEvent(ref, "mousedown").pipe(
      switchMap(() => mousemove$),
      map(e => e.offsetX / ref.clientWidth),
      map(ratio => Math.min(Math.max(ratio, 0), 1))
    );
    const draggingSub = mouseDown$.subscribe(ratio => {
      setVolume(ratio);
      setThumPos(ratio * ref.clientWidth);
    });
    return () => {
      draggingSub.unsubscribe();
    };
  }, []);

  return (
    <VolumeWrapper>
      <Progress value={volume} />
      <Thumb thumbPosition={thumbPos} />
      <Padding ref={volumeRef} />
    </VolumeWrapper>
  );
};

ReactDOM.render(<Volume />, document.querySelector("#app"));