src/index.js
/**
* @file ColorPicker
* @author jingyuanZhang<[email protected]>
* @author leon<[email protected]>
*/
import React, {PropTypes} from 'react';
import InputComponent from 'melon-core/InputComponent';
import {create} from 'melon-core/classname/cxBuilder';
import Icon from 'melon/Icon';
import Button from 'melon/Button';
import dom from 'melon/common/util/dom';
import * as util from './util';
const cx = create('ColorPicker');
/**
* melon 选色器
*/
export default class ColorPicker extends InputComponent {
constructor(props, context) {
super(props, context);
this.onSubmit = this.onSubmit.bind(this);
this.onHueChange = this.onHueChange.bind(this);
this.onSaturationChange = this.onSaturationChange.bind(this);
this.onBoxClick = this.onBoxClick.bind(this);
this.onLabelClick = this.onLabelClick.bind(this);
this.onHexChange = this.onHexChange.bind(this);
this.isRawInputError = this.isRawInputError.bind(this);
this.onClickAway = this.onClickAway.bind(this);
this.state = {
...this.state,
satValue: '00FFE7',
color: '00FFE7',
open: false,
error: false
};
}
componentWillUnmount() {
dom.off(document, 'click', this.onClickAway);
}
/**
* 当选色器打开后,根据点击事件位置判断是都关闭选色器,点击选色器组件外关闭选择器
*
* @param {Object} e 点击事件
*/
onClickAway(e) {
let open = this.state.open;
// alert(dom.contains(this.refs.picker, e.target));
if (open && !dom.contains(this.refs.picker, e.target)) {
this.setState({open: false}, () => {
dom.off(document, 'click', this.onClickAway);
});
}
}
/**
* 点击TextBox时打开颜色选择器,当选择器打开后,再次点击关闭
*
* @param {Object} e 点击事件
*/
onLabelClick(e) {
let open = this.state.open;
this.setState({
open: !open,
top: e.pageY + 20,
left: e.pageX + 20
}, () => {
dom.on(document, 'click', this.onClickAway);
});
}
/**
* 检查用户输入的rgb是否正确
*
* @protected
* @param {number} color 页数
* @return {boolean} error 验证结果
*/
isRawInputError(color) {
return color !== '' && color.length !== 3 && color.length !== 6;
}
/**
* 提交所选颜色
*
* @protected
*/
onSubmit() {
if (this.state.error) {
return;
}
let color = this.state.color;
this.setState({open: false});
super.onChange({
type: 'change',
target: this,
value: color ? '#' + color : ''
});
}
/**
* 用户点击色调块(hue div)选择基本色调时的响应函数
*
* @param {Object} e 用户点击色调块事件
*/
onHueChange(e) {
const {top, left} = this.state;
let huel = e.pageX - left;
let huet = e.pageY - top - 160;
let red = 0;
let green = 0;
let blue = 0;
if ((huel >= 0 && huel <= 240) && (huet >= 0 && huet <= 16)) {
let quyu = parseInt(huel / 40, 10);
let yushu = huel % 40;
switch (quyu) {
case 0:
red = 255;
green = Math.round(255 * yushu / 40);
blue = 0;
break;
case 1:
red = Math.round(255 * (1 - yushu / 40));
green = 255;
blue = 0;
break;
case 2:
red = 0;
green = 255;
blue = Math.round(255 * yushu / 40);
break;
case 3:
red = 0;
green = Math.round(255 * (1 - yushu / 40));
blue = 255;
break;
case 4:
red = Math.round(255 * yushu / 40);
green = 0;
blue = 255;
break;
case 5:
red = 255;
green = 0;
blue = Math.round(255 * (1 - yushu / 40));
break;
default:
break;
}
const colorHEX = `${util.toHex(red)}${util.toHex(green)}${util.toHex(blue)}`;
this.setState({
color: colorHEX,
satValue: colorHEX
});
}
}
/**
* 用户点击了选色器下面的颜色样例后的行为
*
* @protected
* @param {Object} opt 即将验证value值的对象
*/
onBoxClick(opt) {
let err = this.isRawInputError(opt.val);
this.setState({
color: opt.val,
satValue: opt.satVal,
error: err
});
}
/**
* 当用户手动修改输入框的颜色值,旁边的颜色展示区跟着变化
*
* @protected
* @param {Object} e 即将验证value值的对象
*/
onHexChange(e) {
let rgb = e.target.value;
if (rgb.length > 6) {
return;
}
rgb = rgb.replace(/[^0-9a-fA-F]/gi, '');
this.setState({color: rgb.toUpperCase()});
}
/**
* 当用户点击饱和度(saturation)区域时,输入框和颜色展示区显示相应颜色
*
* @protected
* @param {Event} e 事件
*/
onSaturationChange(e) {
const {top, left, satValue} = this.state;
let satl = e.pageX - left;
let satt = e.pageY - top;
if ((satl >= 0 && satl <= 240) && (satt >= 0 && satt <= 150)) {
let percentH = satt / 150;
let percentW = satl / 240;
let colorHEX = [0, 2, 4].reduce((result, item) => {
let part = parseInt('0x' + satValue.slice(item, item + 2), 16);
part = 255 - (255 - part) * percentW;
part = util.toHex(Math.round(part * (1 - percentH)));
return result + part;
}, '');
this.setState({color: colorHEX});
}
}
renderBox(box) {
const {
color,
hue
} = box;
return (
<span
key={color}
className={cx.getPartClassName('box')}
style={{
backgroundColor: '#' + color
}}
onClick={() => this.onBoxClick({val: color, satVal: hue})}>
</span>
);
}
renderBoxes(boxes) {
return (
<div className={cx.getPartClassName('boxes')}>
{boxes.map(item => this.renderBox(item))}
</div>
);
}
/**
* 渲染
*
* @public
* @return {React.Element}
*/
render() {
const {
satValue,
open,
top,
left,
color,
value,
error
} = this.state;
const {
placeholder,
boxes
} = this.props;
return (
<div ref="picker" className={cx(this.props).build()}>
<label onClick={this.onLabelClick}>
<span className={cx.getPartClassName('label-placeholder')}>
{value ? value : placeholder}
</span>
<Icon icon='expand-more'/>
</label>
<div
className={cx.getPartClassName('picker')}
style={{
display: open ? 'block' : 'none',
top: top,
left: left
}}
onClick={this.onSaturationChange}>
<div className={cx.getPartClassName('saturation')}
style={{
backgroundColor: '#' + satValue
}}>
<div className={cx.getPartClassName('white')}>
<div className={cx.getPartClassName('black')}></div>
</div>
</div>
<div className={cx.getPartClassName('hue')} onClick={this.onHueChange}></div>
<span className={cx.getPartClassName('label')}>Hex</span>
<input
className={cx.getPartClassName('rgbstr')}
type="text"
onBlur={() => {
this.setState({
error: this.isRawInputError(color)
});
}}
ref="rgbStr"
placeholder={placeholder}
value={color}
onChange={this.onHexChange}
style={{
border: error ? '1px red solid' : null
}}/>
<div
className={cx.getPartClassName('result')}
style={{
backgroundColor: '#' + color
}}>
</div>
<Button
label="OK"
key="submit"
size="xxs"
type="button"
variants={['secondery']}
className={cx.getPartClassName('submit')}
onClick={this.onSubmit}/>
{this.renderBoxes(boxes)}
</div>
</div>
);
}
}
ColorPicker.displayName = 'ColorPicker';
ColorPicker.defaultProps = {
...InputComponent.defaultProps,
/**
* 选色器标签文字
*/
placeholder: '请选择',
boxes: [
{color: 'FF425E', hue: 'FF0059'},
{color: 'C6EDE8', hue: '00FFF9'},
{color: '66CCCC', hue: '00FFF9'},
{color: 'FFFFFF', hue: '00FFF9'},
{color: '000000', hue: '0079FF'},
{color: 'CCCCCC', hue: '00CCFF'},
{color: '576069', hue: 'FF0026'},
{color: 'E2D3AC', hue: 'FFC600'},
{color: 'FDDA04', hue: 'FFDF00'},
{color: 'E58308', hue: 'FF8C00'},
{color: 'FF99CC', hue: 'FF00BF'},
{color: 'FC9D9B', hue: 'FF0000'},
{color: '59453E', hue: 'FF0000'},
{color: 'CCFF00', hue: 'B3FF00'}
]
};
ColorPicker.propTypes = {
...InputComponent.propTypes,
placeholder: PropTypes.string,
boxes: PropTypes.arrayOf(PropTypes.shape({
color: PropTypes.string.isRequired,
hue: PropTypes.string.isRequired
}))
};