How to customize both dark and light themes in Storybook 6.0

Viewed 8339

I am making a PWA using light and dark themes, and I want to create my Storybook light and dark themes to reflect those.

So I created a function that will return a new Storybook theme object to me if I pass it a Material UI Theme and a base name.

But how do I pass these 2 objects to Storybook as themes?

I have found out that I am supposed to go to manager.js and add the following code

import theme from '../src/theme/theme'
import createThemeFromMUITheme from './create-theme-from-mui-theme'
import addons from '@storybook/addons'

addons.setConfig({
  theme: createThemeFromMUITheme('light', theme.light),
})

But how do I set theme for both light and dark?

I have tried

import theme from '../src/theme/theme'
import createThemeFromMUITheme from './create-theme-from-mui-theme'
import addons from '@storybook/addons'

addons.setConfig({
  theme: {
    light: createThemeFromMUITheme('light', theme.light),
    dark: createThemeFromMUITheme('dark', theme.dark)
  },
})

But that makes the storybook show nothing (it does not fail however)

Help please :-)

EDIT: I have also tried the following

import theme from '../src/theme/theme'
import createThemeFromMUITheme from './create-theme-from-mui-theme'
import addons from '@storybook/addons'

addons.setConfig({
  theme: createThemeFromMUITheme('light', theme.light),
})

addons.setConfig({
  theme: createThemeFromMUITheme('dark', theme.dark),
})

EDIT #2: The returned theme config object from createThemeFromMUITheme is valid BTW

And if anyone should want the function I made to convert a MUI theme object into a SB theme object - then this is it...

(I did not mess with form colors yet...)

import { create } from '@storybook/theming/create'

const createThemeFromMUITheme = (name, theme) => {
  return create({
    base: name,

    colorPrimary: theme.palette.primary.main,
    colorSecondary: theme.palette.secondary.main,

    // UI
    appBg: theme.palette.background.default,
    appContentBg: theme.palette.background.paper,
    appBorderColor: theme.palette.background.paper,
    appBorderRadius: theme.shape.borderRadius,

    // Typography
    fontBase: theme.typography.fontFamily,
    fontCode: 'monospace',

    // Text colors
    textColor: theme.palette.text.primary,
    textInverseColor: theme.palette.text.secondary,

    // Toolbar default and active colors
    barTextColor: theme.palette.text.primary,
    barSelectedColor: theme.palette.text.secondary,
    barBg: theme.palette.background.default,

    brandTitle: 'Add your brand title here',
    brandUrl: 'https://yourbrandurl.com',
    brandImage: 'https://placehold.it/350x150',
  })
}

export default createThemeFromMUITheme

2 Answers

You can only show one Theme for your storybook by default but you can toggle between dark and light theme by using an addon called storybook-dark-mode, This is addon page here

An example put this in preview.js file:


import { addParameters } from '@storybook/react'; // or any other type of storybook

addParameters({
  darkMode: {
    // Set the initial theme
    current: 'light'
    // Override the default dark theme
    dark: { ...themes.dark, appBg: 'black' },
    // Override the default light theme
    light: { ...themes.normal, appBg: 'red' }
  }
});

then you will have a toggle button in your storybook like this

enter image description here

Well - the solution was staring me right in the eyes, but as usual I had not read the documentation enough.

I did have storybook-dark-theme installed, and my idea was to have MUI fully integrated so that the light theme will show my application light theme, and the dark theme will show my dark theme - and the components likewise.

This way the light/dark theme switcher becomes fully global.

It does however take a little more than what you wrote.

And here is what I was aiming at

light dark

And you can download all of it from here Link to my github with the solution

When storybook renders first time it will use the default theme, and as such you will get some flickering before the light/dark themes you have been overriding with kicks in.

First click will make it remain light theme, but now it will use YOUR light theme.

The solution to this is to initialize with the light theme in manager.ts like so

