From a214b5748e15eafdf6117af7a42f7b0dfba3f997 Mon Sep 17 00:00:00 2001 From: Dominik Prokop Date: Mon, 21 Jan 2019 12:15:42 +0100 Subject: [PATCH] Refactor color picker to remove code duplicartion (introduced colorPickerFactory). Allow popver position update on content change --- .../components/ColorPicker/ColorPicker.tsx | 106 +++++++++++------- .../ColorPicker/ColorPickerPopover.tsx | 17 ++- .../ColorPicker/SeriesColorPicker.tsx | 73 +----------- .../ColorPicker/SeriesColorPickerPopover.tsx | 6 +- .../components/ColorPicker/_ColorPicker.scss | 14 ++- .../ThresholdsEditor/ThresholdsEditor.tsx | 2 +- .../src/components/Tooltip/Popper.tsx | 24 ++-- .../components/Tooltip/PopperController.tsx | 12 +- .../grafana-ui/src/utils/colorsPalette.ts | 6 +- .../panel/graph/Legend/LegendSeriesItem.tsx | 3 +- 10 files changed, 125 insertions(+), 138 deletions(-) diff --git a/packages/grafana-ui/src/components/ColorPicker/ColorPicker.tsx b/packages/grafana-ui/src/components/ColorPicker/ColorPicker.tsx index 025417b91b9..a412c0c9d39 100644 --- a/packages/grafana-ui/src/components/ColorPicker/ColorPicker.tsx +++ b/packages/grafana-ui/src/components/ColorPicker/ColorPicker.tsx @@ -1,53 +1,81 @@ import React, { Component, createRef } from 'react'; import PopperController from '../Tooltip/PopperController'; -import Popper from '../Tooltip/Popper'; +import Popper, { RenderPopperArrowFn } from '../Tooltip/Popper'; import { ColorPickerPopover } from './ColorPickerPopover'; import { Themeable, GrafanaTheme } from '../../types'; +import { getColorFromHexRgbOrName } from '../../utils/colorsPalette'; export interface ColorPickerProps extends Themeable { color: string; onChange: (color: string) => void; + withArrow?: boolean; + children?: JSX.Element; } -export class ColorPicker extends Component { - private pickerTriggerRef = createRef(); +export const colorPickerFactory = ( + popover: React.ComponentType, + displayName?: string, + renderPopoverArrowFunction?: RenderPopperArrowFn +) => { + return class ColorPicker extends Component { + static displayName = displayName || 'ColorPicker'; + pickerTriggerRef = createRef(); - render() { - const { theme } = this.props; - return ( - }> - {(showPopper, hidePopper, popperProps) => { - return ( - <> - {this.pickerTriggerRef.current && ( - { - return ( + render() { + const popoverElement = React.createElement(popover, this.props); + const { theme, withArrow, children } = this.props; + + const renderArrow: RenderPopperArrowFn = ({ arrowProps, placement }) => { + return ( +
+ ); + }; + + return ( + + {(showPopper, hidePopper, popperProps) => { + return ( + <> + {this.pickerTriggerRef.current && ( + + )} + + {children ? ( + React.cloneElement(children as JSX.Element, { + ref: this.pickerTriggerRef, + onClick: showPopper, + onMouseLeave: hidePopper, + }) + ) : ( +
+
- ); - }} - /> - )} -
-
-
-
-
- - ); - }} - - ); - } -} +
+
+ )} + + ); + }} + + ); + } + }; +}; -export default ColorPicker; +export default colorPickerFactory(ColorPickerPopover, 'ColorPicker'); diff --git a/packages/grafana-ui/src/components/ColorPicker/ColorPickerPopover.tsx b/packages/grafana-ui/src/components/ColorPicker/ColorPickerPopover.tsx index 518baeb393a..c9242f72ebf 100644 --- a/packages/grafana-ui/src/components/ColorPicker/ColorPickerPopover.tsx +++ b/packages/grafana-ui/src/components/ColorPicker/ColorPickerPopover.tsx @@ -4,10 +4,11 @@ import { getColorName } from '../..//utils/colorsPalette'; import { SpectrumPalette } from './SpectrumPalette'; import { ColorPickerProps } from './ColorPicker'; import { GrafanaTheme, Themeable } from '../../types'; +import { PopperContentProps } from '../Tooltip/PopperController'; // const DEFAULT_COLOR = '#000000'; -export interface Props extends ColorPickerProps, Themeable {} +export interface Props extends ColorPickerProps, Themeable, PopperContentProps {} type PickerType = 'palette' | 'spectrum'; @@ -40,7 +41,7 @@ export class ColorPickerPopover extends React.Component { render() { const { activePicker } = this.state; - const { theme, children } = this.props; + const { theme, children, updatePopperPosition } = this.props; const colorPickerTheme = theme || GrafanaTheme.Dark; return ( @@ -49,7 +50,11 @@ export class ColorPickerPopover extends React.Component {
{ - this.setState({ activePicker: 'palette' }); + this.setState({ activePicker: 'palette' }, () => { + if (updatePopperPosition) { + updatePopperPosition(); + } + }); }} > Default @@ -57,7 +62,11 @@ export class ColorPickerPopover extends React.Component {
{ - this.setState({ activePicker: 'spectrum' }); + this.setState({ activePicker: 'spectrum' }, () => { + if (updatePopperPosition) { + updatePopperPosition(); + } + }); }} > Custom diff --git a/packages/grafana-ui/src/components/ColorPicker/SeriesColorPicker.tsx b/packages/grafana-ui/src/components/ColorPicker/SeriesColorPicker.tsx index cf6dea672c9..c81e9ca8e86 100644 --- a/packages/grafana-ui/src/components/ColorPicker/SeriesColorPicker.tsx +++ b/packages/grafana-ui/src/components/ColorPicker/SeriesColorPicker.tsx @@ -1,78 +1,11 @@ -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 { Themeable, GrafanaTheme } from '../../types'; -import { ColorPickerProps } from './ColorPicker'; +import { ColorPickerProps, colorPickerFactory } from './ColorPicker'; -export interface SeriesColorPickerProps extends ColorPickerProps, Themeable { +export interface SeriesColorPickerProps extends ColorPickerProps { yaxis?: number; optionalClass?: string; onToggleAxis?: () => void; children: JSX.Element; } -export class SeriesColorPicker extends React.Component { - private pickerTriggerRef = createRef(); - colorPickerDrop: any; - - static defaultProps = { - optionalClass: '', - yaxis: undefined, - onToggleAxis: () => {}, - }; - - renderPickerTabs = () => { - const { color, yaxis, onChange, onToggleAxis, theme } = this.props; - return ( - - ); - }; - - render() { - const { children, theme } = this.props; - return ( - - {(showPopper, hidePopper, popperProps) => { - return ( - <> - {this.pickerTriggerRef.current && ( - { - return ( -
- ); - }} - /> - )} - - {React.cloneElement(children, { - ref: this.pickerTriggerRef, - onClick: showPopper, - onMouseLeave: hidePopper, - })} - - ); - }} - - ); - } -} +export default colorPickerFactory(SeriesColorPickerPopover ,'SeriesColorPicker') diff --git a/packages/grafana-ui/src/components/ColorPicker/SeriesColorPickerPopover.tsx b/packages/grafana-ui/src/components/ColorPicker/SeriesColorPickerPopover.tsx index e6cae077cdc..f019dc1634b 100644 --- a/packages/grafana-ui/src/components/ColorPicker/SeriesColorPickerPopover.tsx +++ b/packages/grafana-ui/src/components/ColorPicker/SeriesColorPickerPopover.tsx @@ -2,8 +2,9 @@ import React, { FunctionComponent } from 'react'; import { ColorPickerPopover } from './ColorPickerPopover'; import { Themeable } from '../../types'; import { ColorPickerProps } from './ColorPicker'; +import { PopperContentProps } from '../Tooltip/PopperController'; -export interface SeriesColorPickerPopoverProps extends ColorPickerProps, Themeable { +export interface SeriesColorPickerPopoverProps extends ColorPickerProps, Themeable, PopperContentProps { yaxis?: number; onToggleAxis?: () => void; } @@ -14,9 +15,10 @@ export const SeriesColorPickerPopover: FunctionComponent { return ( - +
{yaxis && }
); diff --git a/packages/grafana-ui/src/components/ColorPicker/_ColorPicker.scss b/packages/grafana-ui/src/components/ColorPicker/_ColorPicker.scss index b1b58f94257..f35e47f126a 100644 --- a/packages/grafana-ui/src/components/ColorPicker/_ColorPicker.scss +++ b/packages/grafana-ui/src/components/ColorPicker/_ColorPicker.scss @@ -1,4 +1,4 @@ -$arrowSize: 10px; +$arrowSize: 15px; .ColorPicker { @extend .popper; } @@ -16,7 +16,7 @@ $arrowSize: 10px; border-right-color: transparent; border-bottom-color: transparent; bottom: -$arrowSize; - left: calc(50% - $arrowSize); + left: calc(50%-#{$arrowSize}); padding-top: $arrowSize; } @@ -26,7 +26,7 @@ $arrowSize: 10px; border-right-color: transparent; border-top-color: transparent; top: 0; - left: calc(50% - $arrowSize); + left: calc(50%-#{$arrowSize}); } &[data-placement^='bottom-start'] { @@ -44,7 +44,7 @@ $arrowSize: 10px; border-right-color: transparent; border-top-color: transparent; top: 0; - left: calc(100% - $arrowSize); + left: calc(100% -$arrowSize); } &[data-placement^='right'] { @@ -53,7 +53,7 @@ $arrowSize: 10px; border-top-color: transparent; border-bottom-color: transparent; left: 0; - top: calc(50% - $arrowSize); + top: calc(50%-#{$arrowSize}); } &[data-placement^='left'] { @@ -62,7 +62,7 @@ $arrowSize: 10px; border-right-color: transparent; border-bottom-color: transparent; right: -$arrowSize; - top: calc(50% - $arrowSize); + top: calc(50%-#{$arrowSize}); } } @@ -148,11 +148,13 @@ $arrowSize: 10px; .ColorPickerPopover__tab--active { background: white; } + .sp-replacer { background: inherit; border: none; color: inherit; padding: 0; + border-radius: 10px; } .sp-replacer:hover, diff --git a/packages/grafana-ui/src/components/ThresholdsEditor/ThresholdsEditor.tsx b/packages/grafana-ui/src/components/ThresholdsEditor/ThresholdsEditor.tsx index 590aca5c7a1..a1677e58e91 100644 --- a/packages/grafana-ui/src/components/ThresholdsEditor/ThresholdsEditor.tsx +++ b/packages/grafana-ui/src/components/ThresholdsEditor/ThresholdsEditor.tsx @@ -2,7 +2,7 @@ import React, { PureComponent } from 'react'; // import tinycolor, { ColorInput } from 'tinycolor2'; import { Threshold } from '../../types'; -import { ColorPicker } from '../ColorPicker/ColorPicker'; +import ColorPicker from '../ColorPicker/ColorPicker'; import { PanelOptionsGroup } from '../PanelOptionsGroup/PanelOptionsGroup'; import { colors } from '../../utils'; diff --git a/packages/grafana-ui/src/components/Tooltip/Popper.tsx b/packages/grafana-ui/src/components/Tooltip/Popper.tsx index 770baab6fb4..fa49596755c 100644 --- a/packages/grafana-ui/src/components/Tooltip/Popper.tsx +++ b/packages/grafana-ui/src/components/Tooltip/Popper.tsx @@ -17,18 +17,20 @@ const transitionStyles: { [key: string]: object } = { exiting: { opacity: 0 }, }; +export type RenderPopperArrowFn = ( + props: { + arrowProps: PopperArrowProps; + placement: string; + } +) => JSX.Element; + interface Props extends React.HTMLAttributes { show: boolean; placement?: PopperJS.Placement; - content: PopperContent; + content: PopperContent; referenceElement: PopperJS.ReferenceObject; wrapperClassName?: string; - renderArrow?: ( - props: { - arrowProps: PopperArrowProps; - placement: string; - } - ) => JSX.Element; + renderArrow?: RenderPopperArrowFn; } class Popper extends PureComponent { @@ -47,7 +49,7 @@ class Popper extends PureComponent { // TODO: move modifiers config to popper controller modifiers={{ preventOverflow: { enabled: true, boundariesElement: 'window' } }} > - {({ ref, style, placement, arrowProps }) => { + {({ ref, style, placement, arrowProps, scheduleUpdate }) => { return (
{ className={`${wrapperClassName}`} >
- {content} + {typeof content === 'string' + ? content + : React.cloneElement(content, { + updatePopperPosition: scheduleUpdate, + })} {renderArrow && renderArrow({ arrowProps, diff --git a/packages/grafana-ui/src/components/Tooltip/PopperController.tsx b/packages/grafana-ui/src/components/Tooltip/PopperController.tsx index aa2777f1c3d..a4bc555a4db 100644 --- a/packages/grafana-ui/src/components/Tooltip/PopperController.tsx +++ b/packages/grafana-ui/src/components/Tooltip/PopperController.tsx @@ -1,12 +1,16 @@ import React from 'react'; import * as PopperJS from 'popper.js'; -export type PopperContent = string | JSX.Element; +// This API allows popovers to update Popper's position when e.g. popover content chaanges +// updatePopperPosition is delivered to content by react-popper +export interface PopperContentProps { updatePopperPosition?: () => void; } + +export type PopperContent = string | React.ReactElement; export interface UsingPopperProps { show?: boolean; placement?: PopperJS.Placement; - content: PopperContent; + content: PopperContent; children: JSX.Element; } @@ -16,13 +20,13 @@ type PopperControllerRenderProp = ( popperProps: { show: boolean; placement: PopperJS.Placement; - content: PopperContent; + content: PopperContent; } ) => JSX.Element; interface Props { placement?: PopperJS.Placement; - content: PopperContent; + content: PopperContent; className?: string; children: PopperControllerRenderProp; } diff --git a/packages/grafana-ui/src/utils/colorsPalette.ts b/packages/grafana-ui/src/utils/colorsPalette.ts index 4238f50b410..881519d6ff5 100644 --- a/packages/grafana-ui/src/utils/colorsPalette.ts +++ b/packages/grafana-ui/src/utils/colorsPalette.ts @@ -125,7 +125,11 @@ const isHex = (color: string) => { return hexRegex.test(color); }; -export const getColorName = (color: string): Color | undefined => { +export const getColorName = (color?: string): Color | undefined => { + if (!color) { + return undefined; + } + if (color.indexOf('rgb') > -1) { return undefined; } diff --git a/public/app/plugins/panel/graph/Legend/LegendSeriesItem.tsx b/public/app/plugins/panel/graph/Legend/LegendSeriesItem.tsx index e54e27869d6..568e38bab2e 100644 --- a/public/app/plugins/panel/graph/Legend/LegendSeriesItem.tsx +++ b/public/app/plugins/panel/graph/Legend/LegendSeriesItem.tsx @@ -172,14 +172,13 @@ class LegendSeriesIcon extends PureComponent { return ( - +