From 3e35021f7ec0208bb2a23375329ab35a1227bd4a Mon Sep 17 00:00:00 2001 From: nikki-kiga <42276368+nikki-kiga@users.noreply.github.com> Date: Mon, 26 Jul 2021 11:11:53 -0700 Subject: [PATCH] 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 Co-authored-by: Ryan McKinley --- .../grafana-data/src/field/fieldOverrides.ts | 5 ++- .../src/themes/colorManipulator.ts | 15 +++++++++ .../ColorPicker/ColorPickerPopover.test.tsx | 4 +-- .../ColorPicker/ColorPickerPopover.tsx | 5 ++- .../ColorPicker/SpectrumPalette.tsx | 33 +++++++++++-------- .../src/components/OptionsUI/color.tsx | 26 +++++++-------- 6 files changed, 54 insertions(+), 34 deletions(-) diff --git a/packages/grafana-data/src/field/fieldOverrides.ts b/packages/grafana-data/src/field/fieldOverrides.ts index 424c03712bc..70ba87247bd 100644 --- a/packages/grafana-data/src/field/fieldOverrides.ts +++ b/packages/grafana-data/src/field/fieldOverrides.ts @@ -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); diff --git a/packages/grafana-data/src/themes/colorManipulator.ts b/packages/grafana-data/src/themes/colorManipulator.ts index a8bd5934a9f..b76552ed0c1 100644 --- a/packages/grafana-data/src/themes/colorManipulator.ts +++ b/packages/grafana-data/src/themes/colorManipulator.ts @@ -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 diff --git a/packages/grafana-ui/src/components/ColorPicker/ColorPickerPopover.test.tsx b/packages/grafana-ui/src/components/ColorPicker/ColorPickerPopover.test.tsx index 1ce5b2f0700..b7398bdd77b 100644 --- a/packages/grafana-ui/src/components/ColorPicker/ColorPickerPopover.test.tsx +++ b/packages/grafana-ui/src/components/ColorPicker/ColorPickerPopover.test.tsx @@ -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', () => { diff --git a/packages/grafana-ui/src/components/ColorPicker/ColorPickerPopover.tsx b/packages/grafana-ui/src/components/ColorPicker/ColorPickerPopover.tsx index 4e1abf412ce..249517eead8 100644 --- a/packages/grafana-ui/src/components/ColorPicker/ColorPickerPopover.tsx +++ b/packages/grafana-ui/src/components/ColorPicker/ColorPickerPopover.tsx @@ -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 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) => { diff --git a/packages/grafana-ui/src/components/ColorPicker/SpectrumPalette.tsx b/packages/grafana-ui/src/components/ColorPicker/SpectrumPalette.tsx index 1c305c38065..ed0ce74a470 100644 --- a/packages/grafana-ui/src/components/ColorPicker/SpectrumPalette.tsx +++ b/packages/grafana-ui/src/components/ColorPicker/SpectrumPalette.tsx @@ -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 = ({ 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 (
@@ -34,7 +41,7 @@ const SpectrumPalette: React.FunctionComponent = ({ 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)}; `, }); diff --git a/packages/grafana-ui/src/components/OptionsUI/color.tsx b/packages/grafana-ui/src/components/OptionsUI/color.tsx index 9cf5c5cd4c4..8c8d926818d 100644 --- a/packages/grafana-ui/src/components/OptionsUI/color.tsx +++ b/packages/grafana-ui/src/components/OptionsUI/color.tsx @@ -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 = ({ value, onChange }) => { - const theme = useTheme(); - const styles = getStyles(theme); + const theme = useTheme2(); + const styles = useStyles2(getStyles); return ( @@ -30,7 +30,7 @@ export const ColorValueEditor: React.FC = ({ 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} />
@@ -40,23 +40,23 @@ export const ColorValueEditor: React.FC = ({ 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}; } `, }; -}); +};