All files / src/components/forms ColorSlider.tsx

0% Statements 0/32
0% Branches 0/2
0% Functions 0/7
0% Lines 0/31

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 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                                                                                                                                                                                                                                           
import React, { useCallback, useMemo, useRef } from "react";
import styled from "styled-components";
 
type ColorSliderProps = {
  value: number;
  steps: number;
  colorAtValue: (value: number) => string;
  onChange: (value: number) => void;
};
 
const Wrapper = styled.div`
  display: flex;
  position: relative;
`;
 
const Color = styled.div`
  flex-grow: 1;
  max-width: 32px;
  height: 32px;
  border: 1px solid transparent;
 
  &:first-of-type {
    border-top-left-radius: 4px;
    border-bottom-left-radius: 4px;
    min-width: 5px;
    flex-shrink: 0;
  }
 
  &:nth-last-of-type(2) {
    border-top-right-radius: 4px;
    border-bottom-right-radius: 4px;
    min-width: 5px;
    flex-shrink: 0;
  }
`;
 
const Handle = styled.div`
  position: absolute;
  width: 10px;
  margin-left: -5px;
  margin-top: -3px;
  height: 100%;
  border-radius: 4px;
  padding: 2px;
  box-shadow: 0px 1px 3px 1px rgba(0, 0, 0, 0.3);
  border: 1px solid ${(props) => props.theme.colors.input.background};
`;
 
const ColorSlider = ({
  value,
  onChange,
  steps,
  colorAtValue,
}: ColorSliderProps) => {
  const boundingRect = useRef<DOMRect>();
 
  const stepValues = useMemo(() => Array.from(Array(steps).keys()), [steps]);
 
  const onMouseMove = useCallback(
    (e: MouseEvent) => {
      Iif (boundingRect.current) {
        const x = e.clientX - boundingRect.current.left;
        const value = x / boundingRect.current.width;
        onChange(value);
      }
    },
    [onChange]
  );
 
  const onMouseUp = useCallback(
    (_e: MouseEvent) => {
      window.removeEventListener("mousemove", onMouseMove);
      window.removeEventListener("mouseup", onMouseUp);
    },
    [onMouseMove]
  );
 
  const onMouseDown = useCallback(
    (e: React.MouseEvent<HTMLDivElement, MouseEvent>) => {
      Iif (!(e.currentTarget instanceof HTMLElement)) {
        return;
      }
      boundingRect.current = e.currentTarget.getBoundingClientRect();
      const x = e.clientX - boundingRect.current.left;
      const value = x / boundingRect.current.width;
      onChange(value);
      window.addEventListener("mousemove", onMouseMove);
      window.addEventListener("mouseup", onMouseUp);
    },
    [onChange, onMouseMove, onMouseUp]
  );
 
  return (
    <Wrapper onMouseDown={onMouseDown}>
      {stepValues.map((stepIndex) => {
        const normalisedValue = stepIndex / (stepValues.length - 1);
        const color = colorAtValue(normalisedValue);
        return (
          <Color
            key={stepIndex}
            style={{
              backgroundColor: color,
            }}
          />
        );
      })}
      <Handle
        style={{
          left: `${value * 100}%`,
          backgroundColor: colorAtValue(value),
        }}
      />
    </Wrapper>
  );
};
 
export default ColorSlider;