Przeglądaj źródła

Merge pull request #15926 from grafana/create-color-picker-trigger-component

Create default ColorPickerTrigger component
Andrej Ocenas 6 lat temu
rodzic
commit
f7a8df6ebd

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

@@ -50,7 +50,16 @@ ColorPickerStories.add('Series color picker', () => {
             color={selectedColor}
             onChange={color => updateSelectedColor(color)}
           >
-            <div style={{ color: selectedColor, cursor: 'pointer' }}>Open color picker</div>
+            {({ ref, showColorPicker, hideColorPicker }) => (
+              <div
+                ref={ref}
+                onMouseLeave={hideColorPicker}
+                onClick={showColorPicker}
+                style={{ color: selectedColor, cursor: 'pointer' }}
+              >
+                Open color picker
+              </div>
+            )}
           </SeriesColorPicker>
         );
       }}

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

@@ -0,0 +1,23 @@
+import React from 'react';
+import renderer from 'react-test-renderer';
+import { ColorPicker } from './ColorPicker';
+import { ColorPickerTrigger } from './ColorPickerTrigger';
+
+describe('ColorPicker', () => {
+  it('renders ColorPickerTrigger component by default', () => {
+    expect(
+      renderer.create(<ColorPicker color="#EAB839" onChange={() => {}} />).root.findByType(ColorPickerTrigger)
+    ).toBeTruthy();
+  });
+
+  it('renders custom trigger when supplied', () => {
+    const div = renderer
+      .create(
+        <ColorPicker color="#EAB839" onChange={() => {}}>
+          {() => <div>Custom trigger</div>}
+        </ColorPicker>
+      )
+      .root.findByType('div');
+    expect(div.children[0]).toBe('Custom trigger');
+  });
+});

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

@@ -1,4 +1,5 @@
 import React, { Component, createRef } from 'react';
+import { omit } from 'lodash';
 import { PopperController } from '../Tooltip/PopperController';
 import { Popper } from '../Tooltip/Popper';
 import { ColorPickerPopover, ColorPickerProps, ColorPickerChangeHandler } from './ColorPickerPopover';
@@ -6,14 +7,29 @@ import { getColorFromHexRgbOrName } from '../../utils/namedColorsPalette';
 import { SeriesColorPickerPopover } from './SeriesColorPickerPopover';
 
 import { withTheme } from '../../themes/ThemeContext';
+import { ColorPickerTrigger } from './ColorPickerTrigger';
+
+/**
+ * If you need custom trigger for the color picker you can do that with a render prop pattern and supply a function
+ * as a child. You will get show/hide function which you can map to desired interaction (like onClick or onMouseLeave)
+ * and a ref which needs to be passed to an HTMLElement for correct positioning. If you want to use class or functional
+ * component as a custom trigger you will need to forward the reference to first HTMLElement child.
+ */
+type ColorPickerTriggerRenderer = (props: {
+  // This should be a React.RefObject<HTMLElement> but due to how object refs are defined you cannot downcast from that
+  // to a specific type like React.RefObject<HTMLDivElement> even though it would be fine in runtime.
+  ref: React.RefObject<any>;
+  showColorPicker: () => void;
+  hideColorPicker: () => void;
+}) => React.ReactNode;
 
 export const colorPickerFactory = <T extends ColorPickerProps>(
   popover: React.ComponentType<T>,
   displayName = 'ColorPicker'
 ) => {
-  return class ColorPicker extends Component<T, any> {
+  return class ColorPicker extends Component<T & { children?: ColorPickerTriggerRenderer }, any> {
     static displayName = displayName;
-    pickerTriggerRef = createRef<HTMLDivElement>();
+    pickerTriggerRef = createRef<any>();
 
     onColorChange = (color: string) => {
       const { onColorChange, onChange } = this.props;
@@ -23,11 +39,11 @@ export const colorPickerFactory = <T extends ColorPickerProps>(
     };
 
     render() {
+      const { theme, children } = this.props;
       const popoverElement = React.createElement(popover, {
-        ...this.props,
+        ...omit(this.props, 'children'),
         onChange: this.onColorChange,
       });
-      const { theme, children } = this.props;
 
       return (
         <PopperController content={popoverElement} hideAfter={300}>
@@ -45,27 +61,21 @@ export const colorPickerFactory = <T extends ColorPickerProps>(
                 )}
 
                 {children ? (
-                  React.cloneElement(children as JSX.Element, {
+                  // Children have a bit weird type due to intersection used in the definition so we need to cast here,
+                  // but the definition is correct and should not allow to pass a children that does not conform to
+                  // ColorPickerTriggerRenderer type.
+                  (children as ColorPickerTriggerRenderer)({
                     ref: this.pickerTriggerRef,
-                    onClick: showPopper,
-                    onMouseLeave: hidePopper,
+                    showColorPicker: showPopper,
+                    hideColorPicker: hidePopper,
                   })
                 ) : (
-                  <div
+                  <ColorPickerTrigger
                     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.type),
-                        }}
-                      />
-                    </div>
-                  </div>
+                    color={getColorFromHexRgbOrName(this.props.color || '#000000', theme.type)}
+                  />
                 )}
               </>
             );

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

@@ -17,8 +17,8 @@ export interface ColorPickerProps extends Themeable {
    */
   onColorChange?: ColorPickerChangeHandler;
   enableNamedColors?: boolean;
-  children?: JSX.Element;
 }
+
 export interface Props<T> extends ColorPickerProps, PopperContentProps {
   customPickers?: T;
 }

+ 56 - 0
packages/grafana-ui/src/components/ColorPicker/ColorPickerTrigger.tsx

@@ -0,0 +1,56 @@
+import React, { forwardRef } from 'react';
+
+interface ColorPickerTriggerProps {
+  onClick: () => void;
+  onMouseLeave: () => void;
+  color: string;
+}
+
+export const ColorPickerTrigger = forwardRef(function ColorPickerTrigger(
+  props: ColorPickerTriggerProps,
+  ref: React.Ref<HTMLDivElement>
+) {
+  return (
+    <div
+      ref={ref}
+      onClick={props.onClick}
+      onMouseLeave={props.onMouseLeave}
+      style={{
+        overflow: 'hidden',
+        background: 'inherit',
+        border: 'none',
+        color: 'inherit',
+        padding: 0,
+        borderRadius: 10,
+        cursor: 'pointer',
+      }}
+    >
+      <div
+        style={{
+          position: 'relative',
+          width: 15,
+          height: 15,
+          border: 'none',
+          margin: 0,
+          float: 'left',
+          zIndex: 0,
+          backgroundImage:
+            // tslint:disable-next-line:max-line-length
+            'url(data:image/png,base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAMCAIAAADZF8uwAAAAGUlEQVQYV2M4gwH+YwCGIasIUwhT25BVBADtzYNYrHvv4gAAAABJRU5ErkJggg==)',
+        }}
+      >
+        <div
+          style={{
+            backgroundColor: props.color,
+            display: 'block',
+            position: 'absolute',
+            top: 0,
+            left: 0,
+            bottom: 0,
+            right: 0,
+          }}
+        />
+      </div>
+    </div>
+  );
+});

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

@@ -161,59 +161,6 @@ $arrowSize: 15px;
   flex-grow: 1;
 }
 
-.sp-replacer {
-  background: inherit;
-  border: none;
-  color: inherit;
-  padding: 0;
-  border-radius: 10px;
-  cursor: pointer;
-}
-
-.sp-replacer:hover,
-.sp-replacer.sp-active {
-  border-color: inherit;
-  color: inherit;
-}
-
-.sp-container {
-  border-radius: 0;
-  background-color: $dropdownBackground;
-  border: none;
-  padding: 0;
-}
-
-.sp-palette-container,
-.sp-picker-container {
-  border: none;
-}
-
-.sp-dd {
-  display: none;
-}
-
-.sp-preview {
-  position: relative;
-  width: 15px;
-  height: 15px;
-  border: none;
-  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: $arrowSize;
   padding-left: 6px;

+ 0 - 1
packages/grafana-ui/src/components/ThresholdsEditor/_ThresholdsEditor.scss

@@ -86,7 +86,6 @@
 
 .thresholds-row-input-inner-color-colorpicker {
   border-radius: 10px;
-  overflow: hidden;
   display: flex;
   align-items: center;
   box-shadow: 0 1px 4px rgba(0, 0, 0, 0.25);

+ 5 - 3
public/app/plugins/panel/graph/Legend/LegendSeriesItem.tsx

@@ -174,9 +174,11 @@ class LegendSeriesIcon extends PureComponent<LegendSeriesIconProps, LegendSeries
         onToggleAxis={this.props.onToggleAxis}
         enableNamedColors
       >
-        <span className="graph-legend-icon">
-          <SeriesIcon color={this.props.color} />
-        </span>
+        {({ ref, showColorPicker, hideColorPicker }) => (
+          <span ref={ref} onClick={showColorPicker} onMouseLeave={hideColorPicker} className="graph-legend-icon">
+            <SeriesIcon color={this.props.color} />
+          </span>
+        )}
       </SeriesColorPicker>
     );
   }