Storybook integration

Has anyone successfully integrated storybook with Frontity.

I’ve gotten the warning:

I’ve followed the guides: https://storybook.js.org/docs/guides/guide-react/ both manual and automatic.

With the automatic I get the issue:

 • Detecting project type. ✓
    We couldn't detect your project type. (code: UNDETECTED)
    You can specify a project type explicitly via `sb init --type <type>` or follow some of the slow start guides: https://storybook.js.org/basics/slow-start-guide/

With Manual - I’ve tried their documentation and failed to get it to work.

Any suggestions?

Hi @maurice.tadros

I initially ran npx -p @storybook/cli sb init and got the same error as you, but was prompted to a select project type. I selected React and everything seemed to work okay.

I could run npm run storybook from within my Frontity project and a page at http://localhost:6006/?path=/story/button--text opened in my browser.

I guess running npx -p @storybook/cli sb init --type react would also work.

BTW I ran the test on a brand new and clean install of Frontity. Try setting up a new Frontity project and see if Storybook will work with that.

Incidentally, I’m otherwise unfamiliar with Storybook.

Hope the above helps. Let us know how you get on.

Thank you @mburridge,

I spent some more time, and it is not working well. For simple cases, it works but when you include a file that webpack compiles it gives you errors.

I made a repo to highlight the issue: https://github.com/Bowriverstudio/frontity-storybook

I believe the issue is related to getting storybook to use frontity’s webpack but I’m not sure how to accomplish that: https://storybook.js.org/docs/configurations/custom-webpack-config/

Thank you for your help.

@maurice.tadros thanks for the repo. I’ve done a PR and solved a couple of problems, but still needs some work. Unfortunately I don’t have time right now, so give it a try and see if you can solve the remaining problem: https://github.com/Bowriverstudio/frontity-storybook/pull/1

1 Like

Hello @luisherranz,

I took a look, and will try to fix the remaining issues.

Thanks for the help,
Maurice

