diff --git a/packages/grafana-ui/src/components/ColorPicker/ColorPicker.tsx b/packages/grafana-ui/src/components/ColorPicker/ColorPicker.tsx index 682d2f1c605..c2697b13e7d 100644 --- a/packages/grafana-ui/src/components/ColorPicker/ColorPicker.tsx +++ b/packages/grafana-ui/src/components/ColorPicker/ColorPicker.tsx @@ -1,69 +1,45 @@ -import React, { Component } from 'react'; -import ReactDOM from 'react-dom'; -import Drop from 'tether-drop'; +import React, { Component, createRef } from 'react'; +import PopperController from '../Tooltip/PopperController'; +import Popper from '../Tooltip/Popper'; import { ColorPickerPopover } from './ColorPickerPopover'; -import { Color } from '../../utils/colorsPalette'; +import { ColorDefinition } from '../../utils/colorsPalette'; interface Props { - /** - * Value to display, either empty (" ") or "X" / "O". - * - * @default " " - **/ - name?: Color; color: string; onChange: (c: string) => void; } export class ColorPicker extends Component { + private pickerTriggerRef = createRef(); pickerElem: HTMLElement | null; colorPickerDrop: any; - openColorPicker = () => { - const dropContent = ; - - const dropContentElem = document.createElement('div'); - ReactDOM.render(dropContent, dropContentElem); - - const drop = new Drop({ - target: this.pickerElem as Element, - content: dropContentElem, - position: 'top center', - classes: 'drop-popover', - openOn: 'click', - hoverCloseDelay: 200, - tetherOptions: { - constraints: [{ to: 'scrollParent', attachment: 'none both' }], - attachment: 'bottom center', - }, - }); - - drop.on('close', this.closeColorPicker); - - this.colorPickerDrop = drop; - this.colorPickerDrop.open(); + onColorSelect = (color: ColorDefinition) => { + this.props.onChange(color.name); }; - closeColorPicker = () => { - setTimeout(() => { - if (this.colorPickerDrop && this.colorPickerDrop.tether) { - this.colorPickerDrop.destroy(); - } - }, 100); - }; - - onColorSelect = (color: string) => { - this.props.onChange(color); + renderPickerTabs = () => { + return {}} />; }; render() { - return ( -
(this.pickerElem = element)}> -
-
-
-
+ + {(showPopper, hidePopper, popperProps) => { + return ( + <> + {this.pickerTriggerRef.current && ( + + )} +
+
+
+
+
+ + ); + }} + ); } } diff --git a/packages/grafana-ui/src/components/ColorPicker/ColorPickerPopover.tsx b/packages/grafana-ui/src/components/ColorPicker/ColorPickerPopover.tsx index e8305c99319..70dca7fa667 100644 --- a/packages/grafana-ui/src/components/ColorPicker/ColorPickerPopover.tsx +++ b/packages/grafana-ui/src/components/ColorPicker/ColorPickerPopover.tsx @@ -1,111 +1,76 @@ -import React from 'react'; -import $ from 'jquery'; -import tinycolor from 'tinycolor2'; -import { ColorPalette } from './ColorPalette'; +import React, { Children } from 'react'; +import NamedColorsPicker from './NamedColorsPicker'; +import { Color } from 'csstype'; +import { ColorDefinition, getColorName } from '../..//utils/colorsPalette'; import { SpectrumPicker } from './SpectrumPicker'; +import { GrafanaTheme } from '../../types'; -const DEFAULT_COLOR = '#000000'; +// const DEFAULT_COLOR = '#000000'; export interface Props { - color: string; - onColorSelect: (c: string) => void; + color: Color | string; + theme?: GrafanaTheme; + onColorSelect: (color: string | ColorDefinition) => void; } -export class ColorPickerPopover extends React.Component { - pickerNavElem: any; +type PickerType = 'palette' | 'spectrum'; +interface State { + activePicker: PickerType; +} + +export class ColorPickerPopover extends React.Component { constructor(props: Props) { super(props); this.state = { - tab: 'palette', - color: this.props.color || DEFAULT_COLOR, - colorString: this.props.color || DEFAULT_COLOR, + activePicker: 'spectrum', }; } - setPickerNavElem(elem: any) { - this.pickerNavElem = $(elem); - } + handleSpectrumColorSelect = (color: any) => { + this.props.onColorSelect(color.toRgbString()); + }; - setColor(color: string) { - const newColor = tinycolor(color); - if (newColor.isValid()) { - this.setState({ color: newColor.toString(), colorString: newColor.toString() }); - this.props.onColorSelect(color); - } - } + renderPicker = () => { + const { activePicker } = this.state; + const { color } = this.props; - 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 }); - }); - } + return activePicker === 'spectrum' ? ( + + ) : ( + + ); + }; render() { - const paletteTab = ( -
- -
- ); - const spectrumTab = ( -
- -
- ); - const currentTab = this.state.tab === 'palette' ? paletteTab : spectrumTab; + const { activePicker } = this.state; + const { theme, children } = this.props; + const colorPickerTheme = theme || GrafanaTheme.Dark; return ( -
- -
{currentTab}
-
- +
+
+
{ + this.setState({ activePicker: 'palette' }); + }} + > + Default +
+
{ + this.setState({ activePicker: 'spectrum' }); + }} + > + Custom +
+
+ +
+ {this.renderPicker()} + {children}
); diff --git a/packages/grafana-ui/src/components/ColorPicker/NamedColorsPicker.tsx b/packages/grafana-ui/src/components/ColorPicker/NamedColorsPicker.tsx index a0732e4e21d..f7cd229dca9 100644 --- a/packages/grafana-ui/src/components/ColorPicker/NamedColorsPicker.tsx +++ b/packages/grafana-ui/src/components/ColorPicker/NamedColorsPicker.tsx @@ -47,19 +47,22 @@ const ColorSwatch: FunctionComponent = ({ ); }; -const ColorsGroup = ({ +interface ColorsGroupProps { + colors: ColorDefinition[]; + selectedColor?: Color; + onColorSelect: ColorChangeHandler; + key?: string; +} +const ColorsGroup: FunctionComponent = ({ colors, selectedColor, onColorSelect, -}: { - colors: ColorDefinition[]; - selectedColor?: Color; - onColorSelect: ColorChangeHandler + ...otherProps }) => { const primaryColor = find(colors, color => !!color.isPrimary); return ( -
+
{primaryColor && ( {colors.map(color => !color.isPrimary && ( -
+
{ swatches.push( - <> - - + ); }); diff --git a/packages/grafana-ui/src/components/ColorPicker/SeriesColorPicker.tsx b/packages/grafana-ui/src/components/ColorPicker/SeriesColorPicker.tsx index 7c3848f6868..40487386a68 100644 --- a/packages/grafana-ui/src/components/ColorPicker/SeriesColorPicker.tsx +++ b/packages/grafana-ui/src/components/ColorPicker/SeriesColorPicker.tsx @@ -1,7 +1,9 @@ -import React from 'react'; -import ReactDOM from 'react-dom'; -import Drop from 'tether-drop'; +import React, { createRef } from 'react'; +import * as PopperJS from 'popper.js'; import { SeriesColorPickerPopover } from './SeriesColorPickerPopover'; +import PopperController from '../Tooltip/PopperController'; +import Popper from '../Tooltip/Popper'; +import { GrafanaTheme } from '../../types'; export interface SeriesColorPickerProps { color: string; @@ -9,10 +11,12 @@ export interface SeriesColorPickerProps { optionalClass?: string; onColorChange: (newColor: string) => void; onToggleAxis?: () => void; + children: JSX.Element; + theme?: GrafanaTheme; } export class SeriesColorPicker extends React.Component { - pickerElem: any; + private pickerTriggerRef = createRef(); colorPickerDrop: any; static defaultProps = { @@ -21,65 +25,46 @@ export class SeriesColorPicker extends React.Component { onToggleAxis: () => {}, }; - constructor(props: SeriesColorPickerProps) { - super(props); - } - - componentWillUnmount() { - this.destroyDrop(); - } - - onClickToOpen = () => { - if (this.colorPickerDrop) { - this.destroyDrop(); - } - - const { color, yaxis, onColorChange, onToggleAxis } = this.props; - const dropContent = ( - + renderPickerTabs = () => { + const { color, yaxis, onColorChange, onToggleAxis, theme } = this.props; + return ( + ); - 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' }], - attachment: 'bottom center', - }, - }); - - 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; + const { children } = this.props; + return ( -
(this.pickerElem = e)} onClick={this.onClickToOpen}> - {children} -
+ + {(showPopper, hidePopper, popperProps) => { + return ( + <> + {this.pickerTriggerRef.current && ( + + )} + {React.cloneElement(children, { + ref: this.pickerTriggerRef, + onClick: showPopper, + onMouseLeave: hidePopper, + })} + + ); + }} + ); } } diff --git a/packages/grafana-ui/src/components/ColorPicker/SeriesColorPickerPopover.tsx b/packages/grafana-ui/src/components/ColorPicker/SeriesColorPickerPopover.tsx index 541a77ddabc..3f4fcc122a3 100644 --- a/packages/grafana-ui/src/components/ColorPicker/SeriesColorPickerPopover.tsx +++ b/packages/grafana-ui/src/components/ColorPicker/SeriesColorPickerPopover.tsx @@ -1,19 +1,24 @@ import React from 'react'; import { ColorPickerPopover } from './ColorPickerPopover'; +import { GrafanaTheme } from '../../types'; export interface SeriesColorPickerPopoverProps { color: string; yaxis?: number; onColorChange: (color: string) => void; onToggleAxis?: () => void; + theme?: GrafanaTheme; } export class SeriesColorPickerPopover extends React.PureComponent { render() { return ( -
- {this.props.yaxis && } - +
+ +
+ {this.props.yaxis && } +
+
); } diff --git a/packages/grafana-ui/src/components/ColorPicker/SpectrumPicker.tsx b/packages/grafana-ui/src/components/ColorPicker/SpectrumPicker.tsx index a225db09046..98b3fc2feec 100644 --- a/packages/grafana-ui/src/components/ColorPicker/SpectrumPicker.tsx +++ b/packages/grafana-ui/src/components/ColorPicker/SpectrumPicker.tsx @@ -6,7 +6,7 @@ import '../../vendor/spectrum'; export interface Props { color: string; options: object; - onColorSelect: (c: string) => void; + onColorSelect: (color: string) => void; } export class SpectrumPicker extends React.Component { diff --git a/packages/grafana-ui/src/components/ColorPicker/_ColorPicker.scss b/packages/grafana-ui/src/components/ColorPicker/_ColorPicker.scss index c0643342307..1a9da6c602d 100644 --- a/packages/grafana-ui/src/components/ColorPicker/_ColorPicker.scss +++ b/packages/grafana-ui/src/components/ColorPicker/_ColorPicker.scss @@ -1,3 +1,54 @@ +.ColorPicker { + .popper__arrow { + border-color: #f7f8fa; + } +} + +.ColorPickerPopover { + border-radius: 3px; +} + +.ColorPickerPopover--light { + color: black; + background: linear-gradient(180deg, #ffffff 0%, #f7f8fa 104.25%); + box-shadow: 0px 2px 4px #dde4ed, 0px 0px 2px #dde4ed; +} + +.ColorPickerPopover--dark { + color: #d8d9da; + background: linear-gradient(180deg, #1e2028 0%, #161719 104.25%); + box-shadow: 0px 2px 4px #000000, 0px 0px 2px #000000; + + .ColorPickerPopover__tab { + background: #303133; + color: white; + } + .ColorPickerPopover__tab--active { + background: none; + } +} + +.ColorPickerPopover__content { + width: 360px; + padding: 32px; +} + +.ColorPickerPopover__tabs { + display: flex; + width: 100%; +} + +.ColorPickerPopover__tab { + width: 50%; + text-align: center; + padding: 8px 0; + background: #dde4ed; + border-radius: 3px; +} + +.ColorPickerPopover__tab--active { + background: white; +} .sp-replacer { background: inherit; border: none; diff --git a/packages/grafana-ui/src/types/index.ts b/packages/grafana-ui/src/types/index.ts index eaa39fb080b..1b1d764e304 100644 --- a/packages/grafana-ui/src/types/index.ts +++ b/packages/grafana-ui/src/types/index.ts @@ -3,3 +3,8 @@ export * from './time'; export * from './panel'; export * from './plugin'; export * from './datasource'; + +export enum GrafanaTheme { + Light = 'light', + Dark = 'dark', +} diff --git a/public/app/core/time_series2.ts b/public/app/core/time_series2.ts index 76af75d0935..9d586a0b4b5 100644 --- a/public/app/core/time_series2.ts +++ b/public/app/core/time_series2.ts @@ -1,6 +1,7 @@ import kbn from 'app/core/utils/kbn'; import { getFlotTickDecimals } from 'app/core/utils/ticks'; import _ from 'lodash'; +import { ColorDefinition } from '@grafana/ui/src/utils/colorsPalette'; function matchSeriesOverride(aliasOrRegex, seriesAlias) { if (!aliasOrRegex) { @@ -356,8 +357,13 @@ export default class TimeSeries { return false; } - setColor(color) { - this.color = color; - this.bars.fillColor = color; + setColor(color: string | ColorDefinition) { + if (typeof color === 'string') { + this.color = color; + this.bars.fillColor = color; + } else { + this.color = color.variants.dark; + this.bars.fillColor = color.variants.dark; + } } }