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

addon-storysource not producing useful code examples using recommended stories approach #12495

Closed
roblevintennis opened this issue Sep 16, 2020 · 13 comments

Comments

@roblevintennis
Copy link

roblevintennis commented Sep 16, 2020

Is your feature request related to a problem? Please describe.

I've been following the tutorials and docs to create many stories using the props driven pattern described there. I have projects in React, Svelte, and Vue, and each have their own storybook with similar configurations.

Using the prescribed patterns in the featured storybook docs/tutorials this is the approach I've used (it's Svelte based, but I've mostly done the same thing for React and Vue with similar results):

import Button from './Button.svelte';

export default {
  title: 'Button',
  component: Button,
  argTypes: {
    label: { control: 'text' },
...and so on
  },
};

const Template = ({ onClick, ...args }) => ({
  Component: Button,
  props: args,
});

export const Primary = Template.bind({});
Primary.args = {
  label: 'Button',
  mode: 'primary',
};
...and so on

When I show code or look at the addon-storysource I see the template code which won't be a helpful dev ux for readers of my design system. Note the examples are really showing the Template intermediary component and are all the same:

image

and same for the addon-storysource tab:

image

Describe the solution you'd like
I'm listing this as a feature as I believe the addon is working as designed. However, I'm confused on the overall result—if this props driven approach to testing is the recommendation, but the code snippet examples derived are essentially meaningless to the dev users, how am I able to achieve the goal of documenting how to use a component?

As a previous user of react-styleguidist (and other similar tools), getting code snippets was fairly trivial. For styleguidist, it would simply output the code blocks within your markdown giving you some control of what the user would see.

I'm fairly new to storybook but have already written a slew of these stories so I'm hoping I've missed something in the usage somehow.

Describe alternatives you've considered
I've seen some older storiesOf examples that seem to use the template or jsx more closely and wonder if those output better code examples. However, I've written so many of these stories using the docs recommended way that it would be a bear to refactor them all, especially if I'm using an older less idiomatic approach then the props driven newer one that's clearly being featured on the docs and tutorial pages for storybook.

Are you able to assist bring the feature to reality?
no | yes, I can...

Additional context

To be clear, I'd expect the source code examples to show something more meaningful like this I've taken from another similar project I've worked on:

image

@roblevintennis
Copy link
Author

roblevintennis commented Sep 16, 2020

Ok, this may turn into a self resolving issue, but I'll keep my notes for others having same quandary :)

I'm starting to learn more digging deeper into the docs section and considering MDX and other alternatives per your writing docs guides:
https://storybook.js.org/docs/react/writing-docs/introduction, and also looking more closely at some of the examples like https://github.com/storybookjs/design-system/blob/master/src/components/Button.stories.js.

So, it looks for React this approach can work (alongside the more modern props approach I showed above):

export const DisabledAll = () => (
  <>
    <Button label="Default Disabled" isDisabled />
    <Button mode="primary" label="Primary Disabled" isDisabled />
    <Button mode="secondary" label="Secondary Disabled" isDisabled />
    <Button mode="secondary" label="Secondary Bordered Disabled" isBordered isDisabled />
  </>
);

This seems to get me the code snippets I'm after more or less (React):

image

So it looks like for React I have a solution.

However, and I could be wrong, but for Vue and Svelte, it seems I need either some sort of special storybook loader that I'm not aware of, or, I have to just create separate example files and then delegate to those.

So here's a new ButtonsDisabled.vue view:

<template>
  <div>
    <AgnosticButton label="Default Disabled" isDisabled />
    <AgnosticButton mode="primary" label="Primary Disabled" isDisabled />
    <AgnosticButton mode="secondary" label="Secondary Disabled" isDisabled />
    <AgnosticButton mode="secondary" label="Secondary Bordered Disabled" isBordered isDisabled />
  </div>
</template>

<script>
import AgnosticButton from "./Button.vue";
export default {
  name: "buttons-disabled",
  components: { AgnosticButton },
};
</script>

