From ac378790166f957e67baa745e0e3fb4f71fce208 Mon Sep 17 00:00:00 2001 From: Dominik Prokop Date: Wed, 16 Jan 2019 18:22:53 +0100 Subject: [PATCH] WIP Basics of named color picker --- .../ColorPicker/ColorPicker.story.tsx | 12 +- .../components/ColorPicker/ColorPicker.tsx | 3 + .../ColorPicker/NamedColorsPicker.story.tsx | 68 ++++++++++ .../ColorPicker/NamedColorsPicker.tsx | 121 +++++++++++++++++ packages/grafana-ui/src/utils/colors.ts | 1 - .../src/utils/colorsPalette.test.ts | 31 +++++ .../grafana-ui/src/utils/colorsPalette.ts | 125 ++++++++++++++++++ yarn.lock | 6 +- 8 files changed, 358 insertions(+), 9 deletions(-) create mode 100644 packages/grafana-ui/src/components/ColorPicker/NamedColorsPicker.story.tsx create mode 100644 packages/grafana-ui/src/components/ColorPicker/NamedColorsPicker.tsx create mode 100644 packages/grafana-ui/src/utils/colorsPalette.test.ts create mode 100644 packages/grafana-ui/src/utils/colorsPalette.ts diff --git a/packages/grafana-ui/src/components/ColorPicker/ColorPicker.story.tsx b/packages/grafana-ui/src/components/ColorPicker/ColorPicker.story.tsx index 234b4fa3a2e..6e029f3cfa8 100644 --- a/packages/grafana-ui/src/components/ColorPicker/ColorPicker.story.tsx +++ b/packages/grafana-ui/src/components/ColorPicker/ColorPicker.story.tsx @@ -1,7 +1,6 @@ import React, { FunctionComponent } from 'react'; import { storiesOf } from '@storybook/react'; -import { ColorPicker } from '@grafana/ui'; -import { withInfo } from '@storybook/addon-info'; +import { ColorPickerPopover } from './ColorPickerPopover'; const CenteredStory: FunctionComponent<{}> = ({ children }) => { return ( @@ -20,6 +19,9 @@ const CenteredStory: FunctionComponent<{}> = ({ children }) => { storiesOf('UI/ColorPicker', module) .addDecorator(story => {story()}) - .add('default', withInfo({inline: true})(() => { - return {}} />; - })); + // .add('Color picker popover', () => { + // return {}} />; + // }) + .add('Named colors swatch', () => { + return {}} />; + }); diff --git a/packages/grafana-ui/src/components/ColorPicker/ColorPicker.tsx b/packages/grafana-ui/src/components/ColorPicker/ColorPicker.tsx index 141d616d4c5..682d2f1c605 100644 --- a/packages/grafana-ui/src/components/ColorPicker/ColorPicker.tsx +++ b/packages/grafana-ui/src/components/ColorPicker/ColorPicker.tsx @@ -2,6 +2,7 @@ import React, { Component } from 'react'; import ReactDOM from 'react-dom'; import Drop from 'tether-drop'; import { ColorPickerPopover } from './ColorPickerPopover'; +import { Color } from '../../utils/colorsPalette'; interface Props { /** @@ -9,6 +10,7 @@ interface Props { * * @default " " **/ + name?: Color; color: string; onChange: (c: string) => void; } @@ -55,6 +57,7 @@ export class ColorPicker extends Component { }; render() { + return (
(this.pickerElem = element)}>
diff --git a/packages/grafana-ui/src/components/ColorPicker/NamedColorsPicker.story.tsx b/packages/grafana-ui/src/components/ColorPicker/NamedColorsPicker.story.tsx new file mode 100644 index 00000000000..1cdf01a2dd3 --- /dev/null +++ b/packages/grafana-ui/src/components/ColorPicker/NamedColorsPicker.story.tsx @@ -0,0 +1,68 @@ +import React, { FunctionComponent } from 'react'; +import { storiesOf } from '@storybook/react'; +import NamedColorsPicker from './NamedColorsPicker'; +import { Color, getColorName } from '@grafana/ui/src/utils/colorsPalette'; + +const CenteredStory: FunctionComponent<{}> = ({ children }) => { + return ( +
+ {children} +
+ ); +}; + +interface StateHolderProps { + initialState: T; + children: (currentState: T, updateState: (nextState: T) => void) => JSX.Element; +} + +class UseState extends React.Component, {value: T}> { + constructor(props: StateHolderProps) { + super(props) + this.state = { + value: props.initialState + } + } + handleStateUpdate = (nextState: T) => { + this.setState({value: nextState}) + } + render() { + return this.props.children(this.state.value, this.handleStateUpdate) + } +} + +storiesOf('UI/ColorPicker', module) + .addDecorator(story => {story()}) + .add('Named colors swatch - support for named colors', () => { + return( + + {(selectedColor, updateSelectedColor) => { + return ( + { updateSelectedColor(color.name);}} + /> + ) + }} + ); + }) + .add('Named colors swatch - support for hex values', () => { + return( + + {(selectedColor, updateSelectedColor) => { + return ( + updateSelectedColor(color.variants.dark)} + /> + ) + }} + ); + }); diff --git a/packages/grafana-ui/src/components/ColorPicker/NamedColorsPicker.tsx b/packages/grafana-ui/src/components/ColorPicker/NamedColorsPicker.tsx new file mode 100644 index 00000000000..a0732e4e21d --- /dev/null +++ b/packages/grafana-ui/src/components/ColorPicker/NamedColorsPicker.tsx @@ -0,0 +1,121 @@ +import React, { FunctionComponent } from 'react'; +import { find, upperFirst } from 'lodash'; +import { Color, ColorsPalete, ColorDefinition } from '../../utils/colorsPalette'; + +type ColorChangeHandler = (color: ColorDefinition) => void; + +enum ColorSwatchVariant { + Small = 'small', + Large = 'large', +} + +interface ColorSwatchProps extends React.DOMAttributes { + color: ColorDefinition; + variant?: ColorSwatchVariant; + isSelected?: boolean; +} + +const ColorSwatch: FunctionComponent = ({ + color, + variant = ColorSwatchVariant.Small, + isSelected, + ...otherProps +}) => { + const isSmall = variant === ColorSwatchVariant.Small; + const swatchSize = isSmall ? '16px' : '32px'; + const swatchStyles = { + width: swatchSize, + height: swatchSize, + borderRadius: '50%', + background: `${color.variants.dark}`, + marginRight: isSmall ? '0px' : '8px', + boxShadow: isSelected ? `inset 0 0 0 2px ${color.variants.dark}, inset 0 0 0 4px white` : 'none', + cursor: isSelected ? 'default' : 'pointer' + }; + + return ( +
+
+ {variant === ColorSwatchVariant.Large && {upperFirst(color.hue)}} +
+ ); +}; + +const ColorsGroup = ({ + colors, + selectedColor, + onColorSelect, +}: { + colors: ColorDefinition[]; + selectedColor?: Color; + onColorSelect: ColorChangeHandler +}) => { + const primaryColor = find(colors, color => !!color.isPrimary); + + return ( +
+ {primaryColor && ( + onColorSelect(primaryColor)} + /> + )} +
+ {colors.map(color => !color.isPrimary && ( +
+ onColorSelect(color)} + /> +
+ ))} +
+
+ ); +}; + + +interface NamedColorsPickerProps { + selectedColor?: Color; + onChange: ColorChangeHandler; +} +const NamedColorsPicker = ({ selectedColor, onChange }: NamedColorsPickerProps) => { + const swatches: JSX.Element[] = []; + + ColorsPalete.forEach((colors, hue) => { + swatches.push( + <> + + + ); + }); + + return ( +
+ {swatches} +
+ ); +}; + +export default NamedColorsPicker; diff --git a/packages/grafana-ui/src/utils/colors.ts b/packages/grafana-ui/src/utils/colors.ts index 263d128aec4..65ee605c33b 100644 --- a/packages/grafana-ui/src/utils/colors.ts +++ b/packages/grafana-ui/src/utils/colors.ts @@ -9,7 +9,6 @@ export const ALERTING_COLOR = 'rgba(237, 46, 24, 1)'; export const NO_DATA_COLOR = 'rgba(150, 150, 150, 1)'; export const PENDING_COLOR = 'rgba(247, 149, 32, 1)'; export const REGION_FILL_ALPHA = 0.09; - export const colors = [ '#7EB26D', // 0: pale green '#EAB839', // 1: mustard diff --git a/packages/grafana-ui/src/utils/colorsPalette.test.ts b/packages/grafana-ui/src/utils/colorsPalette.test.ts new file mode 100644 index 00000000000..41a615410ef --- /dev/null +++ b/packages/grafana-ui/src/utils/colorsPalette.test.ts @@ -0,0 +1,31 @@ +import { getColorName, getColorDefinition, ColorsPalete, buildColorDefinition } from './colorsPalette'; + +describe('colors', () => { + const FakeBlue = buildColorDefinition('blue', 'blue', ['#0000ff', '#00000ee']); + + beforeAll(() => { + ColorsPalete.set('blue', [FakeBlue]) + }); + + describe('getColorDefinition', () => { + it('returns undefined for unknown hex', () => { + expect(getColorDefinition('#ff0000')).toBeUndefined(); + }); + + it('returns definition for known hex', () => { + expect(getColorDefinition(FakeBlue.variants.light)).toEqual(FakeBlue); + expect(getColorDefinition(FakeBlue.variants.dark)).toEqual(FakeBlue); + }); + }); + + describe('getColorName', () => { + it('returns undefined for unknown hex', () => { + expect(getColorName('#ff0000')).toBeUndefined(); + }); + + it('returns name for known hex', () => { + expect(getColorName(FakeBlue.variants.light)).toEqual(FakeBlue.name); + expect(getColorName(FakeBlue.variants.dark)).toEqual(FakeBlue.name); + }); + }); +}); diff --git a/packages/grafana-ui/src/utils/colorsPalette.ts b/packages/grafana-ui/src/utils/colorsPalette.ts new file mode 100644 index 00000000000..2f6300b1e18 --- /dev/null +++ b/packages/grafana-ui/src/utils/colorsPalette.ts @@ -0,0 +1,125 @@ +import { flatten, some, values } from 'lodash'; + +type Hue = 'green' | 'yellow' | 'red' | 'blue' | 'orange' | 'purple'; + +export type Color = + | 'green' + | 'dark-green' + | 'semi-dark-green' + | 'light-green' + | 'super-light-green' + | 'yellow' + | 'dark-yellow' + | 'semi-dark-yellow' + | 'light-yellow' + | 'super-light-yellow' + | 'red' + | 'dark-red' + | 'semi-dark-red' + | 'light-red' + | 'super-light-red' + | 'blue' + | 'dark-blue' + | 'semi-dark-blue' + | 'light-blue' + | 'super-light-blue' + | 'orange' + | 'dark-orange' + | 'semi-dark-orange' + | 'light-orange' + | 'super-light-orange' + | 'purple' + | 'dark-purple' + | 'semi-dark-purple' + | 'light-purple' + | 'super-light-purple'; + +type ThemeVariants = { + dark: string; + light: string; +}; +export type ColorDefinition = { + hue: Hue; + isPrimary?: boolean; + name: Color; + variants: ThemeVariants; +}; + +export const ColorsPalete = new Map(); + +export const buildColorDefinition = ( + hue: Hue, + name: Color, + [light, dark]: string[], + isPrimary?: boolean +): ColorDefinition => ({ + hue, + name, + variants: { + light, + dark, + }, + isPrimary: !!isPrimary, +}); + +export const BasicGreen = buildColorDefinition('green', 'green', ['#53A642', '#53A642'], true); +export const DarkGreen = buildColorDefinition('green', 'dark-green', ['#106100', '#106100']); +export const SemiDarkGreen = buildColorDefinition('green', 'semi-dark-green', ['#388729', '#388729']); +export const LightGreen = buildColorDefinition('green', 'light-green', ['#72C462', '#72C462']); +export const SuperLightGreen = buildColorDefinition('green', 'super-light-green', ['#99E68A', '#99E68A']); + +export const BasicYellow = buildColorDefinition('yellow', 'yellow', ['#F2CA00', '#F2CA00'], true); +export const DarkYellow = buildColorDefinition('yellow', 'dark-yellow', ['#A68A00', '#A68A00']); +export const SemiDarkYellow = buildColorDefinition('yellow', 'semi-dark-yellow', ['#CCAA00', '#CCAA00']); +export const LightYellow = buildColorDefinition('yellow', 'light-yellow', ['#F7D636', '#F7D636']); +export const SuperLightYellow = buildColorDefinition('yellow', 'super-light-yellow', ['#FFEB8A', '#FFEB8A']); + +export const BasicRed = buildColorDefinition('red', 'red', ['#F94462', '#F94462'], true); +export const DarkRed = buildColorDefinition('red', 'dark-red', ['#B3001D', '#B3001D']); +export const SemiDarkRed = buildColorDefinition('red', 'semi-dark-red', ['#D93651', '#D93651']); +export const LightRed = buildColorDefinition('red', 'light-red', ['#F07387', '#F07387']); +export const SuperLightRed = buildColorDefinition('red', 'super-light-red', ['#E6A1AC', '#E6A1AC']); + +export const BasicBlue = buildColorDefinition('blue', 'blue', ['#408BFF', '#408BFF'], true); +export const DarkBlue = buildColorDefinition('blue', 'dark-blue', ['#2155A6', '#2155A6']); +export const SemiDarkBlue = buildColorDefinition('blue', 'semi-dark-blue', ['#3471CF', '#3471CF']); +export const LightBlue = buildColorDefinition('blue', 'light-blue', ['#7DAEFA', '#7DAEFA']); +export const SuperLightBlue = buildColorDefinition('blue', 'super-light-blue', ['#B8D0F5', '#B8D0F5']); + +export const BasicOrange = buildColorDefinition('orange', 'orange', ['#FA6400', '#FA6400'], true); +export const DarkOrange = buildColorDefinition('orange', 'dark-orange', ['#963C00', '#963C00']); +export const SemiDarkOrange = buildColorDefinition('orange', 'semi-dark-orange', ['#ED4B00', '#ED4B00']); +export const LightOrange = buildColorDefinition('orange', 'light-orange', ['#FC934C', '#FC934C']); +export const SuperLightOrange = buildColorDefinition('orange', 'super-light-orange', ['#FFC299', '#FFC299']); + +export const BasicPurple = buildColorDefinition('purple', 'purple', ['#BC67E6', '#BC67E6'], true); +export const DarkPurple = buildColorDefinition('purple', 'dark-purple', ['#701F99', '#701F99']); +export const SemiDarkPurple = buildColorDefinition('purple', 'semi-dark-purple', ['#9E43CC', '#9E43CC']); +export const LightPurple = buildColorDefinition('purple', 'light-purple', ['#D19AED', '#D19AED']); +export const SuperLightPurple = buildColorDefinition('purple', 'super-light-purple', ['#E6CEF2', '#E6CEF2']); + +const greens = [BasicGreen, DarkGreen, SemiDarkGreen, LightGreen, SuperLightGreen]; +const yellows = [BasicYellow, DarkYellow, SemiDarkYellow, LightYellow, SuperLightYellow]; +const reds = [BasicRed, DarkRed, SemiDarkRed, LightRed, SuperLightRed]; +const blues = [BasicBlue, DarkBlue, SemiDarkBlue, LightBlue, SuperLightBlue]; +const oranges = [BasicOrange, DarkOrange, SemiDarkOrange, LightOrange, SuperLightOrange]; +const purples = [BasicPurple, DarkPurple, SemiDarkPurple, LightPurple, SuperLightPurple]; + +ColorsPalete.set('green', greens); +ColorsPalete.set('yellow', yellows); +ColorsPalete.set('red', reds); +ColorsPalete.set('blue', blues); +ColorsPalete.set('orange', oranges); +ColorsPalete.set('purple', purples); + +export const getColorDefinition = (hex: string): ColorDefinition | undefined => { + return flatten(Array.from(ColorsPalete.values())).filter(definition => + some(values(definition.variants), color => color === hex) + )[0]; +}; + +export const getColorName = (hex: string): Color | undefined => { + const definition = getColorDefinition(hex); + + return definition ? definition.name : undefined; +}; diff --git a/yarn.lock b/yarn.lock index 1aa0d7f20c3..32881e10817 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1673,7 +1673,7 @@ resolved "https://registry.yarnpkg.com/@types/jquery/-/jquery-1.10.35.tgz#4e5c2b1e5b3bf0b863efb8c5e70081f52e6c9518" integrity sha512-SVtqEcudm7yjkTwoRA1gC6CNMhGDdMx4Pg8BPdiqI7bXXdCn1BPmtxgeWYQOgDxrq53/5YTlhq5ULxBEAlWIBg== -"@types/lodash@4.14.119", "@types/lodash@^4.14.119": +"@types/lodash@^4.14.119": version "4.14.119" resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.119.tgz#be847e5f4bc3e35e46d041c394ead8b603ad8b39" integrity sha512-Z3TNyBL8Vd/M9D9Ms2S3LmFq2sSMzahodD6rCS9V2N44HUMINb75jNkSuwAx7eo2ufqTdfOdtGQpNbieUjPQmw== @@ -1735,7 +1735,7 @@ dependencies: "@types/react" "*" -"@types/react@*", "@types/react@^16.7.6": +"@types/react@*", "@types/react@16.7.6", "@types/react@^16.7.6": version "16.7.6" resolved "https://registry.yarnpkg.com/@types/react/-/react-16.7.6.tgz#80e4bab0d0731ad3ae51f320c4b08bdca5f03040" integrity sha512-QBUfzftr/8eg/q3ZRgf/GaDP6rTYc7ZNem+g4oZM38C9vXyV8AWRWaTQuW5yCoZTsfHrN7b3DeEiUnqH9SrnpA== @@ -4429,7 +4429,7 @@ caniuse-api@^1.5.2: lodash.memoize "^4.1.2" lodash.uniq "^4.5.0" -caniuse-db@^1.0.30000529, caniuse-db@^1.0.30000634, caniuse-db@^1.0.30000639: +caniuse-db@1.0.30000772, caniuse-db@^1.0.30000529, caniuse-db@^1.0.30000634, caniuse-db@^1.0.30000639: version "1.0.30000772" resolved "https://registry.yarnpkg.com/caniuse-db/-/caniuse-db-1.0.30000772.tgz#51aae891768286eade4a3d8319ea76d6a01b512b" integrity sha1-UarokXaChureSj2DGep21qAbUSs=