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

[Storybook] Add story code snippets #7716

Merged
merged 51 commits into from
Jun 28, 2024

Conversation

mgadewoll
Copy link
Contributor

@mgadewoll mgadewoll commented Apr 26, 2024

Summary

https://eui.elastic.co/pr_7716/storybook/

Important

Disclaimer: This PR is the result of a spacetime/hackathon-y week and therefore mainly focused on building functionality instead of optimizing it as much as possible.

📢 This PR adds code snippets to our stories. 🦄 👩‍💻

The goal of this is to improve devX by providing dynamically updated code snippets for the stories. The code snippet is generated on story load as well as when any args change (e.g. when controls in the controls table are being updated)

This new functionality for code snippets is provided in an additional story panel next to the available panels for "Controls", "Actions" and "Interactions".

Screenshot 2024-04-26 at 19 29 10

The basis for the code snippet generation is heavily inspired (copied and enhanced) on Storybooks Source block. The internal jsxDecorator file was copied and then adjusted to our specific needs. The main functionality to generate a string from react elements comes from the react-element-to-jsx-string package that is used.

The overall setup

We run our custom jsxDecorator on every story as a decorator via preview.tsx. This decorator generates the code snippet as string and sends it via Storybooks Channel events to our newly added custom addon panel which outputs the code string using Storybooks SyntaxHighlighter copmponent.

Screenshot 2024-04-26 at 18 59 57

The main updates to the jsxDecorator on our side are to ensure the code outputs clean and relevant code snippets.
What was considered:

  • rename Emotion wrappers to the actual component name (whenever we use css on a component in a story it will be an Emotion component)
  • rename stateful wrappers that start with the wording Stateful (requires us to follow an agreed convention)
  • remove obsolete fragment wrappers (but keeps needed ones)
  • remove story specific wrappers (e.g. layout or styling)
  • keep related wrappers (parent & subcomponent or related by name)
  • resolve any other unexpected wrapper we might add to structure complex stories
  • rename internal component names (starting with _ underscore, <_Component> is changed to <Component>)
  • reerse-resolve css attribute to ensure it is not transformed to Emotion
  • ensure boolean props are output in a meaningful way (generally as shorthand but keep specifically defined false values where false has meaning)
  • ensure a nice formatting

The updates happen in different stages in jsxDecorator:

  1. pre-conversion: determine what react element should be passed to react-element-to-jsx-string and with which options
  2. conversion: pass react elements to react-element-to-jsx-string
  3. post-conversion: do additional replacements on the returned string
  4. formatting: format the result using prettier

Options

Currently there are a few addon specific parameter options added with this PR that can be used under the key codeSnippet in the parameters config key.

// meta or story config
const meta = {
  title: 'Navigation/EuiButton',
  component: EuiButton,
  parameters: {
    codeSnippet: {
      // Optional way to override selected story args with manual values.
      // This is useful when the story arg would render unreadable or not useful output.
      // You can use interpolation markers #{} to ensure the value is output as is, this
      // is useful for e.g. functions to prevent them from being called.
      args: {
        propA: 'new value for propA',
        propB: "#{someFunctionCall('inputValue')}" // returns: propB={someFunctionCall('inputValue')}
      },
      // will skip code snippet generation for the component or story
      // @default false
      skip: true,
      // Useful for complex story composition wrappers (using the story component as 
      // nested child and not as direct return for `render`).
      // It will skip the outer story wrapper and return the code snippet for its children
      // instead. See the story for `EuiHeader/Multiple Fixed Headers` as an example.
      // @default false
      resolveChildren: true,
      // Useful when the story outputs additional contnt that should not be included in the
      // snippet and instead only the actual story component should be output as snippet.
      // @default false
      resolveStoryElementOnly: true,
      // The jsx renderer removes the story components default props. In case that they should 
      // be added to a specific code snippet it can be enabled by setting this option to `false`.
      // @default true
      removeDefaultProps: false,
    }
  }
}

ℹ️ Stories will also be skipped whenever an anonymous function without args is returned as story function, e.g. from render().

Limitations

⚠️ Currently it's not yet supported to resolve "render functions" (either used as children or any prop value)
Components that make use of render functions (specifically for children) are currently (manually) skipped until support is added.

ℹ️ Currently this implementation is using Storybooks SyntaxHighlighter component to output the code snippets. This works mainly well but seems to have trouble properly detecting the code parts for large snippets which results in some partially uncolored snippets. We could in the future check to replace this component with another to improve.

Screenshot 2024-06-07 at 14 58 00

Screenshot 2024-06-07 at 14 57 18

Screenshots

skipped code snippet generation
Screenshot 2024-04-26 at 20 03 24

Screenshot 2024-04-26 at 19 59 01

Screenshot 2024-04-26 at 20 04 18

Disclaimer

⚠️ This PR currently does not add tests for the jsxDecorator functionality yet.

QA

  • review all stories and their generated code snippets
    • Are there unexpected outputs?
    • Are there opportunities to improve what we output?
    • Does everything run as expected?

