Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature Request: Support for storybook #63

Open
RyanClementsHax opened this issue Oct 19, 2021 · 6 comments
Open

Feature Request: Support for storybook #63

RyanClementsHax opened this issue Oct 19, 2021 · 6 comments
Labels

Comments

@RyanClementsHax
Copy link

RyanClementsHax commented Oct 19, 2021

Thanks for the library! It's very well rounded and more polished than what I was doing on my own.

I'm looking to integrate this library with storybook for my application. When I was using my own theme provider example repo here which is heavily based on this blog post which provides the same solution this repo seeks to encapsulate in a library (i.e. inject script into head to run theme logic before first paint), I was able to easily integrate this with storybook globals. I went a step further to allow for stories to declare they want to be rendered with a certain theme by default with the following preview.js:

import React, { useLayoutEffect } from 'react'
import { usePrevious } from 'react-use'
import { addons } from '@storybook/addons'
import { UPDATE_GLOBALS } from '@storybook/core-events'
import { ThemeContext, updateTheme } from 'components/Theme'

export const globalTypes = {
  theme: {
    name: 'Theme',
    description: 'Color theme',
    defaultValue: 'light',
    toolbar: {
      icon: 'paintbrush',
      items: [
        { value: 'light', title: 'Light' },
        { value: 'dark', title: 'Dark' }
      ]
    }
  }
}

const updateThemeGlobal = theme =>
  addons.getChannel().emit(UPDATE_GLOBALS, {
    globals: { theme }
  })

export const decorators = [
  (Story, { globals, parameters }) => {
    const previousParametersTheme = usePrevious(parameters.theme)

    useLayoutEffect(() => {
      if (
        previousParametersTheme !== parameters.theme &&
        globals.theme !== parameters.theme
      ) {
        updateThemeGlobal(parameters.theme)
      } else {
        updateTheme(globals.theme)
      }
    }, [globals.theme, parameters.theme])

    return (
      <ThemeContext.Provider
        value={{
          theme: globals.theme,
          setTheme: updateThemeGlobal
        }}
      >
        <Story />
      </ThemeContext.Provider>
    )
  }
]

export const parameters = {
  theme: 'light'
}

I would like to accomplish something similar with this repo. i.e. allow outside code to force the theme and allow a custom version of the set theme function so we can keep the globals in sync.

The context object isn't exported from this repo which would allow me to do all of this (alas, I would have to reimplement the logic to "set" the theme onto the dom unless that logic was also exported).

I tried to get a partial solution by using the forceTheme prop on the provider as follows, but that didn't work either. It stayed on light mode even though the global was being reset causing a rerender.

  (Story, { globals }) => {
    return (
      <ThemeProvider
        forcedTheme={globals.theme}
        attribute="class"
        storageKey="themePreference"
      >
        <Story />
      </ThemeProvider>
    )
  }

Is exporting the context object and dom update logic the best solution or is there a better way to achieve what I'm doing?

Thanks for the help and your time; it's greatly appreciated.

@talohana
Copy link

talohana commented Jan 5, 2022

I am also trying to integrate next-themes with storybook, using globalTypes and forcedTheme also renders light mode only

export const decorators = [
  (Story, { globals }) => {
    return (
      <ThemeProvider attribute="class" forcedTheme={globals.theme}>
        <Story />
      </ThemeProvider>
    );
  },
];

export const globalTypes = {
  theme: {
    name: 'Theme',
    defaultValue: 'system',
    toolbar: {
      icon: 'circlehollow',
      items: ['light', 'dark', 'system'],
      showName: true,
    },
  },
};

EDIT:

It does change the theme, but doesn't apply the class attribute, setTheme is calling to changeTheme with updateDom set to false, which in return does not update the class attribute.

@trm217
Copy link
Collaborator

trm217 commented Feb 21, 2022

Storybook is not implemented with Next.js. Since the purpose of this library is a clean implementation of theming in Next.js, I doubt storybook support will be a thing. (Considering CRA and Gatsby are out of scope as well)

@pacocoursey
Copy link
Owner

In v0.2 we're not using any Next.js specific code anymore, so I'll think about making the library more generic to support Storybook or other frameworks. Definitely low priority though – using next-themes with Next.js will always be the focus.

