mirror of
https://github.com/grafana/grafana.git
synced 2025-02-03 12:11:09 -06:00
1d689888b0
* Updated package json but not updated source files * Update eslint plugin * updated files
223 lines
5.4 KiB
TypeScript
223 lines
5.4 KiB
TypeScript
// Copyright (c) 2017 Uber Technologies, Inc.
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
import React, { useContext } from 'react';
|
|
import hoistNonReactStatics from 'hoist-non-react-statics';
|
|
import memoizeOne from 'memoize-one';
|
|
import tinycolor from 'tinycolor2';
|
|
|
|
const COLORS_HEX = [
|
|
'#17B8BE',
|
|
'#F8DCA1',
|
|
'#B7885E',
|
|
'#FFCB99',
|
|
'#F89570',
|
|
'#829AE3',
|
|
'#E79FD5',
|
|
'#1E96BE',
|
|
'#89DAC1',
|
|
'#B3AD9E',
|
|
'#12939A',
|
|
'#DDB27C',
|
|
'#88572C',
|
|
'#FF9833',
|
|
'#EF5D28',
|
|
'#162A65',
|
|
'#DA70BF',
|
|
'#125C77',
|
|
'#4DC19C',
|
|
'#776E57',
|
|
];
|
|
|
|
const COLORS_HEX_DARK = [
|
|
'#17B8BE',
|
|
'#F8DCA1',
|
|
'#B7885E',
|
|
'#FFCB99',
|
|
'#F89570',
|
|
'#829AE3',
|
|
'#E79FD5',
|
|
'#1E96BE',
|
|
'#89DAC1',
|
|
'#B3AD9E',
|
|
'#12939A',
|
|
'#DDB27C',
|
|
'#88572C',
|
|
'#FF9833',
|
|
'#EF5D28',
|
|
'#DA70BF',
|
|
'#4DC19C',
|
|
'#776E57',
|
|
];
|
|
|
|
export type ThemeOptions = Partial<Theme>;
|
|
|
|
export enum ThemeType {
|
|
Dark,
|
|
Light,
|
|
}
|
|
|
|
export type Theme = {
|
|
type: ThemeType;
|
|
servicesColorPalette: string[];
|
|
borderStyle: string;
|
|
components?: {
|
|
TraceName?: {
|
|
fontSize?: number | string;
|
|
};
|
|
};
|
|
};
|
|
|
|
export const defaultTheme: Theme = {
|
|
type: ThemeType.Light,
|
|
borderStyle: '1px solid #bbb',
|
|
servicesColorPalette: COLORS_HEX,
|
|
};
|
|
|
|
export function isLight(theme?: Theme | ThemeOptions) {
|
|
// Light theme is default type not set which only happens if called for ThemeOptions.
|
|
return theme && theme.type ? theme.type === ThemeType.Light : false;
|
|
}
|
|
|
|
const ThemeContext = React.createContext<ThemeOptions | undefined>(undefined);
|
|
ThemeContext.displayName = 'ThemeContext';
|
|
|
|
export const ThemeProvider = ThemeContext.Provider;
|
|
|
|
type ThemeConsumerProps = {
|
|
children: (theme: Theme) => React.ReactNode;
|
|
};
|
|
export function ThemeConsumer(props: ThemeConsumerProps) {
|
|
return (
|
|
<ThemeContext.Consumer>
|
|
{(value: ThemeOptions | undefined) => {
|
|
const theme = memoizedThemeMerge(value);
|
|
return props.children(theme);
|
|
}}
|
|
</ThemeContext.Consumer>
|
|
);
|
|
}
|
|
|
|
const memoizedThemeMerge = memoizeOne((value?: ThemeOptions) => {
|
|
const darkOverrides: Partial<Theme> = {};
|
|
if (!isLight(value)) {
|
|
darkOverrides.servicesColorPalette = COLORS_HEX_DARK;
|
|
}
|
|
return value
|
|
? {
|
|
...defaultTheme,
|
|
...darkOverrides,
|
|
...value,
|
|
}
|
|
: defaultTheme;
|
|
});
|
|
|
|
type WrappedWithThemeComponent<Props> = React.ComponentType<Omit<Props, 'theme'>> & {
|
|
wrapped: React.ComponentType<Props>;
|
|
};
|
|
|
|
export const withTheme = <Props extends { theme: Theme }, Statics extends {} = {}>(
|
|
Component: React.ComponentType<Props>
|
|
): WrappedWithThemeComponent<Props> => {
|
|
let WithTheme: React.ComponentType<Omit<Props, 'theme'>> = (props) => {
|
|
return (
|
|
<ThemeConsumer>
|
|
{(theme: Theme) => {
|
|
return (
|
|
<Component
|
|
{...({
|
|
...props,
|
|
theme,
|
|
} as Props & { theme: Theme })}
|
|
/>
|
|
);
|
|
}}
|
|
</ThemeConsumer>
|
|
);
|
|
};
|
|
|
|
WithTheme.displayName = `WithTheme(${Component.displayName})`;
|
|
WithTheme = hoistNonReactStatics<React.ComponentType<Omit<Props, 'theme'>>, React.ComponentType<Props>>(
|
|
WithTheme,
|
|
Component
|
|
);
|
|
(WithTheme as WrappedWithThemeComponent<Props>).wrapped = Component;
|
|
return WithTheme as WrappedWithThemeComponent<Props>;
|
|
};
|
|
|
|
export function useTheme(): Theme {
|
|
const theme = useContext(ThemeContext);
|
|
return {
|
|
...defaultTheme,
|
|
...theme,
|
|
};
|
|
}
|
|
|
|
export const createStyle = <Fn extends (this: any, ...newArgs: any[]) => ReturnType<Fn>>(fn: Fn) => {
|
|
return memoizeOne(fn);
|
|
};
|
|
|
|
/**
|
|
* Tries to get a dark variant color. Either by simply inverting the luminosity and darkening or lightening the color
|
|
* a bit, or if base is provided, tries 2 variants of lighter and darker colors and checks which is more readable with
|
|
* the base.
|
|
* @param theme
|
|
* @param hex
|
|
* @param base
|
|
*/
|
|
export function autoColor(theme: Theme, hex: string, base?: string) {
|
|
if (isLight(theme)) {
|
|
return hex;
|
|
} else {
|
|
if (base) {
|
|
const color = tinycolor(hex);
|
|
return tinycolor
|
|
.mostReadable(
|
|
base,
|
|
[
|
|
color.clone().lighten(25),
|
|
color.clone().lighten(10),
|
|
color,
|
|
color.clone().darken(10),
|
|
color.clone().darken(25),
|
|
],
|
|
{
|
|
includeFallbackColors: false,
|
|
}
|
|
)
|
|
.toHex8String();
|
|
}
|
|
const color = tinycolor(hex).toHsl();
|
|
color.l = 1 - color.l;
|
|
const newColor = tinycolor(color);
|
|
return newColor.isLight() ? newColor.darken(5).toHex8String() : newColor.lighten(5).toHex8String();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* With theme overrides you can use both number or string (for things like rem units) so this makes sure we convert
|
|
* the value accordingly or use fallback if not set
|
|
*/
|
|
export function safeSize(size: number | string | undefined, fallback: string): string {
|
|
if (!size) {
|
|
return fallback;
|
|
}
|
|
|
|
if (typeof size === 'string') {
|
|
return size;
|
|
} else {
|
|
return `${size}px`;
|
|
}
|
|
}
|