How is it going? Did you figured it out?
When I’m trying to add @storybook/preset-typescript for TSX-files. I’m getting some errors(

@vadzzim,

I got it to a workable point.

My main.js file is:

const path = require("path");

module.exports = {
  stories: [
    
    "../frontity/packages/mars-theme/src/**/*.stor@(y|ies).@(js|jsx|ts|tsx|mdx)",
    "../documentation/**/*.stor@(y|ies).@(js|jsx|ts|tsx|mdx)",
    "../frontity/packages/frontity-components/src/components/**/*.stor@(y|ies).@(js|jsx|ts|tsx|mdx)",
    "../frontity/packages/wc/src/components/**/*.stor@(y|ies).@(js|jsx|ts|tsx|mdx)",
    "../frontity/tailwind/**/*.stor@(y|ies).@(js|jsx|ts|tsx|mdx)",
  ],
  addons: [
    "@storybook/addon-links",
    "@storybook/addon-essentials",
    "@storybook/addon-a11y",
    "@storybook/addon-actions",
    "storybook-formik/register",
    // "@whitespace/storybook-addon-html/register",
  ],
  webpackFinal: (config) => {
    //https://duncanleung.com/emotion-css-prop-jsx-pragma-storybook/
    config.module.rules[0].use[0].options.presets = [
      require.resolve("@babel/preset-react"),
      require.resolve("@babel/preset-env"),
      require.resolve("@emotion/babel-preset-css-prop"),
    ];

    //   // Load images.
    //   {
    // 	  test: /\.(gif|jpe?g|png)$/,
    // 		  loader: 'url-loader?limit=25000',
    // 			  query: {
    // 		  limit: 10000,
    // 			  name: 'static/media/images/[name].[hash:8].[ext]'
    // 	  }
    //   },

    // Frontity components are not compiled (!) so we have to make sure we compile them ourselves:
    const tsRule = config.module.rules.find((rule) => /ts/.test(rule.test));
    tsRule.exclude = /node_modules\/(?!(@frontity\/components|@frontity\/error|@frontity\/connect\/|@frontity\/hooks)\/).*/;
    return config;
  },
};

I needed to include babel.config.js

// Even though storybook doesn't require a babel config, Jest does.
// See https://jestjs.io/docs/en/tutorial-react
module.exports = {
  presets: [
    "@babel/preset-env",
    "@babel/preset-typescript",
    "@babel/preset-react",
  ],
  plugins: ["macros"],
};

I also added a helper file: frontityUtils.js

/**
 * External dependencies
 */
const merge = require("lodash.merge");

/**
 * React / Frontity dependencies
 */
import React from "react";
import { Provider, createStore } from "@frontity/connect";

/**
 * Internal dependencies
 */
import settings from "./sample-data/frontity.settings";

export function withFrontityStore(
  stateOverrides = {},
  defaultOverrides = {},
  extra = {}
) {
  // Deep merge instead of using pread operator (...) - as {state.theme} was getting overwritten.
  // https://lodash.com/docs#merge
  // https://www.javascripttutorial.net/object/javascript-merge-objects/#:~:text=JavaScript%20Merge%20Objects,-Summary%3A%20in%20this&text=To%20merge%20objects%20into%20a,assign()%20method
  let state = {
    ...settings,
    ...{
      router: {
        link: "/",
      },
    },
  };
  merge(state, stateOverrides);

  // Default actions taken from node_modules/@frontity/router/__tests__/index.test.ts
  const defaultActions = {
    router: {
      /*eslint no-unused-vars: ["error", { "args": "none" }]*/
      set: (state) => (link, options) => {
        console.log("Link Clicked");
      },
    },
    theme: {
      toggleMobileMenu: () => {
        console.log("toggleMobileMenu Action");
      },
    },
    ...defaultOverrides,
  };

  // We need to expose a store in order for frontity components to work:
  // Mock store technique taken from frontity test suite: https://github.com/frontity/frontity/blob/83c5eadb4dffc6275fe4d93b8d379c21449a2727/packages/connect/src/__tests__/connect.tests.jsx#L11
  const store = createStore({
    state: state,
    actions: defaultActions,
    ...extra,
  });

  //   console.log("HELPER store", store);
  //   console.log("HELPER extra", extra);
  //   console.log("HELPER state", state);
  //   console.log("HELPER stateOverrides", stateOverrides);

  return function (renderedComponent) {
    return <Provider value={store}>{renderedComponent}</Provider>;
  };
}

My preview.js file is:

import { withFrontityStore } from "./frontityUtils";
import { addDecorator } from "@storybook/react";
import { withConsole } from "@storybook/addon-console";
// import { withHTML } from "@whitespace/storybook-addon-html/react";

export const parameters = {
  actions: { argTypesRegex: "^on[A-Z].*" },
  a11y: {
    element: "#root",
    config: {},
    options: {},
    manual: true,
  },
};

// We need to expose a store in order for frontity components to work:
addDecorator((storyFn) => withFrontityStore()(storyFn()));
addDecorator((storyFn, context) => withConsole()(storyFn)(context));

// storybook-addon-html conflicts with storyshots (known issue: https://github.com/whitespace-se/storybook-addon-html/issues/5)
// so we only enable it if we're not testing:
// if (process.env.NODE_ENV !== "test") {
//   addDecorator(withHTML);
// }
// addDecorator(withHTML);

A sample story overwritting the default state is:

import {
  Meta,
  Story,
  Canvas,
  ArgsTable,
  Description,
} from "@storybook/addon-docs/blocks";
import MobileMenu from "../MobileMenu";
import { withFrontityStore } from "../../../../utils/frontityUtils.js";
const openModal = () =>
  withFrontityStore({ theme: { isMobileMenuOpen: true } })(<MobileMenu />);

<Meta title="Components/Header/MobileMenu" component={MobileMenu} />

# MobileMenu

<Description of={MobileMenu} />

#

<Canvas>
  <Story name="OpenMode">{openModal()}</Story>
</Canvas>

## Props

<ArgsTable of={MobileMenu} />

My current directory structure is:

Project
Project/.storybook
Project/wordpress-theme
Project/frontity

I still have a few issues, but am able to work around them until I’ve got more time to fix them.

If you give me access to your repository I can try to port my storybook setup there.

1 Like

That’s quite awesome @maurice.tadros! :slight_smile:

Could you create a fresh Frontity project (out of mars-theme) and add that configuration and share with us the repository? I’d love to take a look to see if we can do something from our side to ease this for you guys.

Hello,

Just wanted to contribute here. My project is very simple and does not have a lot of dependencies, so this basic set up worked for me:

  • Installed Storybook following the instructions on their website and move the /stories directory inside my Frontity theme. My theme is called melissau

  • .storybook/main.js file. The only change was adding the webpackFinal function and changing the default location of the stories.

const path = require("path");

module.exports = {
  "stories": [
    "../packages/melissau/src/stories/**/*.stories.mdx",
    "../packages/melissau/src/stories/**/*.stories.@(js|jsx|ts|tsx)"
  ],
  "addons": [
    "@storybook/addon-links",
    "@storybook/addon-essentials"
  ],
  webpackFinal: (config) => {
    config.module.rules[0].use[0].options.presets = [
      require.resolve("@babel/preset-react"),
      require.resolve("@babel/preset-env"),
      require.resolve("@emotion/babel-preset-css-prop"),
    ];

    // Frontity components are not compiled (!) so we have to make sure we compile them ourselves:
    const tsRule = config.module.rules.find((rule) => /ts/.test(rule.test));
    tsRule.exclude = /node_modules\/(?!(@frontity\/components|@frontity\/error|@frontity\/connect\/|@frontity\/hooks)\/).*/;
    return config;
  }
}
  • I added the file babel.config.js at the same level as frontity.settings.js. The content of the babel configuration is:
module.exports = {
  presets: [
    "@babel/preset-env",
    "@babel/preset-typescript",
    "@babel/preset-react",
  ],
  plugins: ["macros"],
};

After running Storybook with the previous setup I was able to make imports from Frontity like these:

import {styled} from "frontity";
import Image from "@frontity/components/image"

And it worked great. The only component so far I am not able to import is the Link component from Frontity. I get the state undefined error, probably because the Link component needs the connect function. Anyway, Links are not necessary for the styling on Storybook so I just created a dummy Link component to use in Storybook instead of the Frontity Link component. And when I move the story to component I change the Link component source. Suggestions are well accepted. I want to reach a point where I can just copy and paste the story to component and everything works smoothly.

Just in case here are my Story source files:
Excerpt.js

import React from 'react'
import PropTypes from 'prop-types'
import Image from "@frontity/components/image"
import {Link} from "./dummy-components/Link"
import {styled} from "frontity";
import {getFromTitle} from "../functions";

export const Excerpt = ({post, attachment, type}) => {
  const {link, title, date} = post;
  const isPodcast = type === 'podcast';

  const dateFormatted = new Date(date).toLocaleDateString('en-US', {
    year: 'numeric', month: 'long', day: 'numeric'
  });

  return (
    <article className={type}>
      <Link link={link}>
        <header>
          <h2>{isPodcast ? getFromTitle(title, 'finalTitle') : title.rendered}</h2>
          {isPodcast && <p className="guest">{getFromTitle(title, 'guest')}</p>}
          <p className="date">{dateFormatted}</p>
        </header>
        {attachment && <figure><Image src={attachment.source_url} alt={attachment.alt_text}/></figure>}
      </Link>
    </article>
  );
};

Excerpt.propTypes = {
  /**
   * Post object obtained from the API
   */
  post: PropTypes.shape({
    title: PropTypes.string.isRequired,
    date: PropTypes.string.isRequired,
    link: PropTypes.string.isRequired
  }),
  /**
   * Attachment object from the API.
   */
  attachment: PropTypes.shape({
    source_url: PropTypes.string.isRequired,
    alt_text: PropTypes.string
  }),
  /**
   * Type: post or podcast
   */
  type: PropTypes.string
}

Excerpt.stories.js

import React from 'react';
import {Excerpt} from './Excerpt';

export default {
  title: 'Excerpt',
  component: Excerpt,
  argTypes: {
    url: {control: 'text'},
    alt: {control: 'text'},
    title: {control: 'text'},
    date: {
      control: {
        type: 'date'
      }
    },
    type: {
      control: {
        type: 'select',
        options: ['post', 'podcast'],
      }
    }
  }
}

const Template = (args) => {

  const {url, alt, title, link, date, type} = args;

  // Provide the context
  const attachment = {
    source_url: url,
    alt_text: alt
  }
  const post = {
    title: {
      rendered: title
    },
    link: link,
    date: date
  }

  return <Excerpt post={post} attachment={attachment} type={type}/>
}

export const Loaded = Template.bind({});
Loaded.args = {
  type: 'podcast',
  title: '40: Hypothangry | Dr. Vickie Bhatia',
  date: '2020-08-18T04:00:00',
  url: 'https://melissau.whole30.com/wp-content/uploads/2020/08/Dr.-Vickie-Bhatia.jpg',
  alt: 'Dr. Vickie Bhatia',
  link: '/podcast/40-hypothangry-dr-vickie-bhatia/',
}

Maybe you can start working from my config files which are pretty basic and start adding your dependencies little by little and continue fixing issues. I apologize if some of my terminology is not accurate, I come from back end development and am relatively new to front end development.

Hello @luisherranz and @max

I created a POC repo with the setup I mentioned above and a fresh mars-theme. https://github.com/Bowriverstudio/frontity-wp-storybook

There are a few issues/limitations I mentioned on the readme but it is workable for my use case :slight_smile:

Any tips or suggestions would be welcome. I can also include a “Frontity-Formik” package that I use with contact-form-7 which has a few more stories.

Enjoy the weekend,
Maurice

Awesome. Thanks @maurice.tadros (and @max).

I’ll take a look as soon as I can to see how we can solve those remaining issues.

if you update frontity to the latest version, the storybook stops working.

you can use this repo GitHub - Bowriverstudio/frontity-wp-storybook to replicate this error, just need to update frontity

i have such error in my project

+ 1350 hidden modules

ERROR in ./node_modules/@frontity/connect/src/connect.js 165:15
Module parse failed: Unexpected token (165:15)
You may need an appropriate loader to handle this file type, currently no loaders are configured to process this file. See https://webpack.js.org/concepts#loaders
|         const { state, actions, libraries } = this.context;
|         const props = { ...this.props, state, actions, libraries };
>         return <Comp {...props} />;
|       }
| 
 @ ./node_modules/@frontity/connect/src/index.js 9:0-69 9:0-69 9:0-69 9:0-69
 @ ./.storybook/frontityUtils.js
 @ ./.storybook/preview.js
 @ ./.storybook/preview.js-generated-config-entry.js
1 Like

Hi @prainua

I tried and am not sure why this is happening - I’ve not touched that for a while. I could invite you to a private repo that does have storybook working with the latest version of Frontity. It is not public because there a few things I need to fix to make it useful.

FYI I am really enjoying Storybook - the projects I write are much stronger and quicker using it.

Cheers,
Maurice

1 Like

Hey @maurice.tadros,

I am looking into integrating Storybook with Frontity and I’d love to take a look at your private repository if possible!

My GitHub username is michelegera.

Thanks!

Hello @michelegera ,

Sorry for the delay. The private repo is getting refactored, by a guy I work with. Once he is done with it, I’ll review it and give you access. It is based on Chakra UI, Frontity Storybook. Frontity and Storybook are decoupled. I’m planning on stripping out the client-specific code then make it opensource.

3 Likes