All files / src/components/forms ColorSlider.tsx

56.25% Statements 18/32
0% Branches 0/2
57.14% Functions 4/7
54.84% Lines 17/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 1181x 1x                 1x         1x                                         1x                 42x     1x           42x   42x   42x                     42x               42x                             42x     805x 805x 805x                                     1x  
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 | undefined>(undefined);
 
  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;