$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
This Pen doesn't use any external JavaScript resources.