;
+
+// Use Grafana Dark theme by default
+export const ThemeContext = React.createContext(getTheme(GrafanaThemeType.Dark));
+
+export const withTheme = (Component: React.ComponentType
) => {
+ const WithTheme: React.FunctionComponent> = props => {
+ // @ts-ignore
+ return {theme => };
+ };
+
+ WithTheme.displayName = `WithTheme(${Component.displayName})`;
+
+ return WithTheme;
+};
diff --git a/packages/grafana-ui/src/themes/dark.ts b/packages/grafana-ui/src/themes/dark.ts
new file mode 100644
index 00000000000..deae022f63a
--- /dev/null
+++ b/packages/grafana-ui/src/themes/dark.ts
@@ -0,0 +1,69 @@
+import tinycolor from 'tinycolor2';
+import defaultTheme from './default';
+import { GrafanaTheme, GrafanaThemeType } from '../types/theme';
+
+const basicColors = {
+ black: '#000000',
+ white: '#ffffff',
+ dark1: '#141414',
+ dark2: '#1f1f20',
+ dark3: '#262628',
+ dark4: '#333333',
+ dark5: '#444444',
+ gray1: '#555555',
+ gray2: '#8e8e8e',
+ gray3: '#b3b3b3',
+ gray4: '#d8d9da',
+ gray5: '#ececec',
+ gray6: '#f4f5f8',
+ gray7: '#fbfbfb',
+ grayBlue: '#212327',
+ blue: '#33b5e5',
+ blueDark: '#005f81',
+ blueLight: '#00a8e6', // not used in dark theme
+ green: '#299c46',
+ red: '#d44a3a',
+ yellow: '#ecbb13',
+ pink: '#ff4444',
+ purple: '#9933cc',
+ variable: '#32d1df',
+ orange: '#eb7b18',
+};
+
+const darkTheme: GrafanaTheme = {
+ ...defaultTheme,
+ type: GrafanaThemeType.Dark,
+ name: 'Grafana Dark',
+ colors: {
+ ...basicColors,
+ inputBlack: '#09090b',
+ queryRed: '#e24d42',
+ queryGreen: '#74e680',
+ queryPurple: '#fe85fc',
+ queryKeyword: '#66d9ef',
+ queryOrange: 'eb7b18',
+ online: '#10a345',
+ warn: '#f79520',
+ critical: '#ed2e18',
+ bodyBg: '#171819',
+ pageBg: '#161719',
+ bodyColor: basicColors.gray4,
+ textColor: basicColors.gray4,
+ textColorStrong: basicColors.white,
+ textColorWeak: basicColors.gray2,
+ textColorEmphasis: basicColors.gray5,
+ textColorFaint: basicColors.dark5,
+ linkColor: new tinycolor(basicColors.white).darken(11).toString(),
+ linkColorDisabled: new tinycolor(basicColors.white).darken(11).toString(),
+ linkColorHover: basicColors.white,
+ linkColorExternal: basicColors.blue,
+ headingColor: new tinycolor(basicColors.white).darken(11).toString(),
+ },
+ background: {
+ dropdown: basicColors.dark3,
+ scrollbar: '#aeb5df',
+ scrollbar2: '#3a3a3a',
+ },
+};
+
+export default darkTheme;
diff --git a/packages/grafana-ui/src/themes/default.ts b/packages/grafana-ui/src/themes/default.ts
new file mode 100644
index 00000000000..bf318f526e7
--- /dev/null
+++ b/packages/grafana-ui/src/themes/default.ts
@@ -0,0 +1,62 @@
+
+
+const theme = {
+ name: 'Grafana Default',
+ typography: {
+ fontFamily: {
+ sansSerif: "'Roboto', Helvetica, Arial, sans-serif;",
+ serif: "Georgia, 'Times New Roman', Times, serif;",
+ monospace: "Menlo, Monaco, Consolas, 'Courier New', monospace;"
+ },
+ size: {
+ base: '13px',
+ xs: '10px',
+ s: '12px',
+ m: '14px',
+ l: '18px',
+ },
+ heading: {
+ h1: '2rem',
+ h2: '1.75rem',
+ h3: '1.5rem',
+ h4: '1.3rem',
+ h5: '1.2rem',
+ h6: '1rem',
+ },
+ weight: {
+ light: 300,
+ normal: 400,
+ semibold: 500,
+ },
+ lineHeight: {
+ xs: 1,
+ s: 1.1,
+ m: 4/3,
+ l: 1.5
+ }
+ },
+ brakpoints: {
+ xs: '0',
+ s: '544px',
+ m: '768px',
+ l: '992px',
+ xl: '1200px'
+ },
+ spacing: {
+ xs: '0',
+ s: '0.2rem',
+ m: '1rem',
+ l: '1.5rem',
+ xl: '3rem',
+ gutter: '30px',
+ },
+ border: {
+ radius: {
+ xs: '2px',
+ s: '3px',
+ m: '5px',
+ }
+ }
+};
+
+export default theme;
diff --git a/packages/grafana-ui/src/themes/index.ts b/packages/grafana-ui/src/themes/index.ts
new file mode 100644
index 00000000000..c0d9a4f2d32
--- /dev/null
+++ b/packages/grafana-ui/src/themes/index.ts
@@ -0,0 +1,14 @@
+import darkTheme from './dark';
+import lightTheme from './light';
+import { GrafanaTheme } from '../types/theme';
+
+let themeMock: ((name?: string) => GrafanaTheme) | null;
+
+export let getTheme = (name?: string) => (themeMock && themeMock(name)) || (name === 'light' ? lightTheme : darkTheme);
+
+export const mockTheme = (mock: (name: string) => GrafanaTheme) => {
+ themeMock = mock;
+ return () => {
+ themeMock = null;
+ };
+};
diff --git a/packages/grafana-ui/src/themes/light.ts b/packages/grafana-ui/src/themes/light.ts
new file mode 100644
index 00000000000..fd1f1d05b95
--- /dev/null
+++ b/packages/grafana-ui/src/themes/light.ts
@@ -0,0 +1,70 @@
+import tinycolor from 'tinycolor2';
+import defaultTheme from './default';
+import { GrafanaTheme, GrafanaThemeType } from '../types/theme';
+
+const basicColors = {
+ black: '#000000',
+ white: '#ffffff',
+ dark1: '#13161d',
+ dark2: '#1e2028',
+ dark3: '#303133',
+ dark4: '#35373f',
+ dark5: '#41444b',
+ gray1: '#52545c',
+ gray2: '#767980',
+ gray3: '#acb6bf',
+ gray4: '#c7d0d9',
+ gray5: '#dde4ed',
+ gray6: '#e9edf2',
+ gray7: '#f7f8fa',
+ grayBlue: '#212327', // not used in light theme
+ blue: '#0083b3',
+ blueDark: '#005f81',
+ blueLight: '#00a8e6',
+ green: '#3aa655',
+ red: '#d44939',
+ yellow: '#ff851b',
+ pink: '#e671b8',
+ purple: '#9954bb',
+ variable: '#0083b3',
+ orange: '#ff7941',
+};
+
+const lightTheme: GrafanaTheme = {
+ ...defaultTheme,
+ type: GrafanaThemeType.Light,
+ name: 'Grafana Light',
+ colors: {
+ ...basicColors,
+ variable: basicColors.blue,
+ inputBlack: '#09090b',
+ queryRed: basicColors.red,
+ queryGreen: basicColors.green,
+ queryPurple: basicColors.purple,
+ queryKeyword: basicColors.blue,
+ queryOrange: basicColors.orange,
+ online: '#01a64f',
+ warn: '#f79520',
+ critical: '#ec2128',
+ bodyBg: basicColors.gray7,
+ pageBg: basicColors.gray7,
+ bodyColor: basicColors.gray1,
+ textColor: basicColors.gray1,
+ textColorStrong: basicColors.dark2,
+ textColorWeak: basicColors.gray2,
+ textColorEmphasis: basicColors.gray5,
+ textColorFaint: basicColors.dark4,
+ linkColor: basicColors.gray1,
+ linkColorDisabled: new tinycolor(basicColors.gray1).lighten(30).toString(),
+ linkColorHover: new tinycolor(basicColors.gray1).darken(20).toString(),
+ linkColorExternal: basicColors.blueLight,
+ headingColor: basicColors.gray1,
+ },
+ background: {
+ dropdown: basicColors.white,
+ scrollbar: basicColors.gray5,
+ scrollbar2: basicColors.gray5,
+ },
+};
+
+export default lightTheme;
diff --git a/packages/grafana-ui/src/themes/selectThemeVariant.test.ts b/packages/grafana-ui/src/themes/selectThemeVariant.test.ts
new file mode 100644
index 00000000000..66cb02a2372
--- /dev/null
+++ b/packages/grafana-ui/src/themes/selectThemeVariant.test.ts
@@ -0,0 +1,52 @@
+import { GrafanaThemeType } from '../types/theme';
+import { selectThemeVariant } from './selectThemeVariant';
+import { mockTheme } from './index';
+
+const lightThemeMock = {
+ color: {
+ red: '#ff0000',
+ green: '#00ff00',
+ },
+};
+
+const darkThemeMock = {
+ color: {
+ red: '#ff0000',
+ green: '#00ff00',
+ },
+};
+
+describe('Theme variable variant selector', () => {
+ // @ts-ignore
+ const restoreTheme = mockTheme(name => (name === GrafanaThemeType.Light ? lightThemeMock : darkThemeMock));
+
+ afterAll(() => {
+ restoreTheme();
+ });
+ it('return correct variable value for given theme', () => {
+ const theme = lightThemeMock;
+
+ const selectedValue = selectThemeVariant(
+ {
+ dark: theme.color.red,
+ light: theme.color.green,
+ },
+ GrafanaThemeType.Light
+ );
+
+ expect(selectedValue).toBe(lightThemeMock.color.green);
+ });
+
+ it('return dark theme variant if no theme given', () => {
+ const theme = lightThemeMock;
+
+ const selectedValue = selectThemeVariant(
+ {
+ dark: theme.color.red,
+ light: theme.color.green,
+ }
+ );
+
+ expect(selectedValue).toBe(lightThemeMock.color.red);
+ });
+});
diff --git a/packages/grafana-ui/src/themes/selectThemeVariant.ts b/packages/grafana-ui/src/themes/selectThemeVariant.ts
new file mode 100644
index 00000000000..e7e8e780222
--- /dev/null
+++ b/packages/grafana-ui/src/themes/selectThemeVariant.ts
@@ -0,0 +1,9 @@
+import { GrafanaThemeType } from '../types/theme';
+
+type VariantDescriptor = {
+ [key in GrafanaThemeType]: string | number;
+};
+
+export const selectThemeVariant = (variants: VariantDescriptor, currentTheme?: GrafanaThemeType) => {
+ return variants[currentTheme || GrafanaThemeType.Dark];
+};
diff --git a/packages/grafana-ui/src/types/index.ts b/packages/grafana-ui/src/types/index.ts
index e23b5e63af8..81bdf741f30 100644
--- a/packages/grafana-ui/src/types/index.ts
+++ b/packages/grafana-ui/src/types/index.ts
@@ -1,14 +1,7 @@
+
export * from './data';
export * from './time';
export * from './panel';
export * from './plugin';
export * from './datasource';
-
-export enum GrafanaTheme {
- Light = 'light',
- Dark = 'dark',
-}
-
-export interface Themeable {
- theme?: GrafanaTheme;
-}
+export * from './theme';
diff --git a/packages/grafana-ui/src/types/theme.ts b/packages/grafana-ui/src/types/theme.ts
new file mode 100644
index 00000000000..8a79658b423
--- /dev/null
+++ b/packages/grafana-ui/src/types/theme.ts
@@ -0,0 +1,129 @@
+export enum GrafanaThemeType {
+ Light = 'light',
+ Dark = 'dark',
+}
+
+export interface GrafanaTheme {
+ type: GrafanaThemeType;
+ name: string;
+ // TODO: not sure if should be a part of theme
+ brakpoints: {
+ xs: string;
+ s: string;
+ m: string;
+ l: string;
+ xl: string;
+ };
+ typography: {
+ fontFamily: {
+ sansSerif: string;
+ serif: string;
+ monospace: string;
+ };
+ size: {
+ base: string;
+ xs: string;
+ s: string;
+ m: string;
+ l: string;
+ };
+ weight: {
+ light: number;
+ normal: number;
+ semibold: number;
+ };
+ lineHeight: {
+ xs: number; //1
+ s: number; //1.1
+ m: number; // 4/3
+ l: number; // 1.5
+ };
+ // TODO: Refactor to use size instead of custom defs
+ heading: {
+ h1: string;
+ h2: string;
+ h3: string;
+ h4: string;
+ h5: string;
+ h6: string;
+ };
+ };
+ spacing: {
+ xs: string;
+ s: string;
+ m: string;
+ l: string;
+ gutter: string;
+ };
+ border: {
+ radius: {
+ xs: string;
+ s: string;
+ m: string;
+ };
+ };
+ background: {
+ dropdown: string;
+ scrollbar: string;
+ scrollbar2: string;
+ };
+ colors: {
+ black: string;
+ white: string;
+ dark1: string;
+ dark2: string;
+ dark3: string;
+ dark4: string;
+ dark5: string;
+ gray1: string;
+ gray2: string;
+ gray3: string;
+ gray4: string;
+ gray5: string;
+ gray6: string;
+ gray7: string;
+ grayBlue: string;
+ inputBlack: string;
+
+ // Accent colors
+ blue: string;
+ blueLight: string;
+ blueDark: string;
+ green: string;
+ red: string;
+ yellow: string;
+ pink: string;
+ purple: string;
+ variable: string;
+ orange: string;
+ queryRed: string;
+ queryGreen: string;
+ queryPurple: string;
+ queryKeyword: string;
+ queryOrange: string;
+
+ // Status colors
+ online: string;
+ warn: string;
+ critical: string;
+
+ // TODO: move to background section
+ bodyBg: string;
+ pageBg: string;
+ bodyColor: string;
+ textColor: string;
+ textColorStrong: string;
+ textColorWeak: string;
+ textColorFaint: string;
+ textColorEmphasis: string;
+ linkColor: string;
+ linkColorDisabled: string;
+ linkColorHover: string;
+ linkColorExternal: string;
+ headingColor: string;
+ };
+}
+
+export interface Themeable {
+ theme: GrafanaTheme;
+}
diff --git a/packages/grafana-ui/src/utils/namedColorsPalette.test.ts b/packages/grafana-ui/src/utils/namedColorsPalette.test.ts
index c6a1aaf0dd0..f875b9966f1 100644
--- a/packages/grafana-ui/src/utils/namedColorsPalette.test.ts
+++ b/packages/grafana-ui/src/utils/namedColorsPalette.test.ts
@@ -5,20 +5,20 @@ import {
getColorFromHexRgbOrName,
getColorDefinitionByName,
} from './namedColorsPalette';
-import { GrafanaTheme } from '../types/index';
+import { GrafanaThemeType } from '../types/index';
describe('colors', () => {
const SemiDarkBlue = getColorDefinitionByName('semi-dark-blue');
describe('getColorDefinition', () => {
it('returns undefined for unknown hex', () => {
- expect(getColorDefinition('#ff0000', GrafanaTheme.Light)).toBeUndefined();
- expect(getColorDefinition('#ff0000', GrafanaTheme.Dark)).toBeUndefined();
+ expect(getColorDefinition('#ff0000', GrafanaThemeType.Light)).toBeUndefined();
+ expect(getColorDefinition('#ff0000', GrafanaThemeType.Dark)).toBeUndefined();
});
it('returns definition for known hex', () => {
- expect(getColorDefinition(SemiDarkBlue.variants.light, GrafanaTheme.Light)).toEqual(SemiDarkBlue);
- expect(getColorDefinition(SemiDarkBlue.variants.dark, GrafanaTheme.Dark)).toEqual(SemiDarkBlue);
+ expect(getColorDefinition(SemiDarkBlue.variants.light, GrafanaThemeType.Light)).toEqual(SemiDarkBlue);
+ expect(getColorDefinition(SemiDarkBlue.variants.dark, GrafanaThemeType.Dark)).toEqual(SemiDarkBlue);
});
});
@@ -28,8 +28,8 @@ describe('colors', () => {
});
it('returns name for known hex', () => {
- expect(getColorName(SemiDarkBlue.variants.light, GrafanaTheme.Light)).toEqual(SemiDarkBlue.name);
- expect(getColorName(SemiDarkBlue.variants.dark, GrafanaTheme.Dark)).toEqual(SemiDarkBlue.name);
+ expect(getColorName(SemiDarkBlue.variants.light, GrafanaThemeType.Light)).toEqual(SemiDarkBlue.name);
+ expect(getColorName(SemiDarkBlue.variants.dark, GrafanaThemeType.Dark)).toEqual(SemiDarkBlue.name);
});
});
@@ -53,7 +53,7 @@ describe('colors', () => {
});
it("returns correct variant's hex for known color if theme specified", () => {
- expect(getColorFromHexRgbOrName(SemiDarkBlue.name, GrafanaTheme.Light)).toBe(SemiDarkBlue.variants.light);
+ expect(getColorFromHexRgbOrName(SemiDarkBlue.name, GrafanaThemeType.Light)).toBe(SemiDarkBlue.variants.light);
});
it('returns color if specified as hex or rgb/a', () => {
diff --git a/packages/grafana-ui/src/utils/namedColorsPalette.ts b/packages/grafana-ui/src/utils/namedColorsPalette.ts
index 5312b27ad26..a99a93f4207 100644
--- a/packages/grafana-ui/src/utils/namedColorsPalette.ts
+++ b/packages/grafana-ui/src/utils/namedColorsPalette.ts
@@ -1,5 +1,5 @@
import { flatten } from 'lodash';
-import { GrafanaTheme } from '../types';
+import { GrafanaThemeType } from '../types';
type Hue = 'green' | 'yellow' | 'red' | 'blue' | 'orange' | 'purple';
@@ -68,7 +68,7 @@ export const getColorDefinitionByName = (name: Color): ColorDefinition => {
return flatten(Array.from(getNamedColorPalette().values())).filter(definition => definition.name === name)[0];
};
-export const getColorDefinition = (hex: string, theme: GrafanaTheme): ColorDefinition | undefined => {
+export const getColorDefinition = (hex: string, theme: GrafanaThemeType): ColorDefinition | undefined => {
return flatten(Array.from(getNamedColorPalette().values())).filter(definition => definition.variants[theme] === hex)[0];
};
@@ -77,7 +77,7 @@ const isHex = (color: string) => {
return hexRegex.test(color);
};
-export const getColorName = (color?: string, theme?: GrafanaTheme): Color | undefined => {
+export const getColorName = (color?: string, theme?: GrafanaThemeType): Color | undefined => {
if (!color) {
return undefined;
}
@@ -86,7 +86,7 @@ export const getColorName = (color?: string, theme?: GrafanaTheme): Color | unde
return undefined;
}
if (isHex(color)) {
- const definition = getColorDefinition(color, theme || GrafanaTheme.Dark);
+ const definition = getColorDefinition(color, theme || GrafanaThemeType.Dark);
return definition ? definition.name : undefined;
}
@@ -98,7 +98,7 @@ export const getColorByName = (colorName: string) => {
return definition.length > 0 ? definition[0] : undefined;
};
-export const getColorFromHexRgbOrName = (color: string, theme?: GrafanaTheme): string => {
+export const getColorFromHexRgbOrName = (color: string, theme?: GrafanaThemeType): string => {
if (color.indexOf('rgb') > -1 || isHex(color)) {
return color;
}
@@ -112,14 +112,14 @@ export const getColorFromHexRgbOrName = (color: string, theme?: GrafanaTheme): s
return theme ? colorDefinition.variants[theme] : colorDefinition.variants.dark;
};
-export const getColorForTheme = (color: ColorDefinition, theme?: GrafanaTheme) => {
+export const getColorForTheme = (color: ColorDefinition, theme?: GrafanaThemeType) => {
return theme ? color.variants[theme] : color.variants.dark;
};
const buildNamedColorsPalette = () => {
const palette = new Map();
- const BasicGreen = buildColorDefinition('green', 'green', ['#56A64B', '#73BF69'], true);
+ const BasicGreen = buildColorDefinition('green', 'green', ['#56A64B', '#73BF69'], true);
const DarkGreen = buildColorDefinition('green', 'dark-green', ['#19730E', '#37872D']);
const SemiDarkGreen = buildColorDefinition('green', 'semi-dark-green', ['#37872D', '#56A64B']);
const LightGreen = buildColorDefinition('green', 'light-green', ['#73BF69', '#96D98D']);
diff --git a/packages/grafana-ui/src/utils/storybook/themeKnob.ts b/packages/grafana-ui/src/utils/storybook/themeKnob.ts
deleted file mode 100644
index a3733462bea..00000000000
--- a/packages/grafana-ui/src/utils/storybook/themeKnob.ts
+++ /dev/null
@@ -1,14 +0,0 @@
-import { select } from '@storybook/addon-knobs';
-import { GrafanaTheme } from '../../types';
-
-export const getThemeKnob = (defaultTheme: GrafanaTheme = GrafanaTheme.Dark) => {
- return select(
- 'Theme',
- {
- Default: defaultTheme,
- Light: GrafanaTheme.Light,
- Dark: GrafanaTheme.Dark,
- },
- defaultTheme
- );
-};
diff --git a/packages/grafana-ui/src/utils/storybook/withTheme.tsx b/packages/grafana-ui/src/utils/storybook/withTheme.tsx
new file mode 100644
index 00000000000..5417af1de05
--- /dev/null
+++ b/packages/grafana-ui/src/utils/storybook/withTheme.tsx
@@ -0,0 +1,41 @@
+import React from 'react';
+import { RenderFunction } from '@storybook/react';
+import { ThemeContext } from '../../themes/ThemeContext';
+import { select } from '@storybook/addon-knobs';
+import { getTheme } from '../../themes';
+import { GrafanaThemeType } from '../../types';
+
+const ThemableStory: React.FunctionComponent<{}> = ({ children }) => {
+ const themeKnob = select(
+ 'Theme',
+ {
+ Light: GrafanaThemeType.Light,
+ Dark: GrafanaThemeType.Dark,
+ },
+ GrafanaThemeType.Dark
+ );
+
+ return (
+
+ {children}
+
+
+ );
+};
+
+// Temporary solution. When we update to Storybook V5 we will be able to pass data from decorator to story
+// https://github.com/storybooks/storybook/issues/340#issuecomment-456013702
+export const renderComponentWithTheme = (component: React.ComponentType, props: any) => {
+ return (
+
+ {theme => {
+ return React.createElement(component, {
+ ...props,
+ theme,
+ });
+ }}
+
+ );
+};
+
+export const withTheme = (story: RenderFunction) => {story()};
diff --git a/public/app/core/angular_wrappers.ts b/public/app/core/angular_wrappers.ts
index 4806275e87d..6db442e7470 100644
--- a/public/app/core/angular_wrappers.ts
+++ b/public/app/core/angular_wrappers.ts
@@ -9,7 +9,7 @@ import { TagFilter } from './components/TagFilter/TagFilter';
import { SideMenu } from './components/sidemenu/SideMenu';
import { MetricSelect } from './components/Select/MetricSelect';
import AppNotificationList from './components/AppNotifications/AppNotificationList';
-import { ColorPicker, SeriesColorPickerPopover } from '@grafana/ui';
+import { ColorPicker, SeriesColorPickerPopoverWithTheme } from '@grafana/ui';
export function registerAngularDirectives() {
react2AngularDirective('passwordStrength', PasswordStrength, ['password']);
@@ -27,7 +27,7 @@ export function registerAngularDirectives() {
'color',
['onChange', { watchDepth: 'reference', wrapApply: true }],
]);
- react2AngularDirective('seriesColorPickerPopover', SeriesColorPickerPopover, [
+ react2AngularDirective('seriesColorPickerPopover', SeriesColorPickerPopoverWithTheme, [
'color',
'series',
'onColorChange',
diff --git a/public/app/core/config.ts b/public/app/core/config.ts
index 368b3798117..f4254ac251a 100644
--- a/public/app/core/config.ts
+++ b/public/app/core/config.ts
@@ -1,5 +1,6 @@
import _ from 'lodash';
import { PanelPlugin } from 'app/types/plugins';
+import { GrafanaTheme, getTheme, GrafanaThemeType } from '@grafana/ui';
export interface BuildInfo {
version: string;
@@ -36,8 +37,11 @@ export class Settings {
loginError: any;
viewersCanEdit: boolean;
disableSanitizeHtml: boolean;
+ theme: GrafanaTheme;
constructor(options: Settings) {
+ this.theme = options.bootData.user.lightTheme ? getTheme(GrafanaThemeType.Light) : getTheme(GrafanaThemeType.Dark);
+
const defaults = {
datasources: {},
windowTitlePrefix: 'Grafana - ',
diff --git a/public/app/core/utils/ConfigProvider.tsx b/public/app/core/utils/ConfigProvider.tsx
index 6883401ad27..cb3ad88b191 100644
--- a/public/app/core/utils/ConfigProvider.tsx
+++ b/public/app/core/utils/ConfigProvider.tsx
@@ -1,6 +1,6 @@
import React from 'react';
import config, { Settings } from 'app/core/config';
-import { GrafanaTheme } from '@grafana/ui';
+import { GrafanaThemeType, ThemeContext, getTheme } from '@grafana/ui';
export const ConfigContext = React.createContext(config);
export const ConfigConsumer = ConfigContext.Consumer;
@@ -13,16 +13,20 @@ export const provideConfig = (component: React.ComponentType) => {
return ConfigProvider;
};
-interface ThemeProviderProps {
- children: (theme: GrafanaTheme) => JSX.Element;
-}
+export const getCurrentThemeName = () =>
+ config.bootData.user.lightTheme ? GrafanaThemeType.Light : GrafanaThemeType.Dark;
+export const getCurrentTheme = () => getTheme(getCurrentThemeName());
-export const ThemeProvider = ({ children }: ThemeProviderProps) => {
+export const ThemeProvider = ({ children }: { children: React.ReactNode }) => {
return (
- {({ bootData }) => {
- return children(bootData.user.lightTheme ? GrafanaTheme.Light : GrafanaTheme.Dark);
+ {config => {
+ return {children};
}}
);
};
+
+export const provideTheme = (component: React.ComponentType) => {
+ return provideConfig((props: any) => {React.createElement(component, { ...props })});
+};
diff --git a/public/app/core/utils/react2angular.ts b/public/app/core/utils/react2angular.ts
index 1057f68fcda..eb4bccab267 100644
--- a/public/app/core/utils/react2angular.ts
+++ b/public/app/core/utils/react2angular.ts
@@ -1,11 +1,11 @@
import coreModule from 'app/core/core_module';
-import { provideConfig } from 'app/core/utils/ConfigProvider';
+import { provideTheme } from 'app/core/utils/ConfigProvider';
export function react2AngularDirective(name: string, component: any, options: any) {
coreModule.directive(name, [
'reactDirective',
reactDirective => {
- return reactDirective(provideConfig(component), options);
+ return reactDirective(provideTheme(component), options);
},
]);
}
diff --git a/public/app/plugins/panel/gauge/GaugePanel.tsx b/public/app/plugins/panel/gauge/GaugePanel.tsx
index b6f37dde94f..5cb256ee1aa 100644
--- a/public/app/plugins/panel/gauge/GaugePanel.tsx
+++ b/public/app/plugins/panel/gauge/GaugePanel.tsx
@@ -2,7 +2,7 @@
import React, { PureComponent } from 'react';
// Services & Utils
-import { processTimeSeries } from '@grafana/ui';
+import { processTimeSeries, ThemeContext } from '@grafana/ui';
// Components
import { Gauge } from '@grafana/ui';
@@ -10,7 +10,6 @@ import { Gauge } from '@grafana/ui';
// Types
import { GaugeOptions } from './types';
import { PanelProps, NullValueMode, TimeSeriesValue } from '@grafana/ui/src/types';
-import { ThemeProvider } from 'app/core/utils/ConfigProvider';
interface Props extends PanelProps {}
@@ -38,7 +37,7 @@ export class GaugePanel extends PureComponent {
}
return (
-
+
{theme => (
{
theme={theme}
/>
)}
-
+
);
}
}
diff --git a/public/app/plugins/panel/gauge/GaugePanelOptions.tsx b/public/app/plugins/panel/gauge/GaugePanelOptions.tsx
index 655c596ce84..84726ac88bf 100644
--- a/public/app/plugins/panel/gauge/GaugePanelOptions.tsx
+++ b/public/app/plugins/panel/gauge/GaugePanelOptions.tsx
@@ -11,7 +11,6 @@ import {
import ValueOptions from 'app/plugins/panel/gauge/ValueOptions';
import GaugeOptionsEditor from './GaugeOptionsEditor';
import { GaugeOptions } from './types';
-import { ThemeProvider } from 'app/core/utils/ConfigProvider';
export const defaultProps = {
options: {
@@ -46,24 +45,17 @@ export default class GaugePanelOptions extends PureComponent
- {(theme) => (
- <>
-
-
-
-
-
-
- >
- )}
-
+ return (
+ <>
+
+
+
+
+
+
+
+ >
);
}
}
diff --git a/public/app/plugins/panel/graph/Legend/LegendSeriesItem.tsx b/public/app/plugins/panel/graph/Legend/LegendSeriesItem.tsx
index d62613319b2..2cf45727c4a 100644
--- a/public/app/plugins/panel/graph/Legend/LegendSeriesItem.tsx
+++ b/public/app/plugins/panel/graph/Legend/LegendSeriesItem.tsx
@@ -2,7 +2,6 @@ import React, { PureComponent } from 'react';
import classNames from 'classnames';
import { TimeSeries } from 'app/core/core';
import { SeriesColorPicker } from '@grafana/ui';
-import { ThemeProvider } from 'app/core/utils/ConfigProvider';
export const LEGEND_STATS = ['min', 'max', 'avg', 'current', 'total'];
@@ -168,24 +167,17 @@ class LegendSeriesIcon extends PureComponent
- {theme => {
- return (
-
-
-
-
-
- );
- }}
-
+
+
+
+
+
);
}
}
diff --git a/public/app/plugins/panel/graph/data_processor.ts b/public/app/plugins/panel/graph/data_processor.ts
index 4141d36e273..2966bb33eb4 100644
--- a/public/app/plugins/panel/graph/data_processor.ts
+++ b/public/app/plugins/panel/graph/data_processor.ts
@@ -1,5 +1,5 @@
import _ from 'lodash';
-import { colors, GrafanaTheme, getColorFromHexRgbOrName } from '@grafana/ui';
+import { colors, getColorFromHexRgbOrName } from '@grafana/ui';
import TimeSeries from 'app/core/time_series2';
import config from 'app/core/config';
@@ -113,7 +113,7 @@ export class DataProcessor {
const series = new TimeSeries({
datapoints: datapoints,
alias: alias,
- color: getColorFromHexRgbOrName(color, config.bootData.user.lightTheme ? GrafanaTheme.Light : GrafanaTheme.Dark),
+ color: getColorFromHexRgbOrName(color, config.theme.type),
unit: seriesData.unit,
});
diff --git a/public/app/plugins/panel/graph/graph.ts b/public/app/plugins/panel/graph/graph.ts
index aeb540551b8..54ba4ed1e6f 100755
--- a/public/app/plugins/panel/graph/graph.ts
+++ b/public/app/plugins/panel/graph/graph.ts
@@ -25,7 +25,10 @@ import ReactDOM from 'react-dom';
import { Legend, GraphLegendProps } from './Legend/Legend';
import { GraphCtrl } from './module';
-import { GrafanaTheme, getValueFormat } from '@grafana/ui';
+import { getValueFormat } from '@grafana/ui';
+import { provideTheme } from 'app/core/utils/ConfigProvider';
+
+const LegendWithThemeProvider = provideTheme(Legend);
class GraphElement {
ctrl: GraphCtrl;
@@ -43,6 +46,7 @@ class GraphElement {
legendElem: HTMLElement;
constructor(private scope, private elem, private timeSrv) {
+
this.ctrl = scope.ctrl;
this.dashboard = this.ctrl.dashboard;
this.panel = this.ctrl.panel;
@@ -51,10 +55,7 @@ class GraphElement {
this.panelWidth = 0;
this.eventManager = new EventManager(this.ctrl);
this.thresholdManager = new ThresholdManager(this.ctrl);
- this.timeRegionManager = new TimeRegionManager(
- this.ctrl,
- config.bootData.user.lightTheme ? GrafanaTheme.Light : GrafanaTheme.Dark
- );
+ this.timeRegionManager = new TimeRegionManager(this.ctrl, config.theme.type);
this.tooltip = new GraphTooltip(this.elem, this.ctrl.dashboard, this.scope, () => {
return this.sortedSeries;
});
@@ -109,7 +110,7 @@ class GraphElement {
onToggleAxis: this.ctrl.onToggleAxis,
};
- const legendReactElem = React.createElement(Legend, legendProps);
+ const legendReactElem = React.createElement(LegendWithThemeProvider, legendProps);
ReactDOM.render(legendReactElem, this.legendElem, () => this.renderPanel());
}
diff --git a/public/app/plugins/panel/graph/module.ts b/public/app/plugins/panel/graph/module.ts
index 68d982eab13..3919c4f69a9 100644
--- a/public/app/plugins/panel/graph/module.ts
+++ b/public/app/plugins/panel/graph/module.ts
@@ -10,7 +10,7 @@ import { MetricsPanelCtrl } from 'app/plugins/sdk';
import { DataProcessor } from './data_processor';
import { axesEditorComponent } from './axes_editor';
import config from 'app/core/config';
-import { GrafanaTheme, getColorFromHexRgbOrName } from '@grafana/ui';
+import { getColorFromHexRgbOrName } from '@grafana/ui';
class GraphCtrl extends MetricsPanelCtrl {
static template = template;
@@ -244,7 +244,7 @@ class GraphCtrl extends MetricsPanelCtrl {
}
onColorChange = (series, color) => {
- series.setColor(getColorFromHexRgbOrName(color, config.bootData.user.lightTheme ? GrafanaTheme.Light : GrafanaTheme.Dark));
+ series.setColor(getColorFromHexRgbOrName(color, config.theme.type));
this.panel.aliasColors[series.alias] = color;
this.render();
};
diff --git a/public/app/plugins/panel/graph/time_region_manager.ts b/public/app/plugins/panel/graph/time_region_manager.ts
index 2917583ff36..ea39927bf57 100644
--- a/public/app/plugins/panel/graph/time_region_manager.ts
+++ b/public/app/plugins/panel/graph/time_region_manager.ts
@@ -1,7 +1,7 @@
import 'vendor/flot/jquery.flot';
import _ from 'lodash';
import moment from 'moment';
-import { GrafanaTheme, getColorFromHexRgbOrName } from '@grafana/ui';
+import { GrafanaThemeType, getColorFromHexRgbOrName } from '@grafana/ui';
type TimeRegionColorDefinition = {
fill: string;
@@ -43,7 +43,7 @@ export function getColorModes() {
});
}
-function getColor(timeRegion, theme: GrafanaTheme): TimeRegionColorDefinition {
+function getColor(timeRegion, theme: GrafanaThemeType): TimeRegionColorDefinition {
if (Object.keys(colorModes).indexOf(timeRegion.colorMode) === -1) {
timeRegion.colorMode = 'red';
}
@@ -58,7 +58,7 @@ function getColor(timeRegion, theme: GrafanaTheme): TimeRegionColorDefinition {
const colorMode = colorModes[timeRegion.colorMode];
if (colorMode.themeDependent === true) {
- return theme === GrafanaTheme.Light ? colorMode.lightColor : colorMode.darkColor;
+ return theme === GrafanaThemeType.Light ? colorMode.lightColor : colorMode.darkColor;
}
return {
@@ -71,7 +71,7 @@ export class TimeRegionManager {
plot: any;
timeRegions: any;
- constructor(private panelCtrl, private theme: GrafanaTheme = GrafanaTheme.Dark) {}
+ constructor(private panelCtrl, private theme: GrafanaThemeType = GrafanaThemeType.Dark) {}
draw(plot) {
this.timeRegions = this.panelCtrl.panel.timeRegions;
diff --git a/public/app/plugins/panel/heatmap/color_legend.ts b/public/app/plugins/panel/heatmap/color_legend.ts
index 81329fe297b..dea250abf74 100644
--- a/public/app/plugins/panel/heatmap/color_legend.ts
+++ b/public/app/plugins/panel/heatmap/color_legend.ts
@@ -5,7 +5,7 @@ import { contextSrv } from 'app/core/core';
import { tickStep } from 'app/core/utils/ticks';
import { getColorScale, getOpacityScale } from './color_scale';
import coreModule from 'app/core/core_module';
-import { GrafanaTheme, getColorFromHexRgbOrName } from '@grafana/ui';
+import { GrafanaThemeType, getColorFromHexRgbOrName } from '@grafana/ui';
const LEGEND_HEIGHT_PX = 6;
const LEGEND_WIDTH_PX = 100;
@@ -250,7 +250,7 @@ function drawSimpleOpacityLegend(elem, options) {
.attr('stroke-width', 0)
.attr(
'fill',
- getColorFromHexRgbOrName(options.cardColor, contextSrv.user.lightTheme ? GrafanaTheme.Light : GrafanaTheme.Dark)
+ getColorFromHexRgbOrName(options.cardColor, contextSrv.user.lightTheme ? GrafanaThemeType.Light : GrafanaThemeType.Dark)
)
.style('opacity', d => legendOpacityScale(d));
}
diff --git a/public/app/plugins/panel/heatmap/rendering.ts b/public/app/plugins/panel/heatmap/rendering.ts
index 6489c9e9895..63604382432 100644
--- a/public/app/plugins/panel/heatmap/rendering.ts
+++ b/public/app/plugins/panel/heatmap/rendering.ts
@@ -7,7 +7,7 @@ import * as ticksUtils from 'app/core/utils/ticks';
import { HeatmapTooltip } from './heatmap_tooltip';
import { mergeZeroBuckets } from './heatmap_data_converter';
import { getColorScale, getOpacityScale } from './color_scale';
-import { GrafanaTheme, getColorFromHexRgbOrName, getValueFormat } from '@grafana/ui';
+import { GrafanaThemeType, getColorFromHexRgbOrName, getValueFormat } from '@grafana/ui';
const MIN_CARD_SIZE = 1,
CARD_PADDING = 1,
@@ -663,7 +663,7 @@ export class HeatmapRenderer {
if (this.panel.color.mode === 'opacity') {
return getColorFromHexRgbOrName(
this.panel.color.cardColor,
- contextSrv.user.lightTheme ? GrafanaTheme.Light : GrafanaTheme.Dark
+ contextSrv.user.lightTheme ? GrafanaThemeType.Light : GrafanaThemeType.Dark
);
} else {
return this.colorScale(d.count);
diff --git a/public/app/plugins/panel/singlestat/module.ts b/public/app/plugins/panel/singlestat/module.ts
index 2768951d2ba..21ab32278f8 100644
--- a/public/app/plugins/panel/singlestat/module.ts
+++ b/public/app/plugins/panel/singlestat/module.ts
@@ -8,7 +8,7 @@ import kbn from 'app/core/utils/kbn';
import config from 'app/core/config';
import TimeSeries from 'app/core/time_series2';
import { MetricsPanelCtrl } from 'app/plugins/sdk';
-import { GrafanaTheme, getValueFormat, getColorFromHexRgbOrName } from '@grafana/ui';
+import { GrafanaThemeType, getValueFormat, getColorFromHexRgbOrName } from '@grafana/ui';
class SingleStatCtrl extends MetricsPanelCtrl {
static templateUrl = 'module.html';
@@ -588,10 +588,7 @@ class SingleStatCtrl extends MetricsPanelCtrl {
fill: 1,
zero: false,
lineWidth: 1,
- fillColor: getColorFromHexRgbOrName(
- panel.sparkline.fillColor,
- config.bootData.user.lightTheme ? GrafanaTheme.Light : GrafanaTheme.Dark
- ),
+ fillColor: getColorFromHexRgbOrName(panel.sparkline.fillColor, config.theme.type),
},
},
yaxes: { show: false },
@@ -608,10 +605,7 @@ class SingleStatCtrl extends MetricsPanelCtrl {
const plotSeries = {
data: data.flotpairs,
- color: getColorFromHexRgbOrName(
- panel.sparkline.lineColor,
- config.bootData.user.lightTheme ? GrafanaTheme.Light : GrafanaTheme.Dark
- ),
+ color: getColorFromHexRgbOrName(panel.sparkline.lineColor, config.theme.type),
};
$.plot(plotCanvas, [plotSeries], options);
@@ -630,7 +624,7 @@ class SingleStatCtrl extends MetricsPanelCtrl {
// Map panel colors to hex or rgb/a values
data.colorMap = panel.colors.map(color =>
- getColorFromHexRgbOrName(color, config.bootData.user.lightTheme ? GrafanaTheme.Light : GrafanaTheme.Dark)
+ getColorFromHexRgbOrName(color, config.bootData.user.lightTheme ? GrafanaThemeType.Light : GrafanaThemeType.Dark)
);
const body = panel.gauge.show ? '' : getBigValueHtml();
diff --git a/public/app/plugins/panel/table/module.ts b/public/app/plugins/panel/table/module.ts
index 82763e1839a..268f5aa7ac4 100644
--- a/public/app/plugins/panel/table/module.ts
+++ b/public/app/plugins/panel/table/module.ts
@@ -6,7 +6,6 @@ import { transformDataToTable } from './transformers';
import { tablePanelEditor } from './editor';
import { columnOptionsTab } from './column_options';
import { TableRenderer } from './renderer';
-import { GrafanaTheme } from '@grafana/ui';
class TablePanelCtrl extends MetricsPanelCtrl {
static templateUrl = 'module.html';
@@ -131,7 +130,7 @@ class TablePanelCtrl extends MetricsPanelCtrl {
this.dashboard.isTimezoneUtc(),
this.$sanitize,
this.templateSrv,
- config.bootData.user.lightTheme ? GrafanaTheme.Light : GrafanaTheme.Dark,
+ config.theme.type
);
return super.render(this.table);
diff --git a/public/app/plugins/panel/table/renderer.ts b/public/app/plugins/panel/table/renderer.ts
index 90479a67602..e9bf89f45fe 100644
--- a/public/app/plugins/panel/table/renderer.ts
+++ b/public/app/plugins/panel/table/renderer.ts
@@ -1,7 +1,7 @@
import _ from 'lodash';
import moment from 'moment';
import kbn from 'app/core/utils/kbn';
-import { GrafanaTheme, getValueFormat, getColorFromHexRgbOrName } from '@grafana/ui';
+import { getValueFormat, getColorFromHexRgbOrName, GrafanaThemeType } from '@grafana/ui';
export class TableRenderer {
formatters: any[];
@@ -13,7 +13,7 @@ export class TableRenderer {
private isUtc,
private sanitize,
private templateSrv,
- private theme?: GrafanaTheme
+ private theme?: GrafanaThemeType
) {
this.initColumns();
}
diff --git a/public/app/routes/ReactContainer.tsx b/public/app/routes/ReactContainer.tsx
index d64e74e3949..35c23239561 100644
--- a/public/app/routes/ReactContainer.tsx
+++ b/public/app/routes/ReactContainer.tsx
@@ -5,6 +5,7 @@ import { Provider } from 'react-redux';
import coreModule from 'app/core/core_module';
import { store } from 'app/store/store';
import { ContextSrv } from 'app/core/services/context_srv';
+import { provideTheme } from 'app/core/utils/ConfigProvider';
function WrapInProvider(store, Component, props) {
return (
@@ -49,7 +50,7 @@ export function reactContainer(
document.body.classList.add('is-react');
- ReactDOM.render(WrapInProvider(store, component, props), elem[0]);
+ ReactDOM.render(WrapInProvider(store, provideTheme(component), props), elem[0]);
scope.$on('$destroy', () => {
document.body.classList.remove('is-react');
diff --git a/scripts/webpack/sass.rule.js b/scripts/webpack/sass.rule.js
index 75455fc4184..66a48a12b32 100644
--- a/scripts/webpack/sass.rule.js
+++ b/scripts/webpack/sass.rule.js
@@ -1,6 +1,6 @@
'use strict';
-const MiniCssExtractPlugin = require("mini-css-extract-plugin");
+const MiniCssExtractPlugin = require('mini-css-extract-plugin');
module.exports = function(options) {
return {
@@ -23,7 +23,12 @@ module.exports = function(options) {
config: { path: __dirname + '/postcss.config.js' },
},
},
- { loader: 'sass-loader', options: { sourceMap: options.sourceMap } },
+ {
+ loader: 'sass-loader',
+ options: {
+ sourceMap: options.sourceMap
+ },
+ },
],
};
};
diff --git a/scripts/webpack/webpack.hot.js b/scripts/webpack/webpack.hot.js
index b37e4c08592..c1053f1f7da 100644
--- a/scripts/webpack/webpack.hot.js
+++ b/scripts/webpack/webpack.hot.js
@@ -85,7 +85,9 @@ module.exports = merge(common, {
config: { path: __dirname + '/postcss.config.js' },
},
},
- 'sass-loader', // compiles Sass to CSS
+ {
+ loader: 'sass-loader'
+ }
],
},
{
diff --git a/scripts/webpack/webpack.test.js b/scripts/webpack/webpack.test.js
deleted file mode 100644
index ec9ee5e26df..00000000000
--- a/scripts/webpack/webpack.test.js
+++ /dev/null
@@ -1,38 +0,0 @@
-const webpack = require('webpack');
-const merge = require('webpack-merge');
-const common = require('./webpack.common.js');
-
-config = merge(common, {
- mode: 'development',
- devtool: 'cheap-module-source-map',
-
- externals: {
- 'react/addons': true,
- 'react/lib/ExecutionEnvironment': true,
- 'react/lib/ReactContext': true,
- },
-
- module: {
- rules: [
- {
- test: /\.tsx?$/,
- exclude: /node_modules/,
- use: {
- loader: 'ts-loader',
- options: {
- transpileOnly: true,
- },
- },
- },
- ],
- },
-
- plugins: [
- new webpack.SourceMapDevToolPlugin({
- filename: null, // if no value is provided the sourcemap is inlined
- test: /\.(ts|js)($|\?)/i, // process .js and .ts files only
- }),
- ],
-});
-
-module.exports = config;