Browse Source

WIP Basics of named color picker

Dominik Prokop 7 years ago
parent
commit
ac37879016

+ 7 - 5
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 => <CenteredStory>{story()}</CenteredStory>)
-  .add('default', withInfo({inline: true})(() => {
-    return <ColorPicker color="#ff0000" onChange={() => {}} />;
-  }));
+  // .add('Color picker popover', () => {
+  //   return <ColorPickerPopover color="#ff0000" onColorSelect={() => {}} />;
+  // })
+  .add('Named colors swatch', () => {
+    return <ColorPickerPopover color="#ff0000" onColorSelect={() => {}} />;
+  });

+ 3 - 0
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<Props, any> {
   };
 
   render() {
+
     return (
       <div className="sp-replacer sp-light" onClick={this.openColorPicker} ref={element => (this.pickerElem = element)}>
         <div className="sp-preview">

+ 68 - 0
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 (
+    <div
+      style={{
+        height: '100vh  ',
+        display: 'flex',
+        alignItems: 'center',
+        justifyContent: 'center',
+      }}
+    >
+      {children}
+    </div>
+  );
+};
+
+interface StateHolderProps<T> {
+  initialState: T;
+  children: (currentState: T, updateState: (nextState: T) => void) => JSX.Element;
+}
+
+class UseState<T> extends React.Component<StateHolderProps<T>, {value: T}> {
+  constructor(props: StateHolderProps<T>) {
+    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 => <CenteredStory>{story()}</CenteredStory>)
+  .add('Named colors swatch - support for named colors', () => {
+    return(
+       <UseState initialState="green">
+         {(selectedColor, updateSelectedColor) => {
+            return (
+              <NamedColorsPicker
+                selectedColor={selectedColor as Color}
+                onChange={(color) => { updateSelectedColor(color.name);}}
+              />
+            )
+         }}
+       </UseState>);
+  })
+  .add('Named colors swatch - support for hex values', () => {
+    return(
+       <UseState initialState="#00ff00">
+         {(selectedColor, updateSelectedColor) => {
+            return (
+              <NamedColorsPicker
+                selectedColor={getColorName(selectedColor)}
+                onChange={(color) => updateSelectedColor(color.variants.dark)}
+              />
+            )
+         }}
+       </UseState>);
+  });

+ 121 - 0
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<HTMLDivElement> {
+  color: ColorDefinition;
+  variant?: ColorSwatchVariant;
+  isSelected?: boolean;
+}
+
+const ColorSwatch: FunctionComponent<ColorSwatchProps> = ({
+  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 (
+    <div
+      style={{
+        display: 'flex',
+        alignItems: 'center',
+      }}
+      {...otherProps}
+    >
+      <div style={swatchStyles} />
+      {variant === ColorSwatchVariant.Large && <span>{upperFirst(color.hue)}</span>}
+    </div>
+  );
+};
+
+const ColorsGroup = ({
+  colors,
+  selectedColor,
+  onColorSelect,
+}: {
+  colors: ColorDefinition[];
+  selectedColor?: Color;
+  onColorSelect: ColorChangeHandler
+}) => {
+  const primaryColor = find(colors, color => !!color.isPrimary);
+
+  return (
+    <div style={{ display: 'flex', flexDirection: 'column' }}>
+      {primaryColor && (
+        <ColorSwatch
+          isSelected={primaryColor.name === selectedColor}
+          variant={ColorSwatchVariant.Large}
+          color={primaryColor}
+          onClick={() => onColorSelect(primaryColor)}
+        />
+      )}
+      <div
+        style={{
+          display: 'flex',
+          marginTop: '8px',
+        }}
+      >
+        {colors.map(color => !color.isPrimary && (
+          <div style={{ marginRight: '4px' }}>
+            <ColorSwatch
+              isSelected={color.name === selectedColor}
+              color={color}
+              onClick={() => onColorSelect(color)}
+            />
+          </div>
+        ))}
+      </div>
+    </div>
+  );
+};
+
+
+interface NamedColorsPickerProps {
+  selectedColor?: Color;
+  onChange: ColorChangeHandler;
+}
+const NamedColorsPicker = ({ selectedColor, onChange }: NamedColorsPickerProps) => {
+  const swatches: JSX.Element[] = [];
+
+  ColorsPalete.forEach((colors, hue) => {
+    swatches.push(
+      <>
+        <ColorsGroup selectedColor={selectedColor} colors={colors} onColorSelect={onChange} />
+      </>
+    );
+  });
+
+  return (
+    <div
+      style={{
+        display: 'grid',
+        gridTemplateColumns: 'repeat(3, 1fr)',
+        gridRowGap: '32px',
+        gridColumnGap: '32px',
+      }}
+    >
+      {swatches}
+    </div>
+  );
+};
+
+export default NamedColorsPicker;

+ 0 - 1
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

+ 31 - 0
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);
+    });
+  });
+});

+ 125 - 0
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<Hue, ColorDefinition[]>();
+
+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;
+};

+ 3 - 3
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=