import { addons } from '@storybook/addons'
import { theme as appTheme } from '../src/theme/theme'
import { createThemeFromMuiTheme } from './utils/create-theme-from-mui-theme'

addons.setConfig({
    theme: createThemeFromMuiTheme({
        theme: appTheme.light,
        options: {
            base: 'light',
            brandTitle: 'Storybook with MUI',
            brandUrl: 'https://www.github.com/IgorSzyporyn/storybook-with-mui'
        },
    })
})

And then in preview.ts you need to then set the light/dark overrides like so

import React from 'react'
import { addDecorator } from '@storybook/react'
import { theme as appTheme } from '../src/theme/theme'
import { WithMuiTheme } from './components/WithMuiTheme'
import { createThemeFromMuiTheme } from './utils/create-theme-from-mui-theme'

addDecorator((story) => <WithMuiTheme>{story()}</WithMuiTheme>)

export const parameters = {
    exportedParameter: 'exportedParameter',
    darkMode: {
        current: 'light',
        light: createThemeFromMuiTheme({ theme: appTheme.light, asStorybookTheme: false }),
        dark: createThemeFromMuiTheme({ theme: appTheme.dark, asStorybookTheme: false })
    },
}

And have your decorator wrapper made like this ./components/WithMuiThemeProps

import React from 'react'
import { MuiThemeProvider, CssBaseline, StylesProvider } from '@material-ui/core'
import { useThemeType } from '../hooks/UseThemeType'
import { theme } from '../../src/theme/theme'

type WithMuiThemeProps = {
    children: React.ReactNode
}

export const WithMuiTheme = ({ children }: WithMuiThemeProps) => {
    const themeType = useThemeType()

    return (
        <MuiThemeProvider theme={theme[themeType]}>
            <CssBaseline />
            <StylesProvider injectFirst>{children}</StylesProvider>
        </MuiThemeProvider>
    )
}

And here is the hook that makes the update happen ./hooks/UseThemeType.ts

import React from 'react'
import addons from '@storybook/addons'

const channel = addons.getChannel()

export const useThemeType = () => {
    const [isDark, setDark] = React.useState(false)

    React.useEffect(() => {
        channel.on('DARK_MODE', setDark)
        return () => channel.off('DARK_MODE', setDark)
    }, [channel, setDark])

    const paletteType = isDark ? 'dark' : 'light'

    return paletteType
}

And finally here is ./utils/create-theme-from-mui-theme.ts

import { create } from '@storybook/theming/create'
import { Theme } from '@material-ui/core'

type CreateThemFromMuiTheme = {
    theme: Theme
    options?: Object
    asStorybookTheme?: boolean
}

export const createThemeFromMuiTheme = ({
    theme,
    options = {},
    asStorybookTheme = true,
}: CreateThemFromMuiTheme) => {
    const themeValue = {
        colorPrimary: theme.palette.primary.main,
        colorSecondary: theme.palette.secondary.main,

        // UI
        appBg: theme.palette.background.paper,
        appContentBg: theme.palette.background.default,
        appBorderColor: theme.palette.background.paper,
        appBorderRadius: theme.shape.borderRadius,

        // Typography
        fontBase: theme.typography.fontFamily,
        fontCode: 'monospace',

        // Text colors
        textColor: theme.palette.text.primary,
        textInverseColor: theme.palette.text.secondary,

        // Toolbar default and active colors
        barTextColor: theme.palette.text.secondary,
        barSelectedColor: theme.palette.secondary.main,
        barBg: theme.palette.background.default,

        // Form color
        inputBg: 'transparent',
        inputBorder: 'silver',
        inputTextColor: theme.palette.text.primary,
        inputBorderRadius: theme.shape.borderRadius,

        ...options,
    }

    return asStorybookTheme ? create(themeValue) : themeValue
}

And now you will be able to use the theme fully integrated into Storybook - when you switch storybook theme then Storybook itself will change into the theme AND the components you use will change as well.

Related