فهرست منبع

Merge pull request #14914 from grafana/named-colors

Named colors picker
Torkel Ödegaard 7 سال پیش
والد
کامیت
ca68a88ee1
74فایلهای تغییر یافته به همراه1977 افزوده شده و 3385 حذف شده
  1. 2 0
      packages/grafana-ui/.storybook/addons.ts
  2. 6 0
      packages/grafana-ui/package.json
  3. 94 0
      packages/grafana-ui/src/components/ColorPicker/ColorInput.tsx
  4. 57 19
      packages/grafana-ui/src/components/ColorPicker/ColorPicker.story.tsx
  5. 100 54
      packages/grafana-ui/src/components/ColorPicker/ColorPicker.tsx
  6. 40 0
      packages/grafana-ui/src/components/ColorPicker/ColorPickerPopover.story.tsx
  7. 75 0
      packages/grafana-ui/src/components/ColorPicker/ColorPickerPopover.test.tsx
  8. 102 85
      packages/grafana-ui/src/components/ColorPicker/ColorPickerPopover.tsx
  9. 110 0
      packages/grafana-ui/src/components/ColorPicker/NamedColorsGroup.tsx
  10. 52 0
      packages/grafana-ui/src/components/ColorPicker/NamedColorsPalette.story.tsx
  11. 36 0
      packages/grafana-ui/src/components/ColorPicker/NamedColorsPalette.test.tsx
  12. 39 0
      packages/grafana-ui/src/components/ColorPicker/NamedColorsPalette.tsx
  13. 0 85
      packages/grafana-ui/src/components/ColorPicker/SeriesColorPicker.tsx
  14. 35 14
      packages/grafana-ui/src/components/ColorPicker/SeriesColorPickerPopover.tsx
  15. 23 0
      packages/grafana-ui/src/components/ColorPicker/SpectrumPalette.story.tsx
  16. 100 0
      packages/grafana-ui/src/components/ColorPicker/SpectrumPalette.tsx
  17. 80 0
      packages/grafana-ui/src/components/ColorPicker/SpectrumPalettePointer.tsx
  18. 0 72
      packages/grafana-ui/src/components/ColorPicker/SpectrumPicker.tsx
  19. 192 1
      packages/grafana-ui/src/components/ColorPicker/_ColorPicker.scss
  20. 7 5
      packages/grafana-ui/src/components/CustomScrollbar/CustomScrollbar.tsx
  21. 14 21
      packages/grafana-ui/src/components/Gauge/Gauge.tsx
  22. 7 5
      packages/grafana-ui/src/components/Switch/Switch.tsx
  23. 8 5
      packages/grafana-ui/src/components/ThresholdsEditor/ThresholdsEditor.tsx
  24. 58 43
      packages/grafana-ui/src/components/Tooltip/Popper.tsx
  25. 27 22
      packages/grafana-ui/src/components/Tooltip/PopperController.tsx
  26. 16 1
      packages/grafana-ui/src/components/Tooltip/Tooltip.tsx
  27. 4 5
      packages/grafana-ui/src/components/Tooltip/_Tooltip.scss
  28. 2 2
      packages/grafana-ui/src/components/index.ts
  29. 0 2
      packages/grafana-ui/src/index.scss
  30. 9 0
      packages/grafana-ui/src/types/index.ts
  31. 1 8
      packages/grafana-ui/src/types/panel.ts
  32. 0 1
      packages/grafana-ui/src/utils/colors.ts
  33. 1 0
      packages/grafana-ui/src/utils/index.ts
  34. 66 0
      packages/grafana-ui/src/utils/namedColorsPalette.test.ts
  35. 182 0
      packages/grafana-ui/src/utils/namedColorsPalette.ts
  36. 6 0
      packages/grafana-ui/src/utils/propDeprecationWarning.ts
  37. 38 0
      packages/grafana-ui/src/utils/storybook/UseState.tsx
  38. 14 0
      packages/grafana-ui/src/utils/storybook/themeKnob.ts
  39. 19 0
      packages/grafana-ui/src/utils/storybook/withCenteredStory.tsx
  40. 0 509
      packages/grafana-ui/src/vendor/spectrum.css
  41. 0 2317
      packages/grafana-ui/src/vendor/spectrum.js
  42. 1 0
      public/app/core/angular_wrappers.ts
  43. 0 5
      public/app/core/services/context_srv.ts
  44. 1 1
      public/app/core/time_series2.ts
  45. 28 0
      public/app/core/utils/ConfigProvider.tsx
  46. 2 1
      public/app/core/utils/react2angular.ts
  47. 1 1
      public/app/features/dashboard/dashgrid/DataPanel.tsx
  48. 1 1
      public/app/features/dashboard/dashgrid/PanelHeader/PanelHeaderCorner.tsx
  49. 1 1
      public/app/features/dashboard/panel_editor/PanelEditor.tsx
  50. 1 1
      public/app/features/dashboard/panel_editor/QueryOptions.tsx
  51. 1 1
      public/app/features/dashboard/permissions/DashboardPermissions.tsx
  52. 3 2
      public/app/features/datasources/settings/BasicSettings.tsx
  53. 1 2
      public/app/features/explore/Logs.tsx
  54. 1 1
      public/app/features/folders/FolderPermissions.tsx
  55. 1 2
      public/app/plugins/panel/gauge/GaugeOptionsEditor.tsx
  56. 14 11
      public/app/plugins/panel/gauge/GaugePanel.tsx
  57. 17 8
      public/app/plugins/panel/gauge/GaugePanelOptions.tsx
  58. 1 1
      public/app/plugins/panel/graph/Legend/Legend.tsx
  59. 21 11
      public/app/plugins/panel/graph/Legend/LegendSeriesItem.tsx
  60. 4 3
      public/app/plugins/panel/graph/data_processor.ts
  61. 5 1
      public/app/plugins/panel/graph/graph.ts
  62. 4 2
      public/app/plugins/panel/graph/module.ts
  63. 3 1
      public/app/plugins/panel/graph/series_overrides_ctrl.ts
  64. 12 4
      public/app/plugins/panel/graph/threshold_manager.ts
  65. 18 9
      public/app/plugins/panel/graph/time_region_manager.ts
  66. 3 0
      public/app/plugins/panel/graph/time_regions_form.ts
  67. 1 3
      public/app/plugins/panel/graph2/GraphPanelOptions.tsx
  68. 5 1
      public/app/plugins/panel/heatmap/color_legend.ts
  69. 5 2
      public/app/plugins/panel/heatmap/rendering.ts
  70. 17 3
      public/app/plugins/panel/singlestat/module.ts
  71. 4 1
      public/app/plugins/panel/table/module.ts
  72. 12 3
      public/app/plugins/panel/table/renderer.ts
  73. 21 18
      public/app/plugins/panel/table/specs/renderer.test.ts
  74. 145 8
      yarn.lock

+ 2 - 0
packages/grafana-ui/.storybook/addons.ts

@@ -0,0 +1,2 @@
+import '@storybook/addon-knobs/register';
+import '@storybook/addon-actions/register';

+ 6 - 0
packages/grafana-ui/package.json

@@ -12,11 +12,13 @@
   "license": "ISC",
   "dependencies": {
     "@torkelo/react-select": "2.1.1",
+    "@types/react-color": "^2.14.0",
     "classnames": "^2.2.5",
     "jquery": "^3.2.1",
     "lodash": "^4.17.10",
     "moment": "^2.22.2",
     "react": "^16.6.3",
+    "react-color": "^2.17.0",
     "react-custom-scrollbars": "^4.2.1",
     "react-dom": "^16.6.3",
     "react-highlight-words": "0.11.0",
@@ -28,7 +30,9 @@
     "tinycolor2": "^1.4.1"
   },
   "devDependencies": {
+    "@storybook/addon-actions": "^4.1.7",
     "@storybook/addon-info": "^4.1.6",
+    "@storybook/addon-knobs": "^4.1.7",
     "@storybook/react": "^4.1.4",
     "@types/classnames": "^2.2.6",
     "@types/jest": "^23.3.2",
@@ -39,7 +43,9 @@
     "@types/react-custom-scrollbars": "^4.0.5",
     "@types/react-test-renderer": "^16.0.3",
     "@types/react-transition-group": "^2.0.15",
+    "@types/storybook__addon-actions": "^3.4.1",
     "@types/storybook__addon-info": "^3.4.2",
+    "@types/storybook__addon-knobs": "^4.0.0",
     "@types/storybook__react": "^4.0.0",
     "@types/tether-drop": "^1.4.8",
     "@types/tinycolor2": "^1.4.1",

+ 94 - 0
packages/grafana-ui/src/components/ColorPicker/ColorInput.tsx

@@ -0,0 +1,94 @@
+import React from 'react';
+import { ColorPickerProps } from './ColorPicker';
+import tinycolor from 'tinycolor2';
+import { debounce } from 'lodash';
+
+interface ColorInputState {
+  previousColor: string;
+  value: string;
+}
+
+interface ColorInputProps extends ColorPickerProps {
+  style?: React.CSSProperties;
+}
+
+class ColorInput extends React.PureComponent<ColorInputProps, ColorInputState> {
+  constructor(props: ColorInputProps) {
+    super(props);
+    this.state = {
+      previousColor: props.color,
+      value: props.color,
+    };
+
+    this.updateColor = debounce(this.updateColor, 100);
+  }
+
+  static getDerivedStateFromProps(props: ColorPickerProps, state: ColorInputState) {
+    const newColor = tinycolor(props.color);
+    if (newColor.isValid() && props.color !== state.previousColor) {
+      return {
+        ...state,
+        previousColor: props.color,
+        value: newColor.toString(),
+      };
+    }
+
+    return state;
+  }
+  updateColor = (color: string) => {
+    this.props.onChange(color);
+  };
+
+  handleChange = (event: React.SyntheticEvent<HTMLInputElement>) => {
+    const newColor = tinycolor(event.currentTarget.value);
+
+    this.setState({
+      value: event.currentTarget.value,
+    });
+
+    if (newColor.isValid()) {
+      this.updateColor(newColor.toString());
+    }
+  };
+
+  handleBlur = () => {
+    const newColor = tinycolor(this.state.value);
+
+    if (!newColor.isValid()) {
+      this.setState({
+        value: this.props.color,
+      });
+    }
+  };
+
+  render() {
+    const { value } = this.state;
+    return (
+      <div
+        style={{
+          display: 'flex',
+          ...this.props.style,
+        }}
+      >
+        <div
+          style={{
+            background: this.props.color,
+            width: '35px',
+            height: '35px',
+            flexGrow: 0,
+            borderRadius: '3px 0 0 3px',
+          }}
+        />
+        <div
+          style={{
+            flexGrow: 1,
+          }}
+        >
+          <input className="gf-form-input" value={value} onChange={this.handleChange} onBlur={this.handleBlur} />
+        </div>
+      </div>
+    );
+  }
+}
+
+export default ColorInput;

+ 57 - 19
packages/grafana-ui/src/components/ColorPicker/ColorPicker.story.tsx

@@ -1,25 +1,63 @@
-import React, { FunctionComponent } from 'react';
+import React from 'react';
 import { storiesOf } from '@storybook/react';
-import { ColorPicker } from '@grafana/ui';
-import { withInfo } from '@storybook/addon-info';
+import { withKnobs, boolean } from '@storybook/addon-knobs';
+import { SeriesColorPicker, ColorPicker } from './ColorPicker';
+import { action } from '@storybook/addon-actions';
+import { withCenteredStory } from '../../utils/storybook/withCenteredStory';
+import { UseState } from '../../utils/storybook/UseState';
+import { getThemeKnob } from '../../utils/storybook/themeKnob';
 
-const CenteredStory: FunctionComponent<{}> = ({ children }) => {
+const getColorPickerKnobs = () => {
+  return {
+    selectedTheme: getThemeKnob(),
+    enableNamedColors: boolean('Enable named colors', false),
+  };
+};
+
+const ColorPickerStories = storiesOf('UI/ColorPicker/Pickers', module);
+
+ColorPickerStories.addDecorator(withCenteredStory).addDecorator(withKnobs);
+
+ColorPickerStories.add('default', () => {
+  const { selectedTheme, enableNamedColors } = getColorPickerKnobs();
   return (
-    <div
-      style={{
-        height: '100vh  ',
-        display: 'flex',
-        alignItems: 'center',
-        justifyContent: 'center',
+    <UseState initialState="#00ff00">
+      {(selectedColor, updateSelectedColor) => {
+        return (
+          <ColorPicker
+            enableNamedColors={enableNamedColors}
+            color={selectedColor}
+            onChange={color => {
+              action('Color changed')(color);
+              updateSelectedColor(color);
+            }}
+            theme={selectedTheme || undefined}
+          />
+        );
       }}
-    >
-      {children}
-    </div>
+    </UseState>
   );
-};
+});
 
-storiesOf('UI/ColorPicker', module)
-  .addDecorator(story => <CenteredStory>{story()}</CenteredStory>)
-  .add('default', withInfo({inline: true})(() => {
-    return <ColorPicker color="#ff0000" onChange={() => {}} />;
-  }));
+ColorPickerStories.add('Series color picker', () => {
+  const { selectedTheme, enableNamedColors } = getColorPickerKnobs();
+
+  return (
+    <UseState initialState="#00ff00">
+      {(selectedColor, updateSelectedColor) => {
+        return (
+          <SeriesColorPicker
+            enableNamedColors={enableNamedColors}
+            yaxis={1}
+            onToggleAxis={() => {}}
+            color={selectedColor}
+            onChange={color => updateSelectedColor(color)}
+            theme={selectedTheme || undefined}
+          >
+            <div style={{ color: selectedColor, cursor: 'pointer' }}>Open color picker</div>
+          </SeriesColorPicker>
+        );
+      }}
+    </UseState>
+  );
+});

+ 100 - 54
packages/grafana-ui/src/components/ColorPicker/ColorPicker.tsx

@@ -1,68 +1,114 @@
-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, { RenderPopperArrowFn } from '../Tooltip/Popper';
 import { ColorPickerPopover } from './ColorPickerPopover';
+import { Themeable, GrafanaTheme } from '../../types';
+import { getColorFromHexRgbOrName } from '../../utils/namedColorsPalette';
+import { SeriesColorPickerPopover } from './SeriesColorPickerPopover';
+import propDeprecationWarning from '../../utils/propDeprecationWarning';
 
-interface Props {
-  /**
-   * Value to display, either empty (" ") or "X" / "O".
-   *
-   * @default " "
-   **/
+type ColorPickerChangeHandler = (color: string) => void;
+
+export interface ColorPickerProps extends Themeable {
   color: string;
-  onChange: (c: string) => void;
+  onChange: ColorPickerChangeHandler;
+
+  /**
+   * @deprecated Use onChange instead
+   */
+  onColorChange?: ColorPickerChangeHandler;
+  enableNamedColors?: boolean;
+  withArrow?: boolean;
+  children?: JSX.Element;
 }
 
-export class ColorPicker extends Component<Props, any> {
-  pickerElem: HTMLElement | null;
-  colorPickerDrop: any;
+export const warnAboutColorPickerPropsDeprecation = (componentName: string, props: ColorPickerProps) => {
+  const { onColorChange } = props;
+  if (onColorChange) {
+    propDeprecationWarning(componentName, 'onColorChange', 'onChange');
+  }
+};
 
-  openColorPicker = () => {
-    const dropContent = <ColorPickerPopover color={this.props.color} onColorSelect={this.onColorSelect} />;
+export const colorPickerFactory = <T extends ColorPickerProps>(
+  popover: React.ComponentType<T>,
+  displayName = 'ColorPicker',
+  renderPopoverArrowFunction?: RenderPopperArrowFn
+) => {
+  return class ColorPicker extends Component<T, any> {
+    static displayName = displayName;
+    pickerTriggerRef = createRef<HTMLDivElement>();
 
-    const dropContentElem = document.createElement('div');
-    ReactDOM.render(dropContent, dropContentElem);
+    handleColorChange = (color: string) => {
+      const { onColorChange, onChange } = this.props;
+      const changeHandler = (onColorChange || onChange) as ColorPickerChangeHandler;
 
-    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',
-      },
-    });
+      return changeHandler(color);
+    };
 
-    drop.on('close', this.closeColorPicker);
+    render() {
+      const popoverElement = React.createElement(popover, {
+        ...this.props,
+        onChange: this.handleColorChange,
+      });
+      const { theme, withArrow, children } = this.props;
 
-    this.colorPickerDrop = drop;
-    this.colorPickerDrop.open();
-  };
+      const renderArrow: RenderPopperArrowFn = ({ arrowProps, placement }) => {
+        return (
+          <div
+            {...arrowProps}
+            data-placement={placement}
+            className={`ColorPicker__arrow ColorPicker__arrow--${theme === GrafanaTheme.Light ? 'light' : 'dark'}`}
+          />
+        );
+      };
 
-  closeColorPicker = () => {
-    setTimeout(() => {
-      if (this.colorPickerDrop && this.colorPickerDrop.tether) {
-        this.colorPickerDrop.destroy();
-      }
-    }, 100);
-  };
+      return (
+        <PopperController content={popoverElement} hideAfter={300}>
+          {(showPopper, hidePopper, popperProps) => {
+            return (
+              <>
+                {this.pickerTriggerRef.current && (
+                  <Popper
+                    {...popperProps}
+                    referenceElement={this.pickerTriggerRef.current}
+                    wrapperClassName="ColorPicker"
+                    renderArrow={withArrow && (renderPopoverArrowFunction || renderArrow)}
+                    onMouseLeave={hidePopper}
+                    onMouseEnter={showPopper}
+                  />
+                )}
 
-  onColorSelect = (color: string) => {
-    this.props.onChange(color);
+                {children ? (
+                  React.cloneElement(children as JSX.Element, {
+                    ref: this.pickerTriggerRef,
+                    onClick: showPopper,
+                    onMouseLeave: hidePopper,
+                  })
+                ) : (
+                  <div
+                    ref={this.pickerTriggerRef}
+                    onClick={showPopper}
+                    onMouseLeave={hidePopper}
+                    className="sp-replacer sp-light"
+                  >
+                    <div className="sp-preview">
+                      <div
+                        className="sp-preview-inner"
+                        style={{
+                          backgroundColor: getColorFromHexRgbOrName(this.props.color || '#000000', theme),
+                        }}
+                      />
+                    </div>
+                  </div>
+                )}
+              </>
+            );
+          }}
+        </PopperController>
+      );
+    }
   };
+};
 
-  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>
-    );
-  }
-}
-
-export default ColorPicker;
+export const ColorPicker = colorPickerFactory(ColorPickerPopover, 'ColorPicker');
+export const SeriesColorPicker = colorPickerFactory(SeriesColorPickerPopover, 'SeriesColorPicker');

+ 40 - 0
packages/grafana-ui/src/components/ColorPicker/ColorPickerPopover.story.tsx

@@ -0,0 +1,40 @@
+import React from 'react';
+import { storiesOf } from '@storybook/react';
+import { ColorPickerPopover } from './ColorPickerPopover';
+import { withKnobs } from '@storybook/addon-knobs';
+
+import { withCenteredStory } from '../../utils/storybook/withCenteredStory';
+import { getThemeKnob } from '../../utils/storybook/themeKnob';
+import { SeriesColorPickerPopover } from './SeriesColorPickerPopover';
+
+const ColorPickerPopoverStories = storiesOf('UI/ColorPicker/Popovers', module);
+
+ColorPickerPopoverStories.addDecorator(withCenteredStory).addDecorator(withKnobs);
+
+ColorPickerPopoverStories.add('default', () => {
+  const selectedTheme = getThemeKnob();
+
+  return (
+    <ColorPickerPopover
+      color="#BC67E6"
+      onChange={color => {
+        console.log(color);
+      }}
+      theme={selectedTheme || undefined}
+    />
+  );
+});
+
+ColorPickerPopoverStories.add('SeriesColorPickerPopover', () => {
+  const selectedTheme = getThemeKnob();
+
+  return (
+    <SeriesColorPickerPopover
+      color="#BC67E6"
+      onChange={color => {
+        console.log(color);
+      }}
+      theme={selectedTheme || undefined}
+    />
+  );
+});

+ 75 - 0
packages/grafana-ui/src/components/ColorPicker/ColorPickerPopover.test.tsx

@@ -0,0 +1,75 @@
+import React from 'react';
+import { mount, ReactWrapper } from 'enzyme';
+import { ColorPickerPopover } from './ColorPickerPopover';
+import { getColorDefinitionByName, getNamedColorPalette } from '../../utils/namedColorsPalette';
+import { ColorSwatch } from './NamedColorsGroup';
+import { flatten } from 'lodash';
+import { GrafanaTheme } from '../../types';
+
+const allColors = flatten(Array.from(getNamedColorPalette().values()));
+
+describe('ColorPickerPopover', () => {
+  const BasicGreen = getColorDefinitionByName('green');
+  const BasicBlue = getColorDefinitionByName('blue');
+
+  describe('rendering', () => {
+    it('should render provided color as selected if color provided by name', () => {
+      const wrapper = mount(<ColorPickerPopover color={BasicGreen.name} onChange={() => {}} />);
+      const selectedSwatch = wrapper.find(ColorSwatch).findWhere(node => node.key() === BasicGreen.name);
+      const notSelectedSwatches = wrapper.find(ColorSwatch).filterWhere(node => node.prop('isSelected') === false);
+
+      expect(selectedSwatch.length).toBe(1);
+      expect(notSelectedSwatches.length).toBe(allColors.length - 1);
+      expect(selectedSwatch.prop('isSelected')).toBe(true);
+    });
+
+    it('should render provided color as selected if color provided by hex', () => {
+      const wrapper = mount(<ColorPickerPopover color={BasicGreen.variants.dark} onChange={() => {}} />);
+      const selectedSwatch = wrapper.find(ColorSwatch).findWhere(node => node.key() === BasicGreen.name);
+      const notSelectedSwatches = wrapper.find(ColorSwatch).filterWhere(node => node.prop('isSelected') === false);
+
+      expect(selectedSwatch.length).toBe(1);
+      expect(notSelectedSwatches.length).toBe(allColors.length - 1);
+      expect(selectedSwatch.prop('isSelected')).toBe(true);
+    });
+  });
+
+  describe('named colors support', () => {
+    const onChangeSpy = jest.fn();
+    let wrapper: ReactWrapper;
+
+    afterEach(() => {
+      wrapper.unmount();
+      onChangeSpy.mockClear();
+    });
+
+    it('should pass hex color value to onChange prop by default', () => {
+      wrapper = mount(
+        <ColorPickerPopover color={BasicGreen.variants.dark} onChange={onChangeSpy} theme={GrafanaTheme.Light} />
+      );
+      const basicBlueSwatch = wrapper.find(ColorSwatch).findWhere(node => node.key() === BasicBlue.name);
+
+      basicBlueSwatch.simulate('click');
+
+      expect(onChangeSpy).toBeCalledTimes(1);
+      expect(onChangeSpy).toBeCalledWith(BasicBlue.variants.light);
+    });
+
+    it('should pass color name to onChange prop when named colors enabled', () => {
+      wrapper = mount(
+        <ColorPickerPopover
+          enableNamedColors
+          color={BasicGreen.variants.dark}
+          onChange={onChangeSpy}
+          theme={GrafanaTheme.Light}
+        />
+      );
+      const basicBlueSwatch = wrapper.find(ColorSwatch).findWhere(node => node.key() === BasicBlue.name);
+
+      basicBlueSwatch.simulate('click');
+
+      expect(onChangeSpy).toBeCalledTimes(1);
+      expect(onChangeSpy).toBeCalledWith(BasicBlue.name);
+    });
+  });
+});

+ 102 - 85
packages/grafana-ui/src/components/ColorPicker/ColorPickerPopover.tsx

@@ -1,112 +1,129 @@
 import React from 'react';
-import $ from 'jquery';
-import tinycolor from 'tinycolor2';
-import { ColorPalette } from './ColorPalette';
-import { SpectrumPicker } from './SpectrumPicker';
+import { NamedColorsPalette } from './NamedColorsPalette';
+import { getColorName, getColorFromHexRgbOrName } from '../../utils/namedColorsPalette';
+import { ColorPickerProps, warnAboutColorPickerPropsDeprecation } from './ColorPicker';
+import { GrafanaTheme } from '../../types';
+import { PopperContentProps } from '../Tooltip/PopperController';
+import SpectrumPalette from './SpectrumPalette';
 