@natasha08n
Copy link

natasha08n commented Sep 2, 2022

I managed to make theme changeable in toolbar in storybook

in preview.js

export const globalTypes = {
  theme: {
    name: 'Theme',
    description: 'Global theme for components',
    defaultValue: 'light',
    toolbar: {
      items: ['light', 'dark'],
      showName: true,
      dynamicTitle: true,
    },
  },
};

and also I added decorator in the same file

export const decorators = [withStyle];

And here is withStyle.tsx file

import { FC } from 'react';
import { ColorThemeProvider } from 'modules/styleSystem';

export function withStyle(Story: FC, context: any) {
  return (
    <ColorThemeProvider forcedTheme={context.globals.theme}>
        <Story />
    </ColorThemeProvider>
  );
}

And this is my ColorThemeProvider

import { PropsWithChildren } from 'react';
import { ThemeProvider as NextThemeProvider } from 'next-themes';

export const ColorThemeProvider = ({
  forcedTheme,
  ...props
}: PropsWithChildren<{ forcedTheme?: 'light' | 'dark' }>) => {
  return (
    <NextThemeProvider themes={['light', 'dark']} {...(forcedTheme ? { forcedTheme } : {})}>
      {props.children}
    </NextThemeProvider>
  );
};

Please let me know if it works for you guys

Screen.Recording.2022-09-02.at.20.28.25.mov

@fede-rodes
Copy link

fede-rodes commented Sep 8, 2022

This worked for me:

// ThemeChanger.tsx
import React from "react";
import { useTheme } from "next-themes";
import { useEffect } from "react";

const ThemeChanger = ({ theme }: { theme: "light" | "dark" }) => {
    const { setTheme } = useTheme();

    useEffect(() => {
        setTheme(theme);
    }, [setTheme, theme]);

    return null;
};

export default React.memo(ThemeChanger);

// preview.js
import React from 'react';
import { ThemeProvider } from "next-themes";
import ThemeChanger from './ThemeChanger';

export const decorators = [
    (Story, { globals }) => (
        <ThemeProvider>
            <ThemeChanger
                theme={
                    globals?.backgrounds?.value == null ||
                    globals.backgrounds.value === "#F8F8F8"
                        ? "light"
                        : "dark"
                }
            />
            <Story />
        </ThemeProvider>
    ),
];

export const parameters = {
    actions: { argTypesRegex: "^on[A-Z].*" },
    controls: {
        matchers: {
            color: /(background|color)$/i,
            date: /Date$/,
        },
    },
};

@olaj
Copy link

olaj commented Oct 6, 2022

I used a combination of the above.

// ThemeChanger.tsx
import React from "react";
import { useTheme } from "next-themes";
import { useEffect } from "react";

const ThemeChanger = ({ theme }: { theme: "modern" | "dark" | "classic" }) => {
  const { setTheme } = useTheme();

  useEffect(() => {
    console.log(theme, "theme");
    setTheme(theme);
  }, [setTheme, theme]);

  return null;
};

export default React.memo(ThemeChanger);
//preview.js
import "../public/static/global-styles.scss";
import { ThemeProvider } from "next-themes";
import ThemeChanger from "./ThemeChanger";

export const globalTypes = {
  theme: {
    name: "Theme",
    description: "Global theme for components",
    defaultValue: "modern",
    toolbar: {
      items: ["modern", "dark", "classic"],
      showName: true,
      dynamicTitle: true,
    },
  },
};

export const decorators = [
  (Story, { globals }) => (
    <ThemeProvider>
      <ThemeChanger theme={globals.theme ? globals.theme : "modern"} />
      <Story />
    </ThemeProvider>
  ),
];

export const parameters = {
  actions: { argTypesRegex: "^on[A-Z].*" },
  controls: {
    matchers: {
      color: /(background|color)$/i,
      date: /Date$/,
    },
  },
};

The example from @fede-rodes didn't include a "toolbar switcher" and i grabbed that from @natasha08n example and made some minor changes. Works for me!

Daiius added a commit to Daiius/sekirei-todo that referenced this issue Jun 6, 2024
pacocoursey/next-themes#63
に従って追加してみた、こんなに簡単だったとは...
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

7 participants