From f89da88f0fbdbc7d253171f1e92b5c6fe09ae5c7 Mon Sep 17 00:00:00 2001 From: Ashley Harrison Date: Thu, 6 Feb 2025 09:16:47 +0000 Subject: [PATCH] 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 --- .github/renovate.json5 | 1 - packages/grafana-ui/.storybook/main.ts | 1 - packages/grafana-ui/.storybook/manager.ts | 6 +++-- packages/grafana-ui/.storybook/preview.ts | 26 ++++++++++++------ .../grafana-ui/.storybook/storybookTheme.ts | 10 ++----- packages/grafana-ui/package.json | 1 - .../utils/storybook/ThemedDocsContainer.tsx | 19 +++++++++---- .../src/utils/storybook/withTheme.tsx | 20 ++++++++------ yarn.lock | 27 ++++--------------- 9 files changed, 55 insertions(+), 56 deletions(-) diff --git a/.github/renovate.json5 b/.github/renovate.json5 index 6cc2ac80423..a782ac2c0a0 100644 --- a/.github/renovate.json5 +++ b/.github/renovate.json5 @@ -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" diff --git a/packages/grafana-ui/.storybook/main.ts b/packages/grafana-ui/.storybook/main.ts index 5b169002dc9..401a79ef7ea 100644 --- a/packages/grafana-ui/.storybook/main.ts +++ b/packages/grafana-ui/.storybook/main.ts @@ -43,7 +43,6 @@ const mainConfig: StorybookConfig = { }, }, getAbsolutePath('@storybook/addon-storysource'), - getAbsolutePath('storybook-dark-mode'), getAbsolutePath('@storybook/addon-webpack5-compiler-swc'), ], framework: { diff --git a/packages/grafana-ui/.storybook/manager.ts b/packages/grafana-ui/.storybook/manager.ts index 1caddf59335..1a32c4aeaa4 100644 --- a/packages/grafana-ui/.storybook/manager.ts +++ b/packages/grafana-ui/.storybook/manager.ts @@ -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), }); diff --git a/packages/grafana-ui/.storybook/preview.ts b/packages/grafana-ui/.storybook/preview.ts index cc4be9bfbe8..a8dc3e9f062 100644 --- a/packages/grafana-ui/.storybook/preview.ts +++ b/packages/grafana-ui/.storybook/preview.ts @@ -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(), diff --git a/packages/grafana-ui/.storybook/storybookTheme.ts b/packages/grafana-ui/.storybook/storybookTheme.ts index 32649630d9f..7bb3b29e2cb 100644 --- a/packages/grafana-ui/.storybook/storybookTheme.ts +++ b/packages/grafana-ui/.storybook/storybookTheme.ts @@ -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 }; diff --git a/packages/grafana-ui/package.json b/packages/grafana-ui/package.json index ebe6a77ecdc..c7bc33a0a57 100644 --- a/packages/grafana-ui/package.json +++ b/packages/grafana-ui/package.json @@ -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" diff --git a/packages/grafana-ui/src/utils/storybook/ThemedDocsContainer.tsx b/packages/grafana-ui/src/utils/storybook/ThemedDocsContainer.tsx index 1c989e3fd3d..d6485671d84 100644 --- a/packages/grafana-ui/src/utils/storybook/ThemedDocsContainer.tsx +++ b/packages/grafana-ui/src/utils/storybook/ThemedDocsContainer.tsx @@ -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 ( - + {children} diff --git a/packages/grafana-ui/src/utils/storybook/withTheme.tsx b/packages/grafana-ui/src/utils/storybook/withTheme.tsx index d865769c5ab..aa1f761f85f 100644 --- a/packages/grafana-ui/src/utils/storybook/withTheme.tsx +++ b/packages/grafana-ui/src/utils/storybook/withTheme.tsx @@ -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) => { + 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) => {story()}; + (story, context) => ( + + {story()} + + ); diff --git a/yarn.lock b/yarn.lock index 3ebde167b43..1c4fee5ed1d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -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"