Moved colorpicker to ui/components

This commit is contained in:
Hugo Häggmark
2019-01-10 13:34:23 +01:00
parent 7e106b0f49
commit d376fae393
26 changed files with 157 additions and 159 deletions

View File

@@ -23,7 +23,9 @@
"react-highlight-words": "0.11.0",
"react-popper": "^1.3.0",
"react-transition-group": "^2.2.1",
"react-virtualized": "^9.21.0"
"react-virtualized": "^9.21.0",
"tether-drop": "https://github.com/torkelo/drop/tarball/master",
"tinycolor2": "^1.4.1"
},
"devDependencies": {
"@types/classnames": "^2.2.6",

View File

@@ -0,0 +1,10 @@
import React from 'react';
import renderer from 'react-test-renderer';
import { ColorPalette } from './ColorPalette';
describe('CollorPalette', () => {
it('renders correctly', () => {
const tree = renderer.create(<ColorPalette color="#EAB839" onColorSelect={jest.fn()} />).toJSON();
expect(tree).toMatchSnapshot();
});
});

View File

@@ -0,0 +1,44 @@
import React from 'react';
import { sortedColors } from '../../utils';
export interface Props {
color: string;
onColorSelect: (c: string) => void;
}
export class ColorPalette extends React.Component<Props, any> {
paletteColors: string[];
constructor(props: Props) {
super(props);
this.paletteColors = sortedColors;
this.onColorSelect = this.onColorSelect.bind(this);
}
onColorSelect(color: string) {
return () => {
this.props.onColorSelect(color);
};
}
render() {
const colorPaletteItems = this.paletteColors.map(paletteColor => {
const cssClass = paletteColor.toLowerCase() === this.props.color.toLowerCase() ? 'fa-circle-o' : 'fa-circle';
return (
<i
key={paletteColor}
className={'pointer fa ' + cssClass}
style={{ color: paletteColor }}
onClick={this.onColorSelect(paletteColor)}
>
&nbsp;
</i>
);
});
return (
<div className="graph-legend-popover">
<p className="m-b-0">{colorPaletteItems}</p>
</div>
);
}
}

View File

@@ -0,0 +1,60 @@
import React from 'react';
import ReactDOM from 'react-dom';
import Drop from 'tether-drop';
import { ColorPickerPopover } from './ColorPickerPopover';
export interface Props {
color: string;
onChange: (c: string) => void;
}
export class ColorPicker extends React.Component<Props, any> {
pickerElem: HTMLElement | null;
colorPickerDrop: any;
openColorPicker = () => {
const dropContent = <ColorPickerPopover color={this.props.color} onColorSelect={this.onColorSelect} />;
const dropContentElem = document.createElement('div');
ReactDOM.render(dropContent, dropContentElem);
const drop = new Drop({
target: this.pickerElem,
content: dropContentElem,
position: 'top center',
classes: 'drop-popover',
openOn: 'click',
hoverCloseDelay: 200,
tetherOptions: {
constraints: [{ to: 'scrollParent', attachment: 'none both' }],
},
});
drop.on('close', this.closeColorPicker);
this.colorPickerDrop = drop;
this.colorPickerDrop.open();
};
closeColorPicker = () => {
setTimeout(() => {
if (this.colorPickerDrop && this.colorPickerDrop.tether) {
this.colorPickerDrop.destroy();
}
}, 100);
};
onColorSelect = (color: string) => {
this.props.onChange(color);
};
render() {
return (
<div className="sp-replacer sp-light" onClick={this.openColorPicker} ref={element => (this.pickerElem = element)}>
<div className="sp-preview">
<div className="sp-preview-inner" style={{ backgroundColor: this.props.color }} />
</div>
</div>
);
}
}

View File

@@ -0,0 +1,113 @@
import React from 'react';
import $ from 'jquery';
import tinycolor from 'tinycolor2';
import { ColorPalette } from './ColorPalette';
import { SpectrumPicker } from './SpectrumPicker';
const DEFAULT_COLOR = '#000000';
export interface Props {
color: string;
onColorSelect: (c: string) => void;
}
export class ColorPickerPopover extends React.Component<Props, any> {
pickerNavElem: any;
constructor(props: Props) {
super(props);
this.state = {
tab: 'palette',
color: this.props.color || DEFAULT_COLOR,
colorString: this.props.color || DEFAULT_COLOR,
};
}
setPickerNavElem(elem: any) {
this.pickerNavElem = $(elem);
}
setColor(color: string) {
const newColor = tinycolor(color);
if (newColor.isValid()) {
this.setState({ color: newColor.toString(), colorString: newColor.toString() });
this.props.onColorSelect(color);
}
}
sampleColorSelected(color: string) {
this.setColor(color);
}
spectrumColorSelected(color: any) {
const rgbColor = color.toRgbString();
this.setColor(rgbColor);
}
onColorStringChange(e: any) {
const colorString = e.target.value;
this.setState({ colorString: colorString });
const newColor = tinycolor(colorString);
if (newColor.isValid()) {
// Update only color state
const newColorString = newColor.toString();
this.setState({ color: newColorString });
this.props.onColorSelect(newColorString);
}
}
onColorStringBlur(e: any) {
const colorString = e.target.value;
this.setColor(colorString);
}
componentDidMount() {
this.pickerNavElem.find('li:first').addClass('active');
this.pickerNavElem.on('show', (e: any) => {
// use href attr (#name => name)
const tab = e.target.hash.slice(1);
this.setState({ tab: tab });
});
}
render() {
const paletteTab = (
<div id="palette">
<ColorPalette color={this.state.color} onColorSelect={this.sampleColorSelected.bind(this)} />
</div>
);
const spectrumTab = (
<div id="spectrum">
<SpectrumPicker color={this.state.color} onColorSelect={this.spectrumColorSelected.bind(this)} options={{}} />
</div>
);
const currentTab = this.state.tab === 'palette' ? paletteTab : spectrumTab;
return (
<div className="gf-color-picker">
<ul className="nav nav-tabs" id="colorpickernav" ref={this.setPickerNavElem.bind(this)}>
<li className="gf-tabs-item-colorpicker">
<a href="#palette" data-toggle="tab">
Colors
</a>
</li>
<li className="gf-tabs-item-colorpicker">
<a href="#spectrum" data-toggle="tab">
Custom
</a>
</li>
</ul>
<div className="gf-color-picker__body">{currentTab}</div>
<div>
<input
className="gf-form-input gf-form-input--small"
value={this.state.colorString}
onChange={this.onColorStringChange.bind(this)}
onBlur={this.onColorStringBlur.bind(this)}
/>
</div>
</div>
);
}
}

View File

@@ -0,0 +1,84 @@
import React from 'react';
import ReactDOM from 'react-dom';
import Drop from 'tether-drop';
import { SeriesColorPickerPopover } from './SeriesColorPickerPopover';
export interface SeriesColorPickerProps {
color: string;
yaxis?: number;
optionalClass?: string;
onColorChange: (newColor: string) => void;
onToggleAxis: () => void;
}
export class SeriesColorPicker extends React.Component<SeriesColorPickerProps> {
pickerElem: any;
colorPickerDrop: any;
static defaultProps = {
optionalClass: '',
yaxis: undefined,
onToggleAxis: () => {},
};
constructor(props: SeriesColorPickerProps) {
super(props);
}
componentWillUnmount() {
this.destroyDrop();
}
onClickToOpen = () => {
if (this.colorPickerDrop) {
this.destroyDrop();
}
const { color, yaxis, onColorChange, onToggleAxis } = this.props;
const dropContent = (
<SeriesColorPickerPopover color={color} yaxis={yaxis} onColorChange={onColorChange} onToggleAxis={onToggleAxis} />
);
const dropContentElem = document.createElement('div');
ReactDOM.render(dropContent, dropContentElem);
const drop = new Drop({
target: this.pickerElem,
content: dropContentElem,
position: 'bottom center',
classes: 'drop-popover',
openOn: 'hover',
hoverCloseDelay: 200,
remove: true,
tetherOptions: {
constraints: [{ to: 'scrollParent', attachment: 'none both' }],
},
});
drop.on('close', this.closeColorPicker.bind(this));
this.colorPickerDrop = drop;
this.colorPickerDrop.open();
};
closeColorPicker() {
setTimeout(() => {
this.destroyDrop();
}, 100);
}
destroyDrop() {
if (this.colorPickerDrop && this.colorPickerDrop.tether) {
this.colorPickerDrop.destroy();
this.colorPickerDrop = null;
}
}
render() {
const { optionalClass, children } = this.props;
return (
<div className={optionalClass} ref={e => (this.pickerElem = e)} onClick={this.onClickToOpen}>
{children}
</div>
);
}
}

View File

@@ -0,0 +1,63 @@
import React from 'react';
import { ColorPickerPopover } from './ColorPickerPopover';
export interface SeriesColorPickerPopoverProps {
color: string;
yaxis?: number;
onColorChange: (color: string) => void;
onToggleAxis: () => void;
}
export class SeriesColorPickerPopover extends React.PureComponent<SeriesColorPickerPopoverProps, any> {
render() {
return (
<div className="graph-legend-popover">
{this.props.yaxis && <AxisSelector yaxis={this.props.yaxis} onToggleAxis={this.props.onToggleAxis} />}
<ColorPickerPopover color={this.props.color} onColorSelect={this.props.onColorChange} />
</div>
);
}
}
interface AxisSelectorProps {
yaxis: number;
onToggleAxis: () => void;
}
interface AxisSelectorState {
yaxis: number;
}
export class AxisSelector extends React.PureComponent<AxisSelectorProps, AxisSelectorState> {
constructor(props: AxisSelectorProps) {
super(props);
this.state = {
yaxis: this.props.yaxis,
};
this.onToggleAxis = this.onToggleAxis.bind(this);
}
onToggleAxis() {
this.setState({
yaxis: this.state.yaxis === 2 ? 1 : 2,
});
this.props.onToggleAxis();
}
render() {
const leftButtonClass = this.state.yaxis === 1 ? 'btn-success' : 'btn-inverse';
const rightButtonClass = this.state.yaxis === 2 ? 'btn-success' : 'btn-inverse';
return (
<div className="p-b-1">
<label className="small p-r-1">Y Axis:</label>
<button onClick={this.onToggleAxis} className={'btn btn-small ' + leftButtonClass}>
Left
</button>
<button onClick={this.onToggleAxis} className={'btn btn-small ' + rightButtonClass}>
Right
</button>
</div>
);
}
}

View File

@@ -0,0 +1,72 @@
import React from 'react';
import _ from 'lodash';
import $ from 'jquery';
import 'vendor/spectrum';
export interface Props {
color: string;
options: object;
onColorSelect: (c: string) => void;
}
export class SpectrumPicker extends React.Component<Props, any> {
elem: any;
isMoving: boolean;
constructor(props: Props) {
super(props);
this.onSpectrumMove = this.onSpectrumMove.bind(this);
this.setComponentElem = this.setComponentElem.bind(this);
}
setComponentElem(elem: any) {
this.elem = $(elem);
}
onSpectrumMove(color: any) {
this.isMoving = true;
this.props.onColorSelect(color);
}
componentDidMount() {
const spectrumOptions = _.assignIn(
{
flat: true,
showAlpha: true,
showButtons: false,
color: this.props.color,
appendTo: this.elem,
move: this.onSpectrumMove,
},
this.props.options
);
this.elem.spectrum(spectrumOptions);
this.elem.spectrum('show');
this.elem.spectrum('set', this.props.color);
}
componentWillUpdate(nextProps: any) {
// If user move pointer over spectrum field this produce 'move' event and component
// may update props.color. We don't want to update spectrum color in this case, so we can use
// isMoving flag for tracking moving state. Flag should be cleared in componentDidUpdate() which
// is called after updating occurs (when user finished moving).
if (!this.isMoving) {
this.elem.spectrum('set', nextProps.color);
}
}
componentDidUpdate() {
if (this.isMoving) {
this.isMoving = false;
}
}
componentWillUnmount() {
this.elem.spectrum('destroy');
}
render() {
return <div className="spectrum-container" ref={this.setComponentElem} />;
}
}

View File

@@ -0,0 +1,628 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`CollorPalette renders correctly 1`] = `
<div
className="graph-legend-popover"
>
<p
className="m-b-0"
>
<i
className="pointer fa fa-circle"
onClick={[Function]}
style={
Object {
"color": "#890f02",
}
}
>
 
</i>
<i
className="pointer fa fa-circle"
onClick={[Function]}
style={
Object {
"color": "#58140c",
}
}
>
 
</i>
<i
className="pointer fa fa-circle"
onClick={[Function]}
style={
Object {
"color": "#99440a",
}
}
>
 
</i>
<i
className="pointer fa fa-circle"
onClick={[Function]}
style={
Object {
"color": "#c15c17",
}
}
>
 
</i>
<i
className="pointer fa fa-circle"
onClick={[Function]}
style={
Object {
"color": "#967302",
}
}
>
 
</i>
<i
className="pointer fa fa-circle"
onClick={[Function]}
style={
Object {
"color": "#cca300",
}
}
>
 
</i>
<i
className="pointer fa fa-circle"
onClick={[Function]}
style={
Object {
"color": "#3f6833",
}
}
>
 
</i>
<i
className="pointer fa fa-circle"
onClick={[Function]}
style={
Object {
"color": "#2f575e",
}
}
>
 
</i>
<i
className="pointer fa fa-circle"
onClick={[Function]}
style={
Object {
"color": "#64b0c8",
}
}
>
 
</i>
<i
className="pointer fa fa-circle"
onClick={[Function]}
style={
Object {
"color": "#052b51",
}
}
>
 
</i>
<i
className="pointer fa fa-circle"
onClick={[Function]}
style={
Object {
"color": "#0a50a1",
}
}
>
 
</i>
<i
className="pointer fa fa-circle"
onClick={[Function]}
style={
Object {
"color": "#584477",
}
}
>
 
</i>
<i
className="pointer fa fa-circle"
onClick={[Function]}
style={
Object {
"color": "#3f2b5b",
}
}
>
 
</i>
<i
className="pointer fa fa-circle"
onClick={[Function]}
style={
Object {
"color": "#511749",
}
}
>
 
</i>
<i
className="pointer fa fa-circle"
onClick={[Function]}
style={
Object {
"color": "#e24d42",
}
}
>
 
</i>
<i
className="pointer fa fa-circle"
onClick={[Function]}
style={
Object {
"color": "#bf1b00",
}
}
>
 
</i>
<i
className="pointer fa fa-circle"
onClick={[Function]}
style={
Object {
"color": "#ef843c",
}
}
>
 
</i>
<i
className="pointer fa fa-circle"
onClick={[Function]}
style={
Object {
"color": "#f4d598",
}
}
>
 
</i>
<i
className="pointer fa fa-circle"
onClick={[Function]}
style={
Object {
"color": "#e5ac0e",
}
}
>
 
</i>
<i
className="pointer fa fa-circle"
onClick={[Function]}
style={
Object {
"color": "#9ac48a",
}
}
>
 
</i>
<i
className="pointer fa fa-circle"
onClick={[Function]}
style={
Object {
"color": "#508642",
}
}
>
 
</i>
<i
className="pointer fa fa-circle"
onClick={[Function]}
style={
Object {
"color": "#6ed0e0",
}
}
>
 
</i>
<i
className="pointer fa fa-circle"
onClick={[Function]}
style={
Object {
"color": "#65c5db",
}
}
>
 
</i>
<i
className="pointer fa fa-circle"
onClick={[Function]}
style={
Object {
"color": "#0a437c",
}
}
>
 
</i>
<i
className="pointer fa fa-circle"
onClick={[Function]}
style={
Object {
"color": "#447ebc",
}
}
>
 
</i>
<i
className="pointer fa fa-circle"
onClick={[Function]}
style={
Object {
"color": "#614d93",
}
}
>
 
</i>
<i
className="pointer fa fa-circle"
onClick={[Function]}
style={
Object {
"color": "#d683ce",
}
}
>
 
</i>
<i
className="pointer fa fa-circle"
onClick={[Function]}
style={
Object {
"color": "#6d1f62",
}
}
>
 
</i>
<i
className="pointer fa fa-circle"
onClick={[Function]}
style={
Object {
"color": "#ea6460",
}
}
>
 
</i>
<i
className="pointer fa fa-circle"
onClick={[Function]}
style={
Object {
"color": "#e0752d",
}
}
>
 
</i>
<i
className="pointer fa fa-circle"
onClick={[Function]}
style={
Object {
"color": "#f9934e",
}
}
>
 
</i>
<i
className="pointer fa fa-circle"
onClick={[Function]}
style={
Object {
"color": "#fceaca",
}
}
>
 
</i>
<i
className="pointer fa fa-circle-o"
onClick={[Function]}
style={
Object {
"color": "#eab839",
}
}
>
 
</i>
<i
className="pointer fa fa-circle"
onClick={[Function]}
style={
Object {
"color": "#b7dbab",
}
}
>
 
</i>
<i
className="pointer fa fa-circle"
onClick={[Function]}
style={
Object {
"color": "#629e51",
}
}
>
 
</i>
<i
className="pointer fa fa-circle"
onClick={[Function]}
style={
Object {
"color": "#70dbed",
}
}
>
 
</i>
<i
className="pointer fa fa-circle"
onClick={[Function]}
style={
Object {
"color": "#82b5d8",
}
}
>
 
</i>
<i
className="pointer fa fa-circle"
onClick={[Function]}
style={
Object {
"color": "#1f78c1",
}
}
>
 
</i>
<i
className="pointer fa fa-circle"
onClick={[Function]}
style={
Object {
"color": "#aea2e0",
}
}
>
 
</i>
<i
className="pointer fa fa-circle"
onClick={[Function]}
style={
Object {
"color": "#705da0",
}
}
>
 
</i>
<i
className="pointer fa fa-circle"
onClick={[Function]}
style={
Object {
"color": "#e5a8e2",
}
}
>
 
</i>
<i
className="pointer fa fa-circle"
onClick={[Function]}
style={
Object {
"color": "#962d82",
}
}
>
 
</i>
<i
className="pointer fa fa-circle"
onClick={[Function]}
style={
Object {
"color": "#f29191",
}
}
>
 
</i>
<i
className="pointer fa fa-circle"
onClick={[Function]}
style={
Object {
"color": "#fce2de",
}
}
>
 
</i>
<i
className="pointer fa fa-circle"
onClick={[Function]}
style={
Object {
"color": "#f9ba8f",
}
}
>
 
</i>
<i
className="pointer fa fa-circle"
onClick={[Function]}
style={
Object {
"color": "#f9e2d2",
}
}
>
 
</i>
<i
className="pointer fa fa-circle"
onClick={[Function]}
style={
Object {
"color": "#f2c96d",
}
}
>
 
</i>
<i
className="pointer fa fa-circle"
onClick={[Function]}
style={
Object {
"color": "#e0f9d7",
}
}
>
 
</i>
<i
className="pointer fa fa-circle"
onClick={[Function]}
style={
Object {
"color": "#7eb26d",
}
}
>
 
</i>
<i
className="pointer fa fa-circle"
onClick={[Function]}
style={
Object {
"color": "#cffaff",
}
}
>
 
</i>
<i
className="pointer fa fa-circle"
onClick={[Function]}
style={
Object {
"color": "#badff4",
}
}
>
 
</i>
<i
className="pointer fa fa-circle"
onClick={[Function]}
style={
Object {
"color": "#5195ce",
}
}
>
 
</i>
<i
className="pointer fa fa-circle"
onClick={[Function]}
style={
Object {
"color": "#dedaf7",
}
}
>
 
</i>
<i
className="pointer fa fa-circle"
onClick={[Function]}
style={
Object {
"color": "#806eb7",
}
}
>
 
</i>
<i
className="pointer fa fa-circle"
onClick={[Function]}
style={
Object {
"color": "#f9d9f9",
}
}
>
 
</i>
<i
className="pointer fa fa-circle"
onClick={[Function]}
style={
Object {
"color": "#ba43a9",
}
}
>
 
</i>
</p>
</div>
`;

