$width: 250px;
$spacing: 15px;
$color-red: red;
$color-black: #021f27;
$color-light-black: #243135;
$color-lighter-black: #3a4d52;

body, html {
  width: 100%;
  height: 100%;
  display: flex;
  align-items: center;
  justify-content: center;
  font-family: "Open Sans", sans-serif;
  background-color: #fafcff;
  background-image: 
    radial-gradient(circle, transparent, #afb5bf), 
    linear-gradient(transparent, #d3d8e0);
}

.color-picker {
  background-color: white;
  border-radius: 5px;
  box-shadow: 0 0 15px rgba(0, 0, 0, 0.2);
  
  .shade-selector, 
  .hue-selector {
    position: relative;
    width: $width;
    margin: $spacing;
    cursor: pointer;
    user-select: none;
    border-radius: 3px;
    box-shadow: 
      inset 0 0 1px rgba($color-black, 0.4), 
      inset 0 0 5px rgba($color-lighter-black, 0.2);
  }
  
  .shade-selector {
    height: $width;
    background:
      linear-gradient(transparent, black),
      linear-gradient(90deg, white, transparent),
      linear-gradient(var(--color), var(--color));
  }

  .hue-selector {
    height: 8px;
    background-image: linear-gradient(90deg, red, #ff0, lime, cyan, blue, #f0f, red);

    .pointer {top: 4px}
  }

  .pointer {
    position: absolute;
    z-index: 1;
    width: 16px;
    height: 16px;
    border-radius: 10px;
    border: 2px solid white;
    transform: translate(-10px, -10px);
    box-shadow: 0 0 5px rgba(black, 0.2), inset 0 0 5px rgba(black, 0.2);
    background-color: var(--color);
  }
}

.info-box {
  display: flex;
  justify-content: space-between;
  margin: 30px 10px 10px;

  div {
    position: relative;
    display: flex;
    align-items: center;
    justify-content: center;
    flex-basis: 50px;
    font-size: 12px;
    height: 28px;
    border: 1px solid rgba(black, 0.1);
    border-radius: 6px;
    user-select: all;

    &::before {
      content: attr(title);
      position: absolute;
      top: -18px;
      font-weight: bold;
      font-size: 10px;
    }

    &:first-child {
      flex-basis: 80px;
    }
  }
}
View Compiled
import React, {useCallback, useState, useEffect, useRef, useMemo, memo} from 'https://cdn.skypack.dev/react@17.0.2';
import ReactDOM from 'https://cdn.skypack.dev/react-dom@17.0.2';
import PropTypes from 'https://cdn.skypack.dev/prop-types@15.7.2';
import convert from 'https://cdn.skypack.dev/color-convert@2.0.1';

// Webrix.js components - See https://webrix.amdocs.com/
import {Movable} from 'https://cdn.skypack.dev/webrix@1.4.0/components';
import {useDimensions} from 'https://cdn.skypack.dev/webrix@1.4.0/hooks';

const {transform, trackpad, update} = Movable.Operations;
const {clamp, map} = Movable.Transformers;

const HueSelector = memo(({hsv, onChange}) => {
    const movable = useRef();
    const {width} = useDimensions(movable);
    const [left, setLeft] = useState(0);

    const props = Movable.useMove(useMemo(() => [
        trackpad(movable),
        transform(v => v.left, clamp(0, width)),
        update(next => {
            setLeft(next);
            onChange(convert.hsv.rgb(map(0, width, 0, 360)(next), hsv[1], hsv[2]));
        }),
    ], [onChange, width, hsv]));

    useEffect(() => {
        setLeft(map(0, 360, 0, width)(hsv[0]));
    }, [width]);

    return (
      <Movable className='hue-selector' ref={movable} {...props}>
        <div className='pointer' style={{left, '--color': `rgb(${convert.hsv.rgb(map(0, width, 0, 360)(left), 100, 100)})`}}/>
      </Movable>
    );
});

const ShadeSelector = memo(({onChange, hsv}) => {
    const movable = useRef();
    const {width, height} = useDimensions(movable);
    const hex = useMemo(() => '#' + convert.hsv.hex(hsv[0], 100, 100), [hsv[0]]);
    const [{top, left}, setPosition] = useState({});

    const props = Movable.useMove(useMemo(() => [
        trackpad(movable),
        transform(({top, left}) => ({
            top: clamp(0, height)(top),
            left: clamp(0, width)(left),
        })),
        update(({top, left}) => {
            setPosition({top, left});
            onChange(convert.hsv.rgb(
                hsv[0],
                map(0, width, 0, 100)(left),
                map(0, height, 100, 0)(top),
            ))
        }),
    ], [onChange, width, height, setPosition, hsv]));

    useEffect(() => {
        setPosition({
            top: map(100, 0, 0, height)(hsv[2]),
            left: map(0, 100, 0, width)(hsv[1]),
        });
    }, [width, height]);

    return (
      <Movable className='shade-selector' ref={movable} style={{'--color': hex}} {...props}>
        <div className='pointer' style={{top, left, '--color': `rgb(${convert.hsv.rgb(hsv)})`}}/>
      </Movable>
    );
});

const InfoBox = ({rgb}) => (
  <div className='info-box'>
    <div title='Hex'>#{convert.rgb.hex(rgb)}</div>
    <div title='R'>{rgb[0]}</div>
    <div title='G'>{rgb[1]}</div>
    <div title='B'>{rgb[2]}</div>
  </div>
);

const ColorPicker = ({color, onChange}) => {
  const hsv = convert.rgb.hsv(color);
  return (
    <div className='color-picker'>
      <ShadeSelector hsv={hsv} onChange={onChange}/>
      <HueSelector hsv={hsv} onChange={onChange}/>
      <InfoBox rgb={color}/>
    </div>
  );
};

const App = () => {
    const [color, setColor] = useState([44,192,158]);
    return (
        <ColorPicker color={color} onChange={setColor}/>
    );
};

ReactDOM.render(<App/>, document.body);
View Compiled

External CSS

  1. https://fonts.googleapis.com/css2?family=Open+Sans&amp;display=swap

External JavaScript

This Pen doesn't use any external JavaScript resources.