mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Themes: V8 Theme model (#32342)
* Updated * Progress * Progress * Added spacings * Updated rich color to be more descriptibe * Added more to getRichColor to showcase how it would work * Added more to getRichColor to showcase how it would work * Updated * Started on storybook * Rename to palette * Storybook progress * Minor update * Progress * Progress * removed unused import * Updated * Progress * Added typography to new theme model * Added shadows and zindex to new theme * Updated based on last discussions * Updated * Rename shadows * Moving storybook to new theme, renaming stories and moving to single category * Updated snapshot * Updated jsdoc state tags * Reducing annonying errors
This commit is contained in:
@@ -52,6 +52,7 @@
|
||||
"rollup-plugin-typescript2": "0.29.0",
|
||||
"rollup-plugin-visualizer": "4.2.0",
|
||||
"sinon": "8.1.1",
|
||||
"typescript": "4.1.2"
|
||||
"typescript": "4.1.2",
|
||||
"tinycolor2": "1.4.1"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ export * from './text';
|
||||
export * from './valueFormats';
|
||||
export * from './field';
|
||||
export * from './events';
|
||||
export * from './themes';
|
||||
export {
|
||||
ValueMatcherOptions,
|
||||
BasicValueMatcherOptions,
|
||||
|
||||
56
packages/grafana-data/src/themes/breakpoints.ts
Normal file
56
packages/grafana-data/src/themes/breakpoints.ts
Normal file
@@ -0,0 +1,56 @@
|
||||
/** @beta */
|
||||
export interface ThemeBreakpointValues {
|
||||
xs: number;
|
||||
sm: number;
|
||||
md: number;
|
||||
lg: number;
|
||||
xl: number;
|
||||
xxl: number;
|
||||
}
|
||||
|
||||
/** @beta */
|
||||
export type ThemeBreakpointsKey = keyof ThemeBreakpointValues;
|
||||
|
||||
/** @beta */
|
||||
export interface ThemeBreakpoints {
|
||||
values: ThemeBreakpointValues;
|
||||
keys: string[];
|
||||
unit: string;
|
||||
up: (key: ThemeBreakpointsKey) => string;
|
||||
down: (key: ThemeBreakpointsKey) => string;
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
export function createBreakpoints(): ThemeBreakpoints {
|
||||
const step = 5;
|
||||
const keys = ['xs', 'sm', 'md', 'lg', 'xl', 'xxl'];
|
||||
const unit = 'px';
|
||||
const values: ThemeBreakpointValues = {
|
||||
xs: 0,
|
||||
sm: 544,
|
||||
md: 769, // 1 more than regular ipad in portrait
|
||||
lg: 992,
|
||||
xl: 1200,
|
||||
xxl: 1440,
|
||||
};
|
||||
|
||||
function up(key: ThemeBreakpointsKey | number) {
|
||||
const value = typeof key === 'number' ? key : values[key];
|
||||
return `@media (min-width:${value}${unit})`;
|
||||
}
|
||||
|
||||
function down(key: ThemeBreakpointsKey | number) {
|
||||
const value = typeof key === 'number' ? key : values[key];
|
||||
return `@media (max-width:${value - step / 100}${unit})`;
|
||||
}
|
||||
|
||||
// TODO add functions for between and only
|
||||
|
||||
return {
|
||||
values,
|
||||
up,
|
||||
down,
|
||||
keys,
|
||||
unit,
|
||||
};
|
||||
}
|
||||
402
packages/grafana-data/src/themes/colorManipulator.test.ts
Normal file
402
packages/grafana-data/src/themes/colorManipulator.test.ts
Normal file
@@ -0,0 +1,402 @@
|
||||
import {
|
||||
recomposeColor,
|
||||
hexToRgb,
|
||||
rgbToHex,
|
||||
hslToRgb,
|
||||
darken,
|
||||
decomposeColor,
|
||||
emphasize,
|
||||
alpha,
|
||||
getContrastRatio,
|
||||
getLuminance,
|
||||
lighten,
|
||||
} from './colorManipulator';
|
||||
|
||||
describe('utils/colorManipulator', () => {
|
||||
const origError = console.error;
|
||||
const consoleErrorMock = jest.fn();
|
||||
afterEach(() => (console.error = origError));
|
||||
beforeEach(() => (console.error = consoleErrorMock));
|
||||
|
||||
describe('recomposeColor', () => {
|
||||
it('converts a decomposed rgb color object to a string` ', () => {
|
||||
expect(
|
||||
recomposeColor({
|
||||
type: 'rgb',
|
||||
values: [255, 255, 255],
|
||||
})
|
||||
).toEqual('rgb(255, 255, 255)');
|
||||
});
|
||||
|
||||
it('converts a decomposed rgba color object to a string` ', () => {
|
||||
expect(
|
||||
recomposeColor({
|
||||
type: 'rgba',
|
||||
values: [255, 255, 255, 0.5],
|
||||
})
|
||||
).toEqual('rgba(255, 255, 255, 0.5)');
|
||||
});
|
||||
|
||||
it('converts a decomposed CSS4 color object to a string` ', () => {
|
||||
expect(
|
||||
recomposeColor({
|
||||
type: 'color',
|
||||
colorSpace: 'display-p3',
|
||||
values: [0.5, 0.3, 0.2],
|
||||
})
|
||||
).toEqual('color(display-p3 0.5 0.3 0.2)');
|
||||
});
|
||||
|
||||
it('converts a decomposed hsl color object to a string` ', () => {
|
||||
expect(
|
||||
recomposeColor({
|
||||
type: 'hsl',
|
||||
values: [100, 50, 25],
|
||||
})
|
||||
).toEqual('hsl(100, 50%, 25%)');
|
||||
});
|
||||
|
||||
it('converts a decomposed hsla color object to a string` ', () => {
|
||||
expect(
|
||||
recomposeColor({
|
||||
type: 'hsla',
|
||||
values: [100, 50, 25, 0.5],
|
||||
})
|
||||
).toEqual('hsla(100, 50%, 25%, 0.5)');
|
||||
});
|
||||
});
|
||||
|
||||
describe('hexToRgb', () => {
|
||||
it('converts a short hex color to an rgb color` ', () => {
|
||||
expect(hexToRgb('#9f3')).toEqual('rgb(153, 255, 51)');
|
||||
});
|
||||
|
||||
it('converts a long hex color to an rgb color` ', () => {
|
||||
expect(hexToRgb('#a94fd3')).toEqual('rgb(169, 79, 211)');
|
||||
});
|
||||
|
||||
it('converts a long alpha hex color to an argb color` ', () => {
|
||||
expect(hexToRgb('#111111f8')).toEqual('rgba(17, 17, 17, 0.973)');
|
||||
});
|
||||
});
|
||||
|
||||
describe('rgbToHex', () => {
|
||||
it('converts an rgb color to a hex color` ', () => {
|
||||
expect(rgbToHex('rgb(169, 79, 211)')).toEqual('#a94fd3');
|
||||
});
|
||||
|
||||
it('idempotent', () => {
|
||||
expect(rgbToHex('#A94FD3')).toEqual('#A94FD3');
|
||||
});
|
||||
});
|
||||
|
||||
describe('hslToRgb', () => {
|
||||
it('converts an hsl color to an rgb color` ', () => {
|
||||
expect(hslToRgb('hsl(281, 60%, 57%)')).toEqual('rgb(169, 80, 211)');
|
||||
});
|
||||
|
||||
it('converts an hsla color to an rgba color` ', () => {
|
||||
expect(hslToRgb('hsla(281, 60%, 57%, 0.5)')).toEqual('rgba(169, 80, 211, 0.5)');
|
||||
});
|
||||
|
||||
it('allow to convert values only', () => {
|
||||
expect(hslToRgb(decomposeColor('hsl(281, 60%, 57%)'))).toEqual('rgb(169, 80, 211)');
|
||||
});
|
||||
});
|
||||
|
||||
describe('decomposeColor', () => {
|
||||
it('converts an rgb color string to an object with `type` and `value` keys', () => {
|
||||
const { type, values } = decomposeColor('rgb(255, 255, 255)');
|
||||
expect(type).toEqual('rgb');
|
||||
expect(values).toEqual([255, 255, 255]);
|
||||
});
|
||||
|
||||
it('converts an rgba color string to an object with `type` and `value` keys', () => {
|
||||
const { type, values } = decomposeColor('rgba(255, 255, 255, 0.5)');
|
||||
expect(type).toEqual('rgba');
|
||||
expect(values).toEqual([255, 255, 255, 0.5]);
|
||||
});
|
||||
|
||||
it('converts an hsl color string to an object with `type` and `value` keys', () => {
|
||||
const { type, values } = decomposeColor('hsl(100, 50%, 25%)');
|
||||
expect(type).toEqual('hsl');
|
||||
expect(values).toEqual([100, 50, 25]);
|
||||
});
|
||||
|
||||
it('converts an hsla color string to an object with `type` and `value` keys', () => {
|
||||
const { type, values } = decomposeColor('hsla(100, 50%, 25%, 0.5)');
|
||||
expect(type).toEqual('hsla');
|
||||
expect(values).toEqual([100, 50, 25, 0.5]);
|
||||
});
|
||||
|
||||
it('converts CSS4 color with color space display-3', () => {
|
||||
const { type, values, colorSpace } = decomposeColor('color(display-p3 0 1 0)');
|
||||
expect(type).toEqual('color');
|
||||
expect(colorSpace).toEqual('display-p3');
|
||||
expect(values).toEqual([0, 1, 0]);
|
||||
});
|
||||
|
||||
it('converts an alpha CSS4 color with color space display-3', () => {
|
||||
const { type, values, colorSpace } = decomposeColor('color(display-p3 0 1 0 /0.4)');
|
||||
expect(type).toEqual('color');
|
||||
expect(colorSpace).toEqual('display-p3');
|
||||
expect(values).toEqual([0, 1, 0, 0.4]);
|
||||
});
|
||||
|
||||
it('should throw error with inexistent color color space', () => {
|
||||
const decimposeWithError = () => decomposeColor('color(foo 0 1 0)');
|
||||
expect(decimposeWithError).toThrow();
|
||||
});
|
||||
|
||||
it('idempotent', () => {
|
||||
const output1 = decomposeColor('hsla(100, 50%, 25%, 0.5)');
|
||||
const output2 = decomposeColor(output1);
|
||||
expect(output1).toEqual(output2);
|
||||
});
|
||||
|
||||
it('converts rgba hex', () => {
|
||||
const decomposed = decomposeColor('#111111f8');
|
||||
expect(decomposed).toEqual({
|
||||
type: 'rgba',
|
||||
colorSpace: undefined,
|
||||
values: [17, 17, 17, 0.973],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('getContrastRatio', () => {
|
||||
it('returns a ratio for black : white', () => {
|
||||
expect(getContrastRatio('#000', '#FFF')).toEqual(21);
|
||||
});
|
||||
|
||||
it('returns a ratio for black : black', () => {
|
||||
expect(getContrastRatio('#000', '#000')).toEqual(1);
|
||||
});
|
||||
|
||||
it('returns a ratio for white : white', () => {
|
||||
expect(getContrastRatio('#FFF', '#FFF')).toEqual(1);
|
||||
});
|
||||
|
||||
it('returns a ratio for dark-grey : light-grey', () => {
|
||||
//expect(getContrastRatio('#707070', '#E5E5E5'))to.be.approximately(3.93, 0.01);
|
||||
});
|
||||
|
||||
it('returns a ratio for black : light-grey', () => {
|
||||
//expect(getContrastRatio('#000', '#888')).to.be.approximately(5.92, 0.01);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getLuminance', () => {
|
||||
it('returns a valid luminance for rgb black', () => {
|
||||
expect(getLuminance('rgba(0, 0, 0)')).toEqual(0);
|
||||
expect(getLuminance('rgb(0, 0, 0)')).toEqual(0);
|
||||
expect(getLuminance('color(display-p3 0 0 0)')).toEqual(0);
|
||||
});
|
||||
|
||||
it('returns a valid luminance for rgb white', () => {
|
||||
expect(getLuminance('rgba(255, 255, 255)')).toEqual(1);
|
||||
expect(getLuminance('rgb(255, 255, 255)')).toEqual(1);
|
||||
});
|
||||
|
||||
it('returns a valid luminance for rgb mid-grey', () => {
|
||||
expect(getLuminance('rgba(127, 127, 127)')).toEqual(0.212);
|
||||
expect(getLuminance('rgb(127, 127, 127)')).toEqual(0.212);
|
||||
});
|
||||
|
||||
it('returns a valid luminance for an rgb color', () => {
|
||||
expect(getLuminance('rgb(255, 127, 0)')).toEqual(0.364);
|
||||
});
|
||||
|
||||
it('returns a valid luminance from an hsl color', () => {
|
||||
expect(getLuminance('hsl(100, 100%, 50%)')).toEqual(0.735);
|
||||
});
|
||||
|
||||
it('returns an equal luminance for the same color in different formats', () => {
|
||||
const hsl = 'hsl(100, 100%, 50%)';
|
||||
const rgb = 'rgb(85, 255, 0)';
|
||||
expect(getLuminance(hsl)).toEqual(getLuminance(rgb));
|
||||
});
|
||||
|
||||
it('returns a valid luminance from an CSS4 color', () => {
|
||||
expect(getLuminance('color(display-p3 1 1 0.1)')).toEqual(0.929);
|
||||
});
|
||||
|
||||
it('throw on invalid colors', () => {
|
||||
expect(() => {
|
||||
getLuminance('black');
|
||||
}).toThrowError(/Unsupported 'black' color/);
|
||||
});
|
||||
});
|
||||
|
||||
describe('emphasize', () => {
|
||||
it('lightens a dark rgb color with the coefficient provided', () => {
|
||||
expect(emphasize('rgb(1, 2, 3)', 0.4)).toEqual(lighten('rgb(1, 2, 3)', 0.4));
|
||||
});
|
||||
|
||||
it('darkens a light rgb color with the coefficient provided', () => {
|
||||
expect(emphasize('rgb(250, 240, 230)', 0.3)).toEqual(darken('rgb(250, 240, 230)', 0.3));
|
||||
});
|
||||
|
||||
it('lightens a dark rgb color with the coefficient 0.15 by default', () => {
|
||||
expect(emphasize('rgb(1, 2, 3)')).toEqual(lighten('rgb(1, 2, 3)', 0.15));
|
||||
});
|
||||
|
||||
it('darkens a light rgb color with the coefficient 0.15 by default', () => {
|
||||
expect(emphasize('rgb(250, 240, 230)')).toEqual(darken('rgb(250, 240, 230)', 0.15));
|
||||
});
|
||||
|
||||
it('lightens a dark CSS4 color with the coefficient 0.15 by default', () => {
|
||||
expect(emphasize('color(display-p3 0.1 0.1 0.1)')).toEqual(lighten('color(display-p3 0.1 0.1 0.1)', 0.15));
|
||||
});
|
||||
|
||||
it('darkens a light CSS4 color with the coefficient 0.15 by default', () => {
|
||||
expect(emphasize('color(display-p3 1 1 0.1)')).toEqual(darken('color(display-p3 1 1 0.1)', 0.15));
|
||||
});
|
||||
});
|
||||
|
||||
describe('alpha', () => {
|
||||
it('converts an rgb color to an rgba color with the value provided', () => {
|
||||
expect(alpha('rgb(1, 2, 3)', 0.4)).toEqual('rgba(1, 2, 3, 0.4)');
|
||||
});
|
||||
|
||||
it('updates an CSS4 color with the alpha value provided', () => {
|
||||
expect(alpha('color(display-p3 1 2 3)', 0.4)).toEqual('color(display-p3 1 2 3 /0.4)');
|
||||
});
|
||||
|
||||
it('updates an rgba color with the alpha value provided', () => {
|
||||
expect(alpha('rgba(255, 0, 0, 0.2)', 0.5)).toEqual('rgba(255, 0, 0, 0.5)');
|
||||
});
|
||||
|
||||
it('converts an hsl color to an hsla color with the value provided', () => {
|
||||
expect(alpha('hsl(0, 100%, 50%)', 0.1)).toEqual('hsla(0, 100%, 50%, 0.1)');
|
||||
});
|
||||
|
||||
it('updates an hsla color with the alpha value provided', () => {
|
||||
expect(alpha('hsla(0, 100%, 50%, 0.2)', 0.5)).toEqual('hsla(0, 100%, 50%, 0.5)');
|
||||
});
|
||||
|
||||
it('throw on invalid colors', () => {
|
||||
expect(() => {
|
||||
alpha('white', 0.4);
|
||||
}).toThrowError(/Unsupported 'white' color/);
|
||||
});
|
||||
});
|
||||
|
||||
describe('darken', () => {
|
||||
it("doesn't modify rgb black", () => {
|
||||
expect(darken('rgb(0, 0, 0)', 0.1)).toEqual('rgb(0, 0, 0)');
|
||||
});
|
||||
|
||||
it("doesn't overshoot if an above-range coefficient is supplied", () => {
|
||||
expect(darken('rgb(0, 127, 255)', 1.5)).toEqual('rgb(0, 0, 0)');
|
||||
expect(consoleErrorMock).toHaveBeenCalledWith('The value provided 1.5 is out of range [0, 1].');
|
||||
});
|
||||
|
||||
it("doesn't overshoot if a below-range coefficient is supplied", () => {
|
||||
expect(darken('rgb(0, 127, 255)', -0.1)).toEqual('rgb(0, 127, 255)');
|
||||
expect(consoleErrorMock).toHaveBeenCalledWith('The value provided 1.5 is out of range [0, 1].');
|
||||
});
|
||||
|
||||
it('darkens rgb white to black when coefficient is 1', () => {
|
||||
expect(darken('rgb(255, 255, 255)', 1)).toEqual('rgb(0, 0, 0)');
|
||||
});
|
||||
|
||||
it('retains the alpha value in an rgba color', () => {
|
||||
expect(darken('rgb(0, 0, 0, 0.5)', 0.1)).toEqual('rgb(0, 0, 0, 0.5)');
|
||||
});
|
||||
|
||||
it('darkens rgb white by 10% when coefficient is 0.1', () => {
|
||||
expect(darken('rgb(255, 255, 255)', 0.1)).toEqual('rgb(229, 229, 229)');
|
||||
});
|
||||
|
||||
it('darkens rgb red by 50% when coefficient is 0.5', () => {
|
||||
expect(darken('rgb(255, 0, 0)', 0.5)).toEqual('rgb(127, 0, 0)');
|
||||
});
|
||||
|
||||
it('darkens rgb grey by 50% when coefficient is 0.5', () => {
|
||||
expect(darken('rgb(127, 127, 127)', 0.5)).toEqual('rgb(63, 63, 63)');
|
||||
});
|
||||
|
||||
it("doesn't modify rgb colors when coefficient is 0", () => {
|
||||
expect(darken('rgb(255, 255, 255)', 0)).toEqual('rgb(255, 255, 255)');
|
||||
});
|
||||
|
||||
it('darkens hsl red by 50% when coefficient is 0.5', () => {
|
||||
expect(darken('hsl(0, 100%, 50%)', 0.5)).toEqual('hsl(0, 100%, 25%)');
|
||||
});
|
||||
|
||||
it("doesn't modify hsl colors when coefficient is 0", () => {
|
||||
expect(darken('hsl(0, 100%, 50%)', 0)).toEqual('hsl(0, 100%, 50%)');
|
||||
});
|
||||
|
||||
it("doesn't modify hsl colors when l is 0%", () => {
|
||||
expect(darken('hsl(0, 50%, 0%)', 0.5)).toEqual('hsl(0, 50%, 0%)');
|
||||
});
|
||||
|
||||
it('darkens CSS4 color red by 50% when coefficient is 0.5', () => {
|
||||
expect(darken('color(display-p3 1 0 0)', 0.5)).toEqual('color(display-p3 0.5 0 0)');
|
||||
});
|
||||
|
||||
it("doesn't modify CSS4 color when coefficient is 0", () => {
|
||||
expect(darken('color(display-p3 1 0 0)', 0)).toEqual('color(display-p3 1 0 0)');
|
||||
});
|
||||
});
|
||||
|
||||
describe('lighten', () => {
|
||||
it("doesn't modify rgb white", () => {
|
||||
expect(lighten('rgb(255, 255, 255)', 0.1)).toEqual('rgb(255, 255, 255)');
|
||||
});
|
||||
|
||||
it("doesn't overshoot if an above-range coefficient is supplied", () => {
|
||||
expect(lighten('rgb(0, 127, 255)', 1.5)).toEqual('rgb(255, 255, 255)');
|
||||
});
|
||||
|
||||
it("doesn't overshoot if a below-range coefficient is supplied", () => {
|
||||
expect(lighten('rgb(0, 127, 255)', -0.1)).toEqual('rgb(0, 127, 255)');
|
||||
});
|
||||
|
||||
it('lightens rgb black to white when coefficient is 1', () => {
|
||||
expect(lighten('rgb(0, 0, 0)', 1)).toEqual('rgb(255, 255, 255)');
|
||||
});
|
||||
|
||||
it('retains the alpha value in an rgba color', () => {
|
||||
expect(lighten('rgb(255, 255, 255, 0.5)', 0.1)).toEqual('rgb(255, 255, 255, 0.5)');
|
||||
});
|
||||
|
||||
it('lightens rgb black by 10% when coefficient is 0.1', () => {
|
||||
expect(lighten('rgb(0, 0, 0)', 0.1)).toEqual('rgb(25, 25, 25)');
|
||||
});
|
||||
|
||||
it('lightens rgb red by 50% when coefficient is 0.5', () => {
|
||||
expect(lighten('rgb(255, 0, 0)', 0.5)).toEqual('rgb(255, 127, 127)');
|
||||
});
|
||||
|
||||
it('lightens rgb grey by 50% when coefficient is 0.5', () => {
|
||||
expect(lighten('rgb(127, 127, 127)', 0.5)).toEqual('rgb(191, 191, 191)');
|
||||
});
|
||||
|
||||
it("doesn't modify rgb colors when coefficient is 0", () => {
|
||||
expect(lighten('rgb(127, 127, 127)', 0)).toEqual('rgb(127, 127, 127)');
|
||||
});
|
||||
|
||||
it('lightens hsl red by 50% when coefficient is 0.5', () => {
|
||||
expect(lighten('hsl(0, 100%, 50%)', 0.5)).toEqual('hsl(0, 100%, 75%)');
|
||||
});
|
||||
|
||||
it("doesn't modify hsl colors when coefficient is 0", () => {
|
||||
expect(lighten('hsl(0, 100%, 50%)', 0)).toEqual('hsl(0, 100%, 50%)');
|
||||
});
|
||||
|
||||
it("doesn't modify hsl colors when `l` is 100%", () => {
|
||||
expect(lighten('hsl(0, 50%, 100%)', 0.5)).toEqual('hsl(0, 50%, 100%)');
|
||||
});
|
||||
|
||||
it('lightens CSS4 color red by 50% when coefficient is 0.5', () => {
|
||||
expect(lighten('color(display-p3 1 0 0)', 0.5)).toEqual('color(display-p3 1 0.5 0.5)');
|
||||
});
|
||||
|
||||
it("doesn't modify CSS4 color when coefficient is 0", () => {
|
||||
expect(lighten('color(display-p3 1 0 0)', 0)).toEqual('color(display-p3 1 0 0)');
|
||||
});
|
||||
});
|
||||
});
|
||||
286
packages/grafana-data/src/themes/colorManipulator.ts
Normal file
286
packages/grafana-data/src/themes/colorManipulator.ts
Normal file
@@ -0,0 +1,286 @@
|
||||
// Code based on Material-UI
|
||||
// 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
|
||||
|
||||
/**
|
||||
* Returns a number whose value is limited to the given range.
|
||||
* @param {number} value The value to be clamped
|
||||
* @param {number} min The lower boundary of the output range
|
||||
* @param {number} max The upper boundary of the output range
|
||||
* @returns {number} A number in the range [min, max]
|
||||
*/
|
||||
function clamp(value: number, min = 0, max = 1) {
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
if (value < min || value > max) {
|
||||
console.error(`The value provided ${value} is out of range [${min}, ${max}].`);
|
||||
}
|
||||
}
|
||||
|
||||
return Math.min(Math.max(min, value), max);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a color from CSS hex format to CSS rgb format.
|
||||
* @param {string} color - Hex color, i.e. #nnn or #nnnnnn
|
||||
* @returns {string} A CSS rgb color string
|
||||
*/
|
||||
export function hexToRgb(color: string) {
|
||||
color = color.substr(1);
|
||||
|
||||
const re = new RegExp(`.{1,${color.length >= 6 ? 2 : 1}}`, 'g');
|
||||
let colors = color.match(re);
|
||||
|
||||
if (colors && colors[0].length === 1) {
|
||||
colors = colors.map((n) => n + n);
|
||||
}
|
||||
|
||||
return colors
|
||||
? `rgb${colors.length === 4 ? 'a' : ''}(${colors
|
||||
.map((n, index) => {
|
||||
return index < 3 ? parseInt(n, 16) : Math.round((parseInt(n, 16) / 255) * 1000) / 1000;
|
||||
})
|
||||
.join(', ')})`
|
||||
: '';
|
||||
}
|
||||
|
||||
function intToHex(int: number) {
|
||||
const hex = int.toString(16);
|
||||
return hex.length === 1 ? `0${hex}` : hex;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a color from CSS rgb format to CSS hex format.
|
||||
* @param {string} color - RGB color, i.e. rgb(n, n, n)
|
||||
* @returns {string} A CSS rgb color string, i.e. #nnnnnn
|
||||
*/
|
||||
export function rgbToHex(color: string) {
|
||||
// Idempotent
|
||||
if (color.indexOf('#') === 0) {
|
||||
return color;
|
||||
}
|
||||
|
||||
const { values } = decomposeColor(color);
|
||||
return `#${values.map((n: number) => intToHex(n)).join('')}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a color from hsl format to rgb format.
|
||||
* @param {string} color - HSL color values
|
||||
* @returns {string} rgb color values
|
||||
*/
|
||||
export function hslToRgb(color: string | DecomposeColor) {
|
||||
const parts = decomposeColor(color);
|
||||
const { values } = parts;
|
||||
const h = values[0];
|
||||
const s = values[1] / 100;
|
||||
const l = values[2] / 100;
|
||||
const a = s * Math.min(l, 1 - l);
|
||||
const f = (n: number, k = (n + h / 30) % 12) => l - a * Math.max(Math.min(k - 3, 9 - k, 1), -1);
|
||||
|
||||
let type = 'rgb';
|
||||
const rgb = [Math.round(f(0) * 255), Math.round(f(8) * 255), Math.round(f(4) * 255)];
|
||||
|
||||
if (parts.type === 'hsla') {
|
||||
type += 'a';
|
||||
rgb.push(values[3]);
|
||||
}
|
||||
|
||||
return recomposeColor({ type, values: rgb });
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an object with the type and values of a color.
|
||||
*
|
||||
* Note: Does not support rgb % values.
|
||||
* @param {string} color - CSS color, i.e. one of: #nnn, #nnnnnn, rgb(), rgba(), hsl(), hsla()
|
||||
* @returns {object} - A MUI color object: {type: string, values: number[]}
|
||||
*/
|
||||
export function decomposeColor(color: string | DecomposeColor): DecomposeColor {
|
||||
// Idempotent
|
||||
if (typeof color !== 'string') {
|
||||
return color;
|
||||
}
|
||||
|
||||
if (color.charAt(0) === '#') {
|
||||
return decomposeColor(hexToRgb(color));
|
||||
}
|
||||
|
||||
const marker = color.indexOf('(');
|
||||
const type = color.substring(0, marker);
|
||||
|
||||
if (['rgb', 'rgba', 'hsl', 'hsla', 'color'].indexOf(type) === -1) {
|
||||
throw new Error(
|
||||
`Unsupported '${color}' color. The following formats are supported: #nnn, #nnnnnn, rgb(), rgba(), hsl(), hsla(), color()`
|
||||
);
|
||||
}
|
||||
|
||||
let values: any = color.substring(marker + 1, color.length - 1);
|
||||
let colorSpace;
|
||||
|
||||
if (type === 'color') {
|
||||
values = values.split(' ');
|
||||
colorSpace = values.shift();
|
||||
if (values.length === 4 && values[3].charAt(0) === '/') {
|
||||
values[3] = values[3].substr(1);
|
||||
}
|
||||
if (['srgb', 'display-p3', 'a98-rgb', 'prophoto-rgb', 'rec-2020'].indexOf(colorSpace) === -1) {
|
||||
throw new Error(
|
||||
`Unsupported ${colorSpace} color space. The following color spaces are supported: srgb, display-p3, a98-rgb, prophoto-rgb, rec-2020.`
|
||||
);
|
||||
}
|
||||
} else {
|
||||
values = values.split(',');
|
||||
}
|
||||
|
||||
values = values.map((value: string) => parseFloat(value));
|
||||
return { type, values, colorSpace };
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a color object with type and values to a string.
|
||||
* @param {object} color - Decomposed color
|
||||
* @param {string} color.type - One of: 'rgb', 'rgba', 'hsl', 'hsla'
|
||||
* @param {array} color.values - [n,n,n] or [n,n,n,n]
|
||||
* @returns {string} A CSS color string
|
||||
*/
|
||||
export function recomposeColor(color: DecomposeColor) {
|
||||
const { type, colorSpace } = color;
|
||||
let values: any = color.values;
|
||||
|
||||
if (type.indexOf('rgb') !== -1) {
|
||||
// Only convert the first 3 values to int (i.e. not alpha)
|
||||
values = values.map((n: string, i: number) => (i < 3 ? parseInt(n, 10) : n));
|
||||
} else if (type.indexOf('hsl') !== -1) {
|
||||
values[1] = `${values[1]}%`;
|
||||
values[2] = `${values[2]}%`;
|
||||
}
|
||||
if (type.indexOf('color') !== -1) {
|
||||
values = `${colorSpace} ${values.join(' ')}`;
|
||||
} else {
|
||||
values = `${values.join(', ')}`;
|
||||
}
|
||||
|
||||
return `${type}(${values})`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the contrast ratio between two colors.
|
||||
*
|
||||
* Formula: https://www.w3.org/TR/WCAG20-TECHS/G17.html#G17-tests
|
||||
* @param {string} foreground - CSS color, i.e. one of: #nnn, #nnnnnn, rgb(), rgba(), hsl(), hsla()
|
||||
* @param {string} background - CSS color, i.e. one of: #nnn, #nnnnnn, rgb(), rgba(), hsl(), hsla()
|
||||
* @returns {number} A contrast ratio value in the range 0 - 21.
|
||||
*/
|
||||
export function getContrastRatio(foreground: string, background: string) {
|
||||
const lumA = getLuminance(foreground);
|
||||
const lumB = getLuminance(background);
|
||||
return (Math.max(lumA, lumB) + 0.05) / (Math.min(lumA, lumB) + 0.05);
|
||||
}
|
||||
|
||||
/**
|
||||
* The relative brightness of any point in a color space,
|
||||
* normalized to 0 for darkest black and 1 for lightest white.
|
||||
*
|
||||
* Formula: https://www.w3.org/TR/WCAG20-TECHS/G17.html#G17-tests
|
||||
* @param {string} color - CSS color, i.e. one of: #nnn, #nnnnnn, rgb(), rgba(), hsl(), hsla(), color()
|
||||
* @returns {number} The relative brightness of the color in the range 0 - 1
|
||||
*/
|
||||
export function getLuminance(color: string) {
|
||||
const parts = decomposeColor(color);
|
||||
|
||||
let rgb = parts.type === 'hsl' ? decomposeColor(hslToRgb(color)).values : parts.values;
|
||||
const rgbNumbers = rgb.map((val: any) => {
|
||||
if (parts.type !== 'color') {
|
||||
val /= 255; // normalized
|
||||
}
|
||||
return val <= 0.03928 ? val / 12.92 : ((val + 0.055) / 1.055) ** 2.4;
|
||||
});
|
||||
|
||||
// Truncate at 3 digits
|
||||
return Number((0.2126 * rgbNumbers[0] + 0.7152 * rgbNumbers[1] + 0.0722 * rgbNumbers[2]).toFixed(3));
|
||||
}
|
||||
|
||||
/**
|
||||
* Darken or lighten a color, depending on its luminance.
|
||||
* Light colors are darkened, dark colors are lightened.
|
||||
* @param {string} color - CSS color, i.e. one of: #nnn, #nnnnnn, rgb(), rgba(), hsl(), hsla(), color()
|
||||
* @param {number} coefficient=0.15 - multiplier in the range 0 - 1
|
||||
* @returns {string} A CSS color string. Hex input values are returned as rgb
|
||||
*/
|
||||
export function emphasize(color: string, coefficient = 0.15) {
|
||||
return getLuminance(color) > 0.5 ? darken(color, coefficient) : lighten(color, coefficient);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the absolute transparency of a color.
|
||||
* Any existing alpha values are overwritten.
|
||||
* @param {string} color - CSS color, i.e. one of: #nnn, #nnnnnn, rgb(), rgba(), hsl(), hsla(), color()
|
||||
* @param {number} value - value to set the alpha channel to in the range 0 - 1
|
||||
* @returns {string} A CSS color string. Hex input values are returned as rgb
|
||||
*/
|
||||
export function alpha(color: string, value: number) {
|
||||
const parts = decomposeColor(color);
|
||||
value = clamp(value);
|
||||
|
||||
if (parts.type === 'rgb' || parts.type === 'hsl') {
|
||||
parts.type += 'a';
|
||||
}
|
||||
if (parts.type === 'color') {
|
||||
parts.values[3] = `/${value}`;
|
||||
} else {
|
||||
parts.values[3] = value;
|
||||
}
|
||||
|
||||
return recomposeColor(parts);
|
||||
}
|
||||
|
||||
/**
|
||||
* Darkens a color.
|
||||
* @param {string} color - CSS color, i.e. one of: #nnn, #nnnnnn, rgb(), rgba(), hsl(), hsla(), color()
|
||||
* @param {number} coefficient - multiplier in the range 0 - 1
|
||||
* @returns {string} A CSS color string. Hex input values are returned as rgb
|
||||
*/
|
||||
export function darken(color: string, coefficient: number) {
|
||||
const parts = decomposeColor(color);
|
||||
coefficient = clamp(coefficient);
|
||||
|
||||
if (parts.type.indexOf('hsl') !== -1) {
|
||||
parts.values[2] *= 1 - coefficient;
|
||||
} else if (parts.type.indexOf('rgb') !== -1 || parts.type.indexOf('color') !== -1) {
|
||||
for (let i = 0; i < 3; i += 1) {
|
||||
parts.values[i] *= 1 - coefficient;
|
||||
}
|
||||
}
|
||||
return recomposeColor(parts);
|
||||
}
|
||||
|
||||
/**
|
||||
* Lightens a color.
|
||||
* @param {string} color - CSS color, i.e. one of: #nnn, #nnnnnn, rgb(), rgba(), hsl(), hsla(), color()
|
||||
* @param {number} coefficient - multiplier in the range 0 - 1
|
||||
* @returns {string} A CSS color string. Hex input values are returned as rgb
|
||||
*/
|
||||
export function lighten(color: string, coefficient: number) {
|
||||
const parts = decomposeColor(color);
|
||||
coefficient = clamp(coefficient);
|
||||
|
||||
if (parts.type.indexOf('hsl') !== -1) {
|
||||
parts.values[2] += (100 - parts.values[2]) * coefficient;
|
||||
} else if (parts.type.indexOf('rgb') !== -1) {
|
||||
for (let i = 0; i < 3; i += 1) {
|
||||
parts.values[i] += (255 - parts.values[i]) * coefficient;
|
||||
}
|
||||
} else if (parts.type.indexOf('color') !== -1) {
|
||||
for (let i = 0; i < 3; i += 1) {
|
||||
parts.values[i] += (1 - parts.values[i]) * coefficient;
|
||||
}
|
||||
}
|
||||
|
||||
return recomposeColor(parts);
|
||||
}
|
||||
|
||||
interface DecomposeColor {
|
||||
type: string;
|
||||
values: any;
|
||||
colorSpace?: string;
|
||||
}
|
||||
49
packages/grafana-data/src/themes/colors.ts
Normal file
49
packages/grafana-data/src/themes/colors.ts
Normal file
@@ -0,0 +1,49 @@
|
||||
export const colors = {
|
||||
white: '#fff',
|
||||
black: '#000',
|
||||
|
||||
// New greys palette used by next-gen form elements
|
||||
gray98: '#f7f8fa',
|
||||
gray97: '#f1f5f9',
|
||||
gray95: '#e9edf2',
|
||||
gray90: '#dce1e6',
|
||||
gray85: '#c7d0d9',
|
||||
gray70: '#9fa7b3',
|
||||
gray60: '#7b8087',
|
||||
gray33: '#464c54',
|
||||
gray25: '#2c3235',
|
||||
gray15: '#202226',
|
||||
gray10: '#141619',
|
||||
gray05: '#0b0c0e',
|
||||
|
||||
blueDark1: '#3658E2',
|
||||
blueDark2: '#5B93FF',
|
||||
|
||||
blueLight1: '#276EF1',
|
||||
blueLight2: '#93BDFE',
|
||||
blueLight3: '#1F62E0',
|
||||
|
||||
redDark1: '#D10E5C',
|
||||
redDark2: '#FF5286',
|
||||
|
||||
redLight1: '#CF0E5B',
|
||||
redLight2: '#FF5286',
|
||||
|
||||
green1: '#13875D',
|
||||
green2: '#6CCF8E',
|
||||
|
||||
// New reds palette used by next-gen form elements
|
||||
red88: '#e02f44', // redBase
|
||||
|
||||
// below taken from dark theme
|
||||
redShade: '#c4162a',
|
||||
redBase: '#e02f44',
|
||||
greenBase: '#299c46',
|
||||
greenShade: '#23843b',
|
||||
red: '#d44a3a',
|
||||
yellow: '#ecbb13',
|
||||
purple: '#9933cc',
|
||||
variable: '#32d1df',
|
||||
orange: '#eb7b18',
|
||||
orangeDark: '#ff780a',
|
||||
};
|
||||
27
packages/grafana-data/src/themes/createComponents.ts
Normal file
27
packages/grafana-data/src/themes/createComponents.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
/** @beta */
|
||||
export interface ThemeComponents {
|
||||
/** Applies to normal buttons, inputs, radio buttons, etc */
|
||||
height: {
|
||||
sm: number;
|
||||
md: number;
|
||||
lg: number;
|
||||
};
|
||||
panel: {
|
||||
padding: number;
|
||||
headerHeight: number;
|
||||
};
|
||||
}
|
||||
|
||||
export function createComponents(): ThemeComponents {
|
||||
return {
|
||||
height: {
|
||||
sm: 3,
|
||||
md: 4,
|
||||
lg: 6,
|
||||
},
|
||||
panel: {
|
||||
padding: 1,
|
||||
headerHeight: 4,
|
||||
},
|
||||
};
|
||||
}
|
||||
17
packages/grafana-data/src/themes/createPalette.test.ts
Normal file
17
packages/grafana-data/src/themes/createPalette.test.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import { createPalette } from './createPalette';
|
||||
|
||||
describe('createColors', () => {
|
||||
it('Should enrich colors', () => {
|
||||
const palette = createPalette({});
|
||||
expect(palette.primary.name).toBe('primary');
|
||||
});
|
||||
|
||||
it('Should allow overrides', () => {
|
||||
const palette = createPalette({
|
||||
primary: {
|
||||
main: '#FF0000',
|
||||
},
|
||||
});
|
||||
expect(palette.primary.main).toBe('#FF0000');
|
||||
});
|
||||
});
|
||||
250
packages/grafana-data/src/themes/createPalette.ts
Normal file
250
packages/grafana-data/src/themes/createPalette.ts
Normal file
@@ -0,0 +1,250 @@
|
||||
import { merge } from 'lodash';
|
||||
import { emphasize, getContrastRatio } from './colorManipulator';
|
||||
import { colors } from './colors';
|
||||
import { DeepPartial, ThemePaletteColor } from './types';
|
||||
|
||||
/** @internal */
|
||||
export type ThemePaletteMode = 'light' | 'dark';
|
||||
|
||||
/** @internal */
|
||||
export interface ThemePaletteBase<TColor> {
|
||||
mode: ThemePaletteMode;
|
||||
|
||||
primary: TColor;
|
||||
secondary: TColor;
|
||||
info: TColor;
|
||||
error: TColor;
|
||||
success: TColor;
|
||||
warning: TColor;
|
||||
|
||||
text: {
|
||||
primary: string;
|
||||
secondary: string;
|
||||
disabled: string;
|
||||
link: string;
|
||||
/** Used for auto white or dark text on colored backgrounds */
|
||||
maxContrast: string;
|
||||
};
|
||||
|
||||
layer0: string;
|
||||
layer1: string;
|
||||
layer2: string;
|
||||
|
||||
border0: string;
|
||||
border1: string;
|
||||
border2: string;
|
||||
|
||||
formComponent: {
|
||||
background: string;
|
||||
text: string;
|
||||
border: string;
|
||||
disabledBackground: string;
|
||||
disabledText: string;
|
||||
};
|
||||
|
||||
hoverFactor: number;
|
||||
contrastThreshold: number;
|
||||
tonalOffset: number;
|
||||
}
|
||||
|
||||
/** @beta */
|
||||
export interface ThemePalette extends ThemePaletteBase<ThemePaletteColor> {
|
||||
/** Returns a text color for the background */
|
||||
getContrastText(background: string): string;
|
||||
/* Retruns a hover color for any default color */
|
||||
getHoverColor(defaultColor: string): string;
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
export type ThemePaletteInput = DeepPartial<ThemePaletteBase<ThemePaletteColor>>;
|
||||
|
||||
class DarkPalette implements ThemePaletteBase<Partial<ThemePaletteColor>> {
|
||||
mode: ThemePaletteMode = 'dark';
|
||||
|
||||
text = {
|
||||
primary: 'rgba(255, 255, 255, 0.75)',
|
||||
secondary: 'rgba(255, 255, 255, 0.50)',
|
||||
disabled: 'rgba(255, 255, 255, 0.3)',
|
||||
link: colors.blueDark2,
|
||||
maxContrast: colors.white,
|
||||
};
|
||||
|
||||
primary = {
|
||||
main: colors.blueDark1,
|
||||
border: colors.blueDark2,
|
||||
text: colors.blueDark2,
|
||||
};
|
||||
|
||||
secondary = {
|
||||
main: 'rgba(255,255,255,0.1)',
|
||||
contrastText: 'rgba(255, 255, 255, 0.8)',
|
||||
};
|
||||
|
||||
info = this.primary;
|
||||
|
||||
error = {
|
||||
main: colors.redDark1,
|
||||
border: colors.redDark2,
|
||||
text: colors.redDark2,
|
||||
};
|
||||
|
||||
success = {
|
||||
main: colors.green1,
|
||||
text: colors.green2,
|
||||
border: colors.green2,
|
||||
};
|
||||
|
||||
warning = {
|
||||
main: colors.orange,
|
||||
};
|
||||
|
||||
layer0 = colors.gray05;
|
||||
layer1 = colors.gray10;
|
||||
layer2 = colors.gray15;
|
||||
|
||||
border0 = colors.gray15;
|
||||
border1 = colors.gray25;
|
||||
border2 = colors.gray33;
|
||||
|
||||
formComponent = {
|
||||
background: this.layer0,
|
||||
border: this.border1,
|
||||
text: this.text.primary,
|
||||
disabledText: this.text.disabled,
|
||||
disabledBackground: colors.gray10,
|
||||
};
|
||||
|
||||
contrastThreshold = 3;
|
||||
hoverFactor = 0.15;
|
||||
tonalOffset = 0.1;
|
||||
}
|
||||
|
||||
class LightPalette implements ThemePaletteBase<Partial<ThemePaletteColor>> {
|
||||
mode: ThemePaletteMode = 'light';
|
||||
|
||||
primary = {
|
||||
main: colors.blueLight1,
|
||||
border: colors.blueLight3,
|
||||
text: colors.blueLight3,
|
||||
};
|
||||
|
||||
secondary = {
|
||||
main: 'rgba(0,0,0,0.2)',
|
||||
contrastText: 'rgba(0, 0, 0, 0.87)',
|
||||
};
|
||||
|
||||
info = {
|
||||
main: colors.blueLight1,
|
||||
text: colors.blueLight3,
|
||||
};
|
||||
|
||||
error = {
|
||||
main: colors.redLight1,
|
||||
text: colors.redLight2,
|
||||
border: colors.redLight2,
|
||||
};
|
||||
|
||||
success = {
|
||||
main: colors.greenBase,
|
||||
};
|
||||
|
||||
warning = {
|
||||
main: colors.orange,
|
||||
};
|
||||
|
||||
text = {
|
||||
primary: 'rgba(0, 0, 0, 0.87)',
|
||||
secondary: 'rgba(0, 0, 0, 0.54)',
|
||||
disabled: 'rgba(0, 0, 0, 0.38)',
|
||||
link: this.primary.text,
|
||||
maxContrast: colors.black,
|
||||
};
|
||||
|
||||
layer0 = colors.gray98;
|
||||
layer1 = colors.white;
|
||||
layer2 = colors.gray97;
|
||||
|
||||
border0 = colors.gray90;
|
||||
border1 = colors.gray85;
|
||||
border2 = colors.gray70;
|
||||
|
||||
formComponent = {
|
||||
background: this.layer1,
|
||||
border: this.border2,
|
||||
text: this.text.primary,
|
||||
disabledBackground: colors.gray95,
|
||||
disabledText: this.text.disabled,
|
||||
};
|
||||
|
||||
contrastThreshold = 3;
|
||||
hoverFactor = 0.15;
|
||||
tonalOffset = 0.2;
|
||||
}
|
||||
|
||||
export function createPalette(palette: ThemePaletteInput): ThemePalette {
|
||||
const dark = new DarkPalette();
|
||||
const light = new LightPalette();
|
||||
const base = (palette.mode ?? 'dark') === 'dark' ? dark : light;
|
||||
const {
|
||||
primary = base.primary,
|
||||
secondary = base.secondary,
|
||||
info = base.info,
|
||||
warning = base.warning,
|
||||
success = base.success,
|
||||
error = base.error,
|
||||
tonalOffset = base.tonalOffset,
|
||||
hoverFactor = base.hoverFactor,
|
||||
contrastThreshold = base.contrastThreshold,
|
||||
...other
|
||||
} = palette;
|
||||
|
||||
function getContrastText(background: string) {
|
||||
const contrastText =
|
||||
getContrastRatio(background, dark.text.primary) >= contrastThreshold
|
||||
? dark.text.maxContrast
|
||||
: light.text.maxContrast;
|
||||
// todo, need color framework
|
||||
return contrastText;
|
||||
}
|
||||
|
||||
function getHoverColor(color: string) {
|
||||
return emphasize(color, hoverFactor);
|
||||
}
|
||||
|
||||
const getRichColor = ({ color, name }: GetRichColorProps): ThemePaletteColor => {
|
||||
color = { ...color, name };
|
||||
if (!color.main) {
|
||||
throw new Error(`Missing main color for ${name}`);
|
||||
}
|
||||
if (!color.border) {
|
||||
color.border = color.main;
|
||||
}
|
||||
if (!color.text) {
|
||||
color.text = color.main;
|
||||
}
|
||||
if (!color.contrastText) {
|
||||
color.contrastText = getContrastText(color.main);
|
||||
}
|
||||
return color as ThemePaletteColor;
|
||||
};
|
||||
|
||||
return merge(
|
||||
{
|
||||
...base,
|
||||
primary: getRichColor({ color: primary, name: 'primary' }),
|
||||
secondary: getRichColor({ color: secondary, name: 'secondary' }),
|
||||
info: getRichColor({ color: info, name: 'info' }),
|
||||
error: getRichColor({ color: error, name: 'error' }),
|
||||
success: getRichColor({ color: success, name: 'success' }),
|
||||
warning: getRichColor({ color: warning, name: 'warning' }),
|
||||
getContrastText,
|
||||
getHoverColor,
|
||||
},
|
||||
other
|
||||
);
|
||||
}
|
||||
|
||||
interface GetRichColorProps {
|
||||
color: Partial<ThemePaletteColor>;
|
||||
name: string;
|
||||
}
|
||||
49
packages/grafana-data/src/themes/createShadows.ts
Normal file
49
packages/grafana-data/src/themes/createShadows.ts
Normal file
@@ -0,0 +1,49 @@
|
||||
import { ThemePalette } from './createPalette';
|
||||
|
||||
/** @beta */
|
||||
export interface ThemeShadows {
|
||||
z1: string;
|
||||
z2: string;
|
||||
z3: string;
|
||||
}
|
||||
|
||||
function createDarkShadow(...px: number[]) {
|
||||
const shadowKeyUmbraOpacity = 0.2;
|
||||
const shadowKeyPenumbraOpacity = 0.14;
|
||||
const shadowAmbientShadowOpacity = 0.12;
|
||||
|
||||
return [
|
||||
`${px[0]}px ${px[1]}px ${px[2]}px ${px[3]}px rgba(0,0,0,${shadowKeyUmbraOpacity})`,
|
||||
`${px[4]}px ${px[5]}px ${px[6]}px ${px[7]}px rgba(0,0,0,${shadowKeyPenumbraOpacity})`,
|
||||
`${px[8]}px ${px[9]}px ${px[10]}px ${px[11]}px rgba(0,0,0,${shadowAmbientShadowOpacity})`,
|
||||
].join(',');
|
||||
}
|
||||
|
||||
function createLightShadow(...px: number[]) {
|
||||
const shadowKeyUmbraOpacity = 0.2;
|
||||
const shadowKeyPenumbraOpacity = 0.14;
|
||||
const shadowAmbientShadowOpacity = 0.12;
|
||||
|
||||
return [
|
||||
`${px[0]}px ${px[1]}px ${px[2]}px ${px[3]}px rgba(0,0,0,${shadowKeyUmbraOpacity})`,
|
||||
`${px[4]}px ${px[5]}px ${px[6]}px ${px[7]}px rgba(0,0,0,${shadowKeyPenumbraOpacity})`,
|
||||
`${px[8]}px ${px[9]}px ${px[10]}px ${px[11]}px rgba(0,0,0,${shadowAmbientShadowOpacity})`,
|
||||
].join(',');
|
||||
}
|
||||
|
||||
/** @alpha */
|
||||
export function createShadows(palette: ThemePalette): ThemeShadows {
|
||||
if (palette.mode === 'dark') {
|
||||
return {
|
||||
z1: createDarkShadow(0, 2, 1, -1, 0, 1, 1, 0, 0, 1, 3, 0),
|
||||
z2: createDarkShadow(0, 3, 1, -2, 0, 2, 2, 0, 0, 1, 5, 0),
|
||||
z3: createDarkShadow(0, 3, 3, -2, 0, 3, 4, 0, 0, 1, 8, 0),
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
z1: createLightShadow(0, 2, 1, -1, 0, 1, 1, 0, 0, 1, 3, 0),
|
||||
z2: createLightShadow(0, 3, 1, -2, 0, 2, 2, 0, 0, 1, 5, 0),
|
||||
z3: createLightShadow(0, 3, 3, -2, 0, 3, 4, 0, 0, 1, 8, 0),
|
||||
};
|
||||
}
|
||||
22
packages/grafana-data/src/themes/createShape.ts
Normal file
22
packages/grafana-data/src/themes/createShape.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
/** @beta */
|
||||
export interface ThemeShape {
|
||||
borderRadius: (amount?: number) => string;
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
export interface ThemeShapeInput {
|
||||
borderRadius?: number;
|
||||
}
|
||||
|
||||
export function createShape(options: ThemeShapeInput): ThemeShape {
|
||||
const baseBorderRadius = options.borderRadius ?? 2;
|
||||
|
||||
const borderRadius = (amount?: number) => {
|
||||
const value = (amount ?? 1) * baseBorderRadius;
|
||||
return `${value}px`;
|
||||
};
|
||||
|
||||
return {
|
||||
borderRadius,
|
||||
};
|
||||
}
|
||||
13
packages/grafana-data/src/themes/createSpacing.test.ts
Normal file
13
packages/grafana-data/src/themes/createSpacing.test.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import { createSpacing } from './createSpacing';
|
||||
|
||||
describe('createSpacing', () => {
|
||||
it('Spacing function should handle 0-4 arguments', () => {
|
||||
const spacing = createSpacing();
|
||||
expect(spacing()).toBe('8px');
|
||||
expect(spacing(1)).toBe('8px');
|
||||
expect(spacing(2)).toBe('16px');
|
||||
expect(spacing(1, 2)).toBe('8px 16px');
|
||||
expect(spacing(1, 2, 3)).toBe('8px 16px 24px');
|
||||
expect(spacing(1, 2, 3, 4)).toBe('8px 16px 24px 32px');
|
||||
});
|
||||
});
|
||||
68
packages/grafana-data/src/themes/createSpacing.ts
Normal file
68
packages/grafana-data/src/themes/createSpacing.ts
Normal file
@@ -0,0 +1,68 @@
|
||||
// Code based on Material UI
|
||||
// The MIT License (MIT)
|
||||
// Copyright (c) 2014 Call-Em-All
|
||||
|
||||
/** @internal */
|
||||
export type ThemeSpacingOptions = {
|
||||
gridSize?: number;
|
||||
};
|
||||
|
||||
/** @internal */
|
||||
export type ThemeSpacingArgument = number | string;
|
||||
|
||||
/**
|
||||
* @beta
|
||||
* The different signatures imply different meaning for their arguments that can't be expressed structurally.
|
||||
* We express the difference with variable names.
|
||||
* tslint:disable:unified-signatures */
|
||||
export interface ThemeSpacing {
|
||||
(): string;
|
||||
(value: number): string;
|
||||
(topBottom: ThemeSpacingArgument, rightLeft: ThemeSpacingArgument): string;
|
||||
(top: ThemeSpacingArgument, rightLeft: ThemeSpacingArgument, bottom: ThemeSpacingArgument): string;
|
||||
(
|
||||
top: ThemeSpacingArgument,
|
||||
right: ThemeSpacingArgument,
|
||||
bottom: ThemeSpacingArgument,
|
||||
left: ThemeSpacingArgument
|
||||
): string;
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
export function createSpacing(options: ThemeSpacingOptions = {}): ThemeSpacing {
|
||||
const { gridSize = 8 } = options;
|
||||
|
||||
const transform = (value: ThemeSpacingArgument) => {
|
||||
if (typeof value === 'string') {
|
||||
return value;
|
||||
}
|
||||
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
if (typeof value !== 'number') {
|
||||
console.error(`Expected spacing argument to be a number or a string, got ${value}.`);
|
||||
}
|
||||
}
|
||||
return value * gridSize;
|
||||
};
|
||||
|
||||
const spacing = (...args: Array<number | string>): string => {
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
if (!(args.length <= 4)) {
|
||||
console.error(`Too many arguments provided, expected between 0 and 4, got ${args.length}`);
|
||||
}
|
||||
}
|
||||
|
||||
if (args.length === 0) {
|
||||
args[0] = 1;
|
||||
}
|
||||
|
||||
return args
|
||||
.map((argument) => {
|
||||
const output = transform(argument);
|
||||
return typeof output === 'number' ? `${output}px` : output;
|
||||
})
|
||||
.join(' ');
|
||||
};
|
||||
|
||||
return spacing;
|
||||
}
|
||||
201
packages/grafana-data/src/themes/createTheme.test.ts
Normal file
201
packages/grafana-data/src/themes/createTheme.test.ts
Normal file
@@ -0,0 +1,201 @@
|
||||
import { createTheme } from './createTheme';
|
||||
|
||||
describe('createTheme', () => {
|
||||
it('create default theme', () => {
|
||||
const theme = createTheme();
|
||||
expect(theme).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"breakpoints": Object {
|
||||
"down": [Function],
|
||||
"keys": Array [
|
||||
"xs",
|
||||
"sm",
|
||||
"md",
|
||||
"lg",
|
||||
"xl",
|
||||
"xxl",
|
||||
],
|
||||
"unit": "px",
|
||||
"up": [Function],
|
||||
"values": Object {
|
||||
"lg": 992,
|
||||
"md": 769,
|
||||
"sm": 544,
|
||||
"xl": 1200,
|
||||
"xs": 0,
|
||||
"xxl": 1440,
|
||||
},
|
||||
},
|
||||
"components": Object {
|
||||
"height": Object {
|
||||
"lg": 6,
|
||||
"md": 4,
|
||||
"sm": 3,
|
||||
},
|
||||
"panel": Object {
|
||||
"headerHeight": 4,
|
||||
"padding": 1,
|
||||
},
|
||||
},
|
||||
"isDark": true,
|
||||
"isLight": false,
|
||||
"name": "Dark",
|
||||
"palette": Object {
|
||||
"border0": "#202226",
|
||||
"border1": "#2c3235",
|
||||
"border2": "#464c54",
|
||||
"contrastThreshold": 3,
|
||||
"error": Object {
|
||||
"border": "#FF5286",
|
||||
"contrastText": "#fff",
|
||||
"main": "#D10E5C",
|
||||
"name": "error",
|
||||
"text": "#FF5286",
|
||||
},
|
||||
"formComponent": Object {
|
||||
"background": "#0b0c0e",
|
||||
"border": "#2c3235",
|
||||
"disabledBackground": "#141619",
|
||||
"disabledText": "rgba(255, 255, 255, 0.3)",
|
||||
"text": "rgba(255, 255, 255, 0.75)",
|
||||
},
|
||||
"getContrastText": [Function],
|
||||
"getHoverColor": [Function],
|
||||
"hoverFactor": 0.15,
|
||||
"info": Object {
|
||||
"border": "#5B93FF",
|
||||
"contrastText": "#fff",
|
||||
"main": "#3658E2",
|
||||
"name": "info",
|
||||
"text": "#5B93FF",
|
||||
},
|
||||
"layer0": "#0b0c0e",
|
||||
"layer1": "#141619",
|
||||
"layer2": "#202226",
|
||||
"mode": "dark",
|
||||
"primary": Object {
|
||||
"border": "#5B93FF",
|
||||
"contrastText": "#fff",
|
||||
"main": "#3658E2",
|
||||
"name": "primary",
|
||||
"text": "#5B93FF",
|
||||
},
|
||||
"secondary": Object {
|
||||
"border": "rgba(255,255,255,0.1)",
|
||||
"contrastText": "rgba(255, 255, 255, 0.8)",
|
||||
"main": "rgba(255,255,255,0.1)",
|
||||
"name": "secondary",
|
||||
"text": "rgba(255,255,255,0.1)",
|
||||
},
|
||||
"success": Object {
|
||||
"border": "#6CCF8E",
|
||||
"contrastText": "#fff",
|
||||
"main": "#13875D",
|
||||
"name": "success",
|
||||
"text": "#6CCF8E",
|
||||
},
|
||||
"text": Object {
|
||||
"disabled": "rgba(255, 255, 255, 0.3)",
|
||||
"link": "#5B93FF",
|
||||
"maxContrast": "#fff",
|
||||
"primary": "rgba(255, 255, 255, 0.75)",
|
||||
"secondary": "rgba(255, 255, 255, 0.50)",
|
||||
},
|
||||
"tonalOffset": 0.1,
|
||||
"warning": Object {
|
||||
"border": "#eb7b18",
|
||||
"contrastText": "#000",
|
||||
"main": "#eb7b18",
|
||||
"name": "warning",
|
||||
"text": "#eb7b18",
|
||||
},
|
||||
},
|
||||
"shadows": Object {
|
||||
"z1": "0px 2px 1px -1px rgba(0,0,0,0.2),0px 1px 1px 0px rgba(0,0,0,0.14),0px 1px 3px 0px rgba(0,0,0,0.12)",
|
||||
"z2": "0px 3px 1px -2px rgba(0,0,0,0.2),0px 2px 2px 0px rgba(0,0,0,0.14),0px 1px 5px 0px rgba(0,0,0,0.12)",
|
||||
"z3": "0px 3px 3px -2px rgba(0,0,0,0.2),0px 3px 4px 0px rgba(0,0,0,0.14),0px 1px 8px 0px rgba(0,0,0,0.12)",
|
||||
},
|
||||
"shape": Object {
|
||||
"borderRadius": [Function],
|
||||
},
|
||||
"spacing": [Function],
|
||||
"typography": Object {
|
||||
"body": Object {
|
||||
"fontFamily": "\\"Roboto\\", \\"Helvetica\\", \\"Arial\\", sans-serif",
|
||||
"fontSize": "1rem",
|
||||
"fontWeight": 400,
|
||||
"letterSpacing": "0.01071em",
|
||||
"lineHeight": 1.5,
|
||||
},
|
||||
"fontFamily": "\\"Roboto\\", \\"Helvetica\\", \\"Arial\\", sans-serif",
|
||||
"fontFamilyMonospace": "Menlo, Monaco, Consolas, 'Courier New', monospace",
|
||||
"fontSize": 14,
|
||||
"fontWeightBold": 700,
|
||||
"fontWeightLight": 300,
|
||||
"fontWeightMedium": 500,
|
||||
"fontWeightRegular": 400,
|
||||
"h1": Object {
|
||||
"fontFamily": "\\"Roboto\\", \\"Helvetica\\", \\"Arial\\", sans-serif",
|
||||
"fontSize": "2rem",
|
||||
"fontWeight": 300,
|
||||
"letterSpacing": "-0.05357em",
|
||||
"lineHeight": 1.167,
|
||||
},
|
||||
"h2": Object {
|
||||
"fontFamily": "\\"Roboto\\", \\"Helvetica\\", \\"Arial\\", sans-serif",
|
||||
"fontSize": "1.7142857142857142rem",
|
||||
"fontWeight": 300,
|
||||
"letterSpacing": "-0.02083em",
|
||||
"lineHeight": 1.2,
|
||||
},
|
||||
"h3": Object {
|
||||
"fontFamily": "\\"Roboto\\", \\"Helvetica\\", \\"Arial\\", sans-serif",
|
||||
"fontSize": "1.5rem",
|
||||
"fontWeight": 400,
|
||||
"letterSpacing": "0em",
|
||||
"lineHeight": 1.167,
|
||||
},
|
||||
"h4": Object {
|
||||
"fontFamily": "\\"Roboto\\", \\"Helvetica\\", \\"Arial\\", sans-serif",
|
||||
"fontSize": "1.2857142857142858rem",
|
||||
"fontWeight": 400,
|
||||
"letterSpacing": "0.01389em",
|
||||
"lineHeight": 1.235,
|
||||
},
|
||||
"h5": Object {
|
||||
"fontFamily": "\\"Roboto\\", \\"Helvetica\\", \\"Arial\\", sans-serif",
|
||||
"fontSize": "1.1428571428571428rem",
|
||||
"fontWeight": 400,
|
||||
"letterSpacing": "0em",
|
||||
"lineHeight": 1.334,
|
||||
},
|
||||
"h6": Object {
|
||||
"fontFamily": "\\"Roboto\\", \\"Helvetica\\", \\"Arial\\", sans-serif",
|
||||
"fontSize": "1rem",
|
||||
"fontWeight": 500,
|
||||
"letterSpacing": "0.01071em",
|
||||
"lineHeight": 1.6,
|
||||
},
|
||||
"htmlFontSize": 14,
|
||||
"pxToRem": [Function],
|
||||
"size": Object {
|
||||
"base": "14px",
|
||||
"lg": "18px",
|
||||
"md": "14px",
|
||||
"sm": "12px",
|
||||
"xs": "10px",
|
||||
},
|
||||
},
|
||||
"zIndex": Object {
|
||||
"dropdown": 1030,
|
||||
"modal": 1060,
|
||||
"modalBackdrop": 1050,
|
||||
"navbarFixed": 1000,
|
||||
"sidemenu": 1020,
|
||||
"tooltip": 1040,
|
||||
"typeahead": 1030,
|
||||
},
|
||||
}
|
||||
`);
|
||||
});
|
||||
});
|
||||
67
packages/grafana-data/src/themes/createTheme.ts
Normal file
67
packages/grafana-data/src/themes/createTheme.ts
Normal file
@@ -0,0 +1,67 @@
|
||||
import { createBreakpoints, ThemeBreakpoints } from './breakpoints';
|
||||
import { ThemeComponents, createComponents } from './createComponents';
|
||||
import { createPalette, ThemePalette, ThemePaletteInput } from './createPalette';
|
||||
import { createShadows, ThemeShadows } from './createShadows';
|
||||
import { createShape, ThemeShape, ThemeShapeInput } from './createShape';
|
||||
import { createSpacing, ThemeSpacingOptions, ThemeSpacing } from './createSpacing';
|
||||
import { createTypography, ThemeTypography, ThemeTypographyInput } from './createTypography';
|
||||
import { ThemeZIndices, zIndex } from './zIndex';
|
||||
|
||||
/** @beta */
|
||||
export interface GrafanaThemeV2 {
|
||||
name: string;
|
||||
isDark: boolean;
|
||||
isLight: boolean;
|
||||
palette: ThemePalette;
|
||||
breakpoints: ThemeBreakpoints;
|
||||
spacing: ThemeSpacing;
|
||||
shape: ThemeShape;
|
||||
components: ThemeComponents;
|
||||
typography: ThemeTypography;
|
||||
zIndex: ThemeZIndices;
|
||||
shadows: ThemeShadows;
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
export interface NewThemeOptions {
|
||||
name?: string;
|
||||
palette?: ThemePaletteInput;
|
||||
spacing?: ThemeSpacingOptions;
|
||||
shape?: ThemeShapeInput;
|
||||
typography?: ThemeTypographyInput;
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
export function createTheme(options: NewThemeOptions = {}): GrafanaThemeV2 {
|
||||
const {
|
||||
name = 'Dark',
|
||||
palette: paletteInput = {},
|
||||
spacing: spacingInput = {},
|
||||
shape: shapeInput = {},
|
||||
typography: typographyInput = {},
|
||||
} = options;
|
||||
|
||||
const palette = createPalette(paletteInput);
|
||||
const breakpoints = createBreakpoints();
|
||||
const spacing = createSpacing(spacingInput);
|
||||
const shape = createShape(shapeInput);
|
||||
const components = createComponents();
|
||||
const typography = createTypography(palette, typographyInput);
|
||||
const shadows = createShadows(palette);
|
||||
|
||||
return {
|
||||
name,
|
||||
isDark: palette.mode === 'dark',
|
||||
isLight: palette.mode === 'light',
|
||||
palette,
|
||||
breakpoints,
|
||||
spacing,
|
||||
shape,
|
||||
components,
|
||||
typography,
|
||||
shadows,
|
||||
zIndex: {
|
||||
...zIndex,
|
||||
},
|
||||
};
|
||||
}
|
||||
145
packages/grafana-data/src/themes/createTypography.ts
Normal file
145
packages/grafana-data/src/themes/createTypography.ts
Normal file
@@ -0,0 +1,145 @@
|
||||
// Code based on Material UI
|
||||
// The MIT License (MIT)
|
||||
// Copyright (c) 2014 Call-Em-All
|
||||
|
||||
import { ThemePalette } from './createPalette';
|
||||
|
||||
/** @beta */
|
||||
export interface ThemeTypography {
|
||||
fontFamily: string;
|
||||
fontFamilyMonospace: string;
|
||||
fontSize: number;
|
||||
fontWeightLight: number;
|
||||
fontWeightRegular: number;
|
||||
fontWeightMedium: number;
|
||||
fontWeightBold: number;
|
||||
|
||||
// The font-size on the html element.
|
||||
htmlFontSize?: number;
|
||||
|
||||
h1: ThemeTypographyVariant;
|
||||
h2: ThemeTypographyVariant;
|
||||
h3: ThemeTypographyVariant;
|
||||
h4: ThemeTypographyVariant;
|
||||
h5: ThemeTypographyVariant;
|
||||
h6: ThemeTypographyVariant;
|
||||
|
||||
body: ThemeTypographyVariant;
|
||||
|
||||
/** from legacy old theme */
|
||||
size: {
|
||||
base: string;
|
||||
xs: string;
|
||||
sm: string;
|
||||
md: string;
|
||||
lg: string;
|
||||
};
|
||||
|
||||
pxToRem: (px: number) => string;
|
||||
}
|
||||
|
||||
export interface ThemeTypographyVariant {
|
||||
fontSize: string;
|
||||
fontWeight: number;
|
||||
lineHeight: number;
|
||||
fontFamily: string;
|
||||
letterSpacing?: string;
|
||||
}
|
||||
|
||||
export interface ThemeTypographyInput {
|
||||
fontFamily?: string;
|
||||
fontFamilyMonospace?: string;
|
||||
fontSize?: number;
|
||||
fontWeightLight?: number;
|
||||
fontWeightRegular?: number;
|
||||
fontWeightMedium?: number;
|
||||
fontWeightBold?: number;
|
||||
// hat's the font-size on the html element.
|
||||
// 16px is the default font-size used by browsers.
|
||||
htmlFontSize?: number;
|
||||
}
|
||||
|
||||
const defaultFontFamily = '"Roboto", "Helvetica", "Arial", sans-serif';
|
||||
const defaultFontFamilyMonospace = "Menlo, Monaco, Consolas, 'Courier New', monospace";
|
||||
|
||||
export function createTypography(palette: ThemePalette, typographyInput: ThemeTypographyInput = {}): ThemeTypography {
|
||||
const {
|
||||
fontFamily = defaultFontFamily,
|
||||
fontFamilyMonospace = defaultFontFamilyMonospace,
|
||||
// The default font size of the Material Specification.
|
||||
fontSize = 14, // px
|
||||
fontWeightLight = 300,
|
||||
fontWeightRegular = 400,
|
||||
fontWeightMedium = 500,
|
||||
fontWeightBold = 700,
|
||||
// Tell Grafana-UI what's the font-size on the html element.
|
||||
// 16px is the default font-size used by browsers.
|
||||
htmlFontSize = 14,
|
||||
} = typographyInput;
|
||||
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
if (typeof fontSize !== 'number') {
|
||||
console.error('Grafana-UI: `fontSize` is required to be a number.');
|
||||
}
|
||||
|
||||
if (typeof htmlFontSize !== 'number') {
|
||||
console.error('Grafana-UI: `htmlFontSize` is required to be a number.');
|
||||
}
|
||||
}
|
||||
|
||||
const coef = fontSize / 14;
|
||||
const pxToRem = (size: number) => `${(size / htmlFontSize) * coef}rem`;
|
||||
const buildVariant = (
|
||||
fontWeight: number,
|
||||
size: number,
|
||||
lineHeight: number,
|
||||
letterSpacing: number,
|
||||
casing?: object
|
||||
): ThemeTypographyVariant => ({
|
||||
fontFamily,
|
||||
fontWeight,
|
||||
fontSize: pxToRem(size),
|
||||
// Unitless following https://meyerweb.com/eric/thoughts/2006/02/08/unitless-line-heights/
|
||||
lineHeight,
|
||||
// The letter spacing was designed for the Roboto font-family. Using the same letter-spacing
|
||||
// across font-families can cause issues with the kerning.
|
||||
...(fontFamily === defaultFontFamily ? { letterSpacing: `${round(letterSpacing / size)}em` } : {}),
|
||||
...casing,
|
||||
});
|
||||
|
||||
const variants = {
|
||||
h1: buildVariant(fontWeightLight, 28, 1.167, -1.5),
|
||||
h2: buildVariant(fontWeightLight, 24, 1.2, -0.5),
|
||||
h3: buildVariant(fontWeightRegular, 21, 1.167, 0),
|
||||
h4: buildVariant(fontWeightRegular, 18, 1.235, 0.25),
|
||||
h5: buildVariant(fontWeightRegular, 16, 1.334, 0),
|
||||
h6: buildVariant(fontWeightMedium, 14, 1.6, 0.15),
|
||||
body: buildVariant(fontWeightRegular, 14, 1.5, 0.15),
|
||||
};
|
||||
|
||||
const size = {
|
||||
base: '14px',
|
||||
xs: '10px',
|
||||
sm: '12px',
|
||||
md: '14px',
|
||||
lg: '18px',
|
||||
};
|
||||
|
||||
return {
|
||||
htmlFontSize,
|
||||
pxToRem,
|
||||
fontFamily,
|
||||
fontFamilyMonospace,
|
||||
fontSize,
|
||||
fontWeightLight,
|
||||
fontWeightRegular,
|
||||
fontWeightMedium,
|
||||
fontWeightBold,
|
||||
size,
|
||||
...variants,
|
||||
};
|
||||
}
|
||||
|
||||
function round(value: number) {
|
||||
return Math.round(value * 1e5) / 1e5;
|
||||
}
|
||||
2
packages/grafana-data/src/themes/index.ts
Normal file
2
packages/grafana-data/src/themes/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export { createTheme, GrafanaThemeV2 } from './createTheme';
|
||||
export { ThemePaletteColor } from './types';
|
||||
20
packages/grafana-data/src/themes/types.ts
Normal file
20
packages/grafana-data/src/themes/types.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
/** @alpha */
|
||||
export interface ThemePaletteColor {
|
||||
/** color intent (primary, secondary, info, error, etc) */
|
||||
name: string;
|
||||
/** Main color */
|
||||
main: string;
|
||||
/** Used for text */
|
||||
text: string;
|
||||
/** Used for text */
|
||||
border: string;
|
||||
/** Used subtly colored backgrounds */
|
||||
transparent: string;
|
||||
/** Text color for text ontop of main */
|
||||
contrastText: string;
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
export type DeepPartial<T> = {
|
||||
[P in keyof T]?: DeepPartial<T[P]>;
|
||||
};
|
||||
14
packages/grafana-data/src/themes/zIndex.ts
Normal file
14
packages/grafana-data/src/themes/zIndex.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
// We need to centralize the zIndex definitions as they work
|
||||
// like global values in the browser.
|
||||
export const zIndex = {
|
||||
navbarFixed: 1000,
|
||||
sidemenu: 1020,
|
||||
dropdown: 1030,
|
||||
typeahead: 1030,
|
||||
tooltip: 1040,
|
||||
modalBackdrop: 1050,
|
||||
modal: 1060,
|
||||
};
|
||||
|
||||
/** @beta */
|
||||
export type ThemeZIndices = typeof zIndex;
|
||||
@@ -1,3 +1,5 @@
|
||||
import { GrafanaThemeV2 } from '../themes/createTheme';
|
||||
|
||||
export enum GrafanaThemeType {
|
||||
Light = 'light',
|
||||
Dark = 'dark',
|
||||
@@ -111,6 +113,7 @@ export interface GrafanaThemeCommons {
|
||||
}
|
||||
|
||||
export interface GrafanaTheme extends GrafanaThemeCommons {
|
||||
v2: GrafanaThemeV2;
|
||||
type: GrafanaThemeType;
|
||||
isDark: boolean;
|
||||
isLight: boolean;
|
||||
|
||||
@@ -2,40 +2,39 @@
|
||||
import { create } from '@storybook/theming/create';
|
||||
import lightTheme from '../src/themes/light';
|
||||
import darkTheme from '../src/themes/dark';
|
||||
import ThemeCommons from '../src/themes/default';
|
||||
import { GrafanaTheme } from '@grafana/data';
|
||||
|
||||
const createTheme = (theme: GrafanaTheme) => {
|
||||
return create({
|
||||
base: theme.name.includes('Light') ? 'light' : 'dark',
|
||||
|
||||
colorPrimary: theme.palette.brandPrimary,
|
||||
colorSecondary: theme.palette.brandPrimary,
|
||||
colorPrimary: theme.v2.palette.primary.main,
|
||||
colorSecondary: theme.v2.palette.secondary.main,
|
||||
|
||||
// UI
|
||||
appBg: theme.colors.bodyBg,
|
||||
appContentBg: theme.colors.bodyBg,
|
||||
appBorderColor: theme.colors.pageHeaderBorder,
|
||||
appBorderRadius: 4,
|
||||
appBg: theme.v2.palette.layer0,
|
||||
appContentBg: theme.v2.palette.layer1,
|
||||
appBorderColor: theme.v2.palette.border1,
|
||||
appBorderRadius: theme.v2.shape.borderRadius(1),
|
||||
|
||||
// Typography
|
||||
fontBase: ThemeCommons.typography.fontFamily.sansSerif,
|
||||
fontCode: ThemeCommons.typography.fontFamily.monospace,
|
||||
fontBase: theme.v2.typography.fontFamily,
|
||||
fontCode: theme.v2.typography.fontFamilyMonospace,
|
||||
|
||||
// Text colors
|
||||
textColor: theme.colors.text,
|
||||
textInverseColor: 'rgba(255,255,255,0.9)',
|
||||
textColor: theme.v2.palette.text.primary,
|
||||
textInverseColor: theme.v2.palette.primary.contrastText,
|
||||
|
||||
// Toolbar default and active colors
|
||||
barTextColor: theme.colors.formInputBorderActive,
|
||||
barSelectedColor: theme.palette.brandPrimary,
|
||||
barBg: theme.colors.pageHeaderBg,
|
||||
barTextColor: theme.v2.palette.primary.text,
|
||||
barSelectedColor: theme.v2.palette.getHoverColor(theme.v2.palette.primary.text),
|
||||
barBg: theme.v2.palette.layer1,
|
||||
|
||||
// Form colors
|
||||
inputBg: theme.colors.formInputBg,
|
||||
inputBorder: theme.colors.formInputBorder,
|
||||
inputTextColor: theme.colors.formInputText,
|
||||
inputBorderRadius: 4,
|
||||
inputBg: theme.v2.palette.formComponent.background,
|
||||
inputBorder: theme.v2.palette.formComponent.border,
|
||||
inputTextColor: theme.v2.palette.formComponent.text,
|
||||
inputBorderRadius: theme.v2.shape.borderRadius(1),
|
||||
|
||||
brandTitle: 'Grafana UI',
|
||||
brandUrl: './',
|
||||
|
||||
@@ -42,7 +42,7 @@ export interface RadioButtonGroupProps<T> {
|
||||
disabled?: boolean;
|
||||
disabledOptions?: T[];
|
||||
options: Array<SelectableValue<T>>;
|
||||
onChange?: (value?: T) => void;
|
||||
onChange?: (value: T) => void;
|
||||
size?: RadioButtonSize;
|
||||
fullWidth?: boolean;
|
||||
className?: string;
|
||||
|
||||
@@ -10,29 +10,31 @@ export const getFocusStyle = (theme: GrafanaTheme) => css`
|
||||
`;
|
||||
|
||||
export const sharedInputStyle = (theme: GrafanaTheme, invalid = false) => {
|
||||
const colors = theme.colors;
|
||||
const borderColor = invalid ? theme.palette.redBase : colors.formInputBorder;
|
||||
const palette = theme.v2.palette;
|
||||
const borderColor = invalid ? palette.error.border : palette.formComponent.border;
|
||||
const background = palette.formComponent.background;
|
||||
const textColor = palette.text.primary;
|
||||
|
||||
return css`
|
||||
background-color: ${colors.formInputBg};
|
||||
background-color: ${background};
|
||||
line-height: ${theme.typography.lineHeight.md};
|
||||
font-size: ${theme.typography.size.md};
|
||||
color: ${colors.formInputText};
|
||||
color: ${textColor};
|
||||
border: 1px solid ${borderColor};
|
||||
padding: 0 ${theme.spacing.sm} 0 ${theme.spacing.sm};
|
||||
padding: ${theme.v2.spacing(0, 1, 0, 1)};
|
||||
|
||||
&:-webkit-autofill,
|
||||
&:-webkit-autofill:hover {
|
||||
/* Welcome to 2005. This is a HACK to get rid od Chromes default autofill styling */
|
||||
box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0), inset 0 0 0 100px ${colors.formInputBg}!important;
|
||||
-webkit-text-fill-color: ${colors.formInputText} !important;
|
||||
box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0), inset 0 0 0 100px ${background}!important;
|
||||
-webkit-text-fill-color: ${textColor} !important;
|
||||
}
|
||||
|
||||
&:-webkit-autofill:focus {
|
||||
/* Welcome to 2005. This is a HACK to get rid od Chromes default autofill styling */
|
||||
box-shadow: 0 0 0 2px ${theme.colors.bodyBg}, 0 0 0px 4px ${theme.colors.formFocusOutline},
|
||||
inset 0 0 0 1px rgba(255, 255, 255, 0), inset 0 0 0 100px ${colors.formInputBg}!important;
|
||||
-webkit-text-fill-color: ${colors.formInputText} !important;
|
||||
inset 0 0 0 1px rgba(255, 255, 255, 0), inset 0 0 0 100px ${background}!important;
|
||||
-webkit-text-fill-color: ${textColor} !important;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
@@ -44,12 +46,12 @@ export const sharedInputStyle = (theme: GrafanaTheme, invalid = false) => {
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
background-color: ${colors.formInputBgDisabled};
|
||||
color: ${colors.formInputDisabledText};
|
||||
background-color: ${palette.formComponent.disabledBackground};
|
||||
color: ${palette.text.disabled};
|
||||
}
|
||||
|
||||
&::placeholder {
|
||||
color: ${colors.formInputPlaceholderText};
|
||||
color: ${palette.text.disabled};
|
||||
opacity: 1;
|
||||
}
|
||||
`;
|
||||
|
||||
@@ -30,9 +30,9 @@ interface StyleDeps {
|
||||
}
|
||||
|
||||
export const getInputStyles = stylesFactory(({ theme, invalid = false, width }: StyleDeps) => {
|
||||
const { palette, colors } = theme;
|
||||
const borderRadius = theme.border.radius.sm;
|
||||
const height = theme.spacing.formInputHeight;
|
||||
const theme2 = theme.v2;
|
||||
const borderRadius = theme2.shape.borderRadius(1);
|
||||
const height = theme.v2.components.height.md;
|
||||
|
||||
const prefixSuffixStaticWidth = '28px';
|
||||
const prefixSuffix = css`
|
||||
@@ -48,7 +48,7 @@ export const getInputStyles = stylesFactory(({ theme, invalid = false, width }:
|
||||
height: 100%;
|
||||
/* Min width specified for prefix/suffix classes used outside React component*/
|
||||
min-width: ${prefixSuffixStaticWidth};
|
||||
color: ${theme.colors.textWeak};
|
||||
color: ${theme2.palette.text.secondary};
|
||||
`;
|
||||
|
||||
return {
|
||||
@@ -57,14 +57,14 @@ export const getInputStyles = stylesFactory(({ theme, invalid = false, width }:
|
||||
css`
|
||||
label: input-wrapper;
|
||||
display: flex;
|
||||
width: ${width ? `${8 * width}px` : '100%'};
|
||||
height: ${height}px;
|
||||
width: ${width ? `${theme2.spacing(width)}` : '100%'};
|
||||
height: ${theme2.spacing(height)};
|
||||
border-radius: ${borderRadius};
|
||||
&:hover {
|
||||
> .prefix,
|
||||
.suffix,
|
||||
.input {
|
||||
border-color: ${invalid ? palette.redBase : colors.formInputBorder};
|
||||
border-color: ${invalid ? theme2.palette.error.border : theme2.palette.primary.border};
|
||||
}
|
||||
|
||||
// only show number buttons on hover
|
||||
@@ -147,8 +147,8 @@ export const getInputStyles = stylesFactory(({ theme, invalid = false, width }:
|
||||
`
|
||||
),
|
||||
inputDisabled: css`
|
||||
background-color: ${colors.formInputBgDisabled};
|
||||
color: ${colors.formInputDisabledText};
|
||||
background-color: ${theme2.palette.formComponent.disabledBackground};
|
||||
color: ${theme2.palette.text.disabled};
|
||||
`,
|
||||
addon: css`
|
||||
label: input-addon;
|
||||
@@ -185,8 +185,8 @@ export const getInputStyles = stylesFactory(({ theme, invalid = false, width }:
|
||||
prefixSuffix,
|
||||
css`
|
||||
label: input-prefix;
|
||||
padding-left: ${theme.spacing.sm};
|
||||
padding-right: ${theme.spacing.xs};
|
||||
padding-left: ${theme2.spacing(1)};
|
||||
padding-right: ${theme2.spacing(0.5)};
|
||||
border-right: none;
|
||||
border-top-right-radius: 0;
|
||||
border-bottom-right-radius: 0;
|
||||
@@ -196,8 +196,8 @@ export const getInputStyles = stylesFactory(({ theme, invalid = false, width }:
|
||||
prefixSuffix,
|
||||
css`
|
||||
label: input-suffix;
|
||||
padding-right: ${theme.spacing.sm};
|
||||
padding-left: ${theme.spacing.xs};
|
||||
padding-left: ${theme2.spacing(1)};
|
||||
padding-right: ${theme2.spacing(0.5)};
|
||||
margin-bottom: -2px;
|
||||
border-left: none;
|
||||
border-top-left-radius: 0;
|
||||
@@ -207,7 +207,7 @@ export const getInputStyles = stylesFactory(({ theme, invalid = false, width }:
|
||||
),
|
||||
loadingIndicator: css`
|
||||
& + * {
|
||||
margin-left: ${theme.spacing.xs};
|
||||
margin-left: ${theme2.spacing(0.5)};
|
||||
}
|
||||
`,
|
||||
};
|
||||
|
||||
@@ -2,7 +2,7 @@ import React from 'react';
|
||||
import { Colors } from './Colors';
|
||||
|
||||
export default {
|
||||
title: 'Docs Overview/ThemeColors',
|
||||
title: 'Docs Overview/Theme',
|
||||
component: Colors,
|
||||
decorators: [],
|
||||
parameters: {
|
||||
@@ -0,0 +1,18 @@
|
||||
import React from 'react';
|
||||
import { NewThemeDemo as NewThemeDemoComponent } from './NewThemeDemo';
|
||||
|
||||
export default {
|
||||
title: 'Docs Overview/Theme',
|
||||
component: NewThemeDemoComponent,
|
||||
decorators: [],
|
||||
parameters: {
|
||||
options: {
|
||||
showPanel: false,
|
||||
},
|
||||
docs: {},
|
||||
},
|
||||
};
|
||||
|
||||
export const NewThemeDemo = () => {
|
||||
return <NewThemeDemoComponent />;
|
||||
};
|
||||
248
packages/grafana-ui/src/components/ThemeDemos/NewThemeDemo.tsx
Normal file
248
packages/grafana-ui/src/components/ThemeDemos/NewThemeDemo.tsx
Normal file
@@ -0,0 +1,248 @@
|
||||
import React, { FC, useState } from 'react';
|
||||
import { css, cx } from '@emotion/css';
|
||||
import { useTheme } from '../../themes/ThemeContext';
|
||||
import { Icon } from '../Icon/Icon';
|
||||
import { HorizontalGroup } from '../Layout/Layout';
|
||||
import { GrafanaThemeV2, ThemePaletteColor } from '@grafana/data';
|
||||
import { CollapsableSection } from '../Collapse/CollapsableSection';
|
||||
import { Field } from '../Forms/Field';
|
||||
import { Input } from '../Input/Input';
|
||||
import { RadioButtonGroup } from '../Forms/RadioButtonGroup/RadioButtonGroup';
|
||||
import { Switch } from '../Switch/Switch';
|
||||
import { Button, ButtonVariant } from '../Button';
|
||||
|
||||
interface DemoBoxProps {
|
||||
bg?: string;
|
||||
border?: string;
|
||||
textColor?: string;
|
||||
}
|
||||
|
||||
const DemoBox: FC<DemoBoxProps> = ({ bg, border, children }) => {
|
||||
const style = cx(
|
||||
css`
|
||||
padding: 16px;
|
||||
background: ${bg ?? 'inherit'};
|
||||
width: 100%;
|
||||
`,
|
||||
border
|
||||
? css`
|
||||
border: 1px solid ${border};
|
||||
`
|
||||
: null
|
||||
);
|
||||
|
||||
return <div className={style}>{children}</div>;
|
||||
};
|
||||
|
||||
const DemoText: FC<{ color?: string; bold?: boolean; size?: number }> = ({ color, bold, size, children }) => {
|
||||
const style = css`
|
||||
padding: 4px;
|
||||
color: ${color ?? 'inherit'};
|
||||
font-weight: ${bold ? 500 : 400};
|
||||
font-size: ${size ?? 14}px;
|
||||
`;
|
||||
|
||||
return <div className={style}>{children}</div>;
|
||||
};
|
||||
|
||||
export const NewThemeDemo = () => {
|
||||
const [radioValue, setRadioValue] = useState('v');
|
||||
const [boolValue, setBoolValue] = useState(false);
|
||||
const oldTheme = useTheme();
|
||||
const t = oldTheme.v2;
|
||||
const variants: ButtonVariant[] = ['primary', 'secondary', 'destructive', 'link'];
|
||||
|
||||
const richColors = [
|
||||
t.palette.primary,
|
||||
t.palette.secondary,
|
||||
t.palette.success,
|
||||
t.palette.error,
|
||||
t.palette.warning,
|
||||
t.palette.info,
|
||||
];
|
||||
|
||||
const radioOptions = [
|
||||
{ value: 'h', label: 'Horizontal' },
|
||||
{ value: 'v', label: 'Vertical' },
|
||||
{ value: 'a', label: 'Auto' },
|
||||
];
|
||||
|
||||
return (
|
||||
<div
|
||||
className={css`
|
||||
width: 100%;
|
||||
color: ${t.palette.text.primary};
|
||||
`}
|
||||
>
|
||||
<DemoBox bg={t.palette.layer0}>
|
||||
<CollapsableSection label="Layers" isOpen={true}>
|
||||
<DemoText>t.palette.layer0</DemoText>
|
||||
<DemoBox bg={t.palette.layer1} border={t.palette.border0}>
|
||||
<DemoText>t.palette.layer1 is the main & preferred content </DemoText>
|
||||
<DemoBox bg={t.palette.layer2} border={t.palette.border0}>
|
||||
<DemoText>t.palette.layer2 and t.palette.border.layer1</DemoText>
|
||||
</DemoBox>
|
||||
</DemoBox>
|
||||
</CollapsableSection>
|
||||
<CollapsableSection label="Text colors" isOpen={true}>
|
||||
<HorizontalGroup>
|
||||
<DemoBox>
|
||||
<TextColors t={t} />
|
||||
</DemoBox>
|
||||
<DemoBox bg={t.palette.layer1}>
|
||||
<TextColors t={t} />
|
||||
</DemoBox>
|
||||
<DemoBox bg={t.palette.layer2}>
|
||||
<TextColors t={t} />
|
||||
</DemoBox>
|
||||
</HorizontalGroup>
|
||||
</CollapsableSection>
|
||||
<CollapsableSection label="Rich colors" isOpen={true}>
|
||||
<DemoBox bg={t.palette.layer1}>
|
||||
<table className={colorsTableStyle}>
|
||||
<thead>
|
||||
<tr>
|
||||
<td>name</td>
|
||||
<td>main</td>
|
||||
<td>border & text</td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{richColors.map((color) => (
|
||||
<RichColorDemo key={color.name} color={color} theme={t} />
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</DemoBox>
|
||||
</CollapsableSection>
|
||||
<CollapsableSection label="Forms" isOpen={true}>
|
||||
<DemoBox bg={t.palette.layer1}>
|
||||
<Field label="Input label" description="Field description">
|
||||
<Input placeholder="Placeholder" />
|
||||
</Field>
|
||||
<Field label="Input disabled" disabled>
|
||||
<Input placeholder="Placeholder" value="Disabled value" />
|
||||
</Field>
|
||||
<Field label="Radio label">
|
||||
<RadioButtonGroup options={radioOptions} value={radioValue} onChange={setRadioValue} />
|
||||
</Field>
|
||||
<HorizontalGroup>
|
||||
<Field label="Switch">
|
||||
<Switch value={boolValue} onChange={(e) => setBoolValue(e.currentTarget.checked)} />
|
||||
</Field>
|
||||
<Field label="Switch true">
|
||||
<Switch value={true} />
|
||||
</Field>
|
||||
<Field label="Switch false disabled">
|
||||
<Switch value={false} disabled />
|
||||
</Field>
|
||||
</HorizontalGroup>
|
||||
</DemoBox>
|
||||
</CollapsableSection>
|
||||
<CollapsableSection label="Shadows" isOpen={true}>
|
||||
<DemoBox bg={t.palette.layer1}>
|
||||
<HorizontalGroup>
|
||||
<ShadowDemo name="Z1" shadow={t.shadows.z1} />
|
||||
<ShadowDemo name="Z2" shadow={t.shadows.z2} />
|
||||
<ShadowDemo name="Z3" shadow={t.shadows.z3} />
|
||||
</HorizontalGroup>
|
||||
</DemoBox>
|
||||
</CollapsableSection>
|
||||
<CollapsableSection label="Buttons" isOpen={true}>
|
||||
<DemoBox bg={t.palette.layer1}>
|
||||
<HorizontalGroup>
|
||||
{variants.map((variant) => (
|
||||
<Button variant={variant} key={variant}>
|
||||
{variant}
|
||||
</Button>
|
||||
))}
|
||||
<Button variant="primary" disabled>
|
||||
Disabled primary
|
||||
</Button>
|
||||
</HorizontalGroup>
|
||||
</DemoBox>
|
||||
</CollapsableSection>
|
||||
</DemoBox>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
interface RichColorDemoProps {
|
||||
theme: GrafanaThemeV2;
|
||||
color: ThemePaletteColor;
|
||||
}
|
||||
|
||||
export function RichColorDemo({ theme, color }: RichColorDemoProps) {
|
||||
return (
|
||||
<tr>
|
||||
<td>{color.name}</td>
|
||||
<td>
|
||||
<div
|
||||
className={css`
|
||||
background: ${color.main};
|
||||
border-radius: ${theme.shape.borderRadius()};
|
||||
color: ${color.contrastText};
|
||||
padding: 8px;
|
||||
font-weight: 500;
|
||||
&:hover {
|
||||
background: ${theme.palette.getHoverColor(color.main)};
|
||||
}
|
||||
`}
|
||||
>
|
||||
{color.main}
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<div
|
||||
className={css`
|
||||
border: 1px solid ${color.border};
|
||||
color: ${color.text};
|
||||
border-radius: 4px;
|
||||
padding: 8px;
|
||||
&:hover {
|
||||
color: ${color.text};
|
||||
}
|
||||
`}
|
||||
>
|
||||
{color.text}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
}
|
||||
|
||||
const colorsTableStyle = css`
|
||||
text-align: center;
|
||||
|
||||
td {
|
||||
padding: 8px;
|
||||
text-align: center;
|
||||
}
|
||||
`;
|
||||
|
||||
export function TextColors({ t }: { t: GrafanaThemeV2 }) {
|
||||
return (
|
||||
<>
|
||||
<DemoText color={t.palette.text.primary}>
|
||||
text.primary <Icon name="trash-alt" />
|
||||
</DemoText>
|
||||
<DemoText color={t.palette.text.secondary}>
|
||||
text.secondary <Icon name="trash-alt" />
|
||||
</DemoText>
|
||||
<DemoText color={t.palette.text.disabled}>
|
||||
text.disabled <Icon name="trash-alt" />
|
||||
</DemoText>
|
||||
<DemoText color={t.palette.primary.text}>
|
||||
primary.text <Icon name="trash-alt" />
|
||||
</DemoText>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export function ShadowDemo({ name, shadow }: { name: string; shadow: string }) {
|
||||
const style = css({
|
||||
padding: '16px',
|
||||
boxShadow: shadow,
|
||||
});
|
||||
return <div className={style}>{name}</div>;
|
||||
}
|
||||
@@ -2,7 +2,7 @@ import React from 'react';
|
||||
import { ThemeColors } from './ThemeColors';
|
||||
|
||||
export default {
|
||||
title: 'Docs Overview/ThemeColors',
|
||||
title: 'Docs Overview/Theme',
|
||||
component: ThemeColors,
|
||||
decorators: [],
|
||||
parameters: {
|
||||
@@ -13,6 +13,6 @@ export default {
|
||||
},
|
||||
};
|
||||
|
||||
export const ThemeColorsDemo = () => {
|
||||
export const OldThemeDemo = () => {
|
||||
return <ThemeColors />;
|
||||
};
|
||||
@@ -27,7 +27,10 @@ interface ClickPluginProps extends PlotPluginProps {
|
||||
children: (api: ClickPluginAPI) => React.ReactElement | null;
|
||||
}
|
||||
|
||||
// Exposes API for Graph click interactions
|
||||
/**
|
||||
* @alpha
|
||||
* Exposes API for Graph click interactions
|
||||
*/
|
||||
export const ClickPlugin: React.FC<ClickPluginProps> = ({ id, onClick, children }) => {
|
||||
const pluginId = `ClickPlugin:${id}`;
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@ let ThemeContextMock: React.Context<GrafanaTheme> | null = null;
|
||||
export const memoizedStyleCreators = new WeakMap();
|
||||
|
||||
// Use Grafana Dark theme by default
|
||||
/** @public */
|
||||
export const ThemeContext = React.createContext(getTheme(GrafanaThemeType.Dark));
|
||||
ThemeContext.displayName = 'ThemeContext';
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import defaultTheme, { commonColorsPalette } from './default';
|
||||
import { GrafanaThemeType, GrafanaTheme } from '@grafana/data';
|
||||
import { GrafanaThemeType, GrafanaTheme, createTheme } from '@grafana/data';
|
||||
|
||||
const basicColors = {
|
||||
...commonColorsPalette,
|
||||
@@ -130,6 +130,7 @@ const darkTheme: GrafanaTheme = {
|
||||
shadows: {
|
||||
listItem: 'none',
|
||||
},
|
||||
v2: createTheme({ palette: { mode: 'dark' } }),
|
||||
};
|
||||
|
||||
export default darkTheme;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import defaultTheme, { commonColorsPalette } from './default';
|
||||
import { GrafanaThemeType, GrafanaTheme } from '@grafana/data';
|
||||
import { GrafanaThemeType, GrafanaTheme, createTheme } from '@grafana/data';
|
||||
|
||||
const basicColors = {
|
||||
...commonColorsPalette,
|
||||
@@ -131,6 +131,7 @@ const lightTheme: GrafanaTheme = {
|
||||
shadows: {
|
||||
listItem: 'none',
|
||||
},
|
||||
v2: createTheme({ palette: { mode: 'light' } }),
|
||||
};
|
||||
|
||||
export default lightTheme;
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
import { GrafanaThemeType } from '@grafana/data';
|
||||
|
||||
type VariantDescriptor = { [key in GrafanaThemeType]: string | number };
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
export type VariantDescriptor = { [key in GrafanaThemeType]: string | number };
|
||||
|
||||
/**
|
||||
* @deprecated use theme.isLight ? or theme.isDark instead
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import memoizeOne from 'memoize-one';
|
||||
// import { KeyValue } from '@grafana/data';
|
||||
|
||||
/**
|
||||
* @public
|
||||
* @deprecated use useStyles hook
|
||||
* Creates memoized version of styles creator
|
||||
* @param stylesCreator function accepting dependencies based on which styles are created
|
||||
*/
|
||||
|
||||
@@ -29,7 +29,7 @@ if [ ! -d "$REPORT_PATH" ]; then
|
||||
fi
|
||||
|
||||
WARNINGS_COUNT="$(find "$REPORT_PATH" -type f -name \*.log -print0 | xargs -0 grep -o "Warning: " | wc -l | xargs)"
|
||||
WARNINGS_COUNT_LIMIT=1055
|
||||
WARNINGS_COUNT_LIMIT=1061
|
||||
|
||||
if [ "$WARNINGS_COUNT" -gt $WARNINGS_COUNT_LIMIT ]; then
|
||||
echo -e "API Extractor warnings/errors $WARNINGS_COUNT exceeded $WARNINGS_COUNT_LIMIT so failing build.\n"
|
||||
|
||||
Reference in New Issue
Block a user