Explorar el Código

Migrating color pickers to Popper from drop.js pt1

Dominik Prokop hace 7 años
padre
commit
33fa40a1f3

+ 25 - 49
packages/grafana-ui/src/components/ColorPicker/ColorPicker.tsx

@@ -1,69 +1,45 @@
-import React, { Component } from 'react';
-import ReactDOM from 'react-dom';
-import Drop from 'tether-drop';
+import React, { Component, createRef } from 'react';
+import PopperController from '../Tooltip/PopperController';
+import Popper from '../Tooltip/Popper';
 import { ColorPickerPopover } from './ColorPickerPopover';
-import { Color } from '../../utils/colorsPalette';
+import { ColorDefinition } from '../../utils/colorsPalette';
 
 interface Props {
-  /**
-   * Value to display, either empty (" ") or "X" / "O".
-   *
-   * @default " "
-   **/
-  name?: Color;
   color: string;
   onChange: (c: string) => void;
 }
 
 export class ColorPicker extends Component<Props, any> {
+  private pickerTriggerRef = createRef<HTMLDivElement>();
   pickerElem: HTMLElement | null;
   colorPickerDrop: any;
 
-  openColorPicker = () => {
-    const dropContent = <ColorPickerPopover color={this.props.color} onColorSelect={this.onColorSelect} />;
-
-    const dropContentElem = document.createElement('div');
-    ReactDOM.render(dropContent, dropContentElem);
-
-    const drop = new Drop({
-      target: this.pickerElem as Element,
-      content: dropContentElem,
-      position: 'top center',
-      classes: 'drop-popover',
-      openOn: 'click',
-      hoverCloseDelay: 200,
-      tetherOptions: {
-        constraints: [{ to: 'scrollParent', attachment: 'none both' }],
-        attachment: 'bottom center',
-      },
-    });
-
-    drop.on('close', this.closeColorPicker);
-
-    this.colorPickerDrop = drop;
-    this.colorPickerDrop.open();
+  onColorSelect = (color: ColorDefinition) => {
+    this.props.onChange(color.name);
   };
 
-  closeColorPicker = () => {
-    setTimeout(() => {
-      if (this.colorPickerDrop && this.colorPickerDrop.tether) {
-        this.colorPickerDrop.destroy();
-      }
-    }, 100);
-  };
-
-  onColorSelect = (color: string) => {
-    this.props.onChange(color);
+  renderPickerTabs = () => {
+    return <ColorPickerPopover color="" onColorSelect={() => {}} />;
   };
 
   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>
+      <PopperController content={this.renderPickerTabs}>
+        {(showPopper, hidePopper, popperProps) => {
+          return (
+            <>
+              {this.pickerTriggerRef.current && (
+                <Popper {...popperProps} referenceElement={this.pickerTriggerRef.current} className="ColorPicker" />
+              )}
+              <div ref={this.pickerTriggerRef} onClick={showPopper} className="sp-replacer sp-light">
+                <div className="sp-preview">
+                  <div className="sp-preview-inner" style={{ backgroundColor: this.props.color }} />
+                </div>
+              </div>
+            </>
+          );
+        }}
+      </PopperController>
     );
   }
 }

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

@@ -1,111 +1,76 @@
-import React from 'react';
-import $ from 'jquery';
-import tinycolor from 'tinycolor2';
-import { ColorPalette } from './ColorPalette';
+import React, { Children } from 'react';
+import NamedColorsPicker from './NamedColorsPicker';
+import { Color } from 'csstype';
+import { ColorDefinition, getColorName } from '../..//utils/colorsPalette';
 import { SpectrumPicker } from './SpectrumPicker';
+import { GrafanaTheme } from '../../types';
 
-const DEFAULT_COLOR = '#000000';
+// const DEFAULT_COLOR = '#000000';
 
 export interface Props {
-  color: string;
-  onColorSelect: (c: string) => void;
+  color: Color | string;
+  theme?: GrafanaTheme;
+  onColorSelect: (color: string | ColorDefinition) => void;
 }
 
