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 { getTimeField } from '../dataframe/processDataFrame';
import { mapInternalLinkToExplore } from '../utils/dataLinks'; import { mapInternalLinkToExplore } from '../utils/dataLinks';
import { getTemplateProxyForField } from './templateProxies'; import { getTemplateProxyForField } from './templateProxies';
import tinycolor from 'tinycolor2'; import { asHexString } from '../themes/colorManipulator';
interface OverrideProps { interface OverrideProps {
match: FieldMatcher; 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) // 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 // via a simple length check (in colorManipulator) rather using slow parsing via tinycolor
if (v.color && v.color[0] !== '#') { if (v.color && v.color[0] !== '#') {
let color = tinycolor(v.color); v.color = asHexString(v.color);
v.color = color.getAlpha() < 1 ? color.toHex8String() : color.toHexString();
} }
cache.set(value, v); 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 // 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 // MIT License Copyright (c) 2014 Call-Em-All
import tinycolor from 'tinycolor2';
/** /**
* Returns a number whose value is limited to the given range. * Returns a number whose value is limited to the given range.
* @param value The value to be clamped * @param value The value to be clamped
@ -66,6 +68,19 @@ export function rgbToHex(color: string) {
return `#${values.map((n: number) => intToHex(n)).join('')}`; 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. * Converts a color from hsl format to rgb format.
* @param color - HSL color values * @param color - HSL color values

View File

@ -2,7 +2,7 @@ import React from 'react';
import { mount, ReactWrapper } from 'enzyme'; import { mount, ReactWrapper } from 'enzyme';
import { ColorPickerPopover } from './ColorPickerPopover'; import { ColorPickerPopover } from './ColorPickerPopover';
import { ColorSwatch } from './ColorSwatch'; import { ColorSwatch } from './ColorSwatch';
import { createTheme, getColorForTheme } from '@grafana/data'; import { createTheme } from '@grafana/data';
describe('ColorPickerPopover', () => { describe('ColorPickerPopover', () => {
const theme = createTheme(); const theme = createTheme();
@ -35,7 +35,7 @@ describe('ColorPickerPopover', () => {
basicBlueSwatch.simulate('click'); basicBlueSwatch.simulate('click');
expect(onChangeSpy).toBeCalledTimes(1); 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', () => { 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 { Themeable2 } from '../../types/theme';
import { warnAboutColorPickerPropsDeprecation } from './warnAboutColorPickerPropsDeprecation'; import { warnAboutColorPickerPropsDeprecation } from './warnAboutColorPickerPropsDeprecation';
import { css } from '@emotion/css'; import { css } from '@emotion/css';
import { GrafanaTheme2 } from '@grafana/data'; import { GrafanaTheme2, colorManipulator } from '@grafana/data';
import { stylesFactory, withTheme2 } from '../../themes'; import { stylesFactory, withTheme2 } from '../../themes';
export type ColorPickerChangeHandler = (color: string) => void; export type ColorPickerChangeHandler = (color: string) => void;
@ -55,11 +55,10 @@ class UnThemedColorPickerPopover<T extends CustomPickersDescriptor> extends Reac
handleChange = (color: any) => { handleChange = (color: any) => {
const { onColorChange, onChange, enableNamedColors, theme } = this.props; const { onColorChange, onChange, enableNamedColors, theme } = this.props;
const changeHandler = onColorChange || onChange; const changeHandler = onColorChange || onChange;
if (enableNamedColors) { if (enableNamedColors) {
return changeHandler(color); return changeHandler(color);
} }
changeHandler(theme.visualization.getColorByName(color)); changeHandler(colorManipulator.asHexString(theme.visualization.getColorByName(color)));
}; };
onTabChange = (tab: PickerType | keyof T) => { onTabChange = (tab: PickerType | keyof T) => {

View File

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

View File

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