-const DEFAULT_COLOR = '#000000';
-
-export interface Props {
-  color: string;
-  onColorSelect: (c: string) => void;
+export interface Props<T> extends ColorPickerProps, PopperContentProps {
+  customPickers?: T;
 }
 
-export class ColorPickerPopover extends React.Component<Props, any> {
-  pickerNavElem: any;
+type PickerType = 'palette' | 'spectrum';
+
+interface CustomPickersDescriptor {
+  [key: string]: {
+    tabComponent: React.ComponentType<ColorPickerProps>;
+    name: string;
+  };
+}
+interface State<T> {
+  activePicker: PickerType | keyof T;
+}
 
-  constructor(props: Props) {
+export class ColorPickerPopover<T extends CustomPickersDescriptor> extends React.Component<Props<T>, State<T>> {
+  constructor(props: Props<T>) {
     super(props);
     this.state = {
-      tab: 'palette',
-      color: this.props.color || DEFAULT_COLOR,
-      colorString: this.props.color || DEFAULT_COLOR,
+      activePicker: 'palette',
     };
+    warnAboutColorPickerPropsDeprecation('ColorPickerPopover', props);
   }
 
-  setPickerNavElem(elem: any) {
-    this.pickerNavElem = $(elem);
-  }
+  getTabClassName = (tabName: PickerType | keyof T) => {
+    const { activePicker } = this.state;
+    return `ColorPickerPopover__tab ${activePicker === tabName && 'ColorPickerPopover__tab--active'}`;
+  };
 
-  setColor(color: string) {
-    const newColor = tinycolor(color);
-    if (newColor.isValid()) {
-      this.setState({ color: newColor.toString(), colorString: newColor.toString() });
-      this.props.onColorSelect(color);
-    }
-  }
+  handleChange = (color: any) => {
+    const { onColorChange, onChange, enableNamedColors, theme } = this.props;
+    const changeHandler = onColorChange || onChange;
 
-  sampleColorSelected(color: string) {
-    this.setColor(color);
-  }
+    if (enableNamedColors) {
+      return changeHandler(color);
+    }
+    changeHandler(getColorFromHexRgbOrName(color, theme));
+  };
 
-  spectrumColorSelected(color: any) {
-    const rgbColor = color.toRgbString();
-    this.setColor(rgbColor);
-  }
+  handleTabChange = (tab: PickerType | keyof T) => {
+    return () => this.setState({ activePicker: tab });
+  };
 
-  onColorStringChange(e: any) {
-    const colorString = e.target.value;
-    this.setState({ colorString: colorString });
+  renderPicker = () => {
+    const { activePicker } = this.state;
+    const { color, theme } = this.props;
 
-    const newColor = tinycolor(colorString);
-    if (newColor.isValid()) {
-      // Update only color state
-      const newColorString = newColor.toString();
-      this.setState({ color: newColorString });
-      this.props.onColorSelect(newColorString);
+    switch (activePicker) {
+      case 'spectrum':
+        return <SpectrumPalette color={color} onChange={this.handleChange} theme={theme} />;
+      case 'palette':
+        return <NamedColorsPalette color={getColorName(color, theme)} onChange={this.handleChange} theme={theme} />;
+      default:
+        return this.renderCustomPicker(activePicker);
     }
-  }
+  };
 
-  onColorStringBlur(e: any) {
-    const colorString = e.target.value;
-    this.setColor(colorString);
-  }
+  renderCustomPicker = (tabKey: keyof T) => {
+    const { customPickers, color, theme } = this.props;
+    if (!customPickers) {
+      return null;
+    }
 
-  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 React.createElement(customPickers[tabKey].tabComponent, {
+      color,
+      theme,
+      onChange: this.handleChange,
     });
-  }
+  };
 
-  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>
+  renderCustomPickerTabs = () => {
+    const { customPickers } = this.props;
+
+    if (!customPickers) {
+      return null;
+    }
+
+    return (
+      <>
+        {Object.keys(customPickers).map(key => {
+          return (
+            <div
+              className={this.getTabClassName(key)}
+              onClick={this.handleTabChange(key)}
+              key={key}
+            >
+              {customPickers[key].name}
+            </div>
+          );
+        })}
+      </>
     );
-    const currentTab = this.state.tab === 'palette' ? paletteTab : spectrumTab;
+  };
+
+  render() {
+    const { theme } = this.props;
+    const colorPickerTheme = theme || GrafanaTheme.Dark;
 
     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 className={`ColorPickerPopover ColorPickerPopover--${colorPickerTheme}`}>
+        <div className="ColorPickerPopover__tabs">
+          <div
+            className={this.getTabClassName('palette')}
+            onClick={this.handleTabChange('palette')}
+          >
+            Colors
+          </div>
+          <div
+            className={this.getTabClassName('spectrum')}
+            onClick={this.handleTabChange('spectrum')}
+          >
+            Custom
+          </div>
+          {this.renderCustomPickerTabs()}
         </div>
+
+        <div className="ColorPickerPopover__content">{this.renderPicker()}</div>
       </div>
     );
   }

+ 110 - 0
packages/grafana-ui/src/components/ColorPicker/NamedColorsGroup.tsx

@@ -0,0 +1,110 @@
+import React, { FunctionComponent } from 'react';
+import { Themeable, GrafanaTheme } from '../../types';
+import { ColorDefinition, getColorForTheme } from '../../utils/namedColorsPalette';
+import { Color } from 'csstype';
+import { find, upperFirst } from 'lodash';
+
+type ColorChangeHandler = (color: ColorDefinition) => void;
+
+export enum ColorSwatchVariant {
+  Small = 'small',
+  Large = 'large',
+}
+
+interface ColorSwatchProps extends Themeable, React.DOMAttributes<HTMLDivElement> {
+  color: string;
+  label?: string;
+  variant?: ColorSwatchVariant;
+  isSelected?: boolean;
+}
+
+export const ColorSwatch: FunctionComponent<ColorSwatchProps> = ({
+  color,
+  label,
+  variant = ColorSwatchVariant.Small,
+  isSelected,
+  theme,
+  ...otherProps
+}) => {
+  const isSmall = variant === ColorSwatchVariant.Small;
+  const swatchSize = isSmall ? '16px' : '32px';
+  const selectedSwatchBorder = theme === GrafanaTheme.Light ? '#ffffff' : '#1A1B1F';
+  const swatchStyles = {
+    width: swatchSize,
+    height: swatchSize,
+    borderRadius: '50%',
+    background: `${color}`,
+    marginRight: isSmall ? '0px' : '8px',
+    boxShadow: isSelected ? `inset 0 0 0 2px ${color}, inset 0 0 0 4px ${selectedSwatchBorder}` : 'none',
+  };
+
+  return (
+    <div
+      style={{
+        display: 'flex',
+        alignItems: 'center',
+        cursor: 'pointer',
+      }}
+      {...otherProps}
+    >
+      <div style={swatchStyles} />
+      {variant === ColorSwatchVariant.Large && <span>{label}</span>}
+    </div>
+  );
+};
+
+interface NamedColorsGroupProps extends Themeable {
+  colors: ColorDefinition[];
+  selectedColor?: Color;
+  onColorSelect: ColorChangeHandler;
+  key?: string;
+}
+
+const NamedColorsGroup: FunctionComponent<NamedColorsGroupProps> = ({
+  colors,
+  selectedColor,
+  onColorSelect,
+  theme,
+  ...otherProps
+}) => {
+  const primaryColor = find(colors, color => !!color.isPrimary);
+
+  return (
+    <div {...otherProps} style={{ display: 'flex', flexDirection: 'column' }}>
+      {primaryColor && (
+        <ColorSwatch
+          key={primaryColor.name}
+          isSelected={primaryColor.name === selectedColor}
+          variant={ColorSwatchVariant.Large}
+          color={getColorForTheme(primaryColor, theme)}
+          label={upperFirst(primaryColor.hue)}
+          onClick={() => onColorSelect(primaryColor)}
+          theme={theme}
+        />
+      )}
+      <div
+        style={{
+          display: 'flex',
+          marginTop: '8px',
+        }}
+      >
+        {colors.map(
+          color =>
+            !color.isPrimary && (
+              <div key={color.name} style={{ marginRight: '4px' }}>
+                <ColorSwatch
+                  key={color.name}
+                  isSelected={color.name === selectedColor}
+                  color={getColorForTheme(color, theme)}
+                  onClick={() => onColorSelect(color)}
+                  theme={theme}
+                />
+              </div>
+            )
+        )}
+      </div>
+    </div>
+  );
+};
+
+export default NamedColorsGroup;

+ 52 - 0
packages/grafana-ui/src/components/ColorPicker/NamedColorsPalette.story.tsx

@@ -0,0 +1,52 @@
+import React from 'react';
+import { storiesOf } from '@storybook/react';
+import { NamedColorsPalette } from './NamedColorsPalette';
+import { getColorName, getColorDefinitionByName } from '../../utils/namedColorsPalette';
+import { withKnobs, select } from '@storybook/addon-knobs';
+import { withCenteredStory } from '../../utils/storybook/withCenteredStory';
+import { UseState } from '../../utils/storybook/UseState';
+
+const BasicGreen = getColorDefinitionByName('green');
+const BasicBlue = getColorDefinitionByName('blue');
+const LightBlue = getColorDefinitionByName('light-blue');
+
+const NamedColorsPaletteStories = storiesOf('UI/ColorPicker/Palettes/NamedColorsPalette', module);
+
+NamedColorsPaletteStories.addDecorator(withKnobs).addDecorator(withCenteredStory);
+
+NamedColorsPaletteStories.add('Named colors swatch - support for named colors', () => {
+  const selectedColor = select(
+    'Selected color',
+    {
+      Green: 'green',
+      Red: 'red',
+      'Light blue': 'light-blue',
+    },
+    'red'
+  );
+
+  return (
+    <UseState initialState={selectedColor}>
+      {(selectedColor, updateSelectedColor) => {
+        return <NamedColorsPalette color={selectedColor} onChange={updateSelectedColor} />;
+      }}
+    </UseState>
+  );
+}).add('Named colors swatch - support for hex values', () => {
+  const selectedColor = select(
+    'Selected color',
+    {
+      Green: BasicGreen.variants.dark,
+      Red: BasicBlue.variants.dark,
+      'Light blue': LightBlue.variants.dark,
+    },
+    'red'
+  );
+  return (
+    <UseState initialState={selectedColor}>
+      {(selectedColor, updateSelectedColor) => {
+        return <NamedColorsPalette color={getColorName(selectedColor)} onChange={updateSelectedColor} />;
+      }}
+    </UseState>
+  );
+});

+ 36 - 0
packages/grafana-ui/src/components/ColorPicker/NamedColorsPalette.test.tsx

@@ -0,0 +1,36 @@
+import React from 'react';
+import { mount, ReactWrapper } from 'enzyme';
+import { NamedColorsPalette } from './NamedColorsPalette';
+import { ColorSwatch } from './NamedColorsGroup';
+import { getColorDefinitionByName } from '../../utils';
+import { GrafanaTheme } from '../../types';
+
+describe('NamedColorsPalette', () => {
+
+  const BasicGreen = getColorDefinitionByName('green');
+
+  describe('theme support for named colors', () => {
+    let wrapper: ReactWrapper, selectedSwatch;
+
+    afterEach(() => {
+      wrapper.unmount();
+    });
+
+    it('should render provided color variant specific for theme', () => {
+      wrapper = mount(<NamedColorsPalette color={BasicGreen.name} theme={GrafanaTheme.Dark} onChange={() => {}} />);
+      selectedSwatch = wrapper.find(ColorSwatch).findWhere(node => node.key() === BasicGreen.name);
+      expect(selectedSwatch.prop('color')).toBe(BasicGreen.variants.dark);
+
+      wrapper.unmount();
+      wrapper = mount(<NamedColorsPalette color={BasicGreen.name} theme={GrafanaTheme.Light} onChange={() => {}} />);
+      selectedSwatch = wrapper.find(ColorSwatch).findWhere(node => node.key() === BasicGreen.name);
+      expect(selectedSwatch.prop('color')).toBe(BasicGreen.variants.light);
+    });
+
+    it('should render dar variant of provided color when theme not provided', () => {
+      wrapper = mount(<NamedColorsPalette color={BasicGreen.name} onChange={() => {}} />);
+      selectedSwatch = wrapper.find(ColorSwatch).findWhere(node => node.key() === BasicGreen.name);
+      expect(selectedSwatch.prop('color')).toBe(BasicGreen.variants.dark);
+    });
+  });
+});

+ 39 - 0
packages/grafana-ui/src/components/ColorPicker/NamedColorsPalette.tsx

@@ -0,0 +1,39 @@
+import React from 'react';
+import { Color, getNamedColorPalette } from '../../utils/namedColorsPalette';
+import { Themeable } from '../../types/index';
+import NamedColorsGroup from './NamedColorsGroup';
+
+interface NamedColorsPaletteProps extends Themeable {
+  color?: Color;
+  onChange: (colorName: string) => void;
+}
+
+export const NamedColorsPalette = ({ color, onChange, theme }: NamedColorsPaletteProps) => {
+  const swatches: JSX.Element[] = [];
+  getNamedColorPalette().forEach((colors, hue) => {
+    swatches.push(
+      <NamedColorsGroup
+        key={hue}
+        theme={theme}
+        selectedColor={color}
+        colors={colors}
+        onColorSelect={color => {
+          onChange(color.name);
+        }}
+      />
+    );
+  });
+
+  return (
+    <div
+      style={{
+        display: 'grid',
+        gridTemplateColumns: 'repeat(3, 1fr)',
+        gridRowGap: '24px',
+        gridColumnGap: '24px',
+      }}
+    >
+      {swatches}
+    </div>
+  );
+};

+ 0 - 85
packages/grafana-ui/src/components/ColorPicker/SeriesColorPicker.tsx

@@ -1,85 +0,0 @@
-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' }],
-        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;
-    return (
-      <div className={optionalClass} ref={e => (this.pickerElem = e)} onClick={this.onClickToOpen}>
-        {children}
-      </div>
-    );
-  }
-}

+ 35 - 14
packages/grafana-ui/src/components/ColorPicker/SeriesColorPickerPopover.tsx

@@ -1,23 +1,44 @@
-import React from 'react';
+import React, { FunctionComponent } from 'react';
+
 import { ColorPickerPopover } from './ColorPickerPopover';
+import { ColorPickerProps } from './ColorPicker';
+import { PopperContentProps } from '../Tooltip/PopperController';
+import { Switch } from '../Switch/Switch';
 
-export interface SeriesColorPickerPopoverProps {
-  color: string;
+export interface SeriesColorPickerPopoverProps extends ColorPickerProps, PopperContentProps {
   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>
-    );
-  }
-}
+export const SeriesColorPickerPopover: FunctionComponent<SeriesColorPickerPopoverProps> = props => {
+  const { yaxis, onToggleAxis, color, ...colorPickerProps } = props;
+
+  return (
+    <ColorPickerPopover
+      {...colorPickerProps}
+      color={color || '#000000'}
+      customPickers={{
+        yaxis: {
+          name: 'Y-Axis',
+          tabComponent: () => (
+            <Switch
+              key="yaxisSwitch"
+              label="Use right y-axis"
+              className="ColorPicker__axisSwitch"
+              labelClass="ColorPicker__axisSwitchLabel"
+              checked={yaxis === 2}
+              onChange={() => {
+                if (onToggleAxis) {
+                  onToggleAxis();
+                }
+              }}
+            />
+          ),
+        },
+      }}
+    />
+  );
+};
 
 interface AxisSelectorProps {
   yaxis: number;

+ 23 - 0
packages/grafana-ui/src/components/ColorPicker/SpectrumPalette.story.tsx

@@ -0,0 +1,23 @@
+import React from 'react';
+import { storiesOf } from '@storybook/react';
+import { withKnobs } from '@storybook/addon-knobs';
+
+import SpectrumPalette from './SpectrumPalette';
+import { withCenteredStory } from '../../utils/storybook/withCenteredStory';
+import { UseState } from '../../utils/storybook/UseState';
+import { getThemeKnob } from '../../utils/storybook/themeKnob';
+
+const SpectrumPaletteStories = storiesOf('UI/ColorPicker/Palettes/SpectrumPalette', module);
+
+SpectrumPaletteStories.addDecorator(withCenteredStory).addDecorator(withKnobs);
+
+SpectrumPaletteStories.add('Named colors swatch - support for named colors', () => {
+  const selectedTheme = getThemeKnob();
+  return (
+    <UseState initialState="red">
+      {(selectedColor, updateSelectedColor) => {
+        return <SpectrumPalette theme={selectedTheme} color={selectedColor} onChange={updateSelectedColor} />;
+      }}
+    </UseState>
+  );
+});

+ 100 - 0
packages/grafana-ui/src/components/ColorPicker/SpectrumPalette.tsx

@@ -0,0 +1,100 @@
+import React from 'react';
+import { CustomPicker, ColorResult } from 'react-color';
+
+import { Saturation, Hue, Alpha } from 'react-color/lib/components/common';
+import { getColorFromHexRgbOrName } from '../../utils/namedColorsPalette';
+import tinycolor from 'tinycolor2';
+import ColorInput from './ColorInput';
+import { Themeable, GrafanaTheme } from '../../types';
+import SpectrumPalettePointer, { SpectrumPalettePointerProps } from './SpectrumPalettePointer';
+
+export interface SpectrumPaletteProps extends Themeable {
+  color: string;
+  onChange: (color: string) => void;
+}
+
+const renderPointer = (theme?: GrafanaTheme) => (props: SpectrumPalettePointerProps) => (
+  <SpectrumPalettePointer {...props} theme={theme} />
+);
+
+// @ts-ignore
+const SpectrumPicker = CustomPicker<Themeable>(({ rgb, hsl, onChange, theme }) => {
+  return (
+    <div
+      style={{
+        display: 'flex',
+        width: '100%',
+        flexDirection: 'column',
+      }}
+    >
+      <div
+        style={{
+          display: 'flex',
+        }}
+      >
+        <div
+          style={{
+            display: 'flex',
+            flexGrow: 1,
+            flexDirection: 'column',
+          }}
+        >
+          <div
+            style={{
+              position: 'relative',
+              height: '100px',
+              width: '100%',
+            }}
+          >
+            {/*
+      // @ts-ignore */}
+            <Saturation onChange={onChange} hsl={hsl} hsv={tinycolor(hsl).toHsv()} />
+          </div>
+          <div
+            style={{
+              width: '100%',
+              height: '16px',
+              marginTop: '16px',
+              position: 'relative',
+              background: 'white',
+            }}
+          >
+            {/*
+      // @ts-ignore */}
+            <Alpha rgb={rgb} hsl={hsl} a={rgb.a} onChange={onChange} pointer={renderPointer(theme)} />
+          </div>
+        </div>
+
+        <div
+          style={{
+            position: 'relative',
+            width: '16px',
+            height: '100px',
+            marginLeft: '16px',
+          }}
+        >
+          {/*
+        // @ts-ignore */}
+          <Hue onChange={onChange} hsl={hsl} direction="vertical" pointer={renderPointer(theme)} />
+        </div>
+      </div>
+    </div>
+  );
+});
+
+const SpectrumPalette: React.FunctionComponent<SpectrumPaletteProps> = ({ color, onChange, theme }) => {
+  return (
+    <div>
+      <SpectrumPicker
+        color={tinycolor(getColorFromHexRgbOrName(color)).toRgb()}
+        onChange={(a: ColorResult) => {
+          onChange(tinycolor(a.rgb).toString());
+        }}
+        theme={theme}
+      />
+      <ColorInput color={color} onChange={onChange} style={{ marginTop: '16px' }} />
+    </div>
+  );
+};
+
+export default SpectrumPalette;

+ 80 - 0
packages/grafana-ui/src/components/ColorPicker/SpectrumPalettePointer.tsx

@@ -0,0 +1,80 @@
+import React from 'react';
+import { GrafanaTheme, Themeable } from '../../types';
+
+export interface SpectrumPalettePointerProps extends Themeable {
+  direction?: string;
+}
+
+const SpectrumPalettePointer: React.FunctionComponent<SpectrumPalettePointerProps> = ({
+  theme,
+  direction,
+}) => {
+  const styles = {
+    picker: {
+      width: '16px',
+      height: '16px',
+      transform: direction === 'vertical' ? 'translate(0, -8px)' : 'translate(-8px, 0)',
+    },
+  };
+
+  const pointerColor = theme === GrafanaTheme.Light ? '#3F444D' : '#8E8E8E';
+
+  let pointerStyles: React.CSSProperties = {
+    position: 'absolute',
+    left: '6px',
+    width: '0',
+    height: '0',
+    borderStyle: 'solid',
+    background: 'none',
+  };
+
+  let topArrowStyles: React.CSSProperties = {
+    top: '-7px',
+    borderWidth: '6px 3px 0px 3px',
+    borderColor: `${pointerColor} transparent transparent transparent`,
+  };
+
+  let bottomArrowStyles: React.CSSProperties = {
+    bottom: '-7px',
+    borderWidth: '0px 3px 6px 3px',
+    borderColor: ` transparent transparent ${pointerColor} transparent`,
+  };
+
+  if (direction === 'vertical') {
+    pointerStyles = {
+      ...pointerStyles,
+      left: 'auto',
+    };
+    topArrowStyles = {
+      borderWidth: '3px 0px 3px 6px',
+      borderColor: `transparent transparent transparent ${pointerColor}`,
+      left: '-7px',
+      top: '7px',
+    };
+    bottomArrowStyles = {
+      borderWidth: '3px 6px 3px 0px',
+      borderColor: `transparent ${pointerColor} transparent transparent`,
+      right: '-7px',
+      top: '7px',
+    };
+  }
+
+  return (
+    <div style={styles.picker}>
+      <div
+        style={{
+          ...pointerStyles,
+          ...topArrowStyles,
+        }}
+      />
+      <div
+        style={{
+          ...pointerStyles,
+          ...bottomArrowStyles,
+        }}
+      />
+    </div>
+  );
+};
+
+export default SpectrumPalettePointer;

+ 0 - 72
packages/grafana-ui/src/components/ColorPicker/SpectrumPicker.tsx

@@ -1,72 +0,0 @@
-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} />;
-  }
-}

+ 192 - 1
packages/grafana-ui/src/components/ColorPicker/_ColorPicker.scss

@@ -1,8 +1,172 @@
+$arrowSize: 15px;
+.ColorPicker {
+  @extend .popper;
+  font-size: 12px;
+}
+
+.ColorPicker__arrow {
+  width: 0;
+  height: 0;
+  border-style: solid;
+  position: absolute;
+  margin: 0px;
+
+  &[data-placement^='top'] {
+    border-width: $arrowSize $arrowSize 0 $arrowSize;
+    border-left-color: transparent;
+    border-right-color: transparent;
+    border-bottom-color: transparent;
+    bottom: -$arrowSize;
+    left: calc(50%-#{$arrowSize});
+    padding-top: $arrowSize;
+  }
+
+  &[data-placement^='bottom'] {
+    border-width: 0 $arrowSize $arrowSize $arrowSize;
+    border-left-color: transparent;
+    border-right-color: transparent;
+    border-top-color: transparent;
+    top: 0;
+    left: calc(50%-#{$arrowSize});
+  }
+
+  &[data-placement^='bottom-start'] {
+    border-width: 0 $arrowSize $arrowSize $arrowSize;
+    border-left-color: transparent;
+    border-right-color: transparent;
+    border-top-color: transparent;
+    top: 0;
+    left: $arrowSize;
+  }
+
+  &[data-placement^='bottom-end'] & {
+    border-width: 0 $arrowSize $arrowSize $arrowSize;
+    border-left-color: transparent;
+    border-right-color: transparent;
+    border-top-color: transparent;
+    top: 0;
+    left: calc(100% -$arrowSize);
+  }
+
+  &[data-placement^='right'] {
+    border-width: $arrowSize $arrowSize $arrowSize 0;
+    border-left-color: transparent;
+    border-top-color: transparent;
+    border-bottom-color: transparent;
+    left: 0;
+    top: calc(50%-#{$arrowSize});
+  }
+
+  &[data-placement^='left'] {
+    border-width: $arrowSize 0 $arrowSize $arrowSize;
+    border-top-color: transparent;
+    border-right-color: transparent;
+    border-bottom-color: transparent;
+    right: -$arrowSize;
+    top: calc(50%-#{$arrowSize});
+  }
+}
+
+.ColorPicker__arrow--light {
+  border-color: #ffffff;
+}
+
+.ColorPicker__arrow--dark {
+  border-color: #1e2028;
+}
+
+// Top
+.ColorPicker[data-placement^='top'] {
+  padding-bottom: $arrowSize;
+}
+
+// Bottom
+.ColorPicker[data-placement^='bottom'] {
+  padding-top: $arrowSize;
+}
+
+.ColorPicker[data-placement^='bottom-start'] {
+  padding-top: $arrowSize;
+}
+
+.ColorPicker[data-placement^='bottom-end'] {
+  padding-top: $arrowSize;
+}
+
+// Right
+.ColorPicker[data-placement^='right'] {
+  padding-left: $arrowSize;
+}
+
+// Left
+.ColorPicker[data-placement^='left'] {
+  padding-right: $arrowSize;
+}
+
+.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;
+    cursor: pointer;
+  }
+  .ColorPickerPopover__tab--active {
+    background: none;
+  }
+}
+
+.ColorPickerPopover__content {
+  width: 336px;
+  min-height: 184px;
+  padding: 24px;
+}
+
+.ColorPickerPopover__tabs {
+  display: flex;
+  width: 100%;
+  border-radius: 3px 3px 0 0;
+  overflow: hidden;
+}
+
+.ColorPickerPopover__tab {
+  width: 50%;
+  text-align: center;
+  padding: 8px 0;
+  background: #dde4ed;
+}
+
+.ColorPickerPopover__tab--active {
+  background: white;
+}
+
+.ColorPicker__axisSwitch {
+  width: 100%;
+}
+
+.ColorPicker__axisSwitchLabel {
+  display: flex;
+  flex-grow: 1;
+}
+
 .sp-replacer {
   background: inherit;
   border: none;
   color: inherit;
   padding: 0;
+  border-radius: 10px;
 }
 
 .sp-replacer:hover,
@@ -35,10 +199,22 @@
   margin: 0;
   float: left;
   z-index: 0;
+  background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAMCAIAAADZF8uwAAAAGUlEQVQYV2M4gwH+YwCGIasIUwhT25BVBADtzYNYrHvv4gAAAABJRU5ErkJggg==);
+}
+
+.sp-preview-inner,
+.sp-alpha-inner,
+.sp-thumb-inner {
+  display: block;
+  position: absolute;
+  top: 0;
+  left: 0;
+  bottom: 0;
+  right: 0;
 }
 
 .gf-color-picker__body {
-  padding-bottom: 10px;
+  padding-bottom: $arrowSize;
   padding-left: 6px;
 }
 
@@ -47,3 +223,18 @@
     width: 210px;
   }
 }
+
+// TODO: Remove. This is a temporary solution until color picker popovers are used
+// with Drop.js.
+.drop-popover.drop-popover--transparent {
+  .drop-content {
+    border: none;
+    background: none;
+    padding: 0;
+    max-width: none;
+
+    &:before {
+      display: none;
+    }
+  }
+}

+ 7 - 5
packages/grafana-ui/src/components/CustomScrollbar/CustomScrollbar.tsx

@@ -9,6 +9,8 @@ interface Props {
   autoHideDuration?: number;
   autoMaxHeight?: string;
   hideTracksWhenNotNeeded?: boolean;
+  renderTrackHorizontal?: React.FunctionComponent<any>;
+  renderTrackVertical?: React.FunctionComponent<any>;
   scrollTop?: number;
   setScrollTop: (value: React.MouseEvent<HTMLElement>) => void;
   autoHeightMin?: number | string;
@@ -26,7 +28,7 @@ export class CustomScrollbar extends PureComponent<Props> {
     autoMaxHeight: '100%',
     hideTracksWhenNotNeeded: false,
     setScrollTop: () => {},
-    autoHeightMin: '0'
+    autoHeightMin: '0',
   };
 
   private ref: React.RefObject<Scrollbars>;
@@ -45,7 +47,7 @@ export class CustomScrollbar extends PureComponent<Props> {
       } else {
         ref.scrollTop(this.props.scrollTop);
       }
-   }
+    }
   }
 
   componentDidMount() {
@@ -57,7 +59,7 @@ export class CustomScrollbar extends PureComponent<Props> {
   }
 
   render() {
-    const { customClassName, children, autoMaxHeight } = this.props;
+    const { customClassName, children, autoMaxHeight, renderTrackHorizontal, renderTrackVertical } = this.props;
 
     return (
       <Scrollbars
@@ -67,8 +69,8 @@ export class CustomScrollbar extends PureComponent<Props> {
         // These autoHeightMin & autoHeightMax options affect firefox and chrome differently.
         // Before these where set to inhert but that caused problems with cut of legends in firefox
         autoHeightMax={autoMaxHeight}
-        renderTrackHorizontal={props => <div {...props} className="track-horizontal" />}
-        renderTrackVertical={props => <div {...props} className="track-vertical" />}
+        renderTrackHorizontal={renderTrackHorizontal || (props => <div {...props} className="track-horizontal" />)}
+        renderTrackVertical={renderTrackVertical || (props => <div {...props} className="track-vertical" />)}
         renderThumbHorizontal={props => <div {...props} className="thumb-horizontal" />}
         renderThumbVertical={props => <div {...props} className="thumb-vertical" />}
         renderView={props => <div {...props} className="view" />}

+ 14 - 21
packages/grafana-ui/src/components/Gauge/Gauge.tsx

@@ -1,18 +1,11 @@
 import React, { PureComponent } from 'react';
 import $ from 'jquery';
 
-import {
-  ValueMapping,
-  Threshold,
-  ThemeName,
-  MappingType,
-  BasicGaugeColor,
-  ThemeNames,
-  ValueMap,
-  RangeMap,
-} from '../../types/panel';
+import { ValueMapping, Threshold, MappingType, BasicGaugeColor, ValueMap, RangeMap } from '../../types/panel';
 import { TimeSeriesVMs } from '../../types/series';
 import { getValueFormat } from '../../utils/valueFormats/valueFormats';
+import { GrafanaTheme } from '../../types';
+import { getColorFromHexRgbOrName } from '../../utils/namedColorsPalette';
 
 type TimeSeriesValue = string | number | null;
 
@@ -31,7 +24,7 @@ export interface Props {
   suffix: string;
   unit: string;
   width: number;
-  theme?: ThemeName;
+  theme?: GrafanaTheme;
 }
 
 export class Gauge extends PureComponent<Props> {
@@ -48,7 +41,7 @@ export class Gauge extends PureComponent<Props> {
     thresholds: [],
     unit: 'none',
     stat: 'avg',
-    theme: ThemeNames.Dark,
+    theme: GrafanaTheme.Dark,
   };
 
   componentDidMount() {
@@ -144,29 +137,29 @@ export class Gauge extends PureComponent<Props> {
   }
 
   getFontColor(value: TimeSeriesValue) {
-    const { thresholds } = this.props;
+    const { thresholds, theme } = this.props;
 
     if (thresholds.length === 1) {
-      return thresholds[0].color;
+      return getColorFromHexRgbOrName(thresholds[0].color, theme);
     }
 
     const atThreshold = thresholds.filter(threshold => (value as number) === threshold.value)[0];
     if (atThreshold) {
-      return atThreshold.color;
+      return getColorFromHexRgbOrName(atThreshold.color, theme);
     }
 
     const belowThreshold = thresholds.filter(threshold => (value as number) > threshold.value);
 
     if (belowThreshold.length > 0) {
       const nearestThreshold = belowThreshold.sort((t1, t2) => t2.value - t1.value)[0];
-      return nearestThreshold.color;
+      return getColorFromHexRgbOrName(nearestThreshold.color, theme);
     }
 
     return BasicGaugeColor.Red;
   }
 
   getFormattedThresholds() {
-    const { maxValue, minValue, thresholds } = this.props;
+    const { maxValue, minValue, thresholds, theme } = this.props;
 
     const thresholdsSortedByIndex = [...thresholds].sort((t1, t2) => t1.index - t2.index);
     const lastThreshold = thresholdsSortedByIndex[thresholdsSortedByIndex.length - 1];
@@ -174,13 +167,13 @@ export class Gauge extends PureComponent<Props> {
     const formattedThresholds = [
       ...thresholdsSortedByIndex.map(threshold => {
         if (threshold.index === 0) {
-          return { value: minValue, color: threshold.color };
+          return { value: minValue, color: getColorFromHexRgbOrName(threshold.color, theme) };
         }
 
         const previousThreshold = thresholdsSortedByIndex[threshold.index - 1];
-        return { value: threshold.value, color: previousThreshold.color };
+        return { value: threshold.value, color: getColorFromHexRgbOrName(previousThreshold.color, theme) };
       }),
-      { value: maxValue, color: lastThreshold.color },
+      { value: maxValue, color: getColorFromHexRgbOrName(lastThreshold.color, theme) },
     ];
 
     return formattedThresholds;
@@ -208,7 +201,7 @@ export class Gauge extends PureComponent<Props> {
     }
 
     const dimension = Math.min(width, height * 1.3);
-    const backgroundColor = theme === ThemeNames.Light ? 'rgb(230,230,230)' : 'rgb(38,38,38)';
+    const backgroundColor = theme === GrafanaTheme.Light ? 'rgb(230,230,230)' : 'rgb(38,38,38)';
     const fontScale = parseInt('80', 10) / 100;
     const fontSize = Math.min(dimension / 5, 100) * fontScale;
     const gaugeWidthReduceRatio = showThresholdLabels ? 1.5 : 1;

+ 7 - 5
public/app/core/components/Switch/Switch.tsx → packages/grafana-ui/src/components/Switch/Switch.tsx

@@ -4,10 +4,11 @@ import _ from 'lodash';
 export interface Props {
   label: string;
   checked: boolean;
+  className?: string;
   labelClass?: string;
   switchClass?: string;
   transparent?: boolean;
-  onChange: (event) => any;
+  onChange: (event?: React.SyntheticEvent<HTMLInputElement>) => void;
 }
 
 export interface State {
@@ -19,20 +20,21 @@ export class Switch extends PureComponent<Props, State> {
     id: _.uniqueId(),
   };
 
-  internalOnChange = event => {
+  internalOnChange = (event: React.FormEvent<HTMLInputElement>) => {
     event.stopPropagation();
-    this.props.onChange(event);
+
+    this.props.onChange();
   };
 
   render() {
-    const { labelClass = '', switchClass = '', label, checked, transparent } = this.props;
+    const { labelClass = '', switchClass = '', label, checked, transparent, className } = this.props;
 
     const labelId = `check-${this.state.id}`;
     const labelClassName = `gf-form-label ${labelClass} ${transparent ? 'gf-form-label--transparent' : ''} pointer`;
     const switchClassName = `gf-form-switch ${switchClass} ${transparent ? 'gf-form-switch--transparent' : ''}`;
 
     return (
-      <label htmlFor={labelId} className="gf-form gf-form-switch-container">
+      <label htmlFor={labelId} className={`gf-form gf-form-switch-container ${className}`}>
         {label && <div className={labelClassName}>{label}</div>}
         <div className={switchClassName}>
           <input id={labelId} type="checkbox" checked={checked} onChange={this.internalOnChange} />

+ 8 - 5
packages/grafana-ui/src/components/ThresholdsEditor/ThresholdsEditor.tsx

@@ -1,12 +1,11 @@
 import React, { PureComponent } from 'react';
-// import tinycolor, { ColorInput } from 'tinycolor2';
-
-import { Threshold } from '../../types';
+import { Threshold, Themeable } from '../../types';
 import { ColorPicker } from '../ColorPicker/ColorPicker';
 import { PanelOptionsGroup } from '../PanelOptionsGroup/PanelOptionsGroup';
 import { colors } from '../../utils';
+import { getColorFromHexRgbOrName } from '@grafana/ui';
 
-export interface Props {
+export interface Props extends Themeable {
   thresholds: Threshold[];
   onChange: (thresholds: Threshold[]) => void;
 }
@@ -189,6 +188,7 @@ export class ThresholdsEditor extends PureComponent<Props, State> {
 
   render() {
     const { thresholds } = this.state;
+    const { theme } = this.props;
 
     return (
       <PanelOptionsGroup title="Thresholds">
@@ -199,7 +199,10 @@ export class ThresholdsEditor extends PureComponent<Props, State> {
                 <div className="thresholds-row-add-button" onClick={() => this.onAddThreshold(threshold.index + 1)}>
                   <i className="fa fa-plus" />
                 </div>
-                <div className="thresholds-row-color-indicator" style={{ backgroundColor: threshold.color }} />
+                <div
+                  className="thresholds-row-color-indicator"
+                  style={{ backgroundColor: getColorFromHexRgbOrName(threshold.color, theme) }}
+                />
                 <div className="thresholds-row-input">{this.renderInput(threshold)}</div>
               </div>
             );

+ 58 - 43
packages/grafana-ui/src/components/Tooltip/Popper.tsx

@@ -1,73 +1,88 @@
 import React, { PureComponent } from 'react';
 import * as PopperJS from 'popper.js';
-import { Manager, Popper as ReactPopper } from 'react-popper';
+import { Manager, Popper as ReactPopper, PopperArrowProps } from 'react-popper';
 import { Portal } from '@grafana/ui';
 import Transition from 'react-transition-group/Transition';
-
-export enum Themes {
-  Default = 'popper__background--default',
-  Error = 'popper__background--error',
-  Brand = 'popper__background--brand',
-}
+import { PopperContent } from './PopperController';
 
 const defaultTransitionStyles = {
   transition: 'opacity 200ms linear',
   opacity: 0,
 };
 
-const transitionStyles: {[key: string]: object} = {
+const transitionStyles: { [key: string]: object } = {
   exited: { opacity: 0 },
   entering: { opacity: 0 },
-  entered: { opacity: 1 },
-  exiting: { opacity: 0 },
+  entered: { opacity: 1, transitionDelay: '0s' },
+  exiting: { opacity: 0, transitionDelay: '500ms' },
 };
 
-interface Props extends React.DOMAttributes<HTMLDivElement> {
-  renderContent: (content: any) => any;
+export type RenderPopperArrowFn = (
+  props: {
+    arrowProps: PopperArrowProps;
+    placement: string;
+  }
+) => JSX.Element;
+
+interface Props extends React.HTMLAttributes<HTMLDivElement> {
   show: boolean;
   placement?: PopperJS.Placement;
-  content: string | ((props: any) => JSX.Element);
+  content: PopperContent<any>;
   referenceElement: PopperJS.ReferenceObject;
-  theme?: Themes;
+  wrapperClassName?: string;
+  renderArrow?: RenderPopperArrowFn;
 }
 
 class Popper extends PureComponent<Props> {
   render() {
-    const { renderContent, show, placement, onMouseEnter, onMouseLeave, theme } = this.props;
+    const { show, placement, onMouseEnter, onMouseLeave, className, wrapperClassName, renderArrow } = this.props;
     const { content } = this.props;
 
-    const popperBackgroundClassName = 'popper__background' + (theme ? ' ' + theme : '');
-
     return (
       <Manager>
         <Transition in={show} timeout={100} mountOnEnter={true} unmountOnExit={true}>
-          {transitionState => (
-            <Portal>
-              <ReactPopper placement={placement} referenceElement={this.props.referenceElement}>
-                {({ ref, style, placement, arrowProps }) => {
-                  return (
-                    <div
-                      onMouseEnter={onMouseEnter}
-                      onMouseLeave={onMouseLeave}
-                      ref={ref}
-                      style={{
-                        ...style,
-                        ...defaultTransitionStyles,
-                        ...transitionStyles[transitionState],
-                      }}
-                      data-placement={placement}
-                      className="popper"
-                    >
-                      <div className={popperBackgroundClassName}>
-                        {renderContent(content)}
-                        <div ref={arrowProps.ref} data-placement={placement} className="popper__arrow" />
+          {transitionState => {
+            return (
+              <Portal>
+                <ReactPopper
+                  placement={placement}
+                  referenceElement={this.props.referenceElement}
+                  // TODO: move modifiers config to popper controller
+                  modifiers={{ preventOverflow: { enabled: true, boundariesElement: 'window' } }}
+                >
+                  {({ ref, style, placement, arrowProps, scheduleUpdate }) => {
+                    return (
+                      <div
+                        onMouseEnter={onMouseEnter}
+                        onMouseLeave={onMouseLeave}
+                        ref={ref}
+                        style={{
+                          ...style,
+                          ...defaultTransitionStyles,
+                          ...transitionStyles[transitionState],
+                        }}
+                        data-placement={placement}
+                        className={`${wrapperClassName}`}
+                      >
+                        <div className={className}>
+                          {typeof content === 'string'
+                            ? content
+                            : React.cloneElement(content, {
+                                updatePopperPosition: scheduleUpdate,
+                              })}
+                          {renderArrow &&
+                            renderArrow({
+                              arrowProps,
+                              placement,
+                            })}
+                        </div>
                       </div>
-                    </div>
-                  );
-                }}
-              </ReactPopper>
-            </Portal>
-          )}
+                    );
+                  }}
+                </ReactPopper>
+              </Portal>
+            );
+          }}
         </Transition>
       </Manager>
     );

+ 27 - 22
packages/grafana-ui/src/components/Tooltip/PopperController.tsx

@@ -1,16 +1,19 @@
 import React from 'react';
 import * as PopperJS from 'popper.js';
-import { Themes } from './Popper';
 
-type PopperContent = string | (() => JSX.Element);
+// This API allows popovers to update Popper's position when e.g. popover content changes
+// updatePopperPosition is delivered to content by react-popper
+export interface PopperContentProps {
+  updatePopperPosition?: () => void;
+}
+
+export type PopperContent<T extends PopperContentProps> = string | React.ReactElement<T>;
 
 export interface UsingPopperProps {
   show?: boolean;
   placement?: PopperJS.Placement;
-  content: PopperContent;
+  content: PopperContent<any>;
   children: JSX.Element;
-  renderContent?: (content: PopperContent) => JSX.Element;
-  theme?: Themes;
 }
 
 type PopperControllerRenderProp = (
@@ -19,18 +22,16 @@ type PopperControllerRenderProp = (
   popperProps: {
     show: boolean;
     placement: PopperJS.Placement;
-    content: string | ((props: any) => JSX.Element);
-    renderContent: (content: any) => any;
-    theme?: Themes;
+    content: PopperContent<any>;
   }
 ) => JSX.Element;
 
 interface Props {
   placement?: PopperJS.Placement;
-  content: PopperContent;
+  content: PopperContent<any>;
   className?: string;
   children: PopperControllerRenderProp;
-  theme?: Themes;
+  hideAfter?: number;
 }
 
 interface State {
@@ -39,6 +40,8 @@ interface State {
 }
 
 class PopperController extends React.Component<Props, State> {
+  private hideTimeout: any;
+
   constructor(props: Props) {
     super(props);
 
@@ -60,6 +63,10 @@ class PopperController extends React.Component<Props, State> {
   }
 
   showPopper = () => {
+    if (this.hideTimeout) {
+      clearTimeout(this.hideTimeout);
+    }
+
     this.setState(prevState => ({
       ...prevState,
       show: true,
@@ -67,31 +74,29 @@ class PopperController extends React.Component<Props, State> {
   };
 
   hidePopper = () => {
+    if (this.props.hideAfter !== 0) {
+      this.hideTimeout = setTimeout(() => {
+        this.setState(prevState => ({
+          ...prevState,
+          show: false,
+        }));
+      }, this.props.hideAfter);
+      return;
+    }
     this.setState(prevState => ({
       ...prevState,
       show: false,
     }));
   };
 
-  renderContent(content: PopperContent) {
-    if (typeof content === 'function') {
-      // If it's a function we assume it's a React component
-      const ReactComponent = content;
-      return <ReactComponent />;
-    }
-    return content;
-  }
-
   render() {
-    const { children, content, theme } = this.props;
+    const { children, content } = this.props;
     const { show, placement } = this.state;
 
     return children(this.showPopper, this.hidePopper, {
       show,
       placement,
       content,
-      renderContent: this.renderContent,
-      theme,
     });
   }
 }

+ 16 - 1
packages/grafana-ui/src/components/Tooltip/Tooltip.tsx

@@ -3,8 +3,18 @@ import * as PopperJS from 'popper.js';
 import Popper from './Popper';
 import PopperController, { UsingPopperProps } from './PopperController';
 
-export const Tooltip = ({ children, renderContent, ...controllerProps }: UsingPopperProps) => {
+export enum Themes {
+  Default = 'popper__background--default',
+  Error = 'popper__background--error',
+  Brand = 'popper__background--brand',
+}
+
+interface TooltipProps extends UsingPopperProps {
+  theme?: Themes;
+}
+export const Tooltip = ({ children, theme, ...controllerProps }: TooltipProps) => {
   const tooltipTriggerRef = createRef<PopperJS.ReferenceObject>();
+  const popperBackgroundClassName = 'popper__background' + (theme ? ' ' + theme : '');
 
   return (
     <PopperController {...controllerProps}>
@@ -17,6 +27,11 @@ export const Tooltip = ({ children, renderContent, ...controllerProps }: UsingPo
                 onMouseEnter={showPopper}
                 onMouseLeave={hidePopper}
                 referenceElement={tooltipTriggerRef.current}
+                wrapperClassName='popper'
+                className={popperBackgroundClassName}
+                renderArrow={({ arrowProps, placement }) => (
+                  <div className="popper__arrow" data-placement={placement} {...arrowProps} />
+                )}
               />
             )}
             {React.cloneElement(children, {

+ 4 - 5
packages/grafana-ui/src/components/Tooltip/_Tooltip.scss

@@ -1,6 +1,5 @@
 $popper-margin-from-ref: 5px;
 
-
 @mixin popper-theme($backgroundColor, $arrowColor) {
   background: $backgroundColor;
   .popper__arrow {
@@ -22,6 +21,10 @@ $popper-margin-from-ref: 5px;
   box-shadow: 0 0 2px rgba(0, 0, 0, 0.5);
   padding: 10px;
 
+  .popper__arrow {
+    border-color: $tooltipBackground;
+  }
+
   // Themes
   &.popper__background--error {
     @include popper-theme($tooltipBackgroundError, $tooltipBackgroundError);
@@ -41,10 +44,6 @@ $popper-margin-from-ref: 5px;
   margin: 0px;
 }
 
-.popper__arrow {
-  border-color: $tooltipBackground;
-}
-
 // Top
 .popper[data-placement^='top'] {
   padding-bottom: $popper-margin-from-ref;

+ 2 - 2
packages/grafana-ui/src/components/index.ts

@@ -14,12 +14,12 @@ export { FormLabel } from './FormLabel/FormLabel';
 export { FormField } from './FormField/FormField';
 
 export { LoadingPlaceholder } from './LoadingPlaceholder/LoadingPlaceholder';
-export { ColorPicker } from './ColorPicker/ColorPicker';
+export {  ColorPicker, SeriesColorPicker } from './ColorPicker/ColorPicker';
 export { SeriesColorPickerPopover } from './ColorPicker/SeriesColorPickerPopover';
-export { SeriesColorPicker } from './ColorPicker/SeriesColorPicker';
 export { ThresholdsEditor } from './ThresholdsEditor/ThresholdsEditor';
 export { Graph } from './Graph/Graph';
 export { PanelOptionsGroup } from './PanelOptionsGroup/PanelOptionsGroup';
 export { PanelOptionsGrid } from './PanelOptionsGrid/PanelOptionsGrid';
 export { ValueMappingsEditor } from './ValueMappingsEditor/ValueMappingsEditor';
 export { Gauge } from './Gauge/Gauge';
+export { Switch } from './Switch/Switch';

+ 0 - 2
packages/grafana-ui/src/index.scss

@@ -1,3 +1 @@
-@import 'vendor/spectrum';
 @import 'components/index';
-

+ 9 - 0
packages/grafana-ui/src/types/index.ts

@@ -3,3 +3,12 @@ export * from './time';
 export * from './panel';
 export * from './plugin';
 export * from './datasource';
+
+export enum GrafanaTheme {
+  Light = 'light',
+  Dark = 'dark',
+}
+
+export interface Themeable {
+  theme?: GrafanaTheme;
+}

+ 1 - 8
packages/grafana-ui/src/types/panel.ts

@@ -36,7 +36,7 @@ export interface PanelMenuItem {
 export interface Threshold {
   index: number;
   value: number;
-  color?: string;
+  color: string;
 }
 
 export enum BasicGaugeColor {
@@ -66,10 +66,3 @@ export interface RangeMap extends BaseMap {
   from: string;
   to: string;
 }
-
-export type ThemeName = 'dark' | 'light';
-
-export enum ThemeNames {
-  Dark = 'dark',
-  Light = 'light',
-}

+ 0 - 1
packages/grafana-ui/src/utils/colors.ts

@@ -9,7 +9,6 @@ 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;
-
 export const colors = [
   '#7EB26D', // 0: pale green
   '#EAB839', // 1: mustard

+ 1 - 0
packages/grafana-ui/src/utils/index.ts

@@ -1,3 +1,4 @@
 export * from './processTimeSeries';
 export * from './valueFormats/valueFormats';
 export * from './colors';
+export * from './namedColorsPalette';

+ 66 - 0
packages/grafana-ui/src/utils/namedColorsPalette.test.ts

@@ -0,0 +1,66 @@
+import {
+  getColorName,
+  getColorDefinition,
+  getColorByName,
+  getColorFromHexRgbOrName,
+  getColorDefinitionByName,
+} from './namedColorsPalette';
+import { GrafanaTheme } from '../types/index';
+
+describe('colors', () => {
+  const SemiDarkBlue = getColorDefinitionByName('semi-dark-blue');
+
+  describe('getColorDefinition', () => {
+    it('returns undefined for unknown hex', () => {
+      expect(getColorDefinition('#ff0000', GrafanaTheme.Light)).toBeUndefined();
+      expect(getColorDefinition('#ff0000', GrafanaTheme.Dark)).toBeUndefined();
+    });
+
+    it('returns definition for known hex', () => {
+      expect(getColorDefinition(SemiDarkBlue.variants.light, GrafanaTheme.Light)).toEqual(SemiDarkBlue);
+      expect(getColorDefinition(SemiDarkBlue.variants.dark, GrafanaTheme.Dark)).toEqual(SemiDarkBlue);
+    });
+  });
+
+  describe('getColorName', () => {
+    it('returns undefined for unknown hex', () => {
+      expect(getColorName('#ff0000')).toBeUndefined();
+    });
+
+    it('returns name for known hex', () => {
+      expect(getColorName(SemiDarkBlue.variants.light, GrafanaTheme.Light)).toEqual(SemiDarkBlue.name);
+      expect(getColorName(SemiDarkBlue.variants.dark, GrafanaTheme.Dark)).toEqual(SemiDarkBlue.name);
+    });
+  });
+
+  describe('getColorByName', () => {
+    it('returns undefined for unknown color', () => {
+      expect(getColorByName('aruba-sunshine')).toBeUndefined();
+    });
+
+    it('returns color definiton for known color', () => {
+      expect(getColorByName(SemiDarkBlue.name)).toBe(SemiDarkBlue);
+    });
+  });
+
+  describe('getColorFromHexRgbOrName', () => {
+    it('returns undefined for unknown color', () => {
+      expect(() => getColorFromHexRgbOrName('aruba-sunshine')).toThrow();
+    });
+
+    it('returns dark hex variant for known color if theme not specified', () => {
+      expect(getColorFromHexRgbOrName(SemiDarkBlue.name)).toBe(SemiDarkBlue.variants.dark);
+    });
+
+    it("returns correct variant's hex for known color if theme specified", () => {
+      expect(getColorFromHexRgbOrName(SemiDarkBlue.name, GrafanaTheme.Light)).toBe(SemiDarkBlue.variants.light);
+    });
+
+    it('returns color if specified as hex or rgb/a', () => {
+      expect(getColorFromHexRgbOrName('ff0000')).toBe('ff0000');
+      expect(getColorFromHexRgbOrName('#ff0000')).toBe('#ff0000');
+      expect(getColorFromHexRgbOrName('rgb(0,0,0)')).toBe('rgb(0,0,0)');
+      expect(getColorFromHexRgbOrName('rgba(0,0,0,1)')).toBe('rgba(0,0,0,1)');
+    });
+  });
+});

+ 182 - 0
packages/grafana-ui/src/utils/namedColorsPalette.ts

@@ -0,0 +1,182 @@
+import { flatten } from 'lodash';
+import { GrafanaTheme } from '../types';
+
+type Hue = 'green' | 'yellow' | 'red' | 'blue' | 'orange' | 'purple';
+
+export type Color =
+  | 'green'
+  | 'dark-green'
+  | 'semi-dark-green'
+  | 'light-green'
+  | 'super-light-green'
+  | 'yellow'
+  | 'dark-yellow'
+  | 'semi-dark-yellow'
+  | 'light-yellow'
+  | 'super-light-yellow'
+  | 'red'
+  | 'dark-red'
+  | 'semi-dark-red'
+  | 'light-red'
+  | 'super-light-red'
+  | 'blue'
+  | 'dark-blue'
+  | 'semi-dark-blue'
+  | 'light-blue'
+  | 'super-light-blue'
+  | 'orange'
+  | 'dark-orange'
+  | 'semi-dark-orange'
+  | 'light-orange'
+  | 'super-light-orange'
+  | 'purple'
+  | 'dark-purple'
+  | 'semi-dark-purple'
+  | 'light-purple'
+  | 'super-light-purple';
+
+type ThemeVariants = {
+  dark: string;
+  light: string;
+};
+
+export type ColorDefinition = {
+  hue: Hue;
+  isPrimary?: boolean;
+  name: Color;
+  variants: ThemeVariants;
+};
+
+let colorsPaletteInstance: Map<Hue, ColorDefinition[]>;
+
+const buildColorDefinition = (
+  hue: Hue,
+  name: Color,
+  [light, dark]: string[],
+  isPrimary?: boolean
+): ColorDefinition => ({
+  hue,
+  name,
+  variants: {
+    light,
+    dark,
+  },
+  isPrimary: !!isPrimary,
+});
+
+export const getColorDefinitionByName = (name: Color): ColorDefinition => {
+  return flatten(Array.from(getNamedColorPalette().values())).filter(definition => definition.name === name)[0];
+};
+
+export const getColorDefinition = (hex: string, theme: GrafanaTheme): ColorDefinition | undefined => {
+  return flatten(Array.from(getNamedColorPalette().values())).filter(definition => definition.variants[theme] === hex)[0];
+};
+
+const isHex = (color: string) => {
+  const hexRegex = /^((0x){0,1}|#{0,1})([0-9A-F]{8}|[0-9A-F]{6})$/gi;
+  return hexRegex.test(color);
+};
+
+export const getColorName = (color?: string, theme?: GrafanaTheme): Color | undefined => {
+  if (!color) {
+    return undefined;
+  }
+
+  if (color.indexOf('rgb') > -1) {
+    return undefined;
+  }
+  if (isHex(color)) {
+    const definition = getColorDefinition(color, theme || GrafanaTheme.Dark);
+    return definition ? definition.name : undefined;
+  }
+
+  return color as Color;
+};
+
+export const getColorByName = (colorName: string) => {
+  const definition = flatten(Array.from(getNamedColorPalette().values())).filter(definition => definition.name === colorName);
+  return definition.length > 0 ? definition[0] : undefined;
+};
+
+export const getColorFromHexRgbOrName = (color: string, theme?: GrafanaTheme): string => {
+  if (color.indexOf('rgb') > -1 || isHex(color)) {
+    return color;
+  }
+
+  const colorDefinition = getColorByName(color);
+
+  if (!colorDefinition) {
+    throw new Error('Unknown color');
+  }
+
+  return theme ? colorDefinition.variants[theme] : colorDefinition.variants.dark;
+};
+
+export const getColorForTheme = (color: ColorDefinition, theme?: GrafanaTheme) => {
+  return theme ? color.variants[theme] : color.variants.dark;
+};
+
+const buildNamedColorsPalette = () => {
+  const palette = new Map<Hue, ColorDefinition[]>();
+
+    const BasicGreen = buildColorDefinition('green', 'green', ['#56A64B', '#73BF69'], true);
+  const DarkGreen = buildColorDefinition('green', 'dark-green', ['#19730E', '#37872D']);
+  const SemiDarkGreen = buildColorDefinition('green', 'semi-dark-green', ['#37872D', '#56A64B']);
+  const LightGreen = buildColorDefinition('green', 'light-green', ['#73BF69', '#96D98D']);
+  const SuperLightGreen = buildColorDefinition('green', 'super-light-green', ['#96D98D', '#C8F2C2']);
+
+  const BasicYellow = buildColorDefinition('yellow', 'yellow', ['#F2CC0C', '#FADE2A'], true);
+  const DarkYellow = buildColorDefinition('yellow', 'dark-yellow', ['#CC9D00', '#E0B400']);
+  const SemiDarkYellow = buildColorDefinition('yellow', 'semi-dark-yellow', ['#E0B400', '#F2CC0C']);
+  const LightYellow = buildColorDefinition('yellow', 'light-yellow', ['#FADE2A', '#FFEE52']);
+  const SuperLightYellow = buildColorDefinition('yellow', 'super-light-yellow', ['#FFEE52', '#FFF899']);
+
+  const BasicRed = buildColorDefinition('red', 'red', ['#E02F44', '#F2495C'], true);
+  const DarkRed = buildColorDefinition('red', 'dark-red', ['#AD0317', '#C4162A']);
+  const SemiDarkRed = buildColorDefinition('red', 'semi-dark-red', ['#C4162A', '#E02F44']);
+  const LightRed = buildColorDefinition('red', 'light-red', ['#F2495C', '#FF7383']);
+  const SuperLightRed = buildColorDefinition('red', 'super-light-red', ['#FF7383', '#FFA6B0']);
+
+  const BasicBlue = buildColorDefinition('blue', 'blue', ['#3274D9', '#5794F2'], true);
+  const DarkBlue = buildColorDefinition('blue', 'dark-blue', ['#1250B0', '#1F60C4']);
+  const SemiDarkBlue = buildColorDefinition('blue', 'semi-dark-blue', ['#1F60C4', '#3274D9']);
+  const LightBlue = buildColorDefinition('blue', 'light-blue', ['#5794F2', '#8AB8FF']);
+  const SuperLightBlue = buildColorDefinition('blue', 'super-light-blue', ['#8AB8FF', '#C0D8FF']);
+
+  const BasicOrange = buildColorDefinition('orange', 'orange', ['#FF780A', '#FF9830'], true);
+  const DarkOrange = buildColorDefinition('orange', 'dark-orange', ['#E55400', '#FA6400']);
+  const SemiDarkOrange = buildColorDefinition('orange', 'semi-dark-orange', ['#FA6400', '#FF780A']);
+  const LightOrange = buildColorDefinition('orange', 'light-orange', ['#FF9830', '#FFB357']);
+  const SuperLightOrange = buildColorDefinition('orange', 'super-light-orange', ['#FFB357', '#FFCB7D']);
+
+  const BasicPurple = buildColorDefinition('purple', 'purple', ['#A352CC', '#B877D9'], true);
+  const DarkPurple = buildColorDefinition('purple', 'dark-purple', ['#7C2EA3', '#8F3BB8']);
+  const SemiDarkPurple = buildColorDefinition('purple', 'semi-dark-purple', ['#8F3BB8', '#A352CC']);
+  const LightPurple = buildColorDefinition('purple', 'light-purple', ['#B877D9', '#CA95E5']);
+  const SuperLightPurple = buildColorDefinition('purple', 'super-light-purple', ['#CA95E5', '#DEB6F2']);
+
+  const greens = [BasicGreen, DarkGreen, SemiDarkGreen, LightGreen, SuperLightGreen];
+  const yellows = [BasicYellow, DarkYellow, SemiDarkYellow, LightYellow, SuperLightYellow];
+  const reds = [BasicRed, DarkRed, SemiDarkRed, LightRed, SuperLightRed];
+  const blues = [BasicBlue, DarkBlue, SemiDarkBlue, LightBlue, SuperLightBlue];
+  const oranges = [BasicOrange, DarkOrange, SemiDarkOrange, LightOrange, SuperLightOrange];
+  const purples = [BasicPurple, DarkPurple, SemiDarkPurple, LightPurple, SuperLightPurple];
+
+  palette.set('green', greens);
+  palette.set('yellow', yellows);
+  palette.set('red', reds);
+  palette.set('blue', blues);
+  palette.set('orange', oranges);
+  palette.set('purple', purples);
+
+  return palette;
+};
+
+export const getNamedColorPalette = () => {
+  if (colorsPaletteInstance) {
+    return colorsPaletteInstance;
+  }
+
+  colorsPaletteInstance = buildNamedColorsPalette();
+  return colorsPaletteInstance;
+};

+ 6 - 0
packages/grafana-ui/src/utils/propDeprecationWarning.ts

@@ -0,0 +1,6 @@
+const propDeprecationWarning = (componentName: string, propName: string, newPropName: string) => {
+  const message = `[Deprecation warning] ${componentName}: ${propName} is deprecated. Use ${newPropName} instead`;
+  console.warn(message);
+};
+
+export default propDeprecationWarning;

+ 38 - 0
packages/grafana-ui/src/utils/storybook/UseState.tsx

@@ -0,0 +1,38 @@
+import React from 'react';
+
+interface StateHolderProps<T> {
+  initialState: T;
+  children: (currentState: T, updateState: (nextState: T) => void) => JSX.Element;
+}
+
+export class UseState<T> extends React.Component<StateHolderProps<T>, { value: T; initialState: T }> {
+  constructor(props: StateHolderProps<T>) {
+    super(props);
+    this.state = {
+      value: props.initialState,
+      initialState: props.initialState, // To enable control from knobs
+    };
+  }
+  // @ts-ignore
+  static getDerivedStateFromProps(props: StateHolderProps<{}>, state: { value: any; initialState: any }) {
+    if (props.initialState !== state.initialState) {
+      return {
+        initialState: props.initialState,
+        value: props.initialState,
+      };
+    }
+    return {
+      ...state,
+      value: state.value,
+    };
+  }
+
+  handleStateUpdate = (nextState: T) => {
+    console.log(nextState);
+    this.setState({ value: nextState });
+  };
+
+  render() {
+    return this.props.children(this.state.value, this.handleStateUpdate);
+  }
+}

+ 14 - 0
packages/grafana-ui/src/utils/storybook/themeKnob.ts

@@ -0,0 +1,14 @@
+import { select } from '@storybook/addon-knobs';
+import { GrafanaTheme } from '../../types';
+
+export const getThemeKnob = (defaultTheme: GrafanaTheme = GrafanaTheme.Dark) => {
+  return select(
+    'Theme',
+    {
+      Default: defaultTheme,
+      Light: GrafanaTheme.Light,
+      Dark: GrafanaTheme.Dark,
+    },
+    defaultTheme
+  );
+};

+ 19 - 0
packages/grafana-ui/src/utils/storybook/withCenteredStory.tsx

@@ -0,0 +1,19 @@
+import React from 'react';
+import { RenderFunction } from '@storybook/react';
+
+const CenteredStory: React.FunctionComponent<{}> = ({ children }) => {
+  return (
+    <div
+      style={{
+        height: '100vh  ',
+        display: 'flex',
+        alignItems: 'center',
+        justifyContent: 'center',
+      }}
+    >
+      {children}
+    </div>
+  );
+};
+
+export const withCenteredStory = (story: RenderFunction) => <CenteredStory>{story()}</CenteredStory>;

+ 0 - 509
packages/grafana-ui/src/vendor/spectrum.css

@@ -1,509 +0,0 @@
-/***
-Spectrum Colorpicker v1.3.0
-https://github.com/bgrins/spectrum
-Author: Brian Grinstead
-License: MIT
-***/
-
-.sp-container {
-    position:absolute;
-    top:0;
-    left:0;
-    display:inline-block;
-    *display: inline;
-    *zoom: 1;
-    /* https://github.com/bgrins/spectrum/issues/40 */
-    z-index: 9999994;
-    overflow: hidden;
-}
-.sp-container.sp-flat {
-    position: relative;
-}
-
-/* Fix for * { box-sizing: border-box; } */
-.sp-container,
-.sp-container * {
-    -webkit-box-sizing: content-box;
-       -moz-box-sizing: content-box;
-            box-sizing: content-box;
-}
-
-/* http://ansciath.tumblr.com/post/7347495869/css-aspect-ratio */
-.sp-top {
-  position:relative;
-  width: 100%;
-  display:inline-block;
-}
-.sp-top-inner {
-   position:absolute;
-   top:0;
-   left:0;
-   bottom:0;
-   right:0;
-}
-.sp-color {
-    position: absolute;
-    top:0;
-    left:0;
-    bottom:0;
-    right:20%;
-}
-.sp-hue {
-    position: absolute;
-    top:0;
-    right:0;
-    bottom:0;
-    left:84%;
-    height: 100%;
-}
-
-.sp-clear-enabled .sp-hue {
-    top:33px;
-    height: 77.5%;
-}
-
-.sp-fill {
-    padding-top: 80%;
-}
-.sp-sat, .sp-val {
-    position: absolute;
-    top:0;
-    left:0;
-    right:0;
-    bottom:0;
-}
-
-.sp-alpha-enabled .sp-top {
-    margin-bottom: 18px;
-}
-.sp-alpha-enabled .sp-alpha {
-    display: block;
-}
-.sp-alpha-handle {
-    position:absolute;
-    top:-4px;
-    bottom: -4px;
-    width: 6px;
-    left: 50%;
-    cursor: pointer;
-    border: 1px solid black;
-    background: white;
-    opacity: .8;
-}
-.sp-alpha {
-    display: none;
-    position: absolute;
-    bottom: -14px;
-    right: 0;
-    left: 0;
-    height: 8px;
-}
-.sp-alpha-inner {
-    border: solid 1px #333;
-}
-
-.sp-clear {
-    display: none;
-}
-
-.sp-clear.sp-clear-display {
-    background-position: center;
-}
-
-.sp-clear-enabled .sp-clear {
-    display: block;
-    position:absolute;
-    top:0px;
-    right:0;
-    bottom:0;
-    left:84%;
-    height: 28px;
-}
-
-/* Don't allow text selection */
-.sp-container, .sp-replacer, .sp-preview, .sp-dragger, .sp-slider, .sp-alpha, .sp-clear, .sp-alpha-handle, .sp-container.sp-dragging .sp-input, .sp-container button  {
-    -webkit-user-select:none;
-    -moz-user-select: -moz-none;
-    -o-user-select:none;
-    user-select: none;
-}
-
-.sp-container.sp-input-disabled .sp-input-container {
-    display: none;
-}
-.sp-container.sp-buttons-disabled .sp-button-container {
-    display: none;
-}
-.sp-palette-only .sp-picker-container {
-    display: none;
-}
-.sp-palette-disabled .sp-palette-container {
-    display: none;
-}
-
-.sp-initial-disabled .sp-initial {
-    display: none;
-}
-
-
-/* Gradients for hue, saturation and value instead of images.  Not pretty... but it works */
-.sp-sat {
-    background-image: linear-gradient(to right, #fff, rgba(204, 154, 129, 0));
-    -ms-filter: "progid:DXImageTransform.Microsoft.gradient(GradientType = 1, startColorstr=#FFFFFFFF, endColorstr=#00CC9A81)";
-    filter : progid:DXImageTransform.Microsoft.gradient(GradientType = 1, startColorstr='#FFFFFFFF', endColorstr='#00CC9A81');
-}
-.sp-val {
-    background-image: linear-gradient(to top, #000, rgba(204, 154, 129, 0));
-    -ms-filter: "progid:DXImageTransform.Microsoft.gradient(startColorstr=#00CC9A81, endColorstr=#FF000000)";
-    filter : progid:DXImageTransform.Microsoft.gradient(startColorstr='#00CC9A81', endColorstr='#FF000000');
-}
-
-.sp-hue {
-    background: -moz-linear-gradient(top, #ff0000 0%, #ffff00 17%, #00ff00 33%, #00ffff 50%, #0000ff 67%, #ff00ff 83%, #ff0000 100%);
-    background: -ms-linear-gradient(top, #ff0000 0%, #ffff00 17%, #00ff00 33%, #00ffff 50%, #0000ff 67%, #ff00ff 83%, #ff0000 100%);
-    background: -o-linear-gradient(top, #ff0000 0%, #ffff00 17%, #00ff00 33%, #00ffff 50%, #0000ff 67%, #ff00ff 83%, #ff0000 100%);
-    background: -webkit-gradient(linear, left top, left bottom, from(#ff0000), color-stop(0.17, #ffff00), color-stop(0.33, #00ff00), color-stop(0.5, #00ffff), color-stop(0.67, #0000ff), color-stop(0.83, #ff00ff), to(#ff0000));
-    background: -webkit-linear-gradient(top, #ff0000 0%, #ffff00 17%, #00ff00 33%, #00ffff 50%, #0000ff 67%, #ff00ff 83%, #ff0000 100%);
-}
-
-/* IE filters do not support multiple color stops.
-   Generate 6 divs, line them up, and do two color gradients for each.
-   Yes, really.
- */
-.sp-1 {
-    height:17%;
-    filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff0000', endColorstr='#ffff00');
-}
-.sp-2 {
-    height:16%;
-    filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffff00', endColorstr='#00ff00');
-}
-.sp-3 {
-    height:17%;
-    filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#00ff00', endColorstr='#00ffff');
-}
-.sp-4 {
-    height:17%;
-    filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#00ffff', endColorstr='#0000ff');
-}
-.sp-5 {
-    height:16%;
-    filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#0000ff', endColorstr='#ff00ff');
-}
-.sp-6 {
-    height:17%;
-    filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff00ff', endColorstr='#ff0000');
-}
-
-.sp-hidden {
-    display: none !important;
-}
-
-/* Clearfix hack */
-.sp-cf:before, .sp-cf:after { content: ""; display: table; }
-.sp-cf:after { clear: both; }
-.sp-cf { *zoom: 1; }
-
-/* Mobile devices, make hue slider bigger so it is easier to slide */
-@media (max-device-width: 480px) {
-    .sp-color { right: 40%; }
-    .sp-hue { left: 63%; }
-    .sp-fill { padding-top: 60%; }
-}
-.sp-dragger {
-   border-radius: 5px;
-   height: 5px;
-   width: 5px;
-   border: 1px solid #fff;
-   background: #000;
-   cursor: pointer;
-   position:absolute;
-   top:0;
-   left: 0;
-}
-.sp-slider {
-    position: absolute;
-    top:0;
-    cursor:pointer;
-    height: 3px;
-    left: -1px;
-    right: -1px;
-    border: 1px solid #000;
-    background: white;
-    opacity: .8;
-}
-
-/*
-Theme authors:
-Here are the basic themeable display options (colors, fonts, global widths).
-See http://bgrins.github.io/spectrum/themes/ for instructions.
-*/
-
-.sp-container {
-    border-radius: 0;
-    background-color: #ECECEC;
-    border: solid 1px #f0c49B;
-    padding: 0;
-}
-.sp-container, .sp-container button, .sp-container input, .sp-color, .sp-hue, .sp-clear
-{
-    font: normal 12px "Lucida Grande", "Lucida Sans Unicode", "Lucida Sans", Geneva, Verdana, sans-serif;
-    -webkit-box-sizing: border-box;
-    -moz-box-sizing: border-box;
-    -ms-box-sizing: border-box;
-    box-sizing: border-box;
-}
-.sp-top
-{
-    margin-bottom: 3px;
-}
-.sp-color, .sp-hue, .sp-clear
-{
-    border: solid 1px #666;
-}
-
-/* Input */
-.sp-input-container {
-    float:right;
-    width: 100px;
-    margin-bottom: 4px;
-}
-.sp-initial-disabled  .sp-input-container {
-    width: 100%;
-}
-.sp-input {
-   font-size: 12px !important;
-   border: 1px inset;
-   padding: 4px 5px;
-   margin: 0;
-   width: 100%;
-   background:transparent;
-   border-radius: 3px;
-   color: #222;
-}
-.sp-input:focus  {
-    border: 1px solid orange;
-}
-.sp-input.sp-validation-error
-{
-    border: 1px solid red;
-    background: #fdd;
-}
-.sp-picker-container , .sp-palette-container
-{
-    float:left;
-    position: relative;
-    padding: 10px;
-    padding-bottom: 300px;
-    margin-bottom: -290px;
-}
-.sp-picker-container
-{
-    width: 172px;
-    border-left: solid 1px #fff;
-}
-
-/* Palettes */
-.sp-palette-container
-{
-    border-right: solid 1px #ccc;
-}
-
-.sp-palette .sp-thumb-el {
-    display: block;
-    position:relative;
-    float:left;
-    width: 24px;
-    height: 15px;
-    margin: 3px;
-    cursor: pointer;
-    border:solid 2px transparent;
-}
-.sp-palette .sp-thumb-el:hover, .sp-palette .sp-thumb-el.sp-thumb-active {
-    border-color: orange;
-}
-.sp-thumb-el
-{
-    position:relative;
-}
-
-/* Initial */
-.sp-initial
-{
-    float: left;
-    border: solid 1px #333;
-}
-.sp-initial span {
-    width: 30px;
-    height: 25px;
-    border:none;
-    display:block;
-    float:left;
-    margin:0;
-}
-
-.sp-initial .sp-clear-display {
-    background-position: center;
-}
-
-/* Buttons */
-.sp-button-container {
-    float: right;
-}
-
-/* Replacer (the little preview div that shows up instead of the <input>) */
-.sp-replacer {
-    margin:0;
-    overflow:hidden;
-    cursor:pointer;
-    padding: 4px;
-    display:inline-block;
-    *zoom: 1;
-    *display: inline;
-    border: solid 1px #91765d;
-    background: #eee;
-    color: #333;
-    vertical-align: middle;
-}
-.sp-replacer:hover, .sp-replacer.sp-active {
-    border-color: #F0C49B;
-    color: #111;
-}
-.sp-replacer.sp-disabled {
-    cursor:default;
-    border-color: silver;
-    color: silver;
-}
-.sp-dd {
-    padding: 2px 0;
-    height: 16px;
-    line-height: 16px;
-    float:left;
-    font-size:10px;
-}
-.sp-preview
-{
-    position:relative;
-    width:25px;
-    height: 20px;
-    border: solid 1px #222;
-    margin-right: 5px;
-    float:left;
-    z-index: 0;
-}
-
-.sp-palette
-{
-    *width: 220px;
-    max-width: 220px;
-}
-.sp-palette .sp-thumb-el
-{
-    width:16px;
-    height: 16px;
-    margin:2px 1px;
-    border: solid 1px #d0d0d0;
-}
-
-.sp-container
-{
-    padding-bottom:0;
-}
-
-
-/* Buttons: http://hellohappy.org/css3-buttons/ */
-.sp-container button {
-  background-color: #eeeeee;
-  background-image: -webkit-linear-gradient(top, #eeeeee, #cccccc);
-  background-image: -moz-linear-gradient(top, #eeeeee, #cccccc);
-  background-image: -ms-linear-gradient(top, #eeeeee, #cccccc);
-  background-image: -o-linear-gradient(top, #eeeeee, #cccccc);
-  background-image: linear-gradient(to bottom, #eeeeee, #cccccc);
-  border: 1px solid #ccc;
-  border-bottom: 1px solid #bbb;
-  border-radius: 3px;
-  color: #333;
-  font-size: 14px;
-  line-height: 1;
-  padding: 5px 4px;
-  text-align: center;
-  text-shadow: 0 1px 0 #eee;
-  vertical-align: middle;
-}
-.sp-container button:hover {
-    background-color: #dddddd;
-    background-image: -webkit-linear-gradient(top, #dddddd, #bbbbbb);
-    background-image: -moz-linear-gradient(top, #dddddd, #bbbbbb);
-    background-image: -ms-linear-gradient(top, #dddddd, #bbbbbb);
-    background-image: -o-linear-gradient(top, #dddddd, #bbbbbb);
-    background-image: linear-gradient(to bottom, #dddddd, #bbbbbb);
-    border: 1px solid #bbb;
-    border-bottom: 1px solid #999;
-    cursor: pointer;
-    text-shadow: 0 1px 0 #ddd;
-}
-.sp-container button:active {
-    border: 1px solid #aaa;
-    border-bottom: 1px solid #888;
-    -webkit-box-shadow: inset 0 0 5px 2px #aaaaaa, 0 1px 0 0 #eeeeee;
-    -moz-box-shadow: inset 0 0 5px 2px #aaaaaa, 0 1px 0 0 #eeeeee;
-    -ms-box-shadow: inset 0 0 5px 2px #aaaaaa, 0 1px 0 0 #eeeeee;
-    -o-box-shadow: inset 0 0 5px 2px #aaaaaa, 0 1px 0 0 #eeeeee;
-    box-shadow: inset 0 0 5px 2px #aaaaaa, 0 1px 0 0 #eeeeee;
-}
-.sp-cancel
-{
-    font-size: 11px;
-    color: #d93f3f !important;
-    margin:0;
-    padding:2px;
-    margin-right: 5px;
-    vertical-align: middle;
-    text-decoration:none;
-
-}
-.sp-cancel:hover
-{
-    color: #d93f3f !important;
-    text-decoration: underline;
-}
-
-
-.sp-palette span:hover, .sp-palette span.sp-thumb-active
-{
-    border-color: #000;
-}
-
-.sp-preview, .sp-alpha, .sp-thumb-el
-{
-    position:relative;
-    background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAMCAIAAADZF8uwAAAAGUlEQVQYV2M4gwH+YwCGIasIUwhT25BVBADtzYNYrHvv4gAAAABJRU5ErkJggg==);
-}
-.sp-preview-inner, .sp-alpha-inner, .sp-thumb-inner
-{
-    display:block;
-    position:absolute;
-    top:0;left:0;bottom:0;right:0;
-}
-
-.sp-palette .sp-thumb-inner
-{
-    background-position: 50% 50%;
-    background-repeat: no-repeat;
-}
-
-.sp-palette .sp-thumb-light.sp-thumb-active .sp-thumb-inner
-{
-    background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABIAAAASCAYAAABWzo5XAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAIVJREFUeNpiYBhsgJFMffxAXABlN5JruT4Q3wfi/0DsT64h8UD8HmpIPCWG/KemIfOJCUB+Aoacx6EGBZyHBqI+WsDCwuQ9mhxeg2A210Ntfo8klk9sOMijaURm7yc1UP2RNCMbKE9ODK1HM6iegYLkfx8pligC9lCD7KmRof0ZhjQACDAAceovrtpVBRkAAAAASUVORK5CYII=);
-}
-
-.sp-palette .sp-thumb-dark.sp-thumb-active .sp-thumb-inner
-{
-    background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABIAAAASCAYAAABWzo5XAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAAadEVYdFNvZnR3YXJlAFBhaW50Lk5FVCB2My41LjEwMPRyoQAAAMdJREFUOE+tkgsNwzAMRMugEAahEAahEAZhEAqlEAZhEAohEAYh81X2dIm8fKpEspLGvudPOsUYpxE2BIJCroJmEW9qJ+MKaBFhEMNabSy9oIcIPwrB+afvAUFoK4H0tMaQ3XtlrggDhOVVMuT4E5MMG0FBbCEYzjYT7OxLEvIHQLY2zWwQ3D+9luyOQTfKDiFD3iUIfPk8VqrKjgAiSfGFPecrg6HN6m/iBcwiDAo7WiBeawa+Kwh7tZoSCGLMqwlSAzVDhoK+6vH4G0P5wdkAAAAASUVORK5CYII=);
-}
-
-.sp-clear-display {
-    background-repeat:no-repeat;
-    background-position: center;
-    background-image: url(data:image/gif;base64,R0lGODlhFAAUAPcAAAAAAJmZmZ2dnZ6enqKioqOjo6SkpKWlpaampqenp6ioqKmpqaqqqqurq/Hx8fLy8vT09PX19ff39/j4+Pn5+fr6+vv7+wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAEAAP8ALAAAAAAUABQAAAihAP9FoPCvoMGDBy08+EdhQAIJCCMybCDAAYUEARBAlFiQQoMABQhKUJBxY0SPICEYHBnggEmDKAuoPMjS5cGYMxHW3IiT478JJA8M/CjTZ0GgLRekNGpwAsYABHIypcAgQMsITDtWJYBR6NSqMico9cqR6tKfY7GeBCuVwlipDNmefAtTrkSzB1RaIAoXodsABiZAEFB06gIBWC1mLVgBa0AAOw==);
-}

+ 0 - 2317
packages/grafana-ui/src/vendor/spectrum.js

@@ -1,2317 +0,0 @@
-// Spectrum Colorpicker v1.7.0
-// https://github.com/bgrins/spectrum
-// Author: Brian Grinstead
-// License: MIT
-
-(function (factory) {
-    "use strict";
-
-    if (typeof define === 'function' && define.amd) { // AMD
-        define(['jquery'], factory);
-    }
-    else if (typeof exports == "object" && typeof module == "object") { // CommonJS
-        module.exports = factory;
-    }
-    else { // Browser
-        factory(jQuery);
-    }
-})(function($, undefined) {
-    "use strict";
-
-    var defaultOpts = {
-
-        // Callbacks
-        beforeShow: noop,
-        move: noop,
-        change: noop,
-        show: noop,
-        hide: noop,
-
-        // Options
-        color: false,
-        flat: false,
-        showInput: false,
-        allowEmpty: false,
-        showButtons: true,
-        clickoutFiresChange: true,
-        showInitial: false,
-        showPalette: false,
-        showPaletteOnly: false,
-        hideAfterPaletteSelect: false,
-        togglePaletteOnly: false,
-        showSelectionPalette: true,
-        localStorageKey: false,
-        appendTo: "body",
-        maxSelectionSize: 7,
-        cancelText: "cancel",
-        chooseText: "choose",
-        togglePaletteMoreText: "more",
-        togglePaletteLessText: "less",
-        clearText: "Clear Color Selection",
-        noColorSelectedText: "No Color Selected",
-        preferredFormat: false,
-        className: "", // Deprecated - use containerClassName and replacerClassName instead.
-        containerClassName: "",
-        replacerClassName: "",
-        showAlpha: false,
-        theme: "sp-light",
-        palette: [["#ffffff", "#000000", "#ff0000", "#ff8000", "#ffff00", "#008000", "#0000ff", "#4b0082", "#9400d3"]],
-        selectionPalette: [],
-        disabled: false,
-        offset: null
-    },
-    spectrums = [],
-    IE = !!/msie/i.exec( window.navigator.userAgent ),
-    rgbaSupport = (function() {
-        function contains( str, substr ) {
-            return !!~('' + str).indexOf(substr);
-        }
-
-        var elem = document.createElement('div');
-        var style = elem.style;
-        style.cssText = 'background-color:rgba(0,0,0,.5)';
-        return contains(style.backgroundColor, 'rgba') || contains(style.backgroundColor, 'hsla');
-    })(),
-    replaceInput = [
-        "<div class='sp-replacer'>",
-            "<div class='sp-preview'><div class='sp-preview-inner'></div></div>",
-            "<div class='sp-dd'>&#9660;</div>",
-        "</div>"
-    ].join(''),
-    markup = (function () {
-
-        // IE does not support gradients with multiple stops, so we need to simulate
-        //  that for the rainbow slider with 8 divs that each have a single gradient
-        var gradientFix = "";
-        if (IE) {
-            for (var i = 1; i <= 6; i++) {
-                gradientFix += "<div class='sp-" + i + "'></div>";
-            }
-        }
-
-        return [
-            "<div class='sp-container sp-hidden'>",
-                "<div class='sp-palette-container'>",
-                    "<div class='sp-palette sp-thumb sp-cf'></div>",
-                    "<div class='sp-palette-button-container sp-cf'>",
-                        "<button type='button' class='sp-palette-toggle'></button>",
-                    "</div>",
-                "</div>",
-                "<div class='sp-picker-container'>",
-                    "<div class='sp-top sp-cf'>",
-                        "<div class='sp-fill'></div>",
-                        "<div class='sp-top-inner'>",
-                            "<div class='sp-color'>",
-                                "<div class='sp-sat'>",
-                                    "<div class='sp-val'>",
-                                        "<div class='sp-dragger'></div>",
-                                    "</div>",
-                                "</div>",
-                            "</div>",
-                            "<div class='sp-clear sp-clear-display'>",
-                            "</div>",
-                            "<div class='sp-hue'>",
-                                "<div class='sp-slider'></div>",
-                                gradientFix,
-                            "</div>",
-                        "</div>",
-                        "<div class='sp-alpha'><div class='sp-alpha-inner'><div class='sp-alpha-handle'></div></div></div>",
-                    "</div>",
-                    "<div class='sp-input-container sp-cf'>",
-                        "<input class='sp-input' type='text' spellcheck='false'  />",
-                    "</div>",
-                    "<div class='sp-initial sp-thumb sp-cf'></div>",
-                    "<div class='sp-button-container sp-cf'>",
-                        "<a class='sp-cancel' href='#'></a>",
-                        "<button type='button' class='sp-choose'></button>",
-                    "</div>",
-                "</div>",
-            "</div>"
-        ].join("");
-    })();
-
-    function paletteTemplate (p, color, className, opts) {
-        var html = [];
-        for (var i = 0; i < p.length; i++) {
-            var current = p[i];
-            if(current) {
-                var tiny = tinycolor(current);
-                var c = tiny.toHsl().l < 0.5 ? "sp-thumb-el sp-thumb-dark" : "sp-thumb-el sp-thumb-light";
-                c += (tinycolor.equals(color, current)) ? " sp-thumb-active" : "";
-                var formattedString = tiny.toString(opts.preferredFormat || "rgb");
-                var swatchStyle = rgbaSupport ? ("background-color:" + tiny.toRgbString()) : "filter:" + tiny.toFilter();
-                html.push('<span title="' + formattedString + '" data-color="' + tiny.toRgbString() + '" class="' + c + '"><span class="sp-thumb-inner" style="' + swatchStyle + ';" /></span>');
-            } else {
-                var cls = 'sp-clear-display';
-                html.push($('<div />')
-                    .append($('<span data-color="" style="background-color:transparent;" class="' + cls + '"></span>')
-                        .attr('title', opts.noColorSelectedText)
-                    )
-                    .html()
-                );
-            }
-        }
-        return "<div class='sp-cf " + className + "'>" + html.join('') + "</div>";
-    }
-
-    function hideAll() {
-        for (var i = 0; i < spectrums.length; i++) {
-            if (spectrums[i]) {
-                spectrums[i].hide();
-            }
-        }
-    }
-
-    function instanceOptions(o, callbackContext) {
-        var opts = $.extend({}, defaultOpts, o);
-        opts.callbacks = {
-            'move': bind(opts.move, callbackContext),
-            'change': bind(opts.change, callbackContext),
-            'show': bind(opts.show, callbackContext),
-            'hide': bind(opts.hide, callbackContext),
-            'beforeShow': bind(opts.beforeShow, callbackContext)
-        };
-
-        return opts;
-    }
-
-    function spectrum(element, o) {
-
-        var opts = instanceOptions(o, element),
-            flat = opts.flat,
-            showSelectionPalette = opts.showSelectionPalette,
-            localStorageKey = opts.localStorageKey,
-            theme = opts.theme,
-            callbacks = opts.callbacks,
-            resize = throttle(reflow, 10),
-            visible = false,
-            isDragging = false,
-            dragWidth = 0,
-            dragHeight = 0,
-            dragHelperHeight = 0,
-            slideHeight = 0,
-            slideWidth = 0,
-            alphaWidth = 0,
-            alphaSlideHelperWidth = 0,
-            slideHelperHeight = 0,
-            currentHue = 0,
-            currentSaturation = 0,
-            currentValue = 0,
-            currentAlpha = 1,
-            palette = [],
-            paletteArray = [],
-            paletteLookup = {},
-            selectionPalette = opts.selectionPalette.slice(0),
-            maxSelectionSize = opts.maxSelectionSize,
-            draggingClass = "sp-dragging",
-            shiftMovementDirection = null;
-
-        var doc = element.ownerDocument,
-            body = doc.body,
-            boundElement = $(element),
-            disabled = false,
-            container = $(markup, doc).addClass(theme),
-            pickerContainer = container.find(".sp-picker-container"),
-            dragger = container.find(".sp-color"),
-            dragHelper = container.find(".sp-dragger"),
-            slider = container.find(".sp-hue"),
-            slideHelper = container.find(".sp-slider"),
-            alphaSliderInner = container.find(".sp-alpha-inner"),
-            alphaSlider = container.find(".sp-alpha"),
-            alphaSlideHelper = container.find(".sp-alpha-handle"),
-            textInput = container.find(".sp-input"),
-            paletteContainer = container.find(".sp-palette"),
-            initialColorContainer = container.find(".sp-initial"),
-            cancelButton = container.find(".sp-cancel"),
-            clearButton = container.find(".sp-clear"),
-            chooseButton = container.find(".sp-choose"),
-            toggleButton = container.find(".sp-palette-toggle"),
-            isInput = boundElement.is("input"),
-            isInputTypeColor = isInput && boundElement.attr("type") === "color" && inputTypeColorSupport(),
-            shouldReplace = isInput && !flat,
-            replacer = (shouldReplace) ? $(replaceInput).addClass(theme).addClass(opts.className).addClass(opts.replacerClassName) : $([]),
-            offsetElement = (shouldReplace) ? replacer : boundElement,
-            previewElement = replacer.find(".sp-preview-inner"),
-            initialColor = opts.color || (isInput && boundElement.val()),
-            colorOnShow = false,
-            preferredFormat = opts.preferredFormat,
-            currentPreferredFormat = preferredFormat,
-            clickoutFiresChange = !opts.showButtons || opts.clickoutFiresChange,
-            isEmpty = !initialColor,
-            allowEmpty = opts.allowEmpty && !isInputTypeColor;
-
-        function applyOptions() {
-
-            if (opts.showPaletteOnly) {
-                opts.showPalette = true;
-            }
-
-            toggleButton.text(opts.showPaletteOnly ? opts.togglePaletteMoreText : opts.togglePaletteLessText);
-
-            if (opts.palette) {
-                palette = opts.palette.slice(0);
-                paletteArray = $.isArray(palette[0]) ? palette : [palette];
-                paletteLookup = {};
-                for (var i = 0; i < paletteArray.length; i++) {
-                    for (var j = 0; j < paletteArray[i].length; j++) {
-                        var rgb = tinycolor(paletteArray[i][j]).toRgbString();
-                        paletteLookup[rgb] = true;
-                    }
-                }
-            }
-
-            container.toggleClass("sp-flat", flat);
-            container.toggleClass("sp-input-disabled", !opts.showInput);
-            container.toggleClass("sp-alpha-enabled", opts.showAlpha);
-            container.toggleClass("sp-clear-enabled", allowEmpty);
-            container.toggleClass("sp-buttons-disabled", !opts.showButtons);
-            container.toggleClass("sp-palette-buttons-disabled", !opts.togglePaletteOnly);
-            container.toggleClass("sp-palette-disabled", !opts.showPalette);
-            container.toggleClass("sp-palette-only", opts.showPaletteOnly);
-            container.toggleClass("sp-initial-disabled", !opts.showInitial);
-            container.addClass(opts.className).addClass(opts.containerClassName);
-
-            reflow();
-        }
-
-        function initialize() {
-
-            if (IE) {
-                container.find("*:not(input)").attr("unselectable", "on");
-            }
-
-            applyOptions();
-
-            if (shouldReplace) {
-                boundElement.after(replacer).hide();
-            }
-
-            if (!allowEmpty) {
-                clearButton.hide();
-            }
-
-            if (flat) {
-                boundElement.after(container).hide();
-            }
-            else {
-
-                var appendTo = opts.appendTo === "parent" ? boundElement.parent() : $(opts.appendTo);
-                if (appendTo.length !== 1) {
-                    appendTo = $("body");
-                }
-
-                appendTo.append(container);
-            }
-
-            updateSelectionPaletteFromStorage();
-
-            offsetElement.bind("click.spectrum touchstart.spectrum", function (e) {
-                if (!disabled) {
-                    toggle();
-                }
-
-                e.stopPropagation();
-
-                if (!$(e.target).is("input")) {
-                    e.preventDefault();
-                }
-            });
-
-            if(boundElement.is(":disabled") || (opts.disabled === true)) {
-                disable();
-            }
-
-            // Prevent clicks from bubbling up to document.  This would cause it to be hidden.
-            container.click(stopPropagation);
-
-            // Handle user typed input
-            textInput.change(setFromTextInput);
-            textInput.bind("paste", function () {
-                setTimeout(setFromTextInput, 1);
-            });
-            textInput.keydown(function (e) { if (e.keyCode == 13) { setFromTextInput(); } });
-
-            cancelButton.text(opts.cancelText);
-            cancelButton.bind("click.spectrum", function (e) {
-                e.stopPropagation();
-                e.preventDefault();
-                revert();
-                hide();
-            });
-
-            clearButton.attr("title", opts.clearText);
-            clearButton.bind("click.spectrum", function (e) {
-                e.stopPropagation();
-                e.preventDefault();
-                isEmpty = true;
-                move();
-
-                if(flat) {
-                    //for the flat style, this is a change event
-                    updateOriginalInput(true);
-                }
-            });
-
-            chooseButton.text(opts.chooseText);
-            chooseButton.bind("click.spectrum", function (e) {
-                e.stopPropagation();
-                e.preventDefault();
-
-                if (IE && textInput.is(":focus")) {
-                    textInput.trigger('change');
-                }
-
-                if (isValid()) {
-                    updateOriginalInput(true);
-                    hide();
-                }
-            });
-
-            toggleButton.text(opts.showPaletteOnly ? opts.togglePaletteMoreText : opts.togglePaletteLessText);
-            toggleButton.bind("click.spectrum", function (e) {
-                e.stopPropagation();
-                e.preventDefault();
-
-                opts.showPaletteOnly = !opts.showPaletteOnly;
-
-                // To make sure the Picker area is drawn on the right, next to the
-                // Palette area (and not below the palette), first move the Palette
-                // to the left to make space for the picker, plus 5px extra.
-                // The 'applyOptions' function puts the whole container back into place
-                // and takes care of the button-text and the sp-palette-only CSS class.
-                if (!opts.showPaletteOnly && !flat) {
-                    container.css('left', '-=' + (pickerContainer.outerWidth(true) + 5));
-                }
-                applyOptions();
-            });
-
-            draggable(alphaSlider, function (dragX, dragY, e) {
-                currentAlpha = (dragX / alphaWidth);
-                isEmpty = false;
-                if (e.shiftKey) {
-                    currentAlpha = Math.round(currentAlpha * 10) / 10;
-                }
-
-                move();
-            }, dragStart, dragStop);
-
-            draggable(slider, function (dragX, dragY) {
-                currentHue = parseFloat(dragY / slideHeight);
-                isEmpty = false;
-                if (!opts.showAlpha) {
-                    currentAlpha = 1;
-                }
-                move();
-            }, dragStart, dragStop);
-
-            draggable(dragger, function (dragX, dragY, e) {
-
-                // shift+drag should snap the movement to either the x or y axis.
-                if (!e.shiftKey) {
-                    shiftMovementDirection = null;
-                }
-                else if (!shiftMovementDirection) {
-                    var oldDragX = currentSaturation * dragWidth;
-                    var oldDragY = dragHeight - (currentValue * dragHeight);
-                    var furtherFromX = Math.abs(dragX - oldDragX) > Math.abs(dragY - oldDragY);
-
-                    shiftMovementDirection = furtherFromX ? "x" : "y";
-                }
-
-                var setSaturation = !shiftMovementDirection || shiftMovementDirection === "x";
-                var setValue = !shiftMovementDirection || shiftMovementDirection === "y";
-
-                if (setSaturation) {
-                    currentSaturation = parseFloat(dragX / dragWidth);
-                }
-                if (setValue) {
-                    currentValue = parseFloat((dragHeight - dragY) / dragHeight);
-                }
-
-                isEmpty = false;
-                if (!opts.showAlpha) {
-                    currentAlpha = 1;
-                }
-
-                move();
-
-            }, dragStart, dragStop);
-
-            if (!!initialColor) {
-                set(initialColor);
-
-                // In case color was black - update the preview UI and set the format
-                // since the set function will not run (default color is black).
-                updateUI();
-                currentPreferredFormat = preferredFormat || tinycolor(initialColor).format;
-
-                addColorToSelectionPalette(initialColor);
-            }
-            else {
-                updateUI();
-            }
-
-            if (flat) {
-                show();
-            }
-
-            function paletteElementClick(e) {
-                if (e.data && e.data.ignore) {
-                    set($(e.target).closest(".sp-thumb-el").data("color"));
-                    move();
-                }
-                else {
-                    set($(e.target).closest(".sp-thumb-el").data("color"));
-                    move();
-                    updateOriginalInput(true);
-                    if (opts.hideAfterPaletteSelect) {
-                      hide();
-                    }
-                }
-
-                return false;
-            }
-
-            var paletteEvent = IE ? "mousedown.spectrum" : "click.spectrum touchstart.spectrum";
-            paletteContainer.delegate(".sp-thumb-el", paletteEvent, paletteElementClick);
-            initialColorContainer.delegate(".sp-thumb-el:nth-child(1)", paletteEvent, { ignore: true }, paletteElementClick);
-        }
-
-        function updateSelectionPaletteFromStorage() {
-
-            if (localStorageKey && window.localStorage) {
-
-                // Migrate old palettes over to new format.  May want to remove this eventually.
-                try {
-                    var oldPalette = window.localStorage[localStorageKey].split(",#");
-                    if (oldPalette.length > 1) {
-                        delete window.localStorage[localStorageKey];
-                        $.each(oldPalette, function(i, c) {
-                             addColorToSelectionPalette(c);
-                        });
-                    }
-                }
-                catch(e) { }
-
-                try {
-                    selectionPalette = window.localStorage[localStorageKey].split(";");
-                }
-                catch (e) { }
-            }
-        }
-
-        function addColorToSelectionPalette(color) {
-            if (showSelectionPalette) {
-                var rgb = tinycolor(color).toRgbString();
-                if (!paletteLookup[rgb] && $.inArray(rgb, selectionPalette) === -1) {
-                    selectionPalette.push(rgb);
-                    while(selectionPalette.length > maxSelectionSize) {
-                        selectionPalette.shift();
-                    }
-                }
-
-                if (localStorageKey && window.localStorage) {
-                    try {
-                        window.localStorage[localStorageKey] = selectionPalette.join(";");
-                    }
-                    catch(e) { }
-                }
-            }
-        }
-
-        function getUniqueSelectionPalette() {
-            var unique = [];
-            if (opts.showPalette) {
-                for (var i = 0; i < selectionPalette.length; i++) {
-                    var rgb = tinycolor(selectionPalette[i]).toRgbString();
-
-                    if (!paletteLookup[rgb]) {
-                        unique.push(selectionPalette[i]);
-                    }
-                }
-            }
-
-            return unique.reverse().slice(0, opts.maxSelectionSize);
-        }
-
-        function drawPalette() {
-
-            var currentColor = get();
-
-            var html = $.map(paletteArray, function (palette, i) {
-                return paletteTemplate(palette, currentColor, "sp-palette-row sp-palette-row-" + i, opts);
-            });
-
-            updateSelectionPaletteFromStorage();
-
-            if (selectionPalette) {
-                html.push(paletteTemplate(getUniqueSelectionPalette(), currentColor, "sp-palette-row sp-palette-row-selection", opts));
-            }
-
-            paletteContainer.html(html.join(""));
-        }
-
-        function drawInitial() {
-            if (opts.showInitial) {
-                var initial = colorOnShow;
-                var current = get();
-                initialColorContainer.html(paletteTemplate([initial, current], current, "sp-palette-row-initial", opts));
-            }
-        }
-
-        function dragStart() {
-            if (dragHeight <= 0 || dragWidth <= 0 || slideHeight <= 0) {
-                reflow();
-            }
-            isDragging = true;
-            container.addClass(draggingClass);
-            shiftMovementDirection = null;
-            boundElement.trigger('dragstart.spectrum', [ get() ]);
-        }
-
-        function dragStop() {
-            isDragging = false;
-            container.removeClass(draggingClass);
-            boundElement.trigger('dragstop.spectrum', [ get() ]);
-        }
-
-        function setFromTextInput() {
-
-            var value = textInput.val();
-
-            if ((value === null || value === "") && allowEmpty) {
-                set(null);
-                updateOriginalInput(true);
-            }
-            else {
-                var tiny = tinycolor(value);
-                if (tiny.isValid()) {
-                    set(tiny);
-                    updateOriginalInput(true);
-                }
-                else {
-                    textInput.addClass("sp-validation-error");
-                }
-            }
-        }
-
-        function toggle() {
-            if (visible) {
-                hide();
-            }
-            else {
-                show();
-            }
-        }
-
-        function show() {
-            var event = $.Event('beforeShow.spectrum');
-
-            if (visible) {
-                reflow();
-                return;
-            }
-
-            boundElement.trigger(event, [ get() ]);
-
-            if (callbacks.beforeShow(get()) === false || event.isDefaultPrevented()) {
-                return;
-            }
-
-            hideAll();
-            visible = true;
-
-            $(doc).bind("keydown.spectrum", onkeydown);
-            $(doc).bind("click.spectrum", clickout);
-            $(window).bind("resize.spectrum", resize);
-            replacer.addClass("sp-active");
-            container.removeClass("sp-hidden");
-
-            reflow();
-            updateUI();
-
-            colorOnShow = get();
-
-            drawInitial();
-            callbacks.show(colorOnShow);
-            boundElement.trigger('show.spectrum', [ colorOnShow ]);
-        }
-
-        function onkeydown(e) {
-            // Close on ESC
-            if (e.keyCode === 27) {
-                hide();
-            }
-        }
-
-        function clickout(e) {
-            // Return on right click.
-            if (e.button == 2) { return; }
-
-            // If a drag event was happening during the mouseup, don't hide
-            // on click.
-            if (isDragging) { return; }
-
-            if (clickoutFiresChange) {
-                updateOriginalInput(true);
-            }
-            else {
-                revert();
-            }
-            hide();
-        }
-
-        function hide() {
-            // Return if hiding is unnecessary
-            if (!visible || flat) { return; }
-            visible = false;
-
-            $(doc).unbind("keydown.spectrum", onkeydown);
-            $(doc).unbind("click.spectrum", clickout);
-            $(window).unbind("resize.spectrum", resize);
-
-            replacer.removeClass("sp-active");
-            container.addClass("sp-hidden");
-
-            callbacks.hide(get());
-            boundElement.trigger('hide.spectrum', [ get() ]);
-        }
-
-        function revert() {
-            set(colorOnShow, true);
-        }
-
-        function set(color, ignoreFormatChange) {
-            if (tinycolor.equals(color, get())) {
-                // Update UI just in case a validation error needs
-                // to be cleared.
-                updateUI();
-                return;
-            }
-
-            var newColor, newHsv;
-            if (!color && allowEmpty) {
-                isEmpty = true;
-            } else {
-                isEmpty = false;
-                newColor = tinycolor(color);
-                newHsv = newColor.toHsv();
-
-                currentHue = (newHsv.h % 360) / 360;
-                currentSaturation = newHsv.s;
-                currentValue = newHsv.v;
-                currentAlpha = newHsv.a;
-            }
-            updateUI();
-
-            if (newColor && newColor.isValid() && !ignoreFormatChange) {
-                currentPreferredFormat = preferredFormat || newColor.getFormat();
-            }
-        }
-
-        function get(opts) {
-            opts = opts || { };
-
-            if (allowEmpty && isEmpty) {
-                return null;
-            }
-
-            return tinycolor.fromRatio({
-                h: currentHue,
-                s: currentSaturation,
-                v: currentValue,
-                a: Math.round(currentAlpha * 100) / 100
-            }, { format: opts.format || currentPreferredFormat });
-        }
-
-        function isValid() {
-            return !textInput.hasClass("sp-validation-error");
-        }
-
-        function move() {
-            updateUI();
-
-            callbacks.move(get());
-            boundElement.trigger('move.spectrum', [ get() ]);
-        }
-
-        function updateUI() {
-
-            textInput.removeClass("sp-validation-error");
-
-            updateHelperLocations();
-
-            // Update dragger background color (gradients take care of saturation and value).
-            var flatColor = tinycolor.fromRatio({ h: currentHue, s: 1, v: 1 });
-            dragger.css("background-color", flatColor.toHexString());
-
-            // Get a format that alpha will be included in (hex and names ignore alpha)
-            var format = currentPreferredFormat;
-            if (currentAlpha < 1 && !(currentAlpha === 0 && format === "name")) {
-                if (format === "hex" || format === "hex3" || format === "hex6" || format === "name") {
-                    format = "rgb";
-                }
-            }
-
-            var realColor = get({ format: format }),
-                displayColor = '';
-
-             //reset background info for preview element
-            previewElement.removeClass("sp-clear-display");
-            previewElement.css('background-color', 'transparent');
-
-            if (!realColor && allowEmpty) {
-                // Update the replaced elements background with icon indicating no color selection
-                previewElement.addClass("sp-clear-display");
-            }
-            else {
-                var realHex = realColor.toHexString(),
-                    realRgb = realColor.toRgbString();
-
-                // Update the replaced elements background color (with actual selected color)
-                if (rgbaSupport || realColor.alpha === 1) {
-                    previewElement.css("background-color", realRgb);
-                }
-                else {
-                    previewElement.css("background-color", "transparent");
-                    previewElement.css("filter", realColor.toFilter());
-                }
-
-                if (opts.showAlpha) {
-                    var rgb = realColor.toRgb();
-                    rgb.a = 0;
-                    var realAlpha = tinycolor(rgb).toRgbString();
-                    var gradient = "linear-gradient(left, " + realAlpha + ", " + realHex + ")";
-
-                    if (IE) {
-                        alphaSliderInner.css("filter", tinycolor(realAlpha).toFilter({ gradientType: 1 }, realHex));
-                    }
-                    else {
-                        alphaSliderInner.css("background", "-webkit-" + gradient);
-                        alphaSliderInner.css("background", "-moz-" + gradient);
-                        alphaSliderInner.css("background", "-ms-" + gradient);
-                        // Use current syntax gradient on unprefixed property.
-                        alphaSliderInner.css("background",
-                            "linear-gradient(to right, " + realAlpha + ", " + realHex + ")");
-                    }
-                }
-
-                displayColor = realColor.toString(format);
-            }
-
-            // Update the text entry input as it changes happen
-            if (opts.showInput) {
-                textInput.val(displayColor);
-            }
-
-            if (opts.showPalette) {
-                drawPalette();
-            }
-
-            drawInitial();
-        }
-
-        function updateHelperLocations() {
-            var s = currentSaturation;
-            var v = currentValue;
-
-            if(allowEmpty && isEmpty) {
-                //if selected color is empty, hide the helpers
-                alphaSlideHelper.hide();
-                slideHelper.hide();
-                dragHelper.hide();
-            }
-            else {
-                //make sure helpers are visible
-                alphaSlideHelper.show();
-                slideHelper.show();
-                dragHelper.show();
-
-                // Where to show the little circle in that displays your current selected color
-                var dragX = s * dragWidth;
-                var dragY = dragHeight - (v * dragHeight);
-                dragX = Math.max(
-                    -dragHelperHeight,
-                    Math.min(dragWidth - dragHelperHeight, dragX - dragHelperHeight)
-                );
-                dragY = Math.max(
-                    -dragHelperHeight,
-                    Math.min(dragHeight - dragHelperHeight, dragY - dragHelperHeight)
-                );
-                dragHelper.css({
-                    "top": dragY + "px",
-                    "left": dragX + "px"
-                });
-
-                var alphaX = currentAlpha * alphaWidth;
-                alphaSlideHelper.css({
-                    "left": (alphaX - (alphaSlideHelperWidth / 2)) + "px"
-                });
-
-                // Where to show the bar that displays your current selected hue
-                var slideY = (currentHue) * slideHeight;
-                slideHelper.css({
-                    "top": (slideY - slideHelperHeight) + "px"
-                });
-            }
-        }
-
-        function updateOriginalInput(fireCallback) {
-            var color = get(),
-                displayColor = '',
-                hasChanged = !tinycolor.equals(color, colorOnShow);
-
-            if (color) {
-                displayColor = color.toString(currentPreferredFormat);
-                // Update the selection palette with the current color
-                addColorToSelectionPalette(color);
-            }
-
-            if (isInput) {
-                boundElement.val(displayColor);
-            }
-
-            if (fireCallback && hasChanged) {
-                callbacks.change(color);
-                boundElement.trigger('change', [ color ]);
-            }
-        }
-
-        function reflow() {
-            dragWidth = dragger.width();
-            dragHeight = dragger.height();
-            dragHelperHeight = dragHelper.height();
-            slideWidth = slider.width();
-            slideHeight = slider.height();
-            slideHelperHeight = slideHelper.height();
-            alphaWidth = alphaSlider.width();
-            alphaSlideHelperWidth = alphaSlideHelper.width();
-
-            if (!flat) {
-                container.css("position", "absolute");
-                if (opts.offset) {
-                    container.offset(opts.offset);
-                } else {
-                    container.offset(getOffset(container, offsetElement));
-                }
-            }
-
-            updateHelperLocations();
-
-            if (opts.showPalette) {
-                drawPalette();
-            }
-
-            boundElement.trigger('reflow.spectrum');
-        }
-
-        function destroy() {
-            boundElement.show();
-            offsetElement.unbind("click.spectrum touchstart.spectrum");
-            container.remove();
-            replacer.remove();
-            spectrums[spect.id] = null;
-        }
-
-        function option(optionName, optionValue) {
-            if (optionName === undefined) {
-                return $.extend({}, opts);
-            }
-            if (optionValue === undefined) {
-                return opts[optionName];
-            }
-
-            opts[optionName] = optionValue;
-            applyOptions();
-        }
-
-        function enable() {
-            disabled = false;
-            boundElement.attr("disabled", false);
-            offsetElement.removeClass("sp-disabled");
-        }
-
-        function disable() {
-            hide();
-            disabled = true;
-            boundElement.attr("disabled", true);
-            offsetElement.addClass("sp-disabled");
-        }
-
-        function setOffset(coord) {
-            opts.offset = coord;
-            reflow();
-        }
-
-        initialize();
-
-        var spect = {
-            show: show,
-            hide: hide,
-            toggle: toggle,
-            reflow: reflow,
-            option: option,
-            enable: enable,
-            disable: disable,
-            offset: setOffset,
-            set: function (c) {
-                set(c);
-                updateOriginalInput();
-            },
-            get: get,
-            destroy: destroy,
-            container: container
-        };
-
-        spect.id = spectrums.push(spect) - 1;
-
-        return spect;
-    }
-
-    /**
-    * checkOffset - get the offset below/above and left/right element depending on screen position
-    * Thanks https://github.com/jquery/jquery-ui/blob/master/ui/jquery.ui.datepicker.js
-    */
-    function getOffset(picker, input) {
-        var extraY = 0;
-        var dpWidth = picker.outerWidth();
-        var dpHeight = picker.outerHeight();
-        var inputHeight = input.outerHeight();
-        var doc = picker[0].ownerDocument;
-        var docElem = doc.documentElement;
-        var viewWidth = docElem.clientWidth + $(doc).scrollLeft();
-        var viewHeight = docElem.clientHeight + $(doc).scrollTop();
-        var offset = input.offset();
-        offset.top += inputHeight;
-
-        offset.left -=
-            Math.min(offset.left, (offset.left + dpWidth > viewWidth && viewWidth > dpWidth) ?
-            Math.abs(offset.left + dpWidth - viewWidth) : 0);
-
-        offset.top -=
-            Math.min(offset.top, ((offset.top + dpHeight > viewHeight && viewHeight > dpHeight) ?
-            Math.abs(dpHeight + inputHeight - extraY) : extraY));
-
-        return offset;
-    }
-
-    /**
-    * noop - do nothing
-    */
-    function noop() {
-
-    }
-
-    /**
-    * stopPropagation - makes the code only doing this a little easier to read in line
-    */
-    function stopPropagation(e) {
-        e.stopPropagation();
-    }
-
-    /**
-    * Create a function bound to a given object
-    * Thanks to underscore.js
-    */
-    function bind(func, obj) {
-        var slice = Array.prototype.slice;
-        var args = slice.call(arguments, 2);
-        return function () {
-            return func.apply(obj, args.concat(slice.call(arguments)));
-        };
-    }
-
-    /**
-    * Lightweight drag helper.  Handles containment within the element, so that
-    * when dragging, the x is within [0,element.width] and y is within [0,element.height]
-    */
-    function draggable(element, onmove, onstart, onstop) {
-        onmove = onmove || function () { };
-        onstart = onstart || function () { };
-        onstop = onstop || function () { };
-        var doc = document;
-        var dragging = false;
-        var offset = {};
-        var maxHeight = 0;
-        var maxWidth = 0;
-        var hasTouch = ('ontouchstart' in window);
-
-        var duringDragEvents = {};
-        duringDragEvents["selectstart"] = prevent;
-        duringDragEvents["dragstart"] = prevent;
-        duringDragEvents["touchmove mousemove"] = move;
-        duringDragEvents["touchend mouseup"] = stop;
-
-        function prevent(e) {
-            if (e.stopPropagation) {
-                e.stopPropagation();
-            }
-            if (e.preventDefault) {
-                e.preventDefault();
-            }
-            e.returnValue = false;
-        }
-
-        function move(e) {
-            if (dragging) {
-                // Mouseup happened outside of window
-                if (IE && doc.documentMode < 9 && !e.button) {
-                    return stop();
-                }
-
-                var t0 = e.originalEvent && e.originalEvent.touches && e.originalEvent.touches[0];
-                var pageX = t0 && t0.pageX || e.pageX;
-                var pageY = t0 && t0.pageY || e.pageY;
-
-                var dragX = Math.max(0, Math.min(pageX - offset.left, maxWidth));
-                var dragY = Math.max(0, Math.min(pageY - offset.top, maxHeight));
-
-                if (hasTouch) {
-                    // Stop scrolling in iOS
-                    prevent(e);
-                }
-
-                onmove.apply(element, [dragX, dragY, e]);
-            }
-        }
-
-        function start(e) {
-            var rightclick = (e.which) ? (e.which == 3) : (e.button == 2);
-
-            if (!rightclick && !dragging) {
-                if (onstart.apply(element, arguments) !== false) {
-                    dragging = true;
-                    maxHeight = $(element).height();
-                    maxWidth = $(element).width();
-                    offset = $(element).offset();
-
-                    $(doc).bind(duringDragEvents);
-                    $(doc.body).addClass("sp-dragging");
-
-                    move(e);
-
-                    prevent(e);
-                }
-            }
-        }
-
-        function stop() {
-            if (dragging) {
-                $(doc).unbind(duringDragEvents);
-                $(doc.body).removeClass("sp-dragging");
-
-                // Wait a tick before notifying observers to allow the click event
-                // to fire in Chrome.
-                setTimeout(function() {
-                    onstop.apply(element, arguments);
-                }, 0);
-            }
-            dragging = false;
-        }
-
-        $(element).bind("touchstart mousedown", start);
-    }
-
-    function throttle(func, wait, debounce) {
-        var timeout;
-        return function () {
-            var context = this, args = arguments;
-            var throttler = function () {
-                timeout = null;
-                func.apply(context, args);
-            };
-            if (debounce) clearTimeout(timeout);
-            if (debounce || !timeout) timeout = setTimeout(throttler, wait);
-        };
-    }
-
-    function inputTypeColorSupport() {
-        return $.fn.spectrum.inputTypeColorSupport();
-    }
-
-    /**
-    * Define a jQuery plugin
-    */
-    var dataID = "spectrum.id";
-    $.fn.spectrum = function (opts, extra) {
-
-        if (typeof opts == "string") {
-
-            var returnValue = this;
-            var args = Array.prototype.slice.call( arguments, 1 );
-
-            this.each(function () {
-                var spect = spectrums[$(this).data(dataID)];
-                if (spect) {
-                    var method = spect[opts];
-                    if (!method) {
-                        throw new Error( "Spectrum: no such method: '" + opts + "'" );
-                    }
-
-                    if (opts == "get") {
-                        returnValue = spect.get();
-                    }
-                    else if (opts == "container") {
-                        returnValue = spect.container;
-                    }
-                    else if (opts == "option") {
-                        returnValue = spect.option.apply(spect, args);
-                    }
-                    else if (opts == "destroy") {
-                        spect.destroy();
-                        $(this).removeData(dataID);
-                    }
-                    else {
-                        method.apply(spect, args);
-                    }
-                }
-            });
-
-            return returnValue;
-        }
-
-        // Initializing a new instance of spectrum
-        return this.spectrum("destroy").each(function () {
-            var options = $.extend({}, opts, $(this).data());
-            var spect = spectrum(this, options);
-            $(this).data(dataID, spect.id);
-        });
-    };
-
-    $.fn.spectrum.load = true;
-    $.fn.spectrum.loadOpts = {};
-    $.fn.spectrum.draggable = draggable;
-    $.fn.spectrum.defaults = defaultOpts;
-    $.fn.spectrum.inputTypeColorSupport = function inputTypeColorSupport() {
-        if (typeof inputTypeColorSupport._cachedResult === "undefined") {
-            var colorInput = $("<input type='color' value='!' />")[0];
-            inputTypeColorSupport._cachedResult = colorInput.type === "color" && colorInput.value !== "!";
-        }
-        return inputTypeColorSupport._cachedResult;
-    };
-
-    $.spectrum = { };
-    $.spectrum.localization = { };
-    $.spectrum.palettes = { };
-
-    $.fn.spectrum.processNativeColorInputs = function () {
-        var colorInputs = $("input[type=color]");
-        if (colorInputs.length && !inputTypeColorSupport()) {
-            colorInputs.spectrum({
-                preferredFormat: "hex6"
-            });
-        }
-    };
-
-    // TinyColor v1.1.2
-    // https://github.com/bgrins/TinyColor
-    // Brian Grinstead, MIT License
-
-    (function() {
-
-    var trimLeft = /^[\s,#]+/,
-        trimRight = /\s+$/,
-        tinyCounter = 0,
-        math = Math,
-        mathRound = math.round,
-        mathMin = math.min,
-        mathMax = math.max,
-        mathRandom = math.random;
-
-    var tinycolor = function(color, opts) {
-
-        color = (color) ? color : '';
-        opts = opts || { };
-
-        // If input is already a tinycolor, return itself
-        if (color instanceof tinycolor) {
-           return color;
-        }
-        // If we are called as a function, call using new instead
-        if (!(this instanceof tinycolor)) {
-            return new tinycolor(color, opts);
-        }
-
-        var rgb = inputToRGB(color);
-        this._originalInput = color,
-        this._r = rgb.r,
-        this._g = rgb.g,
-        this._b = rgb.b,
-        this._a = rgb.a,
-        this._roundA = mathRound(100*this._a) / 100,
-        this._format = opts.format || rgb.format;
-        this._gradientType = opts.gradientType;
-
-        // Don't let the range of [0,255] come back in [0,1].
-        // Potentially lose a little bit of precision here, but will fix issues where
-        // .5 gets interpreted as half of the total, instead of half of 1
-        // If it was supposed to be 128, this was already taken care of by `inputToRgb`
-        if (this._r < 1) { this._r = mathRound(this._r); }
-        if (this._g < 1) { this._g = mathRound(this._g); }
-        if (this._b < 1) { this._b = mathRound(this._b); }
-
-        this._ok = rgb.ok;
-        this._tc_id = tinyCounter++;
-    };
-
-    tinycolor.prototype = {
-        isDark: function() {
-            return this.getBrightness() < 128;
-        },
-        isLight: function() {
-            return !this.isDark();
-        },
-        isValid: function() {
-            return this._ok;
-        },
-        getOriginalInput: function() {
-          return this._originalInput;
-        },
-        getFormat: function() {
-            return this._format;
-        },
-        getAlpha: function() {
-            return this._a;
-        },
-        getBrightness: function() {
-            var rgb = this.toRgb();
-            return (rgb.r * 299 + rgb.g * 587 + rgb.b * 114) / 1000;
-        },
-        setAlpha: function(value) {
-            this._a = boundAlpha(value);
-            this._roundA = mathRound(100*this._a) / 100;
-            return this;
-        },
-        toHsv: function() {
-            var hsv = rgbToHsv(this._r, this._g, this._b);
-            return { h: hsv.h * 360, s: hsv.s, v: hsv.v, a: this._a };
-        },
-        toHsvString: function() {
-            var hsv = rgbToHsv(this._r, this._g, this._b);
-            var h = mathRound(hsv.h * 360), s = mathRound(hsv.s * 100), v = mathRound(hsv.v * 100);
-            return (this._a == 1) ?
-              "hsv("  + h + ", " + s + "%, " + v + "%)" :
-              "hsva(" + h + ", " + s + "%, " + v + "%, "+ this._roundA + ")";
-        },
-        toHsl: function() {
-            var hsl = rgbToHsl(this._r, this._g, this._b);
-            return { h: hsl.h * 360, s: hsl.s, l: hsl.l, a: this._a };
-        },
-        toHslString: function() {
-            var hsl = rgbToHsl(this._r, this._g, this._b);
-            var h = mathRound(hsl.h * 360), s = mathRound(hsl.s * 100), l = mathRound(hsl.l * 100);
-            return (this._a == 1) ?
-              "hsl("  + h + ", " + s + "%, " + l + "%)" :
-              "hsla(" + h + ", " + s + "%, " + l + "%, "+ this._roundA + ")";
-        },
-        toHex: function(allow3Char) {
-            return rgbToHex(this._r, this._g, this._b, allow3Char);
-        },
-        toHexString: function(allow3Char) {
-            return '#' + this.toHex(allow3Char);
-        },
-        toHex8: function() {
-            return rgbaToHex(this._r, this._g, this._b, this._a);
-        },
-        toHex8String: function() {
-            return '#' + this.toHex8();
-        },
-        toRgb: function() {
-            return { r: mathRound(this._r), g: mathRound(this._g), b: mathRound(this._b), a: this._a };
-        },
-        toRgbString: function() {
-            return (this._a == 1) ?
-              "rgb("  + mathRound(this._r) + ", " + mathRound(this._g) + ", " + mathRound(this._b) + ")" :
-              "rgba(" + mathRound(this._r) + ", " + mathRound(this._g) + ", " + mathRound(this._b) + ", " + this._roundA + ")";
-        },
-        toPercentageRgb: function() {
-            return { r: mathRound(bound01(this._r, 255) * 100) + "%", g: mathRound(bound01(this._g, 255) * 100) + "%", b: mathRound(bound01(this._b, 255) * 100) + "%", a: this._a };
-        },
-        toPercentageRgbString: function() {
-            return (this._a == 1) ?
-              "rgb("  + mathRound(bound01(this._r, 255) * 100) + "%, " + mathRound(bound01(this._g, 255) * 100) + "%, " + mathRound(bound01(this._b, 255) * 100) + "%)" :
-              "rgba(" + mathRound(bound01(this._r, 255) * 100) + "%, " + mathRound(bound01(this._g, 255) * 100) + "%, " + mathRound(bound01(this._b, 255) * 100) + "%, " + this._roundA + ")";
-        },
-        toName: function() {
-            if (this._a === 0) {
-                return "transparent";
-            }
-
-            if (this._a < 1) {
-                return false;
-            }
-
-            return hexNames[rgbToHex(this._r, this._g, this._b, true)] || false;
-        },
-        toFilter: function(secondColor) {
-            var hex8String = '#' + rgbaToHex(this._r, this._g, this._b, this._a);
-            var secondHex8String = hex8String;
-            var gradientType = this._gradientType ? "GradientType = 1, " : "";
-
-            if (secondColor) {
-                var s = tinycolor(secondColor);
-                secondHex8String = s.toHex8String();
-            }
-
-            return "progid:DXImageTransform.Microsoft.gradient("+gradientType+"startColorstr="+hex8String+",endColorstr="+secondHex8String+")";
-        },
-        toString: function(format) {
-            var formatSet = !!format;
-            format = format || this._format;
-
-            var formattedString = false;
-            var hasAlpha = this._a < 1 && this._a >= 0;
-            var needsAlphaFormat = !formatSet && hasAlpha && (format === "hex" || format === "hex6" || format === "hex3" || format === "name");
-
-            if (needsAlphaFormat) {
-                // Special case for "transparent", all other non-alpha formats
-                // will return rgba when there is transparency.
-                if (format === "name" && this._a === 0) {
-                    return this.toName();
-                }
-                return this.toRgbString();
-            }
-            if (format === "rgb") {
-                formattedString = this.toRgbString();
-            }
-            if (format === "prgb") {
-                formattedString = this.toPercentageRgbString();
-            }
-            if (format === "hex" || format === "hex6") {
-                formattedString = this.toHexString();
-            }
-            if (format === "hex3") {
-                formattedString = this.toHexString(true);
-            }
-            if (format === "hex8") {
-                formattedString = this.toHex8String();
-            }
-            if (format === "name") {
-                formattedString = this.toName();
-            }
-            if (format === "hsl") {
-                formattedString = this.toHslString();
-            }
-            if (format === "hsv") {
-                formattedString = this.toHsvString();
-            }
-
-            return formattedString || this.toHexString();
-        },
-
-        _applyModification: function(fn, args) {
-            var color = fn.apply(null, [this].concat([].slice.call(args)));
-            this._r = color._r;
-            this._g = color._g;
-            this._b = color._b;
-            this.setAlpha(color._a);
-            return this;
-        },
-        lighten: function() {
-            return this._applyModification(lighten, arguments);
-        },
-        brighten: function() {
-            return this._applyModification(brighten, arguments);
-        },
-        darken: function() {
-            return this._applyModification(darken, arguments);
-        },
-        desaturate: function() {
-            return this._applyModification(desaturate, arguments);
-        },
-        saturate: function() {
-            return this._applyModification(saturate, arguments);
-        },
-        greyscale: function() {
-            return this._applyModification(greyscale, arguments);
-        },
-        spin: function() {
-            return this._applyModification(spin, arguments);
-        },
-
-        _applyCombination: function(fn, args) {
-            return fn.apply(null, [this].concat([].slice.call(args)));
-        },
-        analogous: function() {
-            return this._applyCombination(analogous, arguments);
-        },
-        complement: function() {
-            return this._applyCombination(complement, arguments);
-        },
-        monochromatic: function() {
-            return this._applyCombination(monochromatic, arguments);
-        },
-        splitcomplement: function() {
-            return this._applyCombination(splitcomplement, arguments);
-        },
-        triad: function() {
-            return this._applyCombination(triad, arguments);
-        },
-        tetrad: function() {
-            return this._applyCombination(tetrad, arguments);
-        }
-    };
-
-    // If input is an object, force 1 into "1.0" to handle ratios properly
-    // String input requires "1.0" as input, so 1 will be treated as 1
-    tinycolor.fromRatio = function(color, opts) {
-        if (typeof color == "object") {
-            var newColor = {};
-            for (var i in color) {
-                if (color.hasOwnProperty(i)) {
-                    if (i === "a") {
-                        newColor[i] = color[i];
-                    }
-                    else {
-                        newColor[i] = convertToPercentage(color[i]);
-                    }
-                }
-            }
-            color = newColor;
-        }
-
-        return tinycolor(color, opts);
-    };
-
-    // Given a string or object, convert that input to RGB
-    // Possible string inputs:
-    //
-    //     "red"
-    //     "#f00" or "f00"
-    //     "#ff0000" or "ff0000"
-    //     "#ff000000" or "ff000000"
-    //     "rgb 255 0 0" or "rgb (255, 0, 0)"
-    //     "rgb 1.0 0 0" or "rgb (1, 0, 0)"
-    //     "rgba (255, 0, 0, 1)" or "rgba 255, 0, 0, 1"
-    //     "rgba (1.0, 0, 0, 1)" or "rgba 1.0, 0, 0, 1"
-    //     "hsl(0, 100%, 50%)" or "hsl 0 100% 50%"
-    //     "hsla(0, 100%, 50%, 1)" or "hsla 0 100% 50%, 1"
-    //     "hsv(0, 100%, 100%)" or "hsv 0 100% 100%"
-    //
-    function inputToRGB(color) {
-
-        var rgb = { r: 0, g: 0, b: 0 };
-        var a = 1;
-        var ok = false;
-        var format = false;
-
-        if (typeof color == "string") {
-            color = stringInputToObject(color);
-        }
-
-        if (typeof color == "object") {
-            if (color.hasOwnProperty("r") && color.hasOwnProperty("g") && color.hasOwnProperty("b")) {
-                rgb = rgbToRgb(color.r, color.g, color.b);
-                ok = true;
-                format = String(color.r).substr(-1) === "%" ? "prgb" : "rgb";
-            }
-            else if (color.hasOwnProperty("h") && color.hasOwnProperty("s") && color.hasOwnProperty("v")) {
-                color.s = convertToPercentage(color.s);
-                color.v = convertToPercentage(color.v);
-                rgb = hsvToRgb(color.h, color.s, color.v);
-                ok = true;
-                format = "hsv";
-            }
-            else if (color.hasOwnProperty("h") && color.hasOwnProperty("s") && color.hasOwnProperty("l")) {
-                color.s = convertToPercentage(color.s);
-                color.l = convertToPercentage(color.l);
-                rgb = hslToRgb(color.h, color.s, color.l);
-                ok = true;
-                format = "hsl";
-            }
-
-            if (color.hasOwnProperty("a")) {
-                a = color.a;
-            }
-        }
-
-        a = boundAlpha(a);
-
-        return {
-            ok: ok,
-            format: color.format || format,
-            r: mathMin(255, mathMax(rgb.r, 0)),
-            g: mathMin(255, mathMax(rgb.g, 0)),
-            b: mathMin(255, mathMax(rgb.b, 0)),
-            a: a
-        };
-    }
-
-
-    // Conversion Functions
-    // --------------------
-
-    // `rgbToHsl`, `rgbToHsv`, `hslToRgb`, `hsvToRgb` modified from:
-    // <http://mjijackson.com/2008/02/rgb-to-hsl-and-rgb-to-hsv-color-model-conversion-algorithms-in-javascript>
-
-    // `rgbToRgb`
-    // Handle bounds / percentage checking to conform to CSS color spec
-    // <http://www.w3.org/TR/css3-color/>
-    // *Assumes:* r, g, b in [0, 255] or [0, 1]
-    // *Returns:* { r, g, b } in [0, 255]
-    function rgbToRgb(r, g, b){
-        return {
-            r: bound01(r, 255) * 255,
-            g: bound01(g, 255) * 255,
-            b: bound01(b, 255) * 255
-        };
-    }
-
-    // `rgbToHsl`
-    // Converts an RGB color value to HSL.
-    // *Assumes:* r, g, and b are contained in [0, 255] or [0, 1]
-    // *Returns:* { h, s, l } in [0,1]
-    function rgbToHsl(r, g, b) {
-
-        r = bound01(r, 255);
-        g = bound01(g, 255);
-        b = bound01(b, 255);
-
-        var max = mathMax(r, g, b), min = mathMin(r, g, b);
-        var h, s, l = (max + min) / 2;
-
-        if(max == min) {
-            h = s = 0; // achromatic
-        }
-        else {
-            var d = max - min;
-            s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
-            switch(max) {
-                case r: h = (g - b) / d + (g < b ? 6 : 0); break;
-                case g: h = (b - r) / d + 2; break;
-                case b: h = (r - g) / d + 4; break;
-            }
-
-            h /= 6;
-        }
-
-        return { h: h, s: s, l: l };
-    }
-
-    // `hslToRgb`
-    // Converts an HSL color value to RGB.
-    // *Assumes:* h is contained in [0, 1] or [0, 360] and s and l are contained [0, 1] or [0, 100]
-    // *Returns:* { r, g, b } in the set [0, 255]
-    function hslToRgb(h, s, l) {
-        var r, g, b;
-
-        h = bound01(h, 360);
-        s = bound01(s, 100);
-        l = bound01(l, 100);
-
-        function hue2rgb(p, q, t) {
-            if(t < 0) t += 1;
-            if(t > 1) t -= 1;
-            if(t < 1/6) return p + (q - p) * 6 * t;
-            if(t < 1/2) return q;
-            if(t < 2/3) return p + (q - p) * (2/3 - t) * 6;
-            return p;
-        }
-
-        if(s === 0) {
-            r = g = b = l; // achromatic
-        }
-        else {
-            var q = l < 0.5 ? l * (1 + s) : l + s - l * s;
-            var p = 2 * l - q;
-            r = hue2rgb(p, q, h + 1/3);
-            g = hue2rgb(p, q, h);
-            b = hue2rgb(p, q, h - 1/3);
-        }
-
-        return { r: r * 255, g: g * 255, b: b * 255 };
-    }
-
-    // `rgbToHsv`
-    // Converts an RGB color value to HSV
-    // *Assumes:* r, g, and b are contained in the set [0, 255] or [0, 1]
-    // *Returns:* { h, s, v } in [0,1]
-    function rgbToHsv(r, g, b) {
-
-        r = bound01(r, 255);
-        g = bound01(g, 255);
-        b = bound01(b, 255);
-
-        var max = mathMax(r, g, b), min = mathMin(r, g, b);
-        var h, s, v = max;
-
-        var d = max - min;
-        s = max === 0 ? 0 : d / max;
-
-        if(max == min) {
-            h = 0; // achromatic
-        }
-        else {
-            switch(max) {
-                case r: h = (g - b) / d + (g < b ? 6 : 0); break;
-                case g: h = (b - r) / d + 2; break;
-                case b: h = (r - g) / d + 4; break;
-            }
-            h /= 6;
-        }
-        return { h: h, s: s, v: v };
-    }
-
-    // `hsvToRgb`
-    // Converts an HSV color value to RGB.
-    // *Assumes:* h is contained in [0, 1] or [0, 360] and s and v are contained in [0, 1] or [0, 100]
-    // *Returns:* { r, g, b } in the set [0, 255]
-     function hsvToRgb(h, s, v) {
-
-        h = bound01(h, 360) * 6;
-        s = bound01(s, 100);
-        v = bound01(v, 100);
-
-        var i = math.floor(h),
-            f = h - i,
-            p = v * (1 - s),
-            q = v * (1 - f * s),
-            t = v * (1 - (1 - f) * s),
-            mod = i % 6,
-            r = [v, q, p, p, t, v][mod],
-            g = [t, v, v, q, p, p][mod],
-            b = [p, p, t, v, v, q][mod];
-
-        return { r: r * 255, g: g * 255, b: b * 255 };
-    }
-
-    // `rgbToHex`
-    // Converts an RGB color to hex
-    // Assumes r, g, and b are contained in the set [0, 255]
-    // Returns a 3 or 6 character hex
-    function rgbToHex(r, g, b, allow3Char) {
-
-        var hex = [
-            pad2(mathRound(r).toString(16)),
-            pad2(mathRound(g).toString(16)),
-            pad2(mathRound(b).toString(16))
-        ];
-
-        // Return a 3 character hex if possible
-        if (allow3Char && hex[0].charAt(0) == hex[0].charAt(1) && hex[1].charAt(0) == hex[1].charAt(1) && hex[2].charAt(0) == hex[2].charAt(1)) {
-            return hex[0].charAt(0) + hex[1].charAt(0) + hex[2].charAt(0);
-        }
-
-        return hex.join("");
-    }
-        // `rgbaToHex`
-        // Converts an RGBA color plus alpha transparency to hex
-        // Assumes r, g, b and a are contained in the set [0, 255]
-        // Returns an 8 character hex
-        function rgbaToHex(r, g, b, a) {
-
-            var hex = [
-                pad2(convertDecimalToHex(a)),
-                pad2(mathRound(r).toString(16)),
-                pad2(mathRound(g).toString(16)),
-                pad2(mathRound(b).toString(16))
-            ];
-
-            return hex.join("");
-        }
-
-    // `equals`
-    // Can be called with any tinycolor input
-    tinycolor.equals = function (color1, color2) {
-        if (!color1 || !color2) { return false; }
-        return tinycolor(color1).toRgbString() == tinycolor(color2).toRgbString();
-    };
-    tinycolor.random = function() {
-        return tinycolor.fromRatio({
-            r: mathRandom(),
-            g: mathRandom(),
-            b: mathRandom()
-        });
-    };
-
-
-    // Modification Functions
-    // ----------------------
-    // Thanks to less.js for some of the basics here
-    // <https://github.com/cloudhead/less.js/blob/master/lib/less/functions.js>
-
-    function desaturate(color, amount) {
-        amount = (amount === 0) ? 0 : (amount || 10);
-        var hsl = tinycolor(color).toHsl();
-        hsl.s -= amount / 100;
-        hsl.s = clamp01(hsl.s);
-        return tinycolor(hsl);
-    }
-
-    function saturate(color, amount) {
-        amount = (amount === 0) ? 0 : (amount || 10);
-        var hsl = tinycolor(color).toHsl();
-        hsl.s += amount / 100;
-        hsl.s = clamp01(hsl.s);
-        return tinycolor(hsl);
-    }
-
-    function greyscale(color) {
-        return tinycolor(color).desaturate(100);
-    }
-
-    function lighten (color, amount) {
-        amount = (amount === 0) ? 0 : (amount || 10);
-        var hsl = tinycolor(color).toHsl();
-        hsl.l += amount / 100;
-        hsl.l = clamp01(hsl.l);
-        return tinycolor(hsl);
-    }
-
-    function brighten(color, amount) {
-        amount = (amount === 0) ? 0 : (amount || 10);
-        var rgb = tinycolor(color).toRgb();
-        rgb.r = mathMax(0, mathMin(255, rgb.r - mathRound(255 * - (amount / 100))));
-        rgb.g = mathMax(0, mathMin(255, rgb.g - mathRound(255 * - (amount / 100))));
-        rgb.b = mathMax(0, mathMin(255, rgb.b - mathRound(255 * - (amount / 100))));
-        return tinycolor(rgb);
-    }
-
-    function darken (color, amount) {
-        amount = (amount === 0) ? 0 : (amount || 10);
-        var hsl = tinycolor(color).toHsl();
-        hsl.l -= amount / 100;
-        hsl.l = clamp01(hsl.l);
-        return tinycolor(hsl);
-    }
-
-    // Spin takes a positive or negative amount within [-360, 360] indicating the change of hue.
-    // Values outside of this range will be wrapped into this range.
-    function spin(color, amount) {
-        var hsl = tinycolor(color).toHsl();
-        var hue = (mathRound(hsl.h) + amount) % 360;
-        hsl.h = hue < 0 ? 360 + hue : hue;
-        return tinycolor(hsl);
-    }
-
-    // Combination Functions
-    // ---------------------
-    // Thanks to jQuery xColor for some of the ideas behind these
-    // <https://github.com/infusion/jQuery-xcolor/blob/master/jquery.xcolor.js>
-
-    function complement(color) {
-        var hsl = tinycolor(color).toHsl();
-        hsl.h = (hsl.h + 180) % 360;
-        return tinycolor(hsl);
-    }
-
-    function triad(color) {
-        var hsl = tinycolor(color).toHsl();
-        var h = hsl.h;
-        return [
-            tinycolor(color),
-            tinycolor({ h: (h + 120) % 360, s: hsl.s, l: hsl.l }),
-            tinycolor({ h: (h + 240) % 360, s: hsl.s, l: hsl.l })
-        ];
-    }
-
-    function tetrad(color) {
-        var hsl = tinycolor(color).toHsl();
-        var h = hsl.h;
-        return [
-            tinycolor(color),
-            tinycolor({ h: (h + 90) % 360, s: hsl.s, l: hsl.l }),
-            tinycolor({ h: (h + 180) % 360, s: hsl.s, l: hsl.l }),
-            tinycolor({ h: (h + 270) % 360, s: hsl.s, l: hsl.l })
-        ];
-    }
-
-    function splitcomplement(color) {
-        var hsl = tinycolor(color).toHsl();
-        var h = hsl.h;
-        return [
-            tinycolor(color),
-            tinycolor({ h: (h + 72) % 360, s: hsl.s, l: hsl.l}),
-            tinycolor({ h: (h + 216) % 360, s: hsl.s, l: hsl.l})
-        ];
-    }
-
-    function analogous(color, results, slices) {
-        results = results || 6;
-        slices = slices || 30;
-
-        var hsl = tinycolor(color).toHsl();
-        var part = 360 / slices;
-        var ret = [tinycolor(color)];
-
-        for (hsl.h = ((hsl.h - (part * results >> 1)) + 720) % 360; --results; ) {
-            hsl.h = (hsl.h + part) % 360;
-            ret.push(tinycolor(hsl));
-        }
-        return ret;
-    }
-
-    function monochromatic(color, results) {
-        results = results || 6;
-        var hsv = tinycolor(color).toHsv();
-        var h = hsv.h, s = hsv.s, v = hsv.v;
-        var ret = [];
-        var modification = 1 / results;
-
-        while (results--) {
-            ret.push(tinycolor({ h: h, s: s, v: v}));
-            v = (v + modification) % 1;
-        }
-
-        return ret;
-    }
-
-    // Utility Functions
-    // ---------------------
-
-    tinycolor.mix = function(color1, color2, amount) {
-        amount = (amount === 0) ? 0 : (amount || 50);
-
-        var rgb1 = tinycolor(color1).toRgb();
-        var rgb2 = tinycolor(color2).toRgb();
-
-        var p = amount / 100;
-        var w = p * 2 - 1;
-        var a = rgb2.a - rgb1.a;
-
-        var w1;
-
-        if (w * a == -1) {
-            w1 = w;
-        } else {
-            w1 = (w + a) / (1 + w * a);
-        }
-
-        w1 = (w1 + 1) / 2;
-
-        var w2 = 1 - w1;
-
-        var rgba = {
-            r: rgb2.r * w1 + rgb1.r * w2,
-            g: rgb2.g * w1 + rgb1.g * w2,
-            b: rgb2.b * w1 + rgb1.b * w2,
-            a: rgb2.a * p  + rgb1.a * (1 - p)
-        };
-
-        return tinycolor(rgba);
-    };
-
-
-    // Readability Functions
-    // ---------------------
-    // <http://www.w3.org/TR/AERT#color-contrast>
-
-    // `readability`
-    // Analyze the 2 colors and returns an object with the following properties:
-    //    `brightness`: difference in brightness between the two colors
-    //    `color`: difference in color/hue between the two colors
-    tinycolor.readability = function(color1, color2) {
-        var c1 = tinycolor(color1);
-        var c2 = tinycolor(color2);
-        var rgb1 = c1.toRgb();
-        var rgb2 = c2.toRgb();
-        var brightnessA = c1.getBrightness();
-        var brightnessB = c2.getBrightness();
-        var colorDiff = (
-            Math.max(rgb1.r, rgb2.r) - Math.min(rgb1.r, rgb2.r) +
-            Math.max(rgb1.g, rgb2.g) - Math.min(rgb1.g, rgb2.g) +
-            Math.max(rgb1.b, rgb2.b) - Math.min(rgb1.b, rgb2.b)
-        );
-
-        return {
-            brightness: Math.abs(brightnessA - brightnessB),
-            color: colorDiff
-        };
-    };
-
-    // `readable`
-    // http://www.w3.org/TR/AERT#color-contrast
-    // Ensure that foreground and background color combinations provide sufficient contrast.
-    // *Example*
-    //    tinycolor.isReadable("#000", "#111") => false
-    tinycolor.isReadable = function(color1, color2) {
-        var readability = tinycolor.readability(color1, color2);
-        return readability.brightness > 125 && readability.color > 500;
-    };
-
-    // `mostReadable`
-    // Given a base color and a list of possible foreground or background
-    // colors for that base, returns the most readable color.
-    // *Example*
-    //    tinycolor.mostReadable("#123", ["#fff", "#000"]) => "#000"
-    tinycolor.mostReadable = function(baseColor, colorList) {
-        var bestColor = null;
-        var bestScore = 0;
-        var bestIsReadable = false;
-        for (var i=0; i < colorList.length; i++) {
-
-            // We normalize both around the "acceptable" breaking point,
-            // but rank brightness constrast higher than hue.
-
-            var readability = tinycolor.readability(baseColor, colorList[i]);
-            var readable = readability.brightness > 125 && readability.color > 500;
-            var score = 3 * (readability.brightness / 125) + (readability.color / 500);
-
-            if ((readable && ! bestIsReadable) ||
-                (readable && bestIsReadable && score > bestScore) ||
-                ((! readable) && (! bestIsReadable) && score > bestScore)) {
-                bestIsReadable = readable;
-                bestScore = score;
-                bestColor = tinycolor(colorList[i]);
-            }
-        }
-        return bestColor;
-    };
-
-
-    // Big List of Colors
-    // ------------------
-    // <http://www.w3.org/TR/css3-color/#svg-color>
-    var names = tinycolor.names = {
-        aliceblue: "f0f8ff",
-        antiquewhite: "faebd7",
-        aqua: "0ff",
-        aquamarine: "7fffd4",
-        azure: "f0ffff",
-        beige: "f5f5dc",
-        bisque: "ffe4c4",
-        black: "000",
-        blanchedalmond: "ffebcd",
-        blue: "00f",
-        blueviolet: "8a2be2",
-        brown: "a52a2a",
-        burlywood: "deb887",
-        burntsienna: "ea7e5d",
-        cadetblue: "5f9ea0",
-        chartreuse: "7fff00",
-        chocolate: "d2691e",
-        coral: "ff7f50",
-        cornflowerblue: "6495ed",
-        cornsilk: "fff8dc",
-        crimson: "dc143c",
-        cyan: "0ff",
-        darkblue: "00008b",
-        darkcyan: "008b8b",
-        darkgoldenrod: "b8860b",
-        darkgray: "a9a9a9",
-        darkgreen: "006400",
-        darkgrey: "a9a9a9",
-        darkkhaki: "bdb76b",
-        darkmagenta: "8b008b",
-        darkolivegreen: "556b2f",
-        darkorange: "ff8c00",
-        darkorchid: "9932cc",
-        darkred: "8b0000",
-        darksalmon: "e9967a",
-        darkseagreen: "8fbc8f",
-        darkslateblue: "483d8b",
-        darkslategray: "2f4f4f",
-        darkslategrey: "2f4f4f",
-        darkturquoise: "00ced1",
-        darkviolet: "9400d3",
-        deeppink: "ff1493",
-        deepskyblue: "00bfff",
-        dimgray: "696969",
-        dimgrey: "696969",
-        dodgerblue: "1e90ff",
-        firebrick: "b22222",
-        floralwhite: "fffaf0",
-        forestgreen: "228b22",
-        fuchsia: "f0f",
-        gainsboro: "dcdcdc",
-        ghostwhite: "f8f8ff",
-        gold: "ffd700",
-        goldenrod: "daa520",
-        gray: "808080",
-        green: "008000",
-        greenyellow: "adff2f",
-        grey: "808080",
-        honeydew: "f0fff0",
-        hotpink: "ff69b4",
-        indianred: "cd5c5c",
-        indigo: "4b0082",
-        ivory: "fffff0",
-        khaki: "f0e68c",
-        lavender: "e6e6fa",
-        lavenderblush: "fff0f5",
-        lawngreen: "7cfc00",
-        lemonchiffon: "fffacd",
-        lightblue: "add8e6",
-        lightcoral: "f08080",
-        lightcyan: "e0ffff",
-        lightgoldenrodyellow: "fafad2",
-        lightgray: "d3d3d3",
-        lightgreen: "90ee90",
-        lightgrey: "d3d3d3",
-        lightpink: "ffb6c1",
-        lightsalmon: "ffa07a",
-        lightseagreen: "20b2aa",
-        lightskyblue: "87cefa",
-        lightslategray: "789",
-        lightslategrey: "789",
-        lightsteelblue: "b0c4de",
-        lightyellow: "ffffe0",
-        lime: "0f0",
-        limegreen: "32cd32",
-        linen: "faf0e6",
-        magenta: "f0f",
-        maroon: "800000",
-        mediumaquamarine: "66cdaa",
-        mediumblue: "0000cd",
-        mediumorchid: "ba55d3",
-        mediumpurple: "9370db",
-        mediumseagreen: "3cb371",
-        mediumslateblue: "7b68ee",
-        mediumspringgreen: "00fa9a",
-        mediumturquoise: "48d1cc",
-        mediumvioletred: "c71585",
-        midnightblue: "191970",
-        mintcream: "f5fffa",
-        mistyrose: "ffe4e1",
-        moccasin: "ffe4b5",
-        navajowhite: "ffdead",
-        navy: "000080",
-        oldlace: "fdf5e6",
-        olive: "808000",
-        olivedrab: "6b8e23",
-        orange: "ffa500",
-        orangered: "ff4500",
-        orchid: "da70d6",
-        palegoldenrod: "eee8aa",
-        palegreen: "98fb98",
-        paleturquoise: "afeeee",
-        palevioletred: "db7093",
-        papayawhip: "ffefd5",
-        peachpuff: "ffdab9",
-        peru: "cd853f",
-        pink: "ffc0cb",
-        plum: "dda0dd",
-        powderblue: "b0e0e6",
-        purple: "800080",
-        rebeccapurple: "663399",
-        red: "f00",
-        rosybrown: "bc8f8f",
-        royalblue: "4169e1",
-        saddlebrown: "8b4513",
-        salmon: "fa8072",
-        sandybrown: "f4a460",
-        seagreen: "2e8b57",
-        seashell: "fff5ee",
-        sienna: "a0522d",
-        silver: "c0c0c0",
-        skyblue: "87ceeb",
-        slateblue: "6a5acd",
-        slategray: "708090",
-        slategrey: "708090",
-        snow: "fffafa",
-        springgreen: "00ff7f",
-        steelblue: "4682b4",
-        tan: "d2b48c",
-        teal: "008080",
-        thistle: "d8bfd8",
-        tomato: "ff6347",
-        turquoise: "40e0d0",
-        violet: "ee82ee",
-        wheat: "f5deb3",
-        white: "fff",
-        whitesmoke: "f5f5f5",
-        yellow: "ff0",
-        yellowgreen: "9acd32"
-    };
-
-    // Make it easy to access colors via `hexNames[hex]`
-    var hexNames = tinycolor.hexNames = flip(names);
-
-
-    // Utilities
-    // ---------
-
-    // `{ 'name1': 'val1' }` becomes `{ 'val1': 'name1' }`
-    function flip(o) {
-        var flipped = { };
-        for (var i in o) {
-            if (o.hasOwnProperty(i)) {
-                flipped[o[i]] = i;
-            }
-        }
-        return flipped;
-    }
-
-    // Return a valid alpha value [0,1] with all invalid values being set to 1
-    function boundAlpha(a) {
-        a = parseFloat(a);
-
-        if (isNaN(a) || a < 0 || a > 1) {
-            a = 1;
-        }
-
-        return a;
-    }
-
-    // Take input from [0, n] and return it as [0, 1]
-    function bound01(n, max) {
-        if (isOnePointZero(n)) { n = "100%"; }
-
-        var processPercent = isPercentage(n);
-        n = mathMin(max, mathMax(0, parseFloat(n)));
-
-        // Automatically convert percentage into number
-        if (processPercent) {
-            n = parseInt(n * max, 10) / 100;
-        }
-
-        // Handle floating point rounding errors
-        if ((math.abs(n - max) < 0.000001)) {
-            return 1;
-        }
-
-        // Convert into [0, 1] range if it isn't already
-        return (n % max) / parseFloat(max);
-    }
-
-    // Force a number between 0 and 1
-    function clamp01(val) {
-        return mathMin(1, mathMax(0, val));
-    }
-
-    // Parse a base-16 hex value into a base-10 integer
-    function parseIntFromHex(val) {
-        return parseInt(val, 16);
-    }
-
-    // Need to handle 1.0 as 100%, since once it is a number, there is no difference between it and 1
-    // <http://stackoverflow.com/questions/7422072/javascript-how-to-detect-number-as-a-decimal-including-1-0>
-    function isOnePointZero(n) {
-        return typeof n == "string" && n.indexOf('.') != -1 && parseFloat(n) === 1;
-    }
-
-    // Check to see if string passed in is a percentage
-    function isPercentage(n) {
-        return typeof n === "string" && n.indexOf('%') != -1;
-    }
-
-    // Force a hex value to have 2 characters
-    function pad2(c) {
-        return c.length == 1 ? '0' + c : '' + c;
-    }
-
-    // Replace a decimal with it's percentage value
-    function convertToPercentage(n) {
-        if (n <= 1) {
-            n = (n * 100) + "%";
-        }
-
-        return n;
-    }
-
-    // Converts a decimal to a hex value
-    function convertDecimalToHex(d) {
-        return Math.round(parseFloat(d) * 255).toString(16);
-    }
-    // Converts a hex value to a decimal
-    function convertHexToDecimal(h) {
-        return (parseIntFromHex(h) / 255);
-    }
-
-    var matchers = (function() {
-
-        // <http://www.w3.org/TR/css3-values/#integers>
-        var CSS_INTEGER = "[-\\+]?\\d+%?";
-
-        // <http://www.w3.org/TR/css3-values/#number-value>
-        var CSS_NUMBER = "[-\\+]?\\d*\\.\\d+%?";
-
-        // Allow positive/negative integer/number.  Don't capture the either/or, just the entire outcome.
-        var CSS_UNIT = "(?:" + CSS_NUMBER + ")|(?:" + CSS_INTEGER + ")";
-
-        // Actual matching.
-        // Parentheses and commas are optional, but not required.
-        // Whitespace can take the place of commas or opening paren
-        var PERMISSIVE_MATCH3 = "[\\s|\\(]+(" + CSS_UNIT + ")[,|\\s]+(" + CSS_UNIT + ")[,|\\s]+(" + CSS_UNIT + ")\\s*\\)?";
-        var PERMISSIVE_MATCH4 = "[\\s|\\(]+(" + CSS_UNIT + ")[,|\\s]+(" + CSS_UNIT + ")[,|\\s]+(" + CSS_UNIT + ")[,|\\s]+(" + CSS_UNIT + ")\\s*\\)?";
-
-        return {
-            rgb: new RegExp("rgb" + PERMISSIVE_MATCH3),
-            rgba: new RegExp("rgba" + PERMISSIVE_MATCH4),
-            hsl: new RegExp("hsl" + PERMISSIVE_MATCH3),
-            hsla: new RegExp("hsla" + PERMISSIVE_MATCH4),
-            hsv: new RegExp("hsv" + PERMISSIVE_MATCH3),
-            hsva: new RegExp("hsva" + PERMISSIVE_MATCH4),
-            hex3: /^([0-9a-fA-F]{1})([0-9a-fA-F]{1})([0-9a-fA-F]{1})$/,
-            hex6: /^([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})$/,
-            hex8: /^([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})$/
-        };
-    })();
-
-    // `stringInputToObject`
-    // Permissive string parsing.  Take in a number of formats, and output an object
-    // based on detected format.  Returns `{ r, g, b }` or `{ h, s, l }` or `{ h, s, v}`
-    function stringInputToObject(color) {
-
-        color = color.replace(trimLeft,'').replace(trimRight, '').toLowerCase();
-        var named = false;
-        if (names[color]) {
-            color = names[color];
-            named = true;
-        }
-        else if (color == 'transparent') {
-            return { r: 0, g: 0, b: 0, a: 0, format: "name" };
-        }
-
-        // Try to match string input using regular expressions.
-        // Keep most of the number bounding out of this function - don't worry about [0,1] or [0,100] or [0,360]
-        // Just return an object and let the conversion functions handle that.
-        // This way the result will be the same whether the tinycolor is initialized with string or object.
-        var match;
-        if ((match = matchers.rgb.exec(color))) {
-            return { r: match[1], g: match[2], b: match[3] };
-        }
-        if ((match = matchers.rgba.exec(color))) {
-            return { r: match[1], g: match[2], b: match[3], a: match[4] };
-        }
-        if ((match = matchers.hsl.exec(color))) {
-            return { h: match[1], s: match[2], l: match[3] };
-        }
-        if ((match = matchers.hsla.exec(color))) {
-            return { h: match[1], s: match[2], l: match[3], a: match[4] };
-        }
-        if ((match = matchers.hsv.exec(color))) {
-            return { h: match[1], s: match[2], v: match[3] };
-        }
-        if ((match = matchers.hsva.exec(color))) {
-            return { h: match[1], s: match[2], v: match[3], a: match[4] };
-        }
-        if ((match = matchers.hex8.exec(color))) {
-            return {
-                a: convertHexToDecimal(match[1]),
-                r: parseIntFromHex(match[2]),
-                g: parseIntFromHex(match[3]),
-                b: parseIntFromHex(match[4]),
-                format: named ? "name" : "hex8"
-            };
-        }
-        if ((match = matchers.hex6.exec(color))) {
-            return {
-                r: parseIntFromHex(match[1]),
-                g: parseIntFromHex(match[2]),
-                b: parseIntFromHex(match[3]),
-                format: named ? "name" : "hex"
-            };
-        }
-        if ((match = matchers.hex3.exec(color))) {
-            return {
-                r: parseIntFromHex(match[1] + '' + match[1]),
-                g: parseIntFromHex(match[2] + '' + match[2]),
-                b: parseIntFromHex(match[3] + '' + match[3]),
-                format: named ? "name" : "hex"
-            };
-        }
-
-        return false;
-    }
-
-    window.tinycolor = tinycolor;
-    })();
-
-    $(function () {
-        if ($.fn.spectrum.load) {
-            $.fn.spectrum.processNativeColorInputs();
-        }
-    });
-
-});

+ 1 - 0
public/app/core/angular_wrappers.ts

@@ -28,6 +28,7 @@ export function registerAngularDirectives() {
     ['onChange', { watchDepth: 'reference', wrapApply: true }],
   ]);
   react2AngularDirective('seriesColorPickerPopover', SeriesColorPickerPopover, [
+    'color',
     'series',
     'onColorChange',
     'onToggleAxis',

+ 0 - 5
public/app/core/services/context_srv.ts

@@ -2,7 +2,6 @@ import config from 'app/core/config';
 import _ from 'lodash';
 import coreModule from 'app/core/core_module';
 import store from 'app/core/store';
-import { ThemeNames, ThemeName } from '@grafana/ui';
 
 export class User {
   isGrafanaAdmin: any;
@@ -64,10 +63,6 @@ export class ContextSrv {
   hasAccessToExplore() {
     return (this.isEditor || config.viewersCanEdit) && config.exploreEnabled;
   }
-
-  getTheme(): ThemeName {
-    return this.user.lightTheme ? ThemeNames.Light : ThemeNames.Dark;
-  }
 }
 
 const contextSrv = new ContextSrv();

+ 1 - 1
public/app/core/time_series2.ts

@@ -356,7 +356,7 @@ export default class TimeSeries {
     return false;
   }
 
-  setColor(color) {
+  setColor(color: string) {
     this.color = color;
     this.bars.fillColor = color;
   }

+ 28 - 0
public/app/core/utils/ConfigProvider.tsx

@@ -0,0 +1,28 @@
+import React from 'react';
+import config, { Settings } from 'app/core/config';
+import { GrafanaTheme } from '@grafana/ui';
+
+export const ConfigContext = React.createContext<Settings>(config);
+export const ConfigConsumer = ConfigContext.Consumer;
+
+export const provideConfig = (component: React.ComponentType<any>) => {
+  const ConfigProvider = (props: any) => (
+    <ConfigContext.Provider value={config}>{React.createElement(component, { ...props })}</ConfigContext.Provider>
+  );
+
+  return ConfigProvider;
+};
+
+interface ThemeProviderProps {
+  children: (theme: GrafanaTheme) => JSX.Element;
+}
+
+export const ThemeProvider = ({ children }: ThemeProviderProps) => {
+  return (
+    <ConfigConsumer>
+      {({ bootData }) => {
+        return children(bootData.user.lightTheme ? GrafanaTheme.Light : GrafanaTheme.Dark);
+      }}
+    </ConfigConsumer>
+  );
+};

+ 2 - 1
public/app/core/utils/react2angular.ts

@@ -1,10 +1,11 @@
 import coreModule from 'app/core/core_module';
+import { provideConfig } from 'app/core/utils/ConfigProvider';
 
 export function react2AngularDirective(name: string, component: any, options: any) {
   coreModule.directive(name, [
     'reactDirective',
     reactDirective => {
-      return reactDirective(component, options);
+      return reactDirective(provideConfig(component), options);
     },
   ]);
 }

+ 1 - 1
public/app/features/dashboard/dashgrid/DataPanel.tsx

@@ -1,7 +1,7 @@
 // Library
 import React, { Component } from 'react';
 import { Tooltip } from '@grafana/ui';
-import { Themes } from '@grafana/ui/src/components/Tooltip/Popper';
+import { Themes } from '@grafana/ui/src/components/Tooltip/Tooltip';
 
 import ErrorBoundary from 'app/core/components/ErrorBoundary/ErrorBoundary';
 

+ 1 - 1
public/app/features/dashboard/dashgrid/PanelHeader/PanelHeaderCorner.tsx

@@ -77,7 +77,7 @@ export class PanelHeaderCorner extends Component<Props> {
       <>
         {infoMode === InfoModes.Info || infoMode === InfoModes.Links ? (
           <Tooltip
-            content={this.getInfoContent}
+            content={this.getInfoContent()}
             placement="bottom-start"
           >
             <div

+ 1 - 1
public/app/features/dashboard/panel_editor/PanelEditor.tsx

@@ -16,7 +16,7 @@ import { DashboardModel } from '../dashboard_model';
 import { PanelPlugin } from 'app/types/plugins';
 
 import { Tooltip } from '@grafana/ui';
-import { Themes } from '@grafana/ui/src/components/Tooltip/Popper';
+import { Themes } from '@grafana/ui/src/components/Tooltip/Tooltip';
 
 interface PanelEditorProps {
   panel: PanelModel;

+ 1 - 1
public/app/features/dashboard/panel_editor/QueryOptions.tsx

@@ -5,7 +5,7 @@ import React, { PureComponent } from 'react';
 import { isValidTimeSpan } from 'app/core/utils/rangeutil';
 
 // Components
-import { Switch } from 'app/core/components/Switch/Switch';
+import { Switch } from '@grafana/ui';
 import { Input } from 'app/core/components/Form';
 import { EventsWithValidation } from 'app/core/components/Form/Input';
 import { InputStatus } from 'app/core/components/Form/Input';

+ 1 - 1
public/app/features/dashboard/permissions/DashboardPermissions.tsx

@@ -70,7 +70,7 @@ export class DashboardPermissions extends PureComponent<Props, State> {
         <div className="dashboard-settings__header">
           <div className="page-action-bar">
             <h3 className="d-inline-block">Permissions</h3>
-            <Tooltip placement="auto" content={PermissionsInfo}>
+            <Tooltip placement="auto" content={<PermissionsInfo />}>
               <div className="page-sub-heading-icon">
                 <i className="gicon gicon-question gicon--has-hover" />
               </div>

+ 3 - 2
public/app/features/datasources/settings/BasicSettings.tsx

@@ -1,6 +1,5 @@
 import React, { FC } from 'react';
-import { FormLabel } from '@grafana/ui';
-import { Switch } from '../../../core/components/Switch/Switch';
+import { FormLabel, Switch } from '@grafana/ui';
 
 export interface Props {
   dataSourceName: string;
@@ -31,6 +30,8 @@ const BasicSettings: FC<Props> = ({ dataSourceName, isDefault, onDefaultChange,
             required
           />
         </div>
+        {/*
+        //@ts-ignore */}
         <Switch label="Default" checked={isDefault} onChange={event => onDefaultChange(event.target.checked)} />
       </div>
     </div>

+ 1 - 2
public/app/features/explore/Logs.tsx

@@ -4,7 +4,7 @@ import Highlighter from 'react-highlight-words';
 import classnames from 'classnames';
 
 import * as rangeUtil from 'app/core/utils/rangeutil';
-import { RawTimeRange } from '@grafana/ui';
+import { RawTimeRange, Switch } from '@grafana/ui';
 import {
   LogsDedupDescription,
   LogsDedupStrategy,
@@ -20,7 +20,6 @@ import {
   calculateFieldStats,
 } from 'app/core/logs_model';
 import { findHighlightChunksInText } from 'app/core/utils/text';
-import { Switch } from 'app/core/components/Switch/Switch';
 import ToggleButtonGroup, { ToggleButton } from 'app/core/components/ToggleButtonGroup/ToggleButtonGroup';
 
 import Graph from './Graph';

+ 1 - 1
public/app/features/folders/FolderPermissions.tsx

@@ -84,7 +84,7 @@ export class FolderPermissions extends PureComponent<Props, State> {
         <div className="page-container page-body">
           <div className="page-action-bar">
             <h3 className="page-sub-heading">Folder Permissions</h3>
-            <Tooltip placement="auto" content={PermissionsInfo}>
+            <Tooltip placement="auto" content={<PermissionsInfo />}>
               <div className="page-sub-heading-icon">
                 <i className="gicon gicon-question gicon--has-hover" />
               </div>

+ 1 - 2
public/app/plugins/panel/gauge/GaugeOptionsEditor.tsx

@@ -1,7 +1,6 @@
 import React, { PureComponent } from 'react';
-import { FormField, PanelOptionsProps, PanelOptionsGroup } from '@grafana/ui';
+import { FormField, PanelOptionsProps, PanelOptionsGroup, Switch } from '@grafana/ui';
 
-import { Switch } from 'app/core/components/Switch/Switch';
 import { GaugeOptions } from './types';
 
 export default class GaugeOptionsEditor extends PureComponent<PanelOptionsProps<GaugeOptions>> {

+ 14 - 11
public/app/plugins/panel/gauge/GaugePanel.tsx

@@ -2,7 +2,6 @@
 import React, { PureComponent } from 'react';
 
 // Services & Utils
-import { contextSrv } from 'app/core/core';
 import { processTimeSeries } from '@grafana/ui';
 
 // Components
@@ -11,11 +10,11 @@ import { Gauge } from '@grafana/ui';
 // Types
 import { GaugeOptions } from './types';
 import { PanelProps, NullValueMode } from '@grafana/ui/src/types';
+import { ThemeProvider } from 'app/core/utils/ConfigProvider';
 
 interface Props extends PanelProps<GaugeOptions> {}
 
 export class GaugePanel extends PureComponent<Props> {
-
   render() {
     const { timeSeries, width, height, onInterpolate, options } = this.props;
 
@@ -28,15 +27,19 @@ export class GaugePanel extends PureComponent<Props> {
     });
 
     return (
-      <Gauge
-        timeSeries={vmSeries}
-        {...this.props.options}
-        width={width}
-        height={height}
-        prefix={prefix}
-        suffix={suffix}
-        theme={contextSrv.getTheme()}
-      />
+      <ThemeProvider>
+        {(theme) => (
+          <Gauge
+            timeSeries={vmSeries}
+            {...this.props.options}
+            width={width}
+            height={height}
+            prefix={prefix}
+            suffix={suffix}
+            theme={theme}
+          />
+        )}
+      </ThemeProvider>
     );
   }
 }

+ 17 - 8
public/app/plugins/panel/gauge/GaugePanelOptions.tsx

@@ -11,6 +11,7 @@ import {
 import ValueOptions from 'app/plugins/panel/gauge/ValueOptions';
 import GaugeOptionsEditor from './GaugeOptionsEditor';
 import { GaugeOptions } from './types';
+import { ThemeProvider } from 'app/core/utils/ConfigProvider';
 
 export const defaultProps = {
   options: {
@@ -46,15 +47,23 @@ export default class GaugePanelOptions extends PureComponent<PanelOptionsProps<G
   render() {
     const { onChange, options } = this.props;
     return (
-      <>
-        <PanelOptionsGrid>
-          <ValueOptions onChange={onChange} options={options} />
-          <GaugeOptionsEditor onChange={onChange} options={options} />
-          <ThresholdsEditor onChange={this.onThresholdsChanged} thresholds={options.thresholds} />
-        </PanelOptionsGrid>
+      <ThemeProvider>
+        {(theme) => (
+          <>
+            <PanelOptionsGrid>
+              <ValueOptions onChange={onChange} options={options} />
+              <GaugeOptionsEditor onChange={onChange} options={options} />
+              <ThresholdsEditor
+                onChange={this.onThresholdsChanged}
+                thresholds={options.thresholds}
+                theme={theme}
+              />
+            </PanelOptionsGrid>
 
-        <ValueMappingsEditor onChange={this.onValueMappingsChanged} valueMappings={options.valueMappings} />
-      </>
+            <ValueMappingsEditor onChange={this.onValueMappingsChanged} valueMappings={options.valueMappings} />
+          </>
+        )}
+      </ThemeProvider>
     );
   }
 }

+ 1 - 1
public/app/plugins/panel/graph/Legend/Legend.tsx

@@ -311,7 +311,7 @@ class LegendTableHeaderItem extends PureComponent<LegendTableHeaderProps & Legen
 export class Legend extends PureComponent<GraphLegendProps> {
   render() {
     return (
-      <CustomScrollbar>
+      <CustomScrollbar renderTrackHorizontal={(props) => <div {...props} style={{visibility: 'none'}} />}>
         <GraphLegend {...this.props} />
       </CustomScrollbar>
     );

+ 21 - 11
public/app/plugins/panel/graph/Legend/LegendSeriesItem.tsx

@@ -2,6 +2,7 @@ import React, { PureComponent } from 'react';
 import classNames from 'classnames';
 import { TimeSeries } from 'app/core/core';
 import { SeriesColorPicker } from '@grafana/ui';
+import { ThemeProvider } from 'app/core/utils/ConfigProvider';
 
 export const LEGEND_STATS = ['min', 'max', 'avg', 'current', 'total'];
 
@@ -154,8 +155,8 @@ interface LegendSeriesIconState {
   color: string;
 }
 
-function SeriesIcon(props) {
-  return <i className="fa fa-minus pointer" style={{ color: props.color }} />;
+function SeriesIcon({ color }) {
+  return <i className="fa fa-minus pointer" style={{ color }} />;
 }
 
 class LegendSeriesIcon extends PureComponent<LegendSeriesIconProps, LegendSeriesIconState> {
@@ -167,15 +168,24 @@ class LegendSeriesIcon extends PureComponent<LegendSeriesIconProps, LegendSeries
 
   render() {
     return (
-      <SeriesColorPicker
-        optionalClass="graph-legend-icon"
-        yaxis={this.props.yaxis}
-        color={this.props.color}
-        onColorChange={this.props.onColorChange}
-        onToggleAxis={this.props.onToggleAxis}
-      >
-        <SeriesIcon color={this.props.color} />
-      </SeriesColorPicker>
+      <ThemeProvider>
+        {theme => {
+          return (
+            <SeriesColorPicker
+              yaxis={this.props.yaxis}
+              color={this.props.color}
+              onChange={this.props.onColorChange}
+              onToggleAxis={this.props.onToggleAxis}
+              theme={theme}
+              enableNamedColors
+            >
+              <span className="graph-legend-icon">
+                <SeriesIcon color={this.props.color} />
+              </span>
+            </SeriesColorPicker>
+          );
+        }}
+      </ThemeProvider>
     );
   }
 }

+ 4 - 3
public/app/plugins/panel/graph/data_processor.ts

@@ -1,7 +1,7 @@
 import _ from 'lodash';
-import { colors } from '@grafana/ui';
-
+import { colors, GrafanaTheme, getColorFromHexRgbOrName } from '@grafana/ui';
 import TimeSeries from 'app/core/time_series2';
+import config from 'app/core/config';
 
 export class DataProcessor {
   constructor(private panel) {}
@@ -107,12 +107,13 @@ export class DataProcessor {
     const alias = seriesData.target;
 
     const colorIndex = index % colors.length;
+
     const color = this.panel.aliasColors[alias] || colors[colorIndex];
 
     const series = new TimeSeries({
       datapoints: datapoints,
       alias: alias,
-      color: color,
+      color: getColorFromHexRgbOrName(color, config.bootData.user.lightTheme ? GrafanaTheme.Light : GrafanaTheme.Dark),
       unit: seriesData.unit,
     });
 

+ 5 - 1
public/app/plugins/panel/graph/graph.ts

@@ -26,6 +26,7 @@ import ReactDOM from 'react-dom';
 import { Legend, GraphLegendProps } from './Legend/Legend';
 
 import { GraphCtrl } from './module';
+import { GrafanaTheme } from '@grafana/ui';
 
 class GraphElement {
   ctrl: GraphCtrl;
@@ -51,7 +52,10 @@ class GraphElement {
     this.panelWidth = 0;
     this.eventManager = new EventManager(this.ctrl);
     this.thresholdManager = new ThresholdManager(this.ctrl);
-    this.timeRegionManager = new TimeRegionManager(this.ctrl);
+    this.timeRegionManager = new TimeRegionManager(
+      this.ctrl,
+      config.bootData.user.lightTheme ? GrafanaTheme.Light : GrafanaTheme.Dark
+    );
     this.tooltip = new GraphTooltip(this.elem, this.ctrl.dashboard, this.scope, () => {
       return this.sortedSeries;
     });

+ 4 - 2
public/app/plugins/panel/graph/module.ts

@@ -9,6 +9,8 @@ import _ from 'lodash';
 import { MetricsPanelCtrl } from 'app/plugins/sdk';
 import { DataProcessor } from './data_processor';
 import { axesEditorComponent } from './axes_editor';
+import config from 'app/core/config';
+import { GrafanaTheme, getColorFromHexRgbOrName } from '@grafana/ui';
 
 class GraphCtrl extends MetricsPanelCtrl {
   static template = template;
@@ -242,8 +244,8 @@ class GraphCtrl extends MetricsPanelCtrl {
   }
 
   onColorChange = (series, color) => {
-    series.setColor(color);
-    this.panel.aliasColors[series.alias] = series.color;
+    series.setColor(getColorFromHexRgbOrName(color, config.bootData.user.lightTheme ? GrafanaTheme.Light : GrafanaTheme.Dark));
+    this.panel.aliasColors[series.alias] = color;
     this.render();
   };
 

+ 3 - 1
public/app/plugins/panel/graph/series_overrides_ctrl.ts

@@ -53,11 +53,13 @@ export function SeriesOverridesCtrl($scope, $element, popoverSrv) {
       element: $element.find('.dropdown')[0],
       position: 'top center',
       openOn: 'click',
-      template: '<series-color-picker-popover series="series" onColorChange="colorSelected" />',
+      template: '<series-color-picker-popover color="color" onColorChange="colorSelected" />',
+      classNames: 'drop-popover drop-popover--transparent',
       model: {
         autoClose: true,
         colorSelected: $scope.colorSelected,
         series: fakeSeries,
+        color,
       },
       onClose: () => {
         $scope.ctrl.render();

+ 12 - 4
public/app/plugins/panel/graph/threshold_manager.ts

@@ -1,6 +1,7 @@
 import 'vendor/flot/jquery.flot';
 import $ from 'jquery';
 import _ from 'lodash';
+import { getColorFromHexRgbOrName } from '@grafana/ui';
 
 export class ThresholdManager {
   plot: any;
@@ -197,6 +198,7 @@ export class ThresholdManager {
       }
 
       let fillColor, lineColor;
+
       switch (threshold.colorMode) {
         case 'critical': {
           fillColor = 'rgba(234, 112, 112, 0.12)';
@@ -214,6 +216,12 @@ export class ThresholdManager {
           break;
         }
         case 'custom': {
+          if (!threshold.fillColor) {
+            threshold.fillColor = 'rgba(255, 255, 255, 1)';
+          }
+          if (!threshold.lineColor) {
+            threshold.lineColor = 'rgba(255, 255, 255, 0)';
+          }
           fillColor = threshold.fillColor;
           lineColor = threshold.lineColor;
           break;
@@ -225,12 +233,12 @@ export class ThresholdManager {
         if (threshold.yaxis === 'right' && this.hasSecondYAxis) {
           options.grid.markings.push({
             y2axis: { from: threshold.value, to: limit },
-            color: fillColor,
+            color: getColorFromHexRgbOrName(fillColor),
           });
         } else {
           options.grid.markings.push({
             yaxis: { from: threshold.value, to: limit },
-            color: fillColor,
+            color: getColorFromHexRgbOrName(fillColor),
           });
         }
       }
@@ -238,12 +246,12 @@ export class ThresholdManager {
         if (threshold.yaxis === 'right' && this.hasSecondYAxis) {
           options.grid.markings.push({
             y2axis: { from: threshold.value, to: threshold.value },
-            color: lineColor,
+            color: getColorFromHexRgbOrName(lineColor),
           });
         } else {
           options.grid.markings.push({
             yaxis: { from: threshold.value, to: threshold.value },
-            color: lineColor,
+            color: getColorFromHexRgbOrName(lineColor),
           });
         }
       }

+ 18 - 9
public/app/plugins/panel/graph/time_region_manager.ts

@@ -1,7 +1,12 @@
 import 'vendor/flot/jquery.flot';
 import _ from 'lodash';
 import moment from 'moment';
-import config from 'app/core/config';
+import { GrafanaTheme, getColorFromHexRgbOrName } from '@grafana/ui';
+
+type TimeRegionColorDefinition = {
+  fill: string;
+  line: string;
+};
 
 export const colorModes = {
   gray: {
@@ -38,31 +43,35 @@ export function getColorModes() {
   });
 }
 
-function getColor(timeRegion) {
+function getColor(timeRegion, theme: GrafanaTheme): TimeRegionColorDefinition {
   if (Object.keys(colorModes).indexOf(timeRegion.colorMode) === -1) {
     timeRegion.colorMode = 'red';
   }
 
   if (timeRegion.colorMode === 'custom') {
     return {
-      fill: timeRegion.fillColor,
-      line: timeRegion.lineColor,
+      fill: getColorFromHexRgbOrName(timeRegion.fillColor, theme),
+      line: getColorFromHexRgbOrName(timeRegion.lineColor, theme),
     };
   }
 
   const colorMode = colorModes[timeRegion.colorMode];
+
   if (colorMode.themeDependent === true) {
-    return config.bootData.user.lightTheme ? colorMode.lightColor : colorMode.darkColor;
+    return theme === GrafanaTheme.Light ? colorMode.lightColor : colorMode.darkColor;
   }
 
-  return colorMode.color;
+  return {
+    fill: getColorFromHexRgbOrName(colorMode.color.fill, theme),
+    line: getColorFromHexRgbOrName(colorMode.color.line, theme),
+  };
 }
 
 export class TimeRegionManager {
   plot: any;
   timeRegions: any;
 
-  constructor(private panelCtrl) {}
+  constructor(private panelCtrl, private theme: GrafanaTheme = GrafanaTheme.Dark) {}
 
   draw(plot) {
     this.timeRegions = this.panelCtrl.panel.timeRegions;
@@ -76,7 +85,7 @@ export class TimeRegionManager {
 
     const tRange = { from: moment(this.panelCtrl.range.from).utc(), to: moment(this.panelCtrl.range.to).utc() };
 
-    let i, hRange, timeRegion, regions, fromStart, fromEnd, timeRegionColor;
+    let i, hRange, timeRegion, regions, fromStart, fromEnd, timeRegionColor: TimeRegionColorDefinition;
 
     const timeRegionsCopy = panel.timeRegions.map(a => ({ ...a }));
 
@@ -200,7 +209,7 @@ export class TimeRegionManager {
         }
       }
 
-      timeRegionColor = getColor(timeRegion);
+      timeRegionColor = getColor(timeRegion, this.theme);
 
       for (let j = 0; j < regions.length; j++) {
         const r = regions[j];

+ 3 - 0
public/app/plugins/panel/graph/time_regions_form.ts

@@ -35,6 +35,9 @@ export class TimeRegionFormCtrl {
       colorMode: 'background6',
       fill: true,
       line: false,
+      // Default colors for new
+      fillColor: 'rgba(234, 112, 112, 0.12)',
+      lineColor: 'rgba(237, 46, 24, 0.60)'
     });
     this.panelCtrl.render();
   }

+ 1 - 3
public/app/plugins/panel/graph2/GraphPanelOptions.tsx

@@ -2,11 +2,9 @@
 import _ from 'lodash';
 import React, { PureComponent } from 'react';
 
-// Components
-import { Switch } from 'app/core/components/Switch/Switch';
 
 // Types
-import { PanelOptionsProps } from '@grafana/ui';
+import { PanelOptionsProps, Switch } from '@grafana/ui';
 import { Options } from './types';
 
 export class GraphPanelOptions extends PureComponent<PanelOptionsProps<Options>> {

+ 5 - 1
public/app/plugins/panel/heatmap/color_legend.ts

@@ -5,6 +5,7 @@ import { contextSrv } from 'app/core/core';
 import { tickStep } from 'app/core/utils/ticks';
 import { getColorScale, getOpacityScale } from './color_scale';
 import coreModule from 'app/core/core_module';
+import { GrafanaTheme, getColorFromHexRgbOrName } from '@grafana/ui';
 
 const LEGEND_HEIGHT_PX = 6;
 const LEGEND_WIDTH_PX = 100;
@@ -247,7 +248,10 @@ function drawSimpleOpacityLegend(elem, options) {
       .attr('width', rangeStep)
       .attr('height', legendHeight)
       .attr('stroke-width', 0)
-      .attr('fill', options.cardColor)
+      .attr(
+        'fill',
+        getColorFromHexRgbOrName(options.cardColor, contextSrv.user.lightTheme ? GrafanaTheme.Light : GrafanaTheme.Dark)
+      )
       .style('opacity', d => legendOpacityScale(d));
   }
 }

+ 5 - 2
public/app/plugins/panel/heatmap/rendering.ts

@@ -8,6 +8,7 @@ import * as ticksUtils from 'app/core/utils/ticks';
 import { HeatmapTooltip } from './heatmap_tooltip';
 import { mergeZeroBuckets } from './heatmap_data_converter';
 import { getColorScale, getOpacityScale } from './color_scale';
+import { GrafanaTheme, getColorFromHexRgbOrName } from '@grafana/ui';
 
 const MIN_CARD_SIZE = 1,
   CARD_PADDING = 1,
@@ -521,7 +522,6 @@ export class HeatmapRenderer {
     const maxValueAuto = this.data.cardStats.max;
     const maxValue = this.panel.color.max || maxValueAuto;
     const minValue = this.panel.color.min || 0;
-
     const colorScheme = _.find(this.ctrl.colorSchemes, {
       value: this.panel.color.colorScheme,
     });
@@ -662,7 +662,10 @@ export class HeatmapRenderer {
 
   getCardColor(d) {
     if (this.panel.color.mode === 'opacity') {
-      return this.panel.color.cardColor;
+      return getColorFromHexRgbOrName(
+        this.panel.color.cardColor,
+        contextSrv.user.lightTheme ? GrafanaTheme.Light : GrafanaTheme.Dark
+      );
     } else {
       return this.colorScale(d.count);
     }

+ 17 - 3
public/app/plugins/panel/singlestat/module.ts

@@ -8,6 +8,8 @@ import kbn from 'app/core/utils/kbn';
 import config from 'app/core/config';
 import TimeSeries from 'app/core/time_series2';
 import { MetricsPanelCtrl } from 'app/plugins/sdk';
+import { getColorFromHexRgbOrName } from '@grafana/ui';
+import { GrafanaTheme } from '@grafana/ui';
 
 class SingleStatCtrl extends MetricsPanelCtrl {
   static templateUrl = 'module.html';
@@ -479,6 +481,7 @@ class SingleStatCtrl extends MetricsPanelCtrl {
       plotCanvas.css(plotCss);
 
       const thresholds = [];
+
       for (let i = 0; i < data.thresholds.length; i++) {
         thresholds.push({
           value: data.thresholds[i],
@@ -586,7 +589,10 @@ class SingleStatCtrl extends MetricsPanelCtrl {
             fill: 1,
             zero: false,
             lineWidth: 1,
-            fillColor: panel.sparkline.fillColor,
+            fillColor: getColorFromHexRgbOrName(
+              panel.sparkline.fillColor,
+              config.bootData.user.lightTheme ? GrafanaTheme.Light : GrafanaTheme.Dark
+            ),
           },
         },
         yaxes: { show: false },
@@ -603,7 +609,10 @@ class SingleStatCtrl extends MetricsPanelCtrl {
 
       const plotSeries = {
         data: data.flotpairs,
-        color: panel.sparkline.lineColor,
+        color: getColorFromHexRgbOrName(
+          panel.sparkline.lineColor,
+          config.bootData.user.lightTheme ? GrafanaTheme.Light : GrafanaTheme.Dark
+        ),
       };
 
       $.plot(plotCanvas, [plotSeries], options);
@@ -619,12 +628,17 @@ class SingleStatCtrl extends MetricsPanelCtrl {
       data.thresholds = panel.thresholds.split(',').map(strVale => {
         return Number(strVale.trim());
       });
-      data.colorMap = panel.colors;
+
+      // Map panel colors to hex or rgb/a values
+      data.colorMap = panel.colors.map(color =>
+        getColorFromHexRgbOrName(color, config.bootData.user.lightTheme ? GrafanaTheme.Light : GrafanaTheme.Dark)
+      );
 
       const body = panel.gauge.show ? '' : getBigValueHtml();
 
       if (panel.colorBackground) {
         const color = getColorForValue(data, data.value);
+        console.log(color);
         if (color) {
           $panelContainer.css('background-color', color);
           if (scope.fullscreen) {

+ 4 - 1
public/app/plugins/panel/table/module.ts

@@ -1,10 +1,12 @@
 import _ from 'lodash';
 import $ from 'jquery';
 import { MetricsPanelCtrl } from 'app/plugins/sdk';
+import config from 'app/core/config';
 import { transformDataToTable } from './transformers';
 import { tablePanelEditor } from './editor';
 import { columnOptionsTab } from './column_options';
 import { TableRenderer } from './renderer';
+import { GrafanaTheme } from '@grafana/ui';
 
 class TablePanelCtrl extends MetricsPanelCtrl {
   static templateUrl = 'module.html';
@@ -129,7 +131,8 @@ class TablePanelCtrl extends MetricsPanelCtrl {
       this.table,
       this.dashboard.isTimezoneUtc(),
       this.$sanitize,
-      this.templateSrv
+      this.templateSrv,
+      config.bootData.user.lightTheme ? GrafanaTheme.Light : GrafanaTheme.Dark,
     );
 
     return super.render(this.table);

+ 12 - 3
public/app/plugins/panel/table/renderer.ts

@@ -1,12 +1,21 @@
 import _ from 'lodash';
 import moment from 'moment';
 import kbn from 'app/core/utils/kbn';
+import { getColorFromHexRgbOrName } from '@grafana/ui';
+import { GrafanaTheme } from '@grafana/ui';
 
 export class TableRenderer {
   formatters: any[];
   colorState: any;
 
-  constructor(private panel, private table, private isUtc, private sanitize, private templateSrv) {
+  constructor(
+    private panel,
+    private table,
+    private isUtc,
+    private sanitize,
+    private templateSrv,
+    private theme?: GrafanaTheme
+  ) {
     this.initColumns();
   }
 
@@ -49,10 +58,10 @@ export class TableRenderer {
     }
     for (let i = style.thresholds.length; i > 0; i--) {
       if (value >= style.thresholds[i - 1]) {
-        return style.colors[i];
+        return getColorFromHexRgbOrName(style.colors[i], this.theme);
       }
     }
-    return _.first(style.colors);
+    return getColorFromHexRgbOrName(_.first(style.colors), this.theme);
   }
 
   defaultCellFormatter(v, style) {

+ 21 - 18
public/app/plugins/panel/table/specs/renderer.test.ts

@@ -1,8 +1,11 @@
 import _ from 'lodash';
 import TableModel from 'app/core/table_model';
 import { TableRenderer } from '../renderer';
+import { getColorDefinitionByName } from '@grafana/ui';
 
 describe('when rendering table', () => {
+  const SemiDarkOrange = getColorDefinitionByName('semi-dark-orange');
+
   describe('given 13 columns', () => {
     const table = new TableModel();
     table.columns = [
@@ -47,7 +50,7 @@ describe('when rendering table', () => {
           decimals: 1,
           colorMode: 'value',
           thresholds: [50, 80],
-          colors: ['green', 'orange', 'red'],
+          colors: ['#00ff00', SemiDarkOrange.name, 'rgb(1,0,0)'],
         },
         {
           pattern: 'String',
@@ -138,7 +141,7 @@ describe('when rendering table', () => {
           ],
           colorMode: 'value',
           thresholds: [1, 2],
-          colors: ['green', 'orange', 'red'],
+          colors: ['#00ff00', SemiDarkOrange.name, 'rgb(1,0,0)'],
         },
         {
           pattern: 'RangeMappingColored',
@@ -158,7 +161,7 @@ describe('when rendering table', () => {
           ],
           colorMode: 'value',
           thresholds: [2, 5],
-          colors: ['green', 'orange', 'red'],
+          colors: ['#00ff00', SemiDarkOrange.name, 'rgb(1,0,0)'],
         },
       ],
     };
@@ -226,19 +229,19 @@ describe('when rendering table', () => {
       expect(html).toBe('<td>asd</td>');
     });
 
-    it('colored cell should have style', () => {
+    it('colored cell should have style (handles HEX color values)', () => {
       const html = renderer.renderCell(2, 0, 40);
-      expect(html).toBe('<td style="color:green">40.0</td>');
+      expect(html).toBe('<td style="color:#00ff00">40.0</td>');
     });
 
-    it('colored cell should have style', () => {
+    it('colored cell should have style (handles named color values', () => {
       const html = renderer.renderCell(2, 0, 55);
-      expect(html).toBe('<td style="color:orange">55.0</td>');
+      expect(html).toBe(`<td style="color:${SemiDarkOrange.variants.dark}">55.0</td>`);
     });
 
-    it('colored cell should have style', () => {
+    it('colored cell should have style handles(rgb color values)', () => {
       const html = renderer.renderCell(2, 0, 85);
-      expect(html).toBe('<td style="color:red">85.0</td>');
+      expect(html).toBe('<td style="color:rgb(1,0,0)">85.0</td>');
     });
 
     it('unformated undefined should be rendered as string', () => {
@@ -333,47 +336,47 @@ describe('when rendering table', () => {
 
     it('value should be mapped to text and colored cell should have style', () => {
       const html = renderer.renderCell(11, 0, 1);
-      expect(html).toBe('<td style="color:orange">on</td>');
+      expect(html).toBe(`<td style="color:${SemiDarkOrange.variants.dark}">on</td>`);
     });
 
     it('value should be mapped to text and colored cell should have style', () => {
       const html = renderer.renderCell(11, 0, '1');
-      expect(html).toBe('<td style="color:orange">on</td>');
+      expect(html).toBe(`<td style="color:${SemiDarkOrange.variants.dark}">on</td>`);
     });
 
     it('value should be mapped to text and colored cell should have style', () => {
       const html = renderer.renderCell(11, 0, 0);
-      expect(html).toBe('<td style="color:green">off</td>');
+      expect(html).toBe('<td style="color:#00ff00">off</td>');
     });
 
     it('value should be mapped to text and colored cell should have style', () => {
       const html = renderer.renderCell(11, 0, '0');
-      expect(html).toBe('<td style="color:green">off</td>');
+      expect(html).toBe('<td style="color:#00ff00">off</td>');
     });
 
     it('value should be mapped to text and colored cell should have style', () => {
       const html = renderer.renderCell(11, 0, '2.1');
-      expect(html).toBe('<td style="color:red">2.1</td>');
+      expect(html).toBe('<td style="color:rgb(1,0,0)">2.1</td>');
     });
 
     it('value should be mapped to text (range) and colored cell should have style', () => {
       const html = renderer.renderCell(12, 0, 0);
-      expect(html).toBe('<td style="color:green">0</td>');
+      expect(html).toBe('<td style="color:#00ff00">0</td>');
     });
 
     it('value should be mapped to text (range) and colored cell should have style', () => {
       const html = renderer.renderCell(12, 0, 1);
-      expect(html).toBe('<td style="color:green">on</td>');
+      expect(html).toBe('<td style="color:#00ff00">on</td>');
     });
 
     it('value should be mapped to text (range) and colored cell should have style', () => {
       const html = renderer.renderCell(12, 0, 4);
-      expect(html).toBe('<td style="color:orange">off</td>');
+      expect(html).toBe(`<td style="color:${SemiDarkOrange.variants.dark}">off</td>`);
     });
 
     it('value should be mapped to text (range) and colored cell should have style', () => {
       const html = renderer.renderCell(12, 0, '7.1');
-      expect(html).toBe('<td style="color:red">7.1</td>');
+      expect(html).toBe('<td style="color:rgb(1,0,0)">7.1</td>');
     });
   });
 });

+ 145 - 8
yarn.lock

@@ -1031,6 +1031,11 @@
   resolved "https://registry.yarnpkg.com/@emotion/weak-memoize/-/weak-memoize-0.1.3.tgz#b700d97385fa91affed60c71dfd51c67e9dad762"
   integrity sha512-QsYGKdhhuDFNq7bjm2r44y0mp5xW3uO3csuTPDWZc0OIiMQv+AIY5Cqwd4mJiC5N8estVl7qlvOx1hbtOuUWbw==
 
+"@icons/material@^0.2.4":
+  version "0.2.4"
+  resolved "https://registry.yarnpkg.com/@icons/material/-/material-0.2.4.tgz#e90c9f71768b3736e76d7dd6783fc6c2afa88bc8"
+  integrity sha512-QPcGmICAPbGLGb6F/yNf/KzKqvFx8z5qx3D1yFqVAjoFmXK35EgyW+cJ57Te3CNsmzblwtzakLGFqHPqrfb4Tw==
+
 "@mrmlnc/readdir-enhanced@^2.2.1":
   version "2.2.1"
   resolved "https://registry.yarnpkg.com/@mrmlnc/readdir-enhanced/-/readdir-enhanced-2.2.1.tgz#524af240d1a360527b730475ecfa1344aa540dde"
@@ -1068,6 +1073,26 @@
   resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-0.7.0.tgz#9a06f4f137ee84d7df0460c1fdb1135ffa6c50fd"
   integrity sha512-ONhaKPIufzzrlNbqtWFFd+jlnemX6lJAgq9ZeiZtS7I1PIf/la7CW4m83rTXRnVnsMbW2k56pGYu7AUFJD9Pow==
 
+"@storybook/addon-actions@^4.1.7":
+  version "4.1.7"
+  resolved "https://registry.yarnpkg.com/@storybook/addon-actions/-/addon-actions-4.1.7.tgz#7c2625ea45b0c51d907d97d24d8dc56a701967b9"
+  integrity sha512-xhZCkehXZl3FM/2Dw1YOla0KcXyDgPVJbv6fRt0ebbmzaVbeB+cdknZ10SImPbKUWLCdBEKKIAR/CNF29hAH5A==
+  dependencies:
+    "@emotion/core" "^0.13.1"
+    "@emotion/provider" "^0.11.2"
+    "@emotion/styled" "^0.10.6"
+    "@storybook/addons" "4.1.7"
+    "@storybook/components" "4.1.7"
+    "@storybook/core-events" "4.1.7"
+    core-js "^2.5.7"
+    deep-equal "^1.0.1"
+    global "^4.3.2"
+    lodash "^4.17.11"
+    make-error "^1.3.5"
+    prop-types "^15.6.2"
+    react-inspector "^2.3.0"
+    uuid "^3.3.2"
+
 "@storybook/addon-info@^4.1.6":
   version "4.1.6"
   resolved "https://registry.yarnpkg.com/@storybook/addon-info/-/addon-info-4.1.6.tgz#48479487cd13d674807b77dbd42500cd770c954f"
@@ -1085,6 +1110,26 @@
     react-lifecycles-compat "^3.0.4"
     util-deprecate "^1.0.2"
 
+"@storybook/addon-knobs@^4.1.7":
+  version "4.1.7"
+  resolved "https://registry.yarnpkg.com/@storybook/addon-knobs/-/addon-knobs-4.1.7.tgz#298a1bd17f177e4653b073c9bca70c073746daea"
+  integrity sha512-h2YSyZvz+KQIggAKttdTZvRpsCVQUJtyY9mzVHEFDKTccAlC9WjZPD9UDSHYQZKm+0gHtFkxOidFTcjt/5r+8A==
+  dependencies:
+    "@emotion/styled" "^0.10.6"
+    "@storybook/addons" "4.1.7"
+    "@storybook/components" "4.1.7"
+    "@storybook/core-events" "4.1.7"
+    copy-to-clipboard "^3.0.8"
+    core-js "^2.5.7"
+    escape-html "^1.0.3"
+    fast-deep-equal "^2.0.1"
+    global "^4.3.2"
+    prop-types "^15.6.2"
+    qs "^6.5.2"
+    react-color "^2.14.1"
+    react-lifecycles-compat "^3.0.4"
+    util-deprecate "^1.0.2"
+
 "@storybook/addons@4.1.6":
   version "4.1.6"
   resolved "https://registry.yarnpkg.com/@storybook/addons/-/addons-4.1.6.tgz#458c4c6baf8b2acaffb9ced705a8db224bd874b6"
@@ -1095,6 +1140,16 @@
     global "^4.3.2"
     util-deprecate "^1.0.2"
 
+"@storybook/addons@4.1.7":
+  version "4.1.7"
+  resolved "https://registry.yarnpkg.com/@storybook/addons/-/addons-4.1.7.tgz#59ca76fff6594ff196bc58135689d1597cb90a1b"
+  integrity sha512-psKz/uMlImHkuUqdYaEq84Kyh3VIcSo00yHsnpWeq0xv3v6ONQKrFZ0bdmuZT30XQC0sYvXaP+YOKRq/hdgWaQ==
+  dependencies:
+    "@storybook/channels" "4.1.7"
+    "@storybook/components" "4.1.7"
+    global "^4.3.2"
+    util-deprecate "^1.0.2"
+
 "@storybook/channel-postmessage@4.1.6":
   version "4.1.6"
   resolved "https://registry.yarnpkg.com/@storybook/channel-postmessage/-/channel-postmessage-4.1.6.tgz#ff7af1c0ee1ab72ee8f10ec2e29cd413c39fc14d"
@@ -1109,6 +1164,11 @@
   resolved "https://registry.yarnpkg.com/@storybook/channels/-/channels-4.1.6.tgz#52b56f5c94f58442aba2d9c5919bb00ff33475c5"
   integrity sha512-8MqGYypdaPmZR7eORXdxtJijGOz5UMHXoMskVtodvKi26tmltFKX+okXFNh/teKe3+8s0QWkpzM95VI+Z+0qFA==
 
+"@storybook/channels@4.1.7":
+  version "4.1.7"
+  resolved "https://registry.yarnpkg.com/@storybook/channels/-/channels-4.1.7.tgz#bf3412859cca8cd3d221afcd68de348645dbb329"
+  integrity sha512-UVLrCcQ8f52PQYdWh97XcWBdqiXKBznePbd4L36//C6cIJkfwQ5nK9UDzzrCP/tlCnolVxnqRlorns4YeJcrSg==
+
 "@storybook/client-logger@4.1.6":
   version "4.1.6"
   resolved "https://registry.yarnpkg.com/@storybook/client-logger/-/client-logger-4.1.6.tgz#3409c20467abe4f98751db9a8d6769c21d0c80e5"
@@ -1130,11 +1190,32 @@
     react-textarea-autosize "^7.0.4"
     render-fragment "^0.1.1"
 
+"@storybook/components@4.1.7":
+  version "4.1.7"
+  resolved "https://registry.yarnpkg.com/@storybook/components/-/components-4.1.7.tgz#5e7f8247aebe0b79246c19f580e520cc59a39a1d"
+  integrity sha512-PgEREFw58tlyzzGbA7q1WlXgOLk6Dd+qkHZYJ+4JlTAl6kPdjirfRshg0uyvTVy5jGbtDa5mhN7Fo1TtuYiyDw==
+  dependencies:
+    "@emotion/core" "^0.13.1"
+    "@emotion/provider" "^0.11.2"
+    "@emotion/styled" "^0.10.6"
+    global "^4.3.2"
+    lodash "^4.17.11"
+    prop-types "^15.6.2"
+    react-inspector "^2.3.0"
+    react-split-pane "^0.1.84"
+    react-textarea-autosize "^7.0.4"
+    render-fragment "^0.1.1"
+
 "@storybook/core-events@4.1.6":
   version "4.1.6"
   resolved "https://registry.yarnpkg.com/@storybook/core-events/-/core-events-4.1.6.tgz#6c147b171a04cee3ed66990ebdf45be45dc658ba"
   integrity sha512-07ki5+VuruWQv7B1ZBlsNYEVSC3dQwIZKjEFL4aKFO57ruaNijkZTF1QHkSGJapyBPa7+LLM2fXqnBkputoEZw==
 
+"@storybook/core-events@4.1.7":
+  version "4.1.7"
+  resolved "https://registry.yarnpkg.com/@storybook/core-events/-/core-events-4.1.7.tgz#15803967783231dcb96432ad45b360d5e95188df"
+  integrity sha512-IpggH1Br51UAlLaAhNr6qY0IVT5mN1akfeVi1DpCj2ji7zqdfeDO3zmX6aWSjYVefg6OvNol71WKh+wjcYNvXg==
+
 "@storybook/core@4.1.6":
   version "4.1.6"
   resolved "https://registry.yarnpkg.com/@storybook/core/-/core-4.1.6.tgz#818124a8d47c9432637e000e509b01d051b9603e"
@@ -1698,6 +1779,13 @@
   resolved "https://registry.yarnpkg.com/@types/q/-/q-1.5.1.tgz#48fd98c1561fe718b61733daed46ff115b496e18"
   integrity sha512-eqz8c/0kwNi/OEHQfvIuJVLTst3in0e7uTKeuY+WL/zfKn0xVujOTp42bS/vUUokhK5P2BppLd9JXMOMHcgbjA==
 
+"@types/react-color@^2.14.0":
+  version "2.14.0"
+  resolved "https://registry.yarnpkg.com/@types/react-color/-/react-color-2.14.0.tgz#e01f6069902819fe9f6544375829a485f894206f"
+  integrity sha512-5UfGjUsu7bXop7K064nNrIGgS7wPjGEY7Or9tAE6BLslDtfhRnAlqWo9N2BIntEZ/3KpXlRnt0MBuc+hZJTevw==
+  dependencies:
+    "@types/react" "*"
+
 "@types/react-custom-scrollbars@^4.0.5":
   version "4.0.5"
   resolved "https://registry.yarnpkg.com/@types/react-custom-scrollbars/-/react-custom-scrollbars-4.0.5.tgz#8629b4b3164914d27df7cb0ca0a086c0ad406389"
@@ -1743,6 +1831,11 @@
     "@types/prop-types" "*"
     csstype "^2.2.0"
 
+"@types/storybook__addon-actions@^3.4.1":
+  version "3.4.1"
+  resolved "https://registry.yarnpkg.com/@types/storybook__addon-actions/-/storybook__addon-actions-3.4.1.tgz#8f90d76b023b58ee794170f2fe774a3fddda2c1d"
+  integrity sha512-An8pNb1/7QhkdOT8Ht5WjJsSxAh2mWti/J4eILwUHpXVZ1j3xlVaOzwTbg8twN4DjgOAggjEDOj6Bx8YOWh9Pg==
+
 "@types/storybook__addon-info@^3.4.2":
   version "3.4.2"
   resolved "https://registry.yarnpkg.com/@types/storybook__addon-info/-/storybook__addon-info-3.4.2.tgz#31a27e66867f02e593579cc58f54d22e726b0925"
@@ -1750,7 +1843,15 @@
   dependencies:
     "@types/react" "*"
 
-"@types/storybook__react@^4.0.0":
+"@types/storybook__addon-knobs@^4.0.0":
+  version "4.0.0"
+  resolved "https://registry.yarnpkg.com/@types/storybook__addon-knobs/-/storybook__addon-knobs-4.0.0.tgz#b218f0d84888833cc8b8d7a7b524175e8bb3030f"
+  integrity sha512-x3GNz8f0fQv7USvDuVXdZ4p/7nofFHyH6iB/qwR84Yp97xZxOzlQ0SY+6K14tVbdi9P7Qm5DZ2kZr0a+Io8qEQ==
+  dependencies:
+    "@types/react" "*"
+    "@types/storybook__react" "*"
+
+"@types/storybook__react@*", "@types/storybook__react@^4.0.0":
   version "4.0.0"
   resolved "https://registry.yarnpkg.com/@types/storybook__react/-/storybook__react-4.0.0.tgz#52cc452fbab599568d595075a90142ef4a1233f6"
   integrity sha512-Iq3RX953fqZRwWN3jywm8pUx1/Atev+x/9tF7/2CNA+Ii55sGSJJRWMRthUKQXTa3zOexcvfksfVYdUaIZY91w==
@@ -5186,6 +5287,13 @@ copy-descriptor@^0.1.0:
   resolved "https://registry.yarnpkg.com/copy-descriptor/-/copy-descriptor-0.1.1.tgz#676f6eb3c39997c2ee1ac3a924fd6124748f578d"
   integrity sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=
 
+copy-to-clipboard@^3.0.8:
+  version "3.0.8"
+  resolved "https://registry.yarnpkg.com/copy-to-clipboard/-/copy-to-clipboard-3.0.8.tgz#f4e82f4a8830dce4666b7eb8ded0c9bcc313aba9"
+  integrity sha512-c3GdeY8qxCHGezVb1EFQfHYK/8NZRemgcTIzPq7PuxjHAf/raKibn2QdhHPb/y6q74PMgH6yizaDZlRmw6QyKw==
+  dependencies:
+    toggle-selection "^1.0.3"
+
 core-js@^1.0.0:
   version "1.2.7"
   resolved "https://registry.yarnpkg.com/core-js/-/core-js-1.2.7.tgz#652294c14651db28fa93bd2d5ff2983a4f08c636"
@@ -6797,7 +6905,7 @@ es6-weak-map@^2.0.1:
     es6-iterator "^2.0.1"
     es6-symbol "^3.1.1"
 
-escape-html@~1.0.3:
+escape-html@^1.0.3, escape-html@~1.0.3:
   version "1.0.3"
   resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988"
   integrity sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=
@@ -10807,16 +10915,16 @@ lodash.without@~4.4.0:
   resolved "https://registry.yarnpkg.com/lodash.without/-/lodash.without-4.4.0.tgz#3cd4574a00b67bae373a94b748772640507b7aac"
   integrity sha1-PNRXSgC2e643OpS3SHcmQFB7eqw=
 
+lodash@>4.17.4, lodash@^4.0.0, lodash@^4.0.1, lodash@^4.1.1, lodash@^4.13.1, lodash@^4.15.0, lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.2, lodash@^4.17.3, lodash@^4.17.4, lodash@^4.17.5, lodash@^4.3.0, lodash@^4.7.0, lodash@^4.8.0, lodash@~4.17.10, lodash@~4.17.5:
+  version "4.17.11"
+  resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.11.tgz#b39ea6229ef607ecd89e2c8df12536891cac9b8d"
+  integrity sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==
+
 lodash@^3.10.1, lodash@^3.6.0:
   version "3.10.1"
   resolved "https://registry.yarnpkg.com/lodash/-/lodash-3.10.1.tgz#5bf45e8e49ba4189e17d482789dfd15bd140b7b6"
   integrity sha1-W/Rejkm6QYnhfUgnid/RW9FAt7Y=
 
-lodash@^4.0.0, lodash@^4.1.1, lodash@^4.13.1, lodash@^4.15.0, lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.2, lodash@^4.17.3, lodash@^4.17.4, lodash@^4.17.5, lodash@^4.3.0, lodash@^4.7.0, lodash@^4.8.0, lodash@~4.17.10, lodash@~4.17.5:
-  version "4.17.11"
-  resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.11.tgz#b39ea6229ef607ecd89e2c8df12536891cac9b8d"
-  integrity sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==
-
 lodash@~4.3.0:
   version "4.3.0"
   resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.3.0.tgz#efd9c4a6ec53f3b05412429915c3e4824e4d25a4"
@@ -10935,7 +11043,7 @@ make-dir@^1.0.0, make-dir@^1.1.0:
   dependencies:
     pify "^3.0.0"
 
-make-error@1.x:
+make-error@1.x, make-error@^1.3.5:
   version "1.3.5"
   resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.5.tgz#efe4e81f6db28cadd605c70f29c831b58ef776c8"
   integrity sha512-c3sIjNUow0+8swNwVpqoH4YCShKNFkMaw6oH1mNS2haDZQqkeZFlHS3dhoeEbKKmJB4vXpJucU6oH75aDYeE9g==
@@ -11041,6 +11149,11 @@ marksy@^6.1.0:
     he "^1.1.1"
     marked "^0.3.12"
 
+material-colors@^1.2.1:
+  version "1.2.6"
+  resolved "https://registry.yarnpkg.com/material-colors/-/material-colors-1.2.6.tgz#6d1958871126992ceecc72f4bcc4d8f010865f46"
+  integrity sha512-6qE4B9deFBIa9YSpOc9O0Sgc43zTeVYbgDT5veRKSlB2+ZuHNoVVxA1L/ckMUayV9Ay9y7Z/SZCLcGteW9i7bg==
+
 math-expression-evaluator@^1.2.14:
   version "1.2.17"
   resolved "https://registry.yarnpkg.com/math-expression-evaluator/-/math-expression-evaluator-1.2.17.tgz#de819fdbcd84dccd8fae59c6aeb79615b9d266ac"
@@ -13847,6 +13960,18 @@ react-addons-create-fragment@^15.5.3:
     loose-envify "^1.3.1"
     object-assign "^4.1.0"
 
+react-color@^2.14.1, react-color@^2.17.0:
+  version "2.17.0"
+  resolved "https://registry.yarnpkg.com/react-color/-/react-color-2.17.0.tgz#e14b8a11f4e89163f65a34c8b43faf93f7f02aaa"
+  integrity sha512-kJfE5tSaFe6GzalXOHksVjqwCPAsTl+nzS9/BWfP7j3EXbQ4IiLAF9sZGNzk3uq7HfofGYgjmcUgh0JP7xAQ0w==
+  dependencies:
+    "@icons/material" "^0.2.4"
+    lodash ">4.17.4"
+    material-colors "^1.2.1"
+    prop-types "^15.5.10"
+    reactcss "^1.2.0"
+    tinycolor2 "^1.4.1"
+
 react-custom-scrollbars@^4.2.1:
   version "4.2.1"
   resolved "https://registry.yarnpkg.com/react-custom-scrollbars/-/react-custom-scrollbars-4.2.1.tgz#830fd9502927e97e8a78c2086813899b2a8b66db"
@@ -14168,6 +14293,13 @@ react@^16.6.3, react@^16.7.0:
     prop-types "^15.6.2"
     scheduler "^0.12.0"
 
+reactcss@^1.2.0:
+  version "1.2.3"
+  resolved "https://registry.yarnpkg.com/reactcss/-/reactcss-1.2.3.tgz#c00013875e557b1cf0dfd9a368a1c3dab3b548dd"
+  integrity sha512-KiwVUcFu1RErkI97ywr8nvx8dNOpT03rbnma0SSalTYjkrPYaEajR4a/MRt6DZ46K6arDRbWMNHF+xH7G7n/8A==
+  dependencies:
+    lodash "^4.0.1"
+
 read-chunk@^2.1.0:
   version "2.1.0"
   resolved "https://registry.yarnpkg.com/read-chunk/-/read-chunk-2.1.0.tgz#6a04c0928005ed9d42e1a6ac5600e19cbc7ff655"
@@ -16463,6 +16595,11 @@ to-space-case@^1.0.0:
   dependencies:
     to-no-case "^1.0.0"
 
+toggle-selection@^1.0.3:
+  version "1.0.6"
+  resolved "https://registry.yarnpkg.com/toggle-selection/-/toggle-selection-1.0.6.tgz#6e45b1263f2017fa0acc7d89d78b15b8bf77da32"
+  integrity sha1-bkWxJj8gF/oKzH2J14sVuL932jI=
+
 toposort@^1.0.0:
   version "1.0.7"
   resolved "https://registry.yarnpkg.com/toposort/-/toposort-1.0.7.tgz#2e68442d9f64ec720b8cc89e6443ac6caa950029"