@mgadewoll mgadewoll added documentation Issues or PRs that only affect documentation - will not need changelog entries skip-changelog labels Apr 26, 2024
@mgadewoll mgadewoll force-pushed the spacetime/storybook-code-snippets branch from 6c1c416 to 01bfb67 Compare May 10, 2024 15:11
@mgadewoll mgadewoll force-pushed the spacetime/storybook-code-snippets branch from e144ec5 to 6d3413d Compare May 24, 2024 15:50
@mgadewoll mgadewoll force-pushed the spacetime/storybook-code-snippets branch from 41f4c6a to e76dd6f Compare June 7, 2024 12:54
@mgadewoll mgadewoll marked this pull request as ready for review June 7, 2024 14:15
@mgadewoll mgadewoll requested a review from a team as a code owner June 7, 2024 14:15
Comment on lines +43 to +103
<SyntaxHighlighter
language="tsx"
copyable
padded
showLineNumbers={false}
wrapLongLines
>
{code}
</SyntaxHighlighter>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any thoughts about dogfooding EuiCodeBlock for this instead of using Storybook's component? 👀

Copy link
Contributor Author

@mgadewoll mgadewoll Jun 12, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah that's a good idea! I think I briefly had that thought while initially building this but went with the Storybook component just because it works out of the box (as they use it for their docs source code) and saved me some time 😅.
Let me have another look how it would work with our component 👍

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah I realize now why I took the Storybook component. There are issues using the EUI components because emotion is not properly resolved somehow for the addon panel. 🫠

You have tried to stringify object returned from `css` function. It isn't supposed to be used directly (e.g. as value of the `className` prop), but rather handed to emotion so it can handle it (e.g. as value of `css` prop).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Haha whoops 🙈 Yeah, Storybook probably needs the Emotion babel preset which is a whole fun setup thing (https://emotion.sh/docs/css-prop). You could the JSX pragma to see if that works?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But no worries if it doesn't, I'm totally fine with the Storybook component as-is!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah I had a first brief look at it to set this up but it didn't work as expected and it will need some more digging. I think I'd opt for leaving it as is for now and doing the update as a standalone task because I do think it would be generally good to be able to use our component as the Storybook SyntaxHighlighter has some limitation that it does not always properly highlighting the code.

🗒️ What I tried/noticed:

  • Emotion works fine in the preview but not the manager parts
  • I tried adding the emotion preset and it did absolutely nothing (it also seemed like the root babel config is not used - adding a manual config for Storybook with the preset resulted in custom components with css prop working but the imported EUI files still were broken)
  • adding the JSX pragma worked when it was set in the EUI component files that are imported, not when added in the Storybook files (which is not an option)

{code}
</SyntaxHighlighter>
) : (
<Container>{isLoaded ? emptyState : loadingState}</Container>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thoughts on using EuiEmptyPrompt or EuiCallOut instead of this container? Am I being too extra here right now? 😅

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, that's a good idea! Why not reuse what we already have!
Let's see first if we can hide the panel for not available code snippets because we would not need this, if we just hide it 😄

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah wait, for the loading state it would still be used, so yes! I'll check how that'll look like 🙂

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ℹ️ This is blocked due to the same issue around Emotion mentioned here.

mgadewoll and others added 17 commits June 27, 2024 18:54
- uses Story wrapper for ComboBox story as example
- it's now only outputting `<input type="text" />` which is even less helpful than before. We can just move ahead with extra cruft in the snippet for now, that's fine
- still useful even without full render functions
- adds check if single child is story element to prevent overrides and additional checks to ensure proper element

- adds simplification for return undefined
- this will ensure the passed content is output as is instead of being executed or coerced
- ensures forwarded components are properly resolved when checking for the story component instead of being skipped
@mgadewoll mgadewoll force-pushed the spacetime/storybook-code-snippets branch from 54380d3 to 58f724e Compare June 27, 2024 16:58
Copy link
Contributor

@cee-chen cee-chen left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pulled this down to test and the EuiDatePicker story is working perfectly!! Seriously huge kudos for this fantastic work Lene, this is going to be an amazing tool for devs to use! :shipit: 👏 🚀

@mgadewoll mgadewoll enabled auto-merge (squash) June 28, 2024 07:44
@mgadewoll mgadewoll disabled auto-merge June 28, 2024 07:48
@mgadewoll mgadewoll enabled auto-merge (squash) June 28, 2024 08:06
@kibanamachine
Copy link

Preview staging links for this PR:

@elasticmachine
Copy link
Collaborator

💚 Build Succeeded

History

cc @mgadewoll

@mgadewoll mgadewoll merged commit bb9d36a into elastic:main Jun 28, 2024
4 of 5 checks passed
@mgadewoll mgadewoll deleted the spacetime/storybook-code-snippets branch June 28, 2024 08:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
documentation Issues or PRs that only affect documentation - will not need changelog entries skip-changelog
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants