mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
WIP Basics of named color picker
This commit is contained in:
parent
7ad430a6be
commit
ac37879016
@ -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={() => {}} />;
|
||||
});
|
||||
|
@ -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">
|
||||
|
@ -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>);
|
||||
});
|
@ -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;
|
@ -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
packages/grafana-ui/src/utils/colorsPalette.test.ts
Normal file
31
packages/grafana-ui/src/utils/colorsPalette.test.ts
Normal file
@ -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
packages/grafana-ui/src/utils/colorsPalette.ts
Normal file
125
packages/grafana-ui/src/utils/colorsPalette.ts
Normal file
@ -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;
|
||||
};
|
@ -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=
|
||||
|
Loading…
Reference in New Issue
Block a user