-export class ColorPickerPopover extends React.Component<Props, any> {
-  pickerNavElem: any;
+type PickerType = 'palette' | 'spectrum';
 
+interface State {
+  activePicker: PickerType;
+}
+
+export class ColorPickerPopover extends React.Component<Props, State> {
   constructor(props: Props) {
     super(props);
     this.state = {
-      tab: 'palette',
-      color: this.props.color || DEFAULT_COLOR,
-      colorString: this.props.color || DEFAULT_COLOR,
+      activePicker: 'spectrum',
     };
   }
 
-  setPickerNavElem(elem: any) {
-    this.pickerNavElem = $(elem);
-  }
-
-  setColor(color: string) {
-    const newColor = tinycolor(color);
-    if (newColor.isValid()) {
-      this.setState({ color: newColor.toString(), colorString: newColor.toString() });
-      this.props.onColorSelect(color);
-    }
-  }
+  handleSpectrumColorSelect = (color: any) => {
+    this.props.onColorSelect(color.toRgbString());
+  };
 
-  sampleColorSelected(color: string) {
-    this.setColor(color);
-  }
-
-  spectrumColorSelected(color: any) {
-    const rgbColor = color.toRgbString();
-    this.setColor(rgbColor);
-  }
-
-  onColorStringChange(e: any) {
-    const colorString = e.target.value;
-    this.setState({ colorString: colorString });
-
-    const newColor = tinycolor(colorString);
-    if (newColor.isValid()) {
-      // Update only color state
-      const newColorString = newColor.toString();
-      this.setState({ color: newColorString });
-      this.props.onColorSelect(newColorString);
-    }
-  }
+  renderPicker = () => {
+    const { activePicker } = this.state;
+    const { color } = this.props;
 
-  onColorStringBlur(e: any) {
-    const colorString = e.target.value;
-    this.setColor(colorString);
-  }
-
-  componentDidMount() {
-    this.pickerNavElem.find('li:first').addClass('active');
-    this.pickerNavElem.on('show', (e: any) => {
-      // use href attr (#name => name)
-      const tab = e.target.hash.slice(1);
-      this.setState({ tab: tab });
-    });
-  }
+    return activePicker === 'spectrum' ? (
+      <SpectrumPicker color={color} onColorSelect={this.handleSpectrumColorSelect} options={{}} />
+    ) : (
+      <NamedColorsPicker selectedColor={getColorName(color)} onChange={this.props.onColorSelect} />
+    );
+  };
 
   render() {
-    const paletteTab = (
-      <div id="palette">
-        <ColorPalette color={this.state.color} onColorSelect={this.sampleColorSelected.bind(this)} />
-      </div>
-    );
-    const spectrumTab = (
-      <div id="spectrum">
-        <SpectrumPicker color={this.state.color} onColorSelect={this.spectrumColorSelected.bind(this)} options={{}} />
-      </div>
-    );
-    const currentTab = this.state.tab === 'palette' ? paletteTab : spectrumTab;
+    const { activePicker } = this.state;
+    const { theme, children } = 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={`ColorPickerPopover__tab ${activePicker === 'palette' && 'ColorPickerPopover__tab--active'}`}
+            onClick={() => {
+              this.setState({ activePicker: 'palette' });
+            }}
+          >
+            Default
+          </div>
+          <div
+            className={`ColorPickerPopover__tab ${activePicker === 'spectrum' && 'ColorPickerPopover__tab--active'}`}
+            onClick={() => {
+              this.setState({ activePicker: 'spectrum' });
+            }}
+          >
+            Custom
+          </div>
+        </div>
+
+        <div className="ColorPickerPopover__content">
+          {this.renderPicker()}
+          {children}
         </div>
       </div>
     );

+ 11 - 11
packages/grafana-ui/src/components/ColorPicker/NamedColorsPicker.tsx

@@ -47,19 +47,22 @@ const ColorSwatch: FunctionComponent<ColorSwatchProps> = ({
   );
 };
 
-const ColorsGroup = ({
+interface ColorsGroupProps  {
+  colors: ColorDefinition[];
+  selectedColor?: Color;
+  onColorSelect: ColorChangeHandler;
+  key?: string;
+}
+const ColorsGroup: FunctionComponent<ColorsGroupProps> = ({
   colors,
   selectedColor,
   onColorSelect,
-}: {
-  colors: ColorDefinition[];
-  selectedColor?: Color;
-  onColorSelect: ColorChangeHandler
+  ...otherProps
 }) => {
   const primaryColor = find(colors, color => !!color.isPrimary);
 
   return (
-    <div style={{ display: 'flex', flexDirection: 'column' }}>
+    <div  {...otherProps} style={{ display: 'flex', flexDirection: 'column' }}>
       {primaryColor && (
         <ColorSwatch
           isSelected={primaryColor.name === selectedColor}
@@ -75,7 +78,7 @@ const ColorsGroup = ({
         }}
       >
         {colors.map(color => !color.isPrimary && (
-          <div style={{ marginRight: '4px' }}>
+          <div key={color.name} style={{ marginRight: '4px' }}>
             <ColorSwatch
               isSelected={color.name === selectedColor}
               color={color}
@@ -88,7 +91,6 @@ const ColorsGroup = ({
   );
 };
 
-
 interface NamedColorsPickerProps {
   selectedColor?: Color;
   onChange: ColorChangeHandler;
@@ -98,9 +100,7 @@ const NamedColorsPicker = ({ selectedColor, onChange }: NamedColorsPickerProps)
 
   ColorsPalete.forEach((colors, hue) => {
     swatches.push(
-      <>
-        <ColorsGroup selectedColor={selectedColor} colors={colors} onColorSelect={onChange} />
-      </>
+      <ColorsGroup key={hue} selectedColor={selectedColor} colors={colors} onColorSelect={onChange} />
     );
   });
 

+ 43 - 58
packages/grafana-ui/src/components/ColorPicker/SeriesColorPicker.tsx

@@ -1,7 +1,9 @@
-import React from 'react';
-import ReactDOM from 'react-dom';
-import Drop from 'tether-drop';
+import React, { createRef } from 'react';
+import * as PopperJS from 'popper.js';
 import { SeriesColorPickerPopover } from './SeriesColorPickerPopover';
+import PopperController from '../Tooltip/PopperController';
+import Popper from '../Tooltip/Popper';
+import { GrafanaTheme } from '../../types';
 
 export interface SeriesColorPickerProps {
   color: string;
@@ -9,10 +11,12 @@ export interface SeriesColorPickerProps {
   optionalClass?: string;
   onColorChange: (newColor: string) => void;
   onToggleAxis?: () => void;
+  children: JSX.Element;
+  theme?: GrafanaTheme;
 }
 
 export class SeriesColorPicker extends React.Component<SeriesColorPickerProps> {
-  pickerElem: any;
+  private pickerTriggerRef = createRef<PopperJS.ReferenceObject>();
   colorPickerDrop: any;
 
   static defaultProps = {
@@ -21,65 +25,46 @@ export class SeriesColorPicker extends React.Component<SeriesColorPickerProps> {
     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} />
+  renderPickerTabs = () => {
+    const { color, yaxis, onColorChange, onToggleAxis, theme } = this.props;
+    return (
+      <SeriesColorPickerPopover
+        theme={theme}
+        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;
+    const { children } = this.props;
+
     return (
-      <div className={optionalClass} ref={e => (this.pickerElem = e)} onClick={this.onClickToOpen}>
-        {children}
-      </div>
+      <PopperController placement="bottom-start" content={this.renderPickerTabs}>
+        {(showPopper, hidePopper, popperProps) => {
+          return (
+            <>
+              {this.pickerTriggerRef.current && (
+                <Popper
+                  {...popperProps}
+                  onMouseEnter={showPopper}
+                  onMouseLeave={hidePopper}
+                  referenceElement={this.pickerTriggerRef.current}
+                  className="ColorPicker"
+                  arrowClassName="popper__arrow"
+                />
+              )}
+              {React.cloneElement(children, {
+                ref: this.pickerTriggerRef,
+                onClick: showPopper,
+                onMouseLeave: hidePopper,
+              })}
+            </>
+          );
+        }}
+      </PopperController>
     );
   }
 }

+ 8 - 3
packages/grafana-ui/src/components/ColorPicker/SeriesColorPickerPopover.tsx

@@ -1,19 +1,24 @@
 import React from 'react';
 import { ColorPickerPopover } from './ColorPickerPopover';
+import { GrafanaTheme } from '../../types';
 
 export interface SeriesColorPickerPopoverProps {
   color: string;
   yaxis?: number;
   onColorChange: (color: string) => void;
   onToggleAxis?: () => void;
+  theme?: GrafanaTheme;
 }
 
 export class SeriesColorPickerPopover extends React.PureComponent<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>
+        <ColorPickerPopover theme={this.props.theme} color={this.props.color} onColorSelect={this.props.onColorChange}>
+          <div style={{ marginTop: '32px' }}>
+            {this.props.yaxis && <AxisSelector yaxis={this.props.yaxis} onToggleAxis={this.props.onToggleAxis} />}
+          </div>
+        </ColorPickerPopover>
       </div>
     );
   }

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

@@ -6,7 +6,7 @@ import '../../vendor/spectrum';
 export interface Props {
   color: string;
   options: object;
-  onColorSelect: (c: string) => void;
+  onColorSelect: (color: string) => void;
 }
 
 export class SpectrumPicker extends React.Component<Props, any> {

+ 51 - 0
packages/grafana-ui/src/components/ColorPicker/_ColorPicker.scss

@@ -1,3 +1,54 @@
+.ColorPicker {
+  .popper__arrow {
+    border-color: #f7f8fa;
+  }
+}
+
+.ColorPickerPopover {
+  border-radius: 3px;
+}
+
+.ColorPickerPopover--light {
+  color: black;
+  background: linear-gradient(180deg, #ffffff 0%, #f7f8fa 104.25%);
+  box-shadow: 0px 2px 4px #dde4ed, 0px 0px 2px #dde4ed;
+}
+
+.ColorPickerPopover--dark {
+  color: #d8d9da;
+  background: linear-gradient(180deg, #1e2028 0%, #161719 104.25%);
+  box-shadow: 0px 2px 4px #000000, 0px 0px 2px #000000;
+
+  .ColorPickerPopover__tab {
+    background: #303133;
+    color: white;
+  }
+  .ColorPickerPopover__tab--active {
+    background: none;
+  }
+}
+
+.ColorPickerPopover__content {
+  width: 360px;
+  padding: 32px;
+}
+
+.ColorPickerPopover__tabs {
+  display: flex;
+  width: 100%;
+}
+
+.ColorPickerPopover__tab {
+  width: 50%;
+  text-align: center;
+  padding: 8px 0;
+  background: #dde4ed;
+  border-radius: 3px;
+}
+
+.ColorPickerPopover__tab--active {
+  background: white;
+}
 .sp-replacer {
   background: inherit;
   border: none;

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

@@ -3,3 +3,8 @@ export * from './time';
 export * from './panel';
 export * from './plugin';
 export * from './datasource';
+
+export enum GrafanaTheme {
+  Light = 'light',
+  Dark = 'dark',
+}

+ 9 - 3
public/app/core/time_series2.ts

@@ -1,6 +1,7 @@
 import kbn from 'app/core/utils/kbn';
 import { getFlotTickDecimals } from 'app/core/utils/ticks';
 import _ from 'lodash';
+import { ColorDefinition } from '@grafana/ui/src/utils/colorsPalette';
 
 function matchSeriesOverride(aliasOrRegex, seriesAlias) {
   if (!aliasOrRegex) {
@@ -356,8 +357,13 @@ export default class TimeSeries {
     return false;
   }
 
-  setColor(color) {
-    this.color = color;
-    this.bars.fillColor = color;
+  setColor(color: string | ColorDefinition) {
+    if (typeof color === 'string') {
+      this.color = color;
+      this.bars.fillColor = color;
+    } else {
+      this.color = color.variants.dark;
+      this.bars.fillColor = color.variants.dark;
+    }
   }
 }