<ColorPicker />
React component with fully controlled leaves. Essential state centralised.
by mcsf
HTML
<div id="app"></div>
CSS
*:focus {
outline: #f66 dashed;
}
#app {
padding: 1em;
max-width: 20em;
margin: auto;
font-family: sans-serif;
}
.color-picker {
display: flex;
justify-content: center;
flex-direction: column;
text-align: center;
}
.color-picker > * {
margin-bottom: 1em;
}
.alpha, .hue {
display: flex;
justify-content: center;
}
.alpha input, .hue input {
flex-grow: 1;
margin-left: 15px;
}
.checkers {
position: absolute;
z-index: -1;
top: 0; bottom: 0; left: 0; right: 0;
background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='50' height='50'><rect fill='black' x='0' y='0' width='50%' height='50%'/><rect fill='white' x='50%' y='0' width='50%' height='50%'/><rect fill='white' x='0' y='50%' width='50%' height='50%'/><rect fill='black' x='50%' y='50%' width='50%' height='50%'/></svg>");
}
form div {
display: inline-block;
}
form input {
margin: 0 5px;
max-width: 6em;
}
form input[type="number"] {
max-width: 3em;
}
.icon {
display: inline-block;
width: 30px;
height: 30px;
line-height: 30px;
border: 1px solid black;
border-radius: 100%;
background-color: white;
}
.icon.alpha {
background: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='10' height='10'><rect fill='black' x='0' y='0' width='50%' height='50%'/><rect fill='white' x='50%' y='0' width='50%' height='50%'/><rect fill='white' x='0' y='50%' width='50%' height='50%'/><rect fill='black' x='50%' y='50%' width='50%' height='50%'/></svg>");
}
.icon.hue {
background: linear-gradient(to right, #f00 0%, #ff0 17%, #0f0 33%, #0ff 50%, #00f 67%, #f0f 83%, #f00 100%);
}
React
const COLOR_MODES = {
hex: {
fields: [
{ name: 'hex', type: 'text', label: 'Hexadecimal' }
],
encode: hex => [hex],
decode: ([hex]) => hex,
normalize: normalizeHex,
},
rgb: {
fields: [
{ name: 'r', type: '8bit', label: 'Red' },
{ name: 'g', type: '8bit', label: 'Green' },
{ name: 'b', type: '8bit', label: 'Blue' },
],
encode: hex2rgb,
decode: rgb2hex,
},
hsl: {
fields: [
{ name: 'h', type: 'polar', label: 'Hue' },
{ name: 's', type: 'percent', label: 'Saturation' },
{ name: 'l', type: 'percent', label: 'Lightness' },
],
encode: hex2hsl,
decode: hsl2hex,
},
}
const COLOR_FIELDS = {
'text': { type: 'text' },
'8bit': { type: 'number', min: 0, max: 255 },
'percent': { type: 'number', min: 0, max: 100 },
'polar': { type: 'number', min: 0, max: 360 },
}
function convert(src, dst, values) {
if (src === dst) return values
const srcMode = COLOR_MODES[src]
const dstMode = COLOR_MODES[dst]
return dstMode.encode(srcMode.decode(values))
}
class Picker extends React.Component {
constructor(props) {
super(props)
this.state = {
mode: 'hex',
values: ['#ffff00'],
alpha: 100,
}
}
setValues = (values) => {
this.setState({values})
}
setAlpha = (alpha) => {
this.setState({alpha})
}
switchMode = () => {
const modes = Object.keys(COLOR_MODES)
const index = modes.findIndex(m => m === this.state.mode)
const mode = modes[(index + 1) % modes.length]
const values = convert(
this.state.mode,
mode,
this.state.values
)
this.setState({mode, values})
}
render() {
return (
<div className="color-picker">
<Color
mode={this.state.mode}
values={this.state.values}
alpha={this.state.alpha} />
<Alpha
alpha={this.state.alpha}
setAlpha={this.setAlpha} />
<HueSlider
mode={this.state.mode}
...