View File

@@ -2,3 +2,6 @@ export { DeleteButton } from './DeleteButton/DeleteButton';
export { Tooltip } from './Tooltip/Tooltip';
export { Portal } from './Portal/Portal';
export { CustomScrollbar } from './CustomScrollbar/CustomScrollbar';
export { ColorPicker } from './ColorPicker/ColorPicker';
export { SeriesColorPickerPopover } from './ColorPicker/SeriesColorPickerPopover';
export { SeriesColorPicker } from './ColorPicker/SeriesColorPicker';

View File

@@ -2,4 +2,5 @@ export * from './components';
export * from './visualizations';
export * from './types';
export * from './utils';
export { default } from './utils';
export * from './forms';

View File

@@ -0,0 +1,94 @@
import _ from 'lodash';
import tinycolor from 'tinycolor2';
export const PALETTE_ROWS = 4;
export const PALETTE_COLUMNS = 14;
export const DEFAULT_ANNOTATION_COLOR = 'rgba(0, 211, 255, 1)';
export const OK_COLOR = 'rgba(11, 237, 50, 1)';
export const ALERTING_COLOR = 'rgba(237, 46, 24, 1)';
export const NO_DATA_COLOR = 'rgba(150, 150, 150, 1)';
export const PENDING_COLOR = 'rgba(247, 149, 32, 1)';
export const REGION_FILL_ALPHA = 0.09;
const colors = [
'#7EB26D', // 0: pale green
'#EAB839', // 1: mustard
'#6ED0E0', // 2: light blue
'#EF843C', // 3: orange
'#E24D42', // 4: red
'#1F78C1', // 5: ocean
'#BA43A9', // 6: purple
'#705DA0', // 7: violet
'#508642', // 8: dark green
'#CCA300', // 9: dark sand
'#447EBC',
'#C15C17',
'#890F02',
'#0A437C',
'#6D1F62',
'#584477',
'#B7DBAB',
'#F4D598',
'#70DBED',
'#F9BA8F',
'#F29191',
'#82B5D8',
'#E5A8E2',
'#AEA2E0',
'#629E51',
'#E5AC0E',
'#64B0C8',
'#E0752D',
'#BF1B00',
'#0A50A1',
'#962D82',
'#614D93',
'#9AC48A',
'#F2C96D',
'#65C5DB',
'#F9934E',
'#EA6460',
'#5195CE',
'#D683CE',
'#806EB7',
'#3F6833',
'#967302',
'#2F575E',
'#99440A',
'#58140C',
'#052B51',
'#511749',
'#3F2B5B',
'#E0F9D7',
'#FCEACA',
'#CFFAFF',
'#F9E2D2',
'#FCE2DE',
'#BADFF4',
'#F9D9F9',
'#DEDAF7',
];
function sortColorsByHue(hexColors: string[]) {
const hslColors = _.map(hexColors, hexToHsl);
let sortedHSLColors = _.sortBy(hslColors, ['h']);
sortedHSLColors = _.chunk(sortedHSLColors, PALETTE_ROWS);
sortedHSLColors = _.map(sortedHSLColors, chunk => {
return _.sortBy(chunk, 'l');
});
sortedHSLColors = _.flattenDeep(_.zip(...sortedHSLColors));
return _.map(sortedHSLColors, hslToHex);
}
function hexToHsl(color: string) {
return tinycolor(color).toHsl();
}
function hslToHex(color: string) {
return tinycolor(color).toHexString();
}
export let sortedColors = sortColorsByHue(colors);
export default colors;

View File

@@ -1 +1,3 @@
export * from './processTimeSeries';
export * from './colors';
export { default } from './colors';