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

Automated "All stories" story #505

Open
sregg opened this issue Aug 10, 2023 · 7 comments
Open

Automated "All stories" story #505

sregg opened this issue Aug 10, 2023 · 7 comments

Comments

@sregg
Copy link

sregg commented Aug 10, 2023

Is your feature request related to a problem? Please describe.
When I have a lot of stories for a given component, it can be tedious to switch stories (i.e. side bar, click story, side bar, click story, etc...). I'd like to be able to see all stories in one screen. Especially for small components like buttons or list items.

Describe the solution you'd like
It'd be great to have the ability to automatically add an "All" story where all stories are rendered after an other with their title.

Describe alternatives you've considered
Infinite Red's Ignite had some cool components like UseCase for achieving this (see PR when they removed all that here) but the new CSF format doesn't support this (i.e. you can't easily render multiple components in one story).

Are you able to assist bring the feature to reality?
Yes!

Additional context
Example of Story in Ignite:

Something similar but automated would be awesome.

@sregg
Copy link
Author

sregg commented Aug 10, 2023

I just found this: https://storybook.js.org/addons/storybook-addon-variants
It'd be great to support this on mobile or build something similar.

@dannyhw
Copy link
Member

dannyhw commented Aug 10, 2023

You can pretty easily make something like this happen with csf, excuse the formatting I'm just doing this from memory.

const meta = {
   title: "Button",
   component: Button,
}

export const Story1 = {
  args: { 
    text: "text1"
  }
}

export const Story2 = {
  args: { 
    text: "text2"
  }
}

export const AllStory = {
  render ()=>{
    return (<>
      <Button {...Story1.args}>
      <Button {...Story2.args}>
   </>)
 }
}

@sregg
Copy link
Author

sregg commented Aug 10, 2023

Nice! I didn't know about this render() function 🎉
Looks like we could automatize this though.

import React from 'react';

import { CookStats } from './CookStats';

import type { Meta, StoryObj } from '@storybook/react-native';
import type { CookStatsProps } from './types';
import { InlineStorySectionHeader } from '~/storybook/components/InlineStorySectionHeader';

const CookStatsMeta = {
  title: 'Components/CookStats',
  component: CookStats,
  args: {
    size: 'large',
    distanceFromCustomer: '3.8mi',
  },
  argTypes: {
    size: {
      type: { name: 'enum', value: ['small', 'large'] },
    },
  },
} as Meta<CookStatsProps>;

export default CookStatsMeta;

export const WithEverything: StoryObj<CookStatsProps> = {
  args: {
    orderCount: 1126,
    averageRating: 4.7,
    ratingCount: 304,
  },
};

export const NoOrderCount: StoryObj<CookStatsProps> = {
  args: {
    averageRating: 4.2,
    ratingCount: 304,
  },
};

export const NoRating: StoryObj<CookStatsProps> = {
  args: {
    orderCount: 19,
  },
};

export const NoOrderCountNoRating: StoryObj<CookStatsProps> = {
  args: {},
};

export const All: StoryObj<CookStatsProps> = {
  render: () => (
    <>
      <InlineStorySectionHeader title="WithEverything" />
      <CookStats
        {...(CookStatsMeta.args as CookStatsProps)}
        {...WithEverything.args}
      />
      <InlineStorySectionHeader title="NoOrderCount" />
      <CookStats
        {...(CookStatsMeta.args as CookStatsProps)}
        {...NoOrderCount.args}
      />
      <InlineStorySectionHeader title="NoRating" />
      <CookStats
        {...(CookStatsMeta.args as CookStatsProps)}
        {...NoRating.args}
      />
      <InlineStorySectionHeader title="NoOrderCountNoRating" />
      <CookStats
        {...(CookStatsMeta.args as CookStatsProps)}
        {...NoOrderCountNoRating.args}
      />
    </>
  ),
};
image

@sregg
Copy link
Author

sregg commented Aug 10, 2023

I can create a util function that I can reuse in my project:

export function renderAllStories<T>(meta: Meta<T>, stories: StoryObj<T>[]) {
  return (
    <>
      {stories.map((story, index) => (
        <Fragment key={index}>
          <InlineStorySectionHeader title={story.name ?? ''} />
          {meta.component && <meta.component {...meta.args} {...story.args} />}
        </Fragment>
      ))}
    </>
  );
}

and use it like so:

export const All: StoryObj<CookStatsProps> = {
  render: () =>
    renderAllStories(CookStatsMeta, [
      WithEverything,
      NoOrderCount,
      NoRating,
      NoOrderCountNoRating,
    ]),
};

Small question: how does Storybook get the name of the story objects (e.g. WithEverything)?

@dannyhw
Copy link
Member

dannyhw commented Aug 10, 2023

when you import/require a module it has all the named exports and the default export in there like

module.exports = {
 Name1: { stuff here}
default: {meta stuff here}
}

so when you import that file its like

const file = require("location")

this file has all of those things

so yeah you could actually do this more automated yet probably by getting the current module and looping through it

@dannyhw
Copy link
Member

dannyhw commented Aug 10, 2023

@sregg since you can get the current module from just accessing "module", you should be able to do this:

export const AllStory = {
  render: () => {
    return (
      <>
        {Object.entries(module.exports)
          .filter(([key, _val]) => key !== "default")
          .map(([key, val]) => {
            if (val.args) {
              return <Icon key={key} {...val.args} />;
            }
          })}
      </>
    );
  },
};

@sregg
Copy link
Author

sregg commented Aug 11, 2023

Sounds good. I'll see if I have time to build that into this package itself.
Maybe with a showAllStoriesStory flag in Meta.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants