ColorPicker: return values as hex strings and update theme (#37173)

* Color: fix alpha calculation < 16/255 and State-timeline threshold alpha

* SpectrumPalette: Update to getColorByName

* ColorValueEditor: Update to theme2

* SpectrumPalette: Update to theme2

* ColorPickerPopover: Format hex color

* SpectrumPalette: Format hex color

* SpectrumPalette: Refactor hex color alpha helper

* SpectrumPalette: Fix import and update helper

* ColorPickerPopover: Fix test to not make lowercase

Co-authored-by: Leon Sorokin <leeoniya@gmail.com>
Co-authored-by: Ryan McKinley <ryantxu@gmail.com>
This commit is contained in:
nikki-kiga 2021-07-26 11:11:53 -07:00 committed by GitHub
parent be4b753aa0
commit 3e35021f7e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 54 additions and 34 deletions

View File

@ -32,7 +32,7 @@ import { getFrameDisplayName } from './fieldState';
import { getTimeField } from '../dataframe/processDataFrame';
import { mapInternalLinkToExplore } from '../utils/dataLinks';
import { getTemplateProxyForField } from './templateProxies';
import tinycolor from 'tinycolor2';
import { asHexString } from '../themes/colorManipulator';
interface OverrideProps {
match: FieldMatcher;
@ -227,8 +227,7 @@ function cachingDisplayProcessor(disp: DisplayProcessor, maxCacheSize = 2500): D
// convert to hex6 or hex8 so downstream we can cheaply test for alpha (and set new alpha)
// via a simple length check (in colorManipulator) rather using slow parsing via tinycolor
if (v.color && v.color[0] !== '#') {
let color = tinycolor(v.color);
v.color = color.getAlpha() < 1 ? color.toHex8String() : color.toHexString();
v.color = asHexString(v.color);
}
cache.set(value, v);

View File

@ -2,6 +2,8 @@
// https://github.com/mui-org/material-ui/blob/1b096070faf102281f8e3c4f9b2bf50acf91f412/packages/material-ui/src/styles/colorManipulator.js#L97
// MIT License Copyright (c) 2014 Call-Em-All
import tinycolor from 'tinycolor2';
/**
* Returns a number whose value is limited to the given range.
* @param value The value to be clamped
@ -66,6 +68,19 @@ export function rgbToHex(color: string) {
return `#${values.map((n: number) => intToHex(n)).join('')}`;
}
/**
* Converts a color to hex6 format if there is no alpha, hex8 if there is.
* @param color - Hex, RGB, HSL color
* @returns A hex color string, i.e. #ff0000 or #ff0000ff
*/
export function asHexString(color: string): string {
if (color[0] === '#') {
return color;
}
const tColor = tinycolor(color);
return tColor.getAlpha() === 1 ? tColor.toHexString() : tColor.toHex8String();
}
/**
* Converts a color from hsl format to rgb format.
* @param color - HSL color values

View File

@ -2,7 +2,7 @@ import React from 'react';
import { mount, ReactWrapper } from 'enzyme';
import { ColorPickerPopover } from './ColorPickerPopover';
import { ColorSwatch } from './ColorSwatch';
import { createTheme, getColorForTheme } from '@grafana/data';
import { createTheme } from '@grafana/data';
describe('ColorPickerPopover', () => {
const theme = createTheme();
@ -35,7 +35,7 @@ describe('ColorPickerPopover', () => {
basicBlueSwatch.simulate('click');
expect(onChangeSpy).toBeCalledTimes(1);
expect(onChangeSpy).toBeCalledWith(getColorForTheme('green', theme.v1));
expect(onChangeSpy).toBeCalledWith(theme.visualization.getColorByName('green'));
});
it('should pass color name to onChange prop when named colors enabled', () => {

View File

@ -5,7 +5,7 @@ import SpectrumPalette from './SpectrumPalette';
import { Themeable2 } from '../../types/theme';
import { warnAboutColorPickerPropsDeprecation } from './warnAboutColorPickerPropsDeprecation';
import { css } from '@emotion/css';
import { GrafanaTheme2 } from '@grafana/data';
import { GrafanaTheme2, colorManipulator } from '@grafana/data';
import { stylesFactory, withTheme2 } from '../../themes';
export type ColorPickerChangeHandler = (color: string) => void;
@ -55,11 +55,10 @@ class UnThemedColorPickerPopover<T extends CustomPickersDescriptor> extends Reac
handleChange = (color: any) => {
const { onColorChange, onChange, enableNamedColors, theme } = this.props;
const changeHandler = onColorChange || onChange;
if (enableNamedColors) {
return changeHandler(color);
}
changeHandler(theme.visualization.getColorByName(color));
changeHandler(colorManipulator.asHexString(theme.visualization.getColorByName(color)));
};
onTabChange = (tab: PickerType | keyof T) => {

View File

@ -3,9 +3,9 @@ import React, { useMemo, useState } from 'react';
import { RgbaStringColorPicker } from 'react-colorful';
import tinycolor from 'tinycolor2';
import ColorInput from './ColorInput';
import { GrafanaTheme, getColorForTheme } from '@grafana/data';
import { GrafanaTheme2, colorManipulator } from '@grafana/data';
import { css, cx } from '@emotion/css';
import { useStyles, useTheme2 } from '../../themes';
import { useStyles2, useTheme2 } from '../../themes';
import { useThrottleFn } from 'react-use';
export interface SpectrumPaletteProps {
@ -15,16 +15,23 @@ export interface SpectrumPaletteProps {
const SpectrumPalette: React.FunctionComponent<SpectrumPaletteProps> = ({ color, onChange }) => {
const [currentColor, setColor] = useState(color);
useThrottleFn(onChange, 500, [currentColor]);
useThrottleFn(
(c) => {
onChange(colorManipulator.asHexString(theme.visualization.getColorByName(c)));
},
500,
[currentColor]
);
const theme = useTheme2();
const styles = useStyles(getStyles);
const styles = useStyles2(getStyles);
const rgbaString = useMemo(() => {
return currentColor.startsWith('rgba')
? currentColor
: tinycolor(getColorForTheme(currentColor, theme.v1)).toRgbString();
}, [currentColor, theme]);
: tinycolor(theme.visualization.getColorByName(color)).toRgbString();
}, [currentColor, theme, color]);
return (
<div className={styles.wrapper}>
@ -34,7 +41,7 @@ const SpectrumPalette: React.FunctionComponent<SpectrumPaletteProps> = ({ color,
);
};
const getStyles = (theme: GrafanaTheme) => ({
const getStyles = (theme: GrafanaTheme2) => ({
wrapper: css`
flex-grow: 1;
`,
@ -45,24 +52,24 @@ const getStyles = (theme: GrafanaTheme) => ({
.react-colorful {
&__saturation {
border-radius: ${theme.border.radius.sm} ${theme.border.radius.sm} 0 0;
border-radius: ${theme.v1.border.radius.sm} ${theme.v1.border.radius.sm} 0 0;
}
&__alpha {
border-radius: 0 0 ${theme.border.radius.sm} ${theme.border.radius.sm};
border-radius: 0 0 ${theme.v1.border.radius.sm} ${theme.v1.border.radius.sm};
}
&__alpha,
&__hue {
height: ${theme.spacing.md};
height: ${theme.spacing(2)};
position: relative;
}
&__pointer {
height: ${theme.spacing.md};
width: ${theme.spacing.md};
height: ${theme.spacing(2)};
width: ${theme.spacing(2)};
}
}
`,
colorInput: css`
margin-top: ${theme.spacing.md};
margin-top: ${theme.spacing(2)};
`,
});

View File

@ -1,7 +1,7 @@
import React from 'react';
import { getColorForTheme, GrafanaTheme } from '@grafana/data';
import { GrafanaTheme2 } from '@grafana/data';
import { ColorPicker } from '../ColorPicker/ColorPicker';
import { stylesFactory, useTheme } from '../../themes';
import { useTheme2, useStyles2 } from '../../themes';
import { css } from '@emotion/css';
import { ColorSwatch } from '../ColorPicker/ColorSwatch';
@ -17,8 +17,8 @@ export interface ColorValueEditorProps {
* @alpha
* */
export const ColorValueEditor: React.FC<ColorValueEditorProps> = ({ value, onChange }) => {
const theme = useTheme();
const styles = getStyles(theme);
const theme = useTheme2();
const styles = useStyles2(getStyles);
return (
<ColorPicker color={value ?? ''} onChange={onChange} enableNamedColors={true}>
@ -30,7 +30,7 @@ export const ColorValueEditor: React.FC<ColorValueEditorProps> = ({ value, onCha
ref={ref}
onClick={showColorPicker}
onMouseLeave={hideColorPicker}
color={value ? getColorForTheme(value, theme) : theme.colors.formInputBorder}
color={value ? theme.visualization.getColorByName(value) : theme.components.input.borderColor}
/>
</div>
</div>
@ -40,23 +40,23 @@ export const ColorValueEditor: React.FC<ColorValueEditorProps> = ({ value, onCha
);
};
const getStyles = stylesFactory((theme: GrafanaTheme) => {
const getStyles = (theme: GrafanaTheme2) => {
return {
spot: css`
color: ${theme.colors.text};
background: ${theme.colors.formInputBg};
background: ${theme.components.input.background};
padding: 3px;
height: ${theme.spacing.formInputHeight}px;
border: 1px solid ${theme.colors.formInputBorder};
height: ${theme.v1.spacing.formInputHeight}px;
border: 1px solid ${theme.components.input.borderColor};
display: flex;
flex-direction: row;
align-items: center;
&:hover {
border: 1px solid ${theme.colors.formInputBorderHover};
border: 1px solid ${theme.components.input.borderHover};
}
`,
colorPicker: css`
padding: 0 ${theme.spacing.sm};
padding: 0 ${theme.spacing(1)};
`,
colorText: css`
cursor: pointer;
@ -64,10 +64,10 @@ const getStyles = stylesFactory((theme: GrafanaTheme) => {
`,
trashIcon: css`
cursor: pointer;
color: ${theme.colors.textWeak};
color: ${theme.colors.text.secondary};
&:hover {
color: ${theme.colors.text};
}
`,
};
});
};