And then when I use that in my story like:

import ButtonsDisabled from './ButtonsDisabled.vue';
export const ButtonsDisabledTest = (args) => ({
  title: 'Buttons Disabled',
  components: { ButtonsDisabled },
  template: `<buttons-disabled />`,
});

I get this uninteresting rendering in terms of the code source example:

image

So this last example was Vue, but I have similar results/issues with Svelte. It seems that I have to place the templates / views in another files e.g. view.svelte or view.vue and then delegate from my story, but that results in a not so useful example code snippet.

@shilman
Copy link
Member

shilman commented Sep 16, 2020

If you write the code like this:

export const Foo = () => <Button>bar</Button>

The source code should show:

() => <Button>bar</Button>

In 6.0 we've introduced the Args construct, which you're using in the example above.

As the Story function becomes more abstract, it gets further from what you'd want, as you note in the issue.

In React, I've introduced dynamic snippet rendering, so that we render the JSX based on the live values of the args being passed into the function.

So the if { children: "bar" } was passed into this story:

export const Foo = ({ children }) => <Button>{children}</Button>

The source block would display:

<Button>bar</Button>

This was implemented in #11332. There are followup issues for some of the popular frameworks: #11400, #11588, #10617

If you're interested in contributing an implementation for your favorite framework, I'm happy to help guide. Otherwise, you can either use the non-args stories, or you can manually override the source snippet to be anything you want using the docs.source.code story parameter.

Would like to roll as many of these out as I can in 6.1. The current setup leaves a lot to be desired.

@roblevintennis
Copy link
Author

Thanks for the timely reply sir! I see addons/docs/src/blocks/SourceContainer.tsx seems to do a lot of the heavy lifting there. In any event, I think this will work itself out in time esp. with these dynamic snippet rendering things you're starting to add!

I'm not sure I can commit to contributing atm but I'll keep it in mind and can empathize that you'd prefer to shepherd collective ownership and contribution from the community!

@roblevintennis
Copy link
Author

roblevintennis commented Sep 17, 2020

So I'm confused why React works so well (but Vue and Svelte do not) when I do something like this I get the snippet I'd expect:

export const DisabledAll = () => (
  <>
    <Button label="Default Disabled" isDisabled />
    <Button mode="primary" label="Primary Disabled" isDisabled />
    <Button mode="secondary" label="Secondary Disabled" isDisabled />
    <Button mode="secondary" label="Secondary Bordered Disabled" isBordered isDisabled />
  </>
);

image

image

The above two screen shots are perfect for my use case. So, after refactoring, React is working well for my use case.


Vue

First, I cannot use JSX (or really <template> idiom) within mystory.stories.js so I have to utilize a sub-view (wish I could create a MyVueStories.stories.vue and for Svelte similarly MySvelteStories.stories.svelte) and here's what I've tried for that using the sub-view approach:

<template>
  <div>
    <AgnosticButton label="Default Disabled" isDisabled />
    <AgnosticButton mode="primary" label="Primary Disabled" isDisabled />
    <AgnosticButton mode="secondary" label="Secondary Disabled" isDisabled />
    <AgnosticButton mode="secondary" label="Secondary Bordered Disabled" isBordered isDisabled />
  </div>
</template>

<script>
// ButtonsDisabled.vue
import AgnosticButton from "./Button.vue";
export default {
  name: "buttons-disabled",
  components: { AgnosticButton },
};
</script>
import ButtonsDisabled from './ButtonsDisabled.vue';

export default {
  title: 'Button',
  component: AgnosticButton,
  // etc.
}

