mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Refactor color picker to remove code duplicartion (introduced colorPickerFactory). Allow popver position update on content change
This commit is contained in:
parent
4f516faa82
commit
a214b5748e
@ -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<ColorPickerProps & Themeable, any> {
|
||||
private pickerTriggerRef = createRef<HTMLDivElement>();
|
||||
export const colorPickerFactory = <T extends ColorPickerProps>(
|
||||
popover: React.ComponentType<T>,
|
||||
displayName?: string,
|
||||
renderPopoverArrowFunction?: RenderPopperArrowFn
|
||||
) => {
|
||||
return class ColorPicker extends Component<T, any> {
|
||||
static displayName = displayName || 'ColorPicker';
|
||||
pickerTriggerRef = createRef<HTMLDivElement>();
|
||||
|
||||
render() {
|
||||
const { theme } = this.props;
|
||||
return (
|
||||
<PopperController placement="bottom-start" content={<ColorPickerPopover {...this.props} />}>
|
||||
{(showPopper, hidePopper, popperProps) => {
|
||||
return (
|
||||
<>
|
||||
{this.pickerTriggerRef.current && (
|
||||
<Popper
|
||||
{...popperProps}
|
||||
referenceElement={this.pickerTriggerRef.current}
|
||||
className="ColorPicker"
|
||||
renderArrow={({ arrowProps, placement }) => {
|
||||
return (
|
||||
render() {
|
||||
const popoverElement = React.createElement(popover, this.props);
|
||||
const { theme, withArrow, children } = this.props;
|
||||
|
||||
const renderArrow: RenderPopperArrowFn = ({ arrowProps, placement }) => {
|
||||
return (
|
||||
<div
|
||||
{...arrowProps}
|
||||
data-placement={placement}
|
||||
className={`ColorPicker__arrow ColorPicker__arrow--${theme === GrafanaTheme.Light ? 'light' : 'dark'}`}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<PopperController content={popoverElement} placement="bottom-start">
|
||||
{(showPopper, hidePopper, popperProps) => {
|
||||
return (
|
||||
<>
|
||||
{this.pickerTriggerRef.current && (
|
||||
<Popper
|
||||
{...popperProps}
|
||||
referenceElement={this.pickerTriggerRef.current}
|
||||
wrapperClassName="ColorPicker"
|
||||
renderArrow={withArrow && (renderPopoverArrowFunction || renderArrow)}
|
||||
onMouseLeave={hidePopper}
|
||||
onMouseEnter={showPopper}
|
||||
/>
|
||||
)}
|
||||
|
||||
{children ? (
|
||||
React.cloneElement(children as JSX.Element, {
|
||||
ref: this.pickerTriggerRef,
|
||||
onClick: showPopper,
|
||||
onMouseLeave: hidePopper,
|
||||
})
|
||||
) : (
|
||||
<div ref={this.pickerTriggerRef} onClick={showPopper} className="sp-replacer sp-light">
|
||||
<div className="sp-preview">
|
||||
<div
|
||||
{...arrowProps}
|
||||
data-placement={placement}
|
||||
className={`ColorPicker__arrow ColorPicker__arrow--${
|
||||
theme === GrafanaTheme.Light ? 'light' : 'dark'
|
||||
}`}
|
||||
className="sp-preview-inner"
|
||||
style={{
|
||||
backgroundColor: getColorFromHexRgbOrName(this.props.color, theme),
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
<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>
|
||||
);
|
||||
}
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}}
|
||||
</PopperController>
|
||||
);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
export default ColorPicker;
|
||||
export default colorPickerFactory(ColorPickerPopover, 'ColorPicker');
|
||||
|
@ -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<Props, State> {
|
||||
|
||||
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<Props, State> {
|
||||
<div
|
||||
className={`ColorPickerPopover__tab ${activePicker === 'palette' && 'ColorPickerPopover__tab--active'}`}
|
||||
onClick={() => {
|
||||
this.setState({ activePicker: 'palette' });
|
||||
this.setState({ activePicker: 'palette' }, () => {
|
||||
if (updatePopperPosition) {
|
||||
updatePopperPosition();
|
||||
}
|
||||
});
|
||||
}}
|
||||
>
|
||||
Default
|
||||
@ -57,7 +62,11 @@ export class ColorPickerPopover extends React.Component<Props, State> {
|
||||
<div
|
||||
className={`ColorPickerPopover__tab ${activePicker === 'spectrum' && 'ColorPickerPopover__tab--active'}`}
|
||||
onClick={() => {
|
||||
this.setState({ activePicker: 'spectrum' });
|
||||
this.setState({ activePicker: 'spectrum' }, () => {
|
||||
if (updatePopperPosition) {
|
||||
updatePopperPosition();
|
||||
}
|
||||
});
|
||||
}}
|
||||
>
|
||||
Custom
|
||||
|
@ -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<SeriesColorPickerProps> {
|
||||
private pickerTriggerRef = createRef<PopperJS.ReferenceObject>();
|
||||
colorPickerDrop: any;
|
||||
|
||||
static defaultProps = {
|
||||
optionalClass: '',
|
||||
yaxis: undefined,
|
||||
onToggleAxis: () => {},
|
||||
};
|
||||
|
||||
renderPickerTabs = () => {
|
||||
const { color, yaxis, onChange, onToggleAxis, theme } = this.props;
|
||||
return (
|
||||
<SeriesColorPickerPopover
|
||||
theme={theme}
|
||||
color={color}
|
||||
yaxis={yaxis}
|
||||
onChange={onChange}
|
||||
onToggleAxis={onToggleAxis}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
render() {
|
||||
const { children, theme } = this.props;
|
||||
return (
|
||||
<PopperController placement="bottom-start" content={this.renderPickerTabs()}>
|
||||
{(showPopper, hidePopper, popperProps) => {
|
||||
return (
|
||||
<>
|
||||
{this.pickerTriggerRef.current && (
|
||||
<Popper
|
||||
{...popperProps}
|
||||
onMouseEnter={showPopper}
|
||||
onMouseLeave={hidePopper}
|
||||
referenceElement={this.pickerTriggerRef.current}
|
||||
wrapperClassName="ColorPicker"
|
||||
renderArrow={({ arrowProps, placement }) => {
|
||||
return (
|
||||
<div
|
||||
{...arrowProps}
|
||||
data-placement={placement}
|
||||
className={`ColorPicker__arrow ColorPicker__arrow--${
|
||||
theme === GrafanaTheme.Light ? 'light' : 'dark'
|
||||
}`}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
{React.cloneElement(children, {
|
||||
ref: this.pickerTriggerRef,
|
||||
onClick: showPopper,
|
||||
onMouseLeave: hidePopper,
|
||||
})}
|
||||
</>
|
||||
);
|
||||
}}
|
||||
</PopperController>
|
||||
);
|
||||
}
|
||||
}
|
||||
export default colorPickerFactory(SeriesColorPickerPopover ,'SeriesColorPicker')
|
||||
|
@ -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<SeriesColorPickerPopove
|
||||
theme,
|
||||
yaxis,
|
||||
onToggleAxis,
|
||||
updatePopperPosition
|
||||
}) => {
|
||||
return (
|
||||
<ColorPickerPopover theme={theme} color={color} onChange={onChange}>
|
||||
<ColorPickerPopover theme={theme} color={color} onChange={onChange} updatePopperPosition={updatePopperPosition}>
|
||||
<div style={{ marginTop: '32px' }}>{yaxis && <AxisSelector yaxis={yaxis} onToggleAxis={onToggleAxis} />}</div>
|
||||
</ColorPickerPopover>
|
||||
);
|
||||
|
@ -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,
|
||||
|
@ -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';
|
||||
|
||||
|
@ -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<HTMLDivElement> {
|
||||
show: boolean;
|
||||
placement?: PopperJS.Placement;
|
||||
content: PopperContent;
|
||||
content: PopperContent<any>;
|
||||
referenceElement: PopperJS.ReferenceObject;
|
||||
wrapperClassName?: string;
|
||||
renderArrow?: (
|
||||
props: {
|
||||
arrowProps: PopperArrowProps;
|
||||
placement: string;
|
||||
}
|
||||
) => JSX.Element;
|
||||
renderArrow?: RenderPopperArrowFn;
|
||||
}
|
||||
|
||||
class Popper extends PureComponent<Props> {
|
||||
@ -47,7 +49,7 @@ class Popper extends PureComponent<Props> {
|
||||
// TODO: move modifiers config to popper controller
|
||||
modifiers={{ preventOverflow: { enabled: true, boundariesElement: 'window' } }}
|
||||
>
|
||||
{({ ref, style, placement, arrowProps }) => {
|
||||
{({ ref, style, placement, arrowProps, scheduleUpdate }) => {
|
||||
return (
|
||||
<div
|
||||
onMouseEnter={onMouseEnter}
|
||||
@ -62,7 +64,11 @@ class Popper extends PureComponent<Props> {
|
||||
className={`${wrapperClassName}`}
|
||||
>
|
||||
<div className={className}>
|
||||
{content}
|
||||
{typeof content === 'string'
|
||||
? content
|
||||
: React.cloneElement(content, {
|
||||
updatePopperPosition: scheduleUpdate,
|
||||
})}
|
||||
{renderArrow &&
|
||||
renderArrow({
|
||||
arrowProps,
|
||||
|
@ -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<T extends PopperContentProps> = string | React.ReactElement<T>;
|
||||
|
||||
export interface UsingPopperProps {
|
||||
show?: boolean;
|
||||
placement?: PopperJS.Placement;
|
||||
content: PopperContent;
|
||||
content: PopperContent<any>;
|
||||
children: JSX.Element;
|
||||
}
|
||||
|
||||
@ -16,13 +20,13 @@ type PopperControllerRenderProp = (
|
||||
popperProps: {
|
||||
show: boolean;
|
||||
placement: PopperJS.Placement;
|
||||
content: PopperContent;
|
||||
content: PopperContent<any>;
|
||||
}
|
||||
) => JSX.Element;
|
||||
|
||||
interface Props {
|
||||
placement?: PopperJS.Placement;
|
||||
content: PopperContent;
|
||||
content: PopperContent<any>;
|
||||
className?: string;
|
||||
children: PopperControllerRenderProp;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -172,14 +172,13 @@ class LegendSeriesIcon extends PureComponent<LegendSeriesIconProps, LegendSeries
|
||||
{theme => {
|
||||
return (
|
||||
<SeriesColorPicker
|
||||
optionalClass="graph-legend-icon"
|
||||
yaxis={this.props.yaxis}
|
||||
color={this.props.color}
|
||||
onChange={this.props.onColorChange}
|
||||
onToggleAxis={this.props.onToggleAxis}
|
||||
theme={theme}
|
||||
>
|
||||
<span>
|
||||
<span className="graph-legend-icon">
|
||||
<SeriesIcon color={this.props.color} />
|
||||
</span>
|
||||
</SeriesColorPicker>
|
||||
|
Loading…
Reference in New Issue
Block a user