|
| 1 | +import React from 'react'; |
| 2 | +import fillColorWheel from '@radial-color-picker/color-wheel'; |
| 3 | +import Rotator from '@radial-color-picker/rotator'; |
| 4 | + |
| 5 | +const noop = () => {}; |
| 6 | + |
| 7 | +export default class ColorPicker extends React.Component { |
| 8 | + paletteRef = React.createRef(); |
| 9 | + rotatorRef = React.createRef(); |
| 10 | + elRef = React.createRef(); |
| 11 | + |
| 12 | + rotator = null; |
| 13 | + |
| 14 | + state = { |
| 15 | + isKnobIn: !this.props.initiallyCollapsed, |
| 16 | + isPaletteIn: !this.props.initiallyCollapsed, |
| 17 | + isPressed: false, |
| 18 | + isRippling: false, |
| 19 | + isDragging: false, |
| 20 | + }; |
| 21 | + |
| 22 | + static defaultProps = { |
| 23 | + hue: 0, |
| 24 | + saturation: 100, |
| 25 | + luminosity: 50, |
| 26 | + alpha: 1, |
| 27 | + step: 2, |
| 28 | + mouseScroll: false, |
| 29 | + variant: 'collapsible', // collapsible | persistent |
| 30 | + disabled: false, |
| 31 | + initiallyCollapsed: false, |
| 32 | + onInput: noop, |
| 33 | + onChange: noop, |
| 34 | + }; |
| 35 | + |
| 36 | + componentDidMount() { |
| 37 | + if (this.props.mouseScroll) { |
| 38 | + this.rotatorRef.current.addEventListener('wheel', this.onScroll); |
| 39 | + } |
| 40 | + |
| 41 | + if (this.props.initiallyCollapsed && this.props.variant === 'persistent') { |
| 42 | + console.warn(`Incorrect config: using variant="persistent" and initiallyCollapsed={true} at the same time is not supported.`); |
| 43 | + } |
| 44 | + |
| 45 | + const isConicGradientSupported = getComputedStyle(this.paletteRef.current) |
| 46 | + .backgroundImage |
| 47 | + .includes('conic'); |
| 48 | + |
| 49 | + if (!isConicGradientSupported) { |
| 50 | + fillColorWheel( |
| 51 | + this.paletteRef.current.firstElementChild, |
| 52 | + this.elRef.current.offsetWidth || 280 |
| 53 | + ); |
| 54 | + } |
| 55 | + |
| 56 | + this.rotator = new Rotator(this.rotatorRef.current, { |
| 57 | + angle: this.props.hue, |
| 58 | + onRotate: this.updateColor, |
| 59 | + onDragStart: () => { |
| 60 | + this.setState({ isDragging: true }); |
| 61 | + }, |
| 62 | + onDragStop: () => { |
| 63 | + this.setState({ isDragging: false }); |
| 64 | + }, |
| 65 | + }); |
| 66 | + } |
| 67 | + |
| 68 | + componentDidUpdate(prevProps) { |
| 69 | + if (this.props.hue !== prevProps.hue) { |
| 70 | + this.rotator.angle = this.props.hue; |
| 71 | + } |
| 72 | + } |
| 73 | + |
| 74 | + componentWillUnmount() { |
| 75 | + this.rotator.destroy(); |
| 76 | + this.rotator = null; |
| 77 | + |
| 78 | + if (this.props.mouseScroll) { |
| 79 | + this.rotatorRef.current.removeEventListener('wheel', this.onScroll); |
| 80 | + } |
| 81 | + } |
| 82 | + |
| 83 | + onScroll = ev => { |
| 84 | + if (this.state.isPressed || !this.state.isKnobIn) |
| 85 | + return; |
| 86 | + |
| 87 | + ev.preventDefault(); |
| 88 | + |
| 89 | + if (ev.deltaY > 0) { |
| 90 | + this.rotator.angle += this.props.step; |
| 91 | + } else { |
| 92 | + this.rotator.angle -= this.props.step; |
| 93 | + } |
| 94 | + |
| 95 | + this.updateColor(this.rotator.angle); |
| 96 | + }; |
| 97 | + |
| 98 | + onKeyUp = ev => { |
| 99 | + if (ev.key === 'Enter') { |
| 100 | + this.selectColor(); |
| 101 | + } |
| 102 | + }; |
| 103 | + |
| 104 | + onKeyDown = ev => { |
| 105 | + if (this.props.disabled || this.state.isPressed || !this.state.isKnobIn) |
| 106 | + return; |
| 107 | + |
| 108 | + const isIncrementing = ev.key === 'ArrowUp' || ev.key === 'ArrowRight'; |
| 109 | + const isDecrementing = ev.key === 'ArrowDown' || ev.key === 'ArrowLeft'; |
| 110 | + |
| 111 | + if (isIncrementing || isDecrementing) { |
| 112 | + ev.preventDefault(); |
| 113 | + |
| 114 | + let multiplier = isIncrementing ? 1 : -1; |
| 115 | + |
| 116 | + if (ev.ctrlKey) { |
| 117 | + multiplier *= 6; |
| 118 | + } else if (ev.shiftKey) { |
| 119 | + multiplier *= 3; |
| 120 | + } |
| 121 | + |
| 122 | + this.rotator.angle += this.props.step * multiplier; |
| 123 | + this.updateColor(this.rotator.angle); |
| 124 | + } |
| 125 | + }; |
| 126 | + |
| 127 | + updateColor = hue => { |
| 128 | + this.props.onInput(hue); |
| 129 | + }; |
| 130 | + |
| 131 | + rotateToMouse = ev => { |
| 132 | + if (this.state.isPressed || !this.state.isKnobIn || ev.target !== this.rotatorRef.current) |
| 133 | + return; |
| 134 | + |
| 135 | + this.rotator.setAngleFromEvent(ev); |
| 136 | + }; |
| 137 | + |
| 138 | + selectColor = () => { |
| 139 | + this.setState({ isPressed: true }); |
| 140 | + |
| 141 | + if (this.state.isPaletteIn && this.state.isKnobIn) { |
| 142 | + this.props.onChange(this.props.hue); |
| 143 | + this.setState({ isRippling: true }); |
| 144 | + } else { |
| 145 | + this.setState({ isPaletteIn: true }); |
| 146 | + } |
| 147 | + }; |
| 148 | + |
| 149 | + togglePicker = () => { |
| 150 | + if (this.props.variant !== 'persistent') { |
| 151 | + if (this.state.isKnobIn) { |
| 152 | + this.setState({ isKnobIn: false }); |
| 153 | + } else { |
| 154 | + this.setState({ |
| 155 | + isKnobIn: true, |
| 156 | + isPaletteIn: true, |
| 157 | + }); |
| 158 | + } |
| 159 | + } |
| 160 | + |
| 161 | + this.setState({ |
| 162 | + isRippling: false, |
| 163 | + isPressed: false, |
| 164 | + }); |
| 165 | + }; |
| 166 | + |
| 167 | + hidePalette = () => { |
| 168 | + if (!this.state.isKnobIn) { |
| 169 | + this.setState({ isPaletteIn: false }); |
| 170 | + } |
| 171 | + }; |
| 172 | + |
| 173 | + render() { |
| 174 | + const { disabled, hue, saturation, luminosity, alpha } = this.props; |
| 175 | + const { isDragging, isPressed, isPaletteIn, isKnobIn, isRippling } = this.state; |
| 176 | + |
| 177 | + const color = `hsla(${hue}, ${saturation}%, ${luminosity}%, ${alpha})`; |
| 178 | + |
| 179 | + return ( |
| 180 | + <div |
| 181 | + ref={this.elRef} |
| 182 | + className={`rcp ${isDragging ? 'dragging' : ''} ${disabled ? 'disabled' : ''}`.trim()} |
| 183 | + tabIndex={disabled ? -1 : 0} |
| 184 | + onKeyUp={this.onKeyUp} |
| 185 | + onKeyDown={this.onKeyDown} |
| 186 | + > |
| 187 | + <div |
| 188 | + ref={this.paletteRef} |
| 189 | + className={`rcp__palette ${isPaletteIn ? 'in' : 'out'}`} |
| 190 | + > |
| 191 | + <canvas /> |
| 192 | + </div> |
| 193 | + |
| 194 | + <div |
| 195 | + ref={this.rotatorRef} |
| 196 | + className="rcp__rotator" |
| 197 | + style={{ pointerEvents: disabled || isPressed || !isKnobIn ? 'none' : null }} |
| 198 | + onDoubleClick={this.rotateToMouse} |
| 199 | + > |
| 200 | + <div |
| 201 | + className={`rcp__knob ${isKnobIn ? 'in' : 'out'}`} |
| 202 | + onTransitionEnd={this.hidePalette} |
| 203 | + /> |
| 204 | + </div> |
| 205 | + |
| 206 | + <div |
| 207 | + className={`rcp__ripple ${isRippling ? 'rippling' : ''}`.trim()} |
| 208 | + style={{ borderColor: color }} |
| 209 | + /> |
| 210 | + |
| 211 | + <button |
| 212 | + type="button" |
| 213 | + className={`rcp__well ${isPressed ? 'pressed' : ''}`.trim()} |
| 214 | + style={{ backgroundColor: color }} |
| 215 | + onClick={this.selectColor} |
| 216 | + onAnimationEnd={this.togglePicker} |
| 217 | + /> |
| 218 | + </div> |
| 219 | + ); |
| 220 | + } |
| 221 | +} |
0 commit comments