export const ButtonsDisabledAll = () => ({
  title: 'Buttons Disabled',
  components: { ButtonsDisabled },
  template: `<buttons-disabled />`,
  parameters: {
    docs: {
      source: {
        code: `
          <div>
            <AgnosticButton label="Default Disabled" isDisabled />
            <AgnosticButton mode="primary" label="Primary Disabled" isDisabled />
            <AgnosticButton mode="secondary" label="Secondary Disabled" isDisabled />
            <AgnosticButton mode="secondary" label="Secondary Bordered Disabled" isBordered isDisabled />
          </div>
        `
      }
    }
  }
});

image

image

Is this the expected output? You can see I've attempted to leverage the docs.source.code mechanism, but I would have thought that would produce output somewhere in the rendered page canvas somehow. Obviously, noob here and I'm not sure how that's supposed to work exactly just referencing https://storybook.js.org/docs/react/writing-docs/doc-blocks#source.

Also, it's not ideal since I have to manually duplicate the code. But I'm just trying things out. I haven't yet started trying to hack on the storybook codebase yet, but was hoping I wouldn't have to do so to achieve code snippets for Vue/Svelte. Lmk if there's anything I'm doing wrong or something else I can try before resorting to working on a patch upstream. Thanks for your guidance 🙏

@roblevintennis
Copy link
Author

roblevintennis commented Sep 17, 2020

Ok, I'm leaving all my user-land mistakes above in case someone else is having same issues :) Here's what fixed the docs.source.code part (my fault for straying from docs in earlier attempts):

export const ButtonsDisabledAll = () => ({
  title: 'Buttons Disabled',
  components: { ButtonsDisabled },
  template: `<buttons-disabled />`,
});
ButtonsDisabledAll.parameters = {
  docs: {
    source: {
      code: `
<div>
  <AgnosticButton label="Default Disabled" isDisabled />
  <AgnosticButton mode="primary" label="Primary Disabled" isDisabled />
  <AgnosticButton mode="secondary" label="Secondary Disabled" isDisabled />
  <AgnosticButton mode="secondary" label="Secondary Bordered Disabled" isBordered isDisabled />
</div>
        `
    }
  },
}

So this works pretty well for snippet in Docs tab:

image

But for the Story tab:

image

I suppose I could just go without using the addon-storysource since the Docs is showing the source there 🤔 -- still not ideal to have to manually copy code snippets into docs.source.code as those will likely diverge, but at least it's sort of working.


I also realized that if I comprimise with the docs.source.code idiom, I can also do the props approach with it and it works ok:

export const Primary = Template.bind({});
Primary.args = {
  label: 'Primary',
  mode: 'primary',
};
Primary.parameters = {
  docs: {
    source: {
      code: `<AgnosticButton mode="primary" label="Primary" />`
    }
  },
}

image

