diff --git a/style_guides/themes.md b/style_guides/themes.md new file mode 100644 index 00000000000..e9a9f83215b --- /dev/null +++ b/style_guides/themes.md @@ -0,0 +1,103 @@ +## Core changes + +JS is the primary source of theme variables for Grafana. Theme definitions are located in `@grafana/ui/themes` directory. + +#### Themes are implemented in pure js. + +This is because our goal is to share variables between app and SASS. To achieve that themes are necessary during build time to be exposed to sass loader via `node-sass` functions (https://github.com/sass/node-sass/blob/master/README.md#functions--v300---experimental). This retrieval is implemented in `getThemeVariable(variablePath, themeName)`. + +#### Themes are available to React components via `ThemeContext` + +Context is available via `import { ThemeContext } from '@grafana/ui';` + +**If you skip `themeName` param, then dark theme's variant will be used** + +## Using themes in Grafana + +### SASS + +`getThemeVariable` is a function, that's available in sass files. Use it i.e. like this: + +```scss +// In theme agnostic SASS file +.foo { + font-size: getThemeVariable('typography.size.m'); +} + +// In *.[themeName].scss +.bar { + background-color: getThemeVariable('colors.blueLight', '[themeName]'); +} +``` + +### React + +#### Using `ThemeContext` directly + +```ts +import { ThemeContext } from '@grafana/ui'; + +{theme => }; +``` + +#### Using `withTheme` HOC + +With this method your component will be automatically wrapped in `ThemeContext.Consumer` and provided with current theme via `theme` prop. Component used with `withTheme` must implement `Themeable` interface. + +```ts +import { ThemeContext, Themeable } from '@grafana/ui'; + +interface FooProps extends Themeable {} + +const Foo: React.FunctionComponent = () => ... + +export default withTheme(Foo); +``` + +### Storybook + +All stories are wrapped with `ThemeContext.Provider` using global decorator. To render `Themeable` component that's not wrapped by `withTheme` HOC you either create a new component in your story: + +```tsx +// Foo.story.tsx +const FooWithTheme = withTheme(Foo); + +FooStories.add('Story' () => { + return +}); +``` + +or use `renderComponentWithTheme` helper: + +```tsx +// Bar.story.tsx + +BarStories.add('Story' () => { + return renderComponentWithTheme(Bar, /* pass props here */) +}); +``` + +### Angular + +There should be very few cases when theme would be used in Angular context. For this purpise there is a function available that retrieves current theme: `import { getCurrentTheme } from app/core/utils/ConfigProvider` + +## Limitations + +- #### Hot updates + Changes in JS theme files _are not subject of hot updates_ during development. This applies to styles that comes from SASS files (which means 100% until we introduce css in js approach). This is a consequence of the fact that `getThemeVariable` util is executed during webpack pipeline. +- #### You must ensure ThemeContext provider is available in a React tree + By default all react2angular directives have `ThemeContext.Provider` ensured. But, there are cases where we create another React tree via `ReactDOM.render`. This happens in case of graph legend rendering and `ReactContainer` directive. In such cases theme consumption will fail. To make sure theme context is available in such cases, you need to wrap your rendered component with ThemeContext.Provider using `provideTheme` function: + +```typescript +// graph.ts +import { provideTheme } from 'app/core/utils/ConfigProvider'; + +// Create component with ThemeContext.Provider first. +// Otherwise React will create new components every time it renders! +const LegendWithThemeProvider = provideTheme(Legend); + +const legendReactElem = React.createElement(LegendWithThemeProvider, legendProps); +ReactDOM.render(legendReactElem, this.legendElem, () => this.renderPanel()); +``` + +`provideTheme` makes current theme available via ThemeContext by checking if user has `lightTheme` set in her boot data.