Storybook: Support an arbitrary number of themes (#100111)

* support more themes in storybook

* default to dark theme

* fix type error

* change theme in docs container

* add TODO

* only show extra themes in development mode

* add comment
This commit is contained in:
Ashley Harrison
2025-02-06 09:16:47 +00:00
committed by GitHub
parent 36275a5510
commit f89da88f0f
9 changed files with 55 additions and 56 deletions

View File

@@ -13,7 +13,6 @@
"slate-react", // we don't want to continue using this on the long run, use Monaco editor instead of Slate
"@types/slate-react", // we don't want to continue using this on the long run, use Monaco editor instead of Slate
"@types/slate", // we don't want to continue using this on the long run, use Monaco editor instead of Slate
"storybook-dark-mode", // 4.0.2 causes storybook 8.4 to break with react hooks errors
// Temporarily pause updating lerna and nx until we resolve build issues
"lerna",
"nx"

View File

@@ -43,7 +43,6 @@ const mainConfig: StorybookConfig = {
},
},
getAbsolutePath('@storybook/addon-storysource'),
getAbsolutePath('storybook-dark-mode'),
getAbsolutePath('@storybook/addon-webpack5-compiler-swc'),
],
framework: {

View File

@@ -1,6 +1,8 @@
import { addons } from '@storybook/manager-api';
import { GrafanaDark } from './storybookTheme';
import { getThemeById } from '@grafana/data';
import { createStorybookTheme } from './storybookTheme';
const systemTheme = getThemeById('system');
addons.setConfig({
isFullscreen: false,
panelPosition: 'right',
@@ -10,5 +12,5 @@ addons.setConfig({
sidebar: {
showRoots: true,
},
theme: GrafanaDark,
theme: createStorybookTheme(systemTheme),
});

View File

@@ -1,6 +1,6 @@
import { Preview } from '@storybook/react';
import 'jquery';
import { getTimeZone, getTimeZones } from '@grafana/data';
import { getBuiltInThemes, getTimeZone, getTimeZones, GrafanaTheme2 } from '@grafana/data';
import '../../../public/vendor/flot/jquery.flot.js';
import '../../../public/vendor/flot/jquery.flot.selection';
@@ -20,10 +20,9 @@ import { ThemedDocsContainer } from '../src/utils/storybook/ThemedDocsContainer'
import lightTheme from '../../../public/sass/grafana.light.scss';
// @ts-ignore
import darkTheme from '../../../public/sass/grafana.dark.scss';
import { GrafanaDark, GrafanaLight } from './storybookTheme';
const handleThemeChange = (theme: any) => {
if (theme !== 'light') {
const handleThemeChange = (theme: GrafanaTheme2) => {
if (theme.colors.mode !== 'light') {
lightTheme.unuse();
darkTheme.use();
} else {
@@ -32,14 +31,12 @@ const handleThemeChange = (theme: any) => {
}
};
const showExtraThemes = process.env.NODE_ENV === 'development';
const preview: Preview = {
decorators: [withTheme(handleThemeChange), withTimeZone()],
parameters: {
actions: { argTypesRegex: '^on[A-Z].*' },
darkMode: {
dark: GrafanaDark,
light: GrafanaLight,
},
docs: {
container: ThemedDocsContainer,
},
@@ -68,6 +65,19 @@ const preview: Preview = {
},
},
globalTypes: {
theme: {
name: 'Theme',
description: 'Global theme for components',
defaultValue: 'system',
toolbar: {
icon: 'paintbrush',
items: getBuiltInThemes(showExtraThemes).map((theme) => ({
value: theme.id,
title: theme.name,
})),
showName: true,
},
},
timeZone: {
description: 'Set the timezone for the storybook preview',
defaultValue: getTimeZone(),

View File

@@ -1,8 +1,7 @@
import { GrafanaTheme2, createTheme } from '@grafana/data';
//@ts-ignore
import { GrafanaTheme2 } from '@grafana/data';
import { create } from '@storybook/theming';
const createStorybookTheme = (theme: GrafanaTheme2) => {
export const createStorybookTheme = (theme: GrafanaTheme2) => {
return create({
base: theme.colors.mode,
colorPrimary: theme.colors.primary.main,
@@ -38,8 +37,3 @@ const createStorybookTheme = (theme: GrafanaTheme2) => {
brandImage: `public/img/grafana_text_logo-${theme.colors.mode}.svg`,
});
};
const GrafanaLight = createStorybookTheme(createTheme({ colors: { mode: 'light' } }));
const GrafanaDark = createStorybookTheme(createTheme({ colors: { mode: 'dark' } }));
export { GrafanaLight, GrafanaDark };

View File

@@ -180,7 +180,6 @@
"rollup-plugin-svg-import": "3.0.0",
"sass-loader": "16.0.4",
"storybook": "^8.4.2",
"storybook-dark-mode": "4.0.1",
"style-loader": "4.0.0",
"typescript": "5.7.3",
"webpack": "5.97.1"

View File

@@ -1,9 +1,10 @@
// Wrap the DocsContainer for storybook-dark-mode theme switching support.
// Wrap the DocsContainer for theme switching support.
import { DocsContainer, DocsContextProps } from '@storybook/addon-docs';
import * as React from 'react';
import { useDarkMode } from 'storybook-dark-mode';
import { GrafanaLight, GrafanaDark } from '../../../.storybook/storybookTheme';
import { getThemeById } from '@grafana/data';
import { createStorybookTheme } from '../../../.storybook/storybookTheme';
import { GlobalStyles } from '../../themes';
type Props = {
@@ -12,10 +13,18 @@ type Props = {
};
export const ThemedDocsContainer = ({ children, context }: Props) => {
const dark = useDarkMode();
// Default to system theme for pages that don't have associated stories
// Currently this is only the case for the docs `Intro` page
let themeId = 'system';
if (context.componentStories().length > 0) {
const story = context.storyById();
const { globals } = context.getStoryContext(story);
themeId = globals.theme;
}
const theme = getThemeById(themeId);
return (
<DocsContainer theme={dark ? GrafanaDark : GrafanaLight} context={context}>
<DocsContainer theme={createStorybookTheme(theme)} context={context}>
<GlobalStyles />
{children}
</DocsContainer>

View File

@@ -1,17 +1,17 @@
import { Decorator } from '@storybook/react';
import * as React from 'react';
import { useDarkMode } from 'storybook-dark-mode';
import { createTheme, GrafanaTheme2, ThemeContext } from '@grafana/data';
import { getThemeById, GrafanaTheme2, ThemeContext } from '@grafana/data';
import { GlobalStyles } from '../../themes/GlobalStyles/GlobalStyles';
type SassThemeChangeHandler = (theme: GrafanaTheme2) => void;
const ThemeableStory = ({
children,
handleSassThemeChange,
}: React.PropsWithChildren<{ handleSassThemeChange: SassThemeChangeHandler }>) => {
const theme = createTheme({ colors: { mode: useDarkMode() ? 'dark' : 'light' } });
interface ThemeableStoryProps {
themeId: string;
handleSassThemeChange: SassThemeChangeHandler;
}
const ThemeableStory = ({ children, handleSassThemeChange, themeId }: React.PropsWithChildren<ThemeableStoryProps>) => {
const theme = getThemeById(themeId);
handleSassThemeChange(theme);
@@ -38,4 +38,8 @@ const ThemeableStory = ({
export const withTheme =
(handleSassThemeChange: SassThemeChangeHandler): Decorator =>
// eslint-disable-next-line react/display-name
(story) => <ThemeableStory handleSassThemeChange={handleSassThemeChange}>{story()}</ThemeableStory>;
(story, context) => (
<ThemeableStory themeId={context.globals.theme} handleSassThemeChange={handleSassThemeChange}>
{story()}
</ThemeableStory>
);

View File

@@ -4129,7 +4129,6 @@ __metadata:
slate-plain-serializer: "npm:0.7.13"
slate-react: "npm:0.22.10"
storybook: "npm:^8.4.2"
storybook-dark-mode: "npm:4.0.1"
style-loader: "npm:4.0.0"
tinycolor2: "npm:1.6.0"
tslib: "npm:2.8.1"
@@ -7524,7 +7523,7 @@ __metadata:
languageName: node
linkType: hard
"@storybook/components@npm:8.4.4, @storybook/components@npm:^8.0.0, @storybook/components@npm:^8.4.2":
"@storybook/components@npm:8.4.4, @storybook/components@npm:^8.4.2":
version: 8.4.4
resolution: "@storybook/components@npm:8.4.4"
peerDependencies:
@@ -7533,7 +7532,7 @@ __metadata:
languageName: node
linkType: hard
"@storybook/core-events@npm:^8.0.0, @storybook/core-events@npm:^8.4.2":
"@storybook/core-events@npm:^8.4.2":
version: 8.4.4
resolution: "@storybook/core-events@npm:8.4.4"
peerDependencies:
@@ -7605,7 +7604,7 @@ __metadata:
languageName: node
linkType: hard
"@storybook/icons@npm:^1.2.12, @storybook/icons@npm:^1.2.5":
"@storybook/icons@npm:^1.2.12":
version: 1.2.12
resolution: "@storybook/icons@npm:1.2.12"
peerDependencies:
@@ -7615,7 +7614,7 @@ __metadata:
languageName: node
linkType: hard
"@storybook/manager-api@npm:8.4.4, @storybook/manager-api@npm:^8.0.0, @storybook/manager-api@npm:^8.4.2":
"@storybook/manager-api@npm:8.4.4, @storybook/manager-api@npm:^8.4.2":
version: 8.4.4
resolution: "@storybook/manager-api@npm:8.4.4"
peerDependencies:
@@ -7766,7 +7765,7 @@ __metadata:
languageName: node
linkType: hard
"@storybook/theming@npm:8.4.4, @storybook/theming@npm:^8.0.0, @storybook/theming@npm:^8.4.2":
"@storybook/theming@npm:8.4.4, @storybook/theming@npm:^8.4.2":
version: 8.4.4
resolution: "@storybook/theming@npm:8.4.4"
peerDependencies:
@@ -28886,22 +28885,6 @@ __metadata:
languageName: node
linkType: hard
"storybook-dark-mode@npm:4.0.1":
version: 4.0.1
resolution: "storybook-dark-mode@npm:4.0.1"
dependencies:
"@storybook/components": "npm:^8.0.0"
"@storybook/core-events": "npm:^8.0.0"
"@storybook/global": "npm:^5.0.0"
"@storybook/icons": "npm:^1.2.5"
"@storybook/manager-api": "npm:^8.0.0"
"@storybook/theming": "npm:^8.0.0"
fast-deep-equal: "npm:^3.1.3"
memoizerific: "npm:^1.11.3"
checksum: 10/3225e5bdaba0ea76b65d642202d9712d7de234e3b5673fb46e444892ab114be207dd287778e2002b662ec35bb8153d2624ff280ce51c5299fb13c711431dad40
languageName: node
linkType: hard
"storybook@npm:^8.4.2":
version: 8.4.4
resolution: "storybook@npm:8.4.4"