In a way, this is closer to being satisfactory then the sub-view approach (if it's the best we can do), since the props and the hard coded docs.source.code snippet are right besides each other (label and mode in this case) so it's a bit more likely that divergent props would be caught be the eye maybe.


I'm still wondering if there's a more ideal approach that will synchronize what's rendered w/source snippets better for Vue and Svelte. Any ideas or recommendations?

@netzfluencer
Copy link

yes I would also be really interested in having shown the exact source code of the generated vue-component over the written storysource code. Our developers would prefer to copy the snippet of the component. In the past we used the addon https://github.com/pocka/storybook-addon-vue-info.

@stale
Copy link

stale bot commented Dec 26, 2020

Hi everyone! Seems like there hasn't been much going on in this issue lately. If there are still questions, comments, or bugs, please feel free to continue the discussion. Unfortunately, we don't have time to get to every issue. We are always open to contributions so please send us a pull request if you would like to help. Inactive issues will be closed after 30 days. Thanks!

@stale stale bot added the inactive label Dec 26, 2020
@curtgrimes
Copy link

This issue is still relevant.

@stale stale bot removed the inactive label Dec 26, 2020
@roblevintennis
Copy link
Author

Yes, it will be nice when we no longer have to hard code the resulting template code which is error prone and tedious. I wish I had time to help with this but still do not. I'll leave it open for now. Thanks for chiming in to confirm it's still relevant @curtgrimes

@shilman
Copy link
Member

shilman commented Jan 2, 2021

@netzfluencer Dynamic snippet rendering for Vue has been implemented in 6.1 in #12812 using an improvement on addon-view-info by @pocka. React is in #11332. NOTE: these are for the Source block in Storybook Docs, not for addon-storysource.

@curtgrimes @roblevintennis if either of you is interested in implementing this for the framework of your choice, I'd be happy to help guide.

@atflick
Copy link

atflick commented Aug 13, 2021

Does this work or how do you activate? I'm still getting this in source after updating to latest:

(args, { argTypes }) => ({
  components: { EnDropdown },
  props: Object.keys(argTypes),
  template: `<EnDropdown v-bind="$props"></EnDropdown>`,
})

@shilman
Copy link
Member

shilman commented Jun 8, 2023

We’re cleaning house! Storybook has changed a lot since this issue was created and we don’t know if it’s still valid. Please open a new issue referencing this one if:

@shilman shilman closed this as not planned Won't fix, can't repro, duplicate, stale Jun 8, 2023
@raythurnvoid
Copy link

To whoever ends up here without a solution here's mine.

I'm on Storybook 8.4.x and i use Vite with React.

Luckly we have Claude and other LLMs so i was able to make it work. Instead of relying on storybook's source generation i copy-paste the code manually into docs.source.code or i ask the LLM to generate it for me and then i've created a custom addon to show the same source component available in the docs, also in the addons panel for each story.

Custom addon file: .storybook/addons/source-tab/manager.tsx

import React from "react";
import { addons, types } from "@storybook/manager-api";
import { AddonPanel } from "@storybook/components";
import { Source } from "@storybook/blocks";
import { useStorybookApi, useStorybookState } from "@storybook/manager-api";
import { useTheme } from "@storybook/theming";

const ADDON_ID = "storybook/source-tab";
const PANEL_ID = `${ADDON_ID}/panel`;

const SourcePanel = () => {
	const api = useStorybookApi();
	const state = useStorybookState();
	const theme = useTheme();
	const story = api.getData(state.storyId, state.refId);

	if (!story) {
		return null;
	}

	return (
		<div style={{ padding: "1rem" }}>
			<Source
				code={story.parameters?.docs?.source?.code || "// No source available"}
				language="tsx"
				dark={theme.base === "dark"}
			/>
		</div>
	);
};

addons.register(ADDON_ID, () => {
	addons.add(PANEL_ID, {
		type: types.PANEL,
		title: "Source",
		render: ({ active }) => (
			<AddonPanel active={active || false}>
				<SourcePanel />
			</AddonPanel>
		),
	});
});

My .storybook/main.ts

import type { StorybookConfig } from "@storybook/react-vite";

const config: StorybookConfig = {
	stories: ["../src/**/*.mdx", "../src/**/*.stories.@(js|jsx|mjs|ts|tsx)"],
	addons: [
		"@storybook/addon-onboarding",
		{
			name: "@storybook/addon-essentials",
			options: {
				actions: false,
				interactions: false,
				controls: false,
			},
		},
		"@chromatic-com/storybook",
		"storybook-dark-mode",
		"./addons/source-tab/manager.tsx",
	],
	framework: {
		name: "@storybook/react-vite",
		options: {},
	},
};
export default config;

My stories then look like this:

export const Primary: Story = {
	args: {
		variant: "primary",
		children: <SybButtonText>Button</SybButtonText>,
	},
	parameters: {
		controls: {
			exclude: [...excluded_controls_non_default_stories, "variant"],
		},
		docs: {
			source: {
				code: source`
					<SybButton variant="primary">
						<SybButtonText>Button</SybButtonText>
					</SybButton>
				`,
			},
		},
	},
};

This is the result

Image

May seem a nightmare to maintain but actually is not, I use cursor and ask it to generate all the stories for me and keep the source up to date.

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

No branches or pull requests

6 participants