-
Notifications
You must be signed in to change notification settings - Fork 4.6k
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
Implement StorybookJS #8497
Implement StorybookJS #8497
Changes from all commits
777e92c
b7ec960
03bb03d
8f39e3a
105c9df
93990ff
1c669ec
b6c4bc3
941d5b3
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,75 @@ | ||
const { propNames } = require("@chakra-ui/react") | ||
|
||
module.exports = { | ||
stories: [ | ||
{ | ||
directory: "../src/components", | ||
titlePrefix: "Components", | ||
files: "**/*.stories.tsx", | ||
}, | ||
], | ||
addons: [ | ||
"@storybook/addon-links", | ||
"@storybook/addon-essentials", | ||
"@storybook/addon-interactions", | ||
// https://storybook.js.org/addons/@storybook/addon-a11y/ | ||
"@storybook/addon-a11y", | ||
"@chakra-ui/storybook-addon", | ||
], | ||
framework: "@storybook/react", | ||
refs: { | ||
"@chakra-ui/react": { | ||
disable: true, | ||
}, | ||
}, | ||
core: { | ||
builder: "webpack5", | ||
}, | ||
features: { | ||
storyStoreV7: true, | ||
emotionAlias: false, | ||
}, | ||
webpackFinal: async (config) => { | ||
// Transpile Gatsby module because Gatsby includes un-transpiled ES6 code. | ||
config.module.rules[0].exclude = [ | ||
/node_modules\/(?!(gatsby|gatsby-script)\/)/, | ||
] | ||
// Remove core-js to prevent issues with Storybook | ||
config.module.rules[0].exclude = [/core-js/] | ||
// Use babel-plugin-remove-graphql-queries to remove static queries from components when rendering in storybook | ||
config.module.rules[0].use[0].options.plugins.push( | ||
require.resolve("babel-plugin-remove-graphql-queries") | ||
) | ||
config.resolve.mainFields = ["browser", "module", "main"] | ||
return config | ||
}, | ||
typescript: { | ||
check: false, | ||
checkOptions: {}, | ||
reactDocgen: "react-docgen-typescript", | ||
reactDocgenTypescriptOptions: { | ||
compilerOptions: { | ||
allowSyntheticDefaultImports: false, | ||
esModuleInterop: false, | ||
}, | ||
shouldExtractLiteralValuesFromEnum: true, | ||
/** | ||
* For handling bloated controls table of Chakra Props | ||
* | ||
* https://github.com/chakra-ui/chakra-ui/issues/2009#issuecomment-852793946 | ||
*/ | ||
propFilter: (prop) => { | ||
const excludedPropNames = propNames.concat([ | ||
"as", | ||
"apply", | ||
"sx", | ||
"__css", | ||
]) | ||
const isStyledSystemProp = excludedPropNames.includes(prop.name) | ||
const isHTMLElementProp = | ||
prop.parent?.fileName.includes("node_modules") ?? false | ||
return !(isStyledSystemProp || isHTMLElementProp) | ||
}, | ||
}, | ||
}, | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
import { action } from "@storybook/addon-actions" | ||
import theme from "../src/@chakra-ui/gatsby-plugin/theme" | ||
import { theme as DefaultChakraTheme } from "@chakra-ui/react" | ||
|
||
const chakraBreakpointArray = Object.entries(DefaultChakraTheme.breakpoints) | ||
|
||
// Gatsby's Link overrides: | ||
// Gatsby Link calls the `enqueue` & `hovering` methods on the global variable ___loader. | ||
// This global object isn't set in storybook context, requiring you to override it to empty functions (no-op), | ||
// so Gatsby Link doesn't throw errors. | ||
global.___loader = { | ||
enqueue: () => {}, | ||
hovering: () => {}, | ||
} | ||
// This global variable prevents the "__BASE_PATH__ is not defined" error inside Storybook. | ||
global.__BASE_PATH__ = "/" | ||
|
||
// Navigating through a gatsby app using gatsby-link or any other gatsby component will use the `___navigate` method. | ||
// In Storybook, it makes more sense to log an action than doing an actual navigate. Check out the actions addon docs for more info: https://storybook.js.org/docs/react/essentials/actions | ||
|
||
// @ts-ignore | ||
window.___navigate = (pathname) => { | ||
action("NavigateTo:")(pathname) | ||
} | ||
|
||
export const parameters = { | ||
actions: { argTypesRegex: "^on[A-Z].*" }, | ||
controls: { | ||
matchers: { | ||
color: /(background|color)$/i, | ||
date: /Date$/, | ||
}, | ||
}, | ||
chakra: { | ||
theme, | ||
}, | ||
// Modify viewport selection to match Chakra breakpoints (or custom breakpoints) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sweet 👍 |
||
viewport: { | ||
viewports: chakraBreakpointArray.reduce((prevVal, currVal) => { | ||
const [token, key] = currVal | ||
|
||
// Unnecessary breakpoint | ||
if (token === "base") return { ...prevVal } | ||
|
||
return { | ||
...prevVal, | ||
[token]: { | ||
name: token, | ||
styles: { | ||
width: key, | ||
height: "600px", | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. wondering why do we need this height 🤔 ? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Without a specified height set, an error would occur from the viewport tool: By the way, the viewport addon (part of the initial setup) comes with a collection of viewports that can also be applied to view stories with different common device specs. |
||
}, | ||
}, | ||
} | ||
}, {}), | ||
}, | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,94 @@ | ||
# Applying Storybook to Components and Pages | ||
|
||
## Overview | ||
|
||
StorybookJS is a UI tool for isolating UI components to visually test their styles and states. | ||
|
||
This is great for checking the various iterations of a component in a sandbox versus scowering all the pages in a large scale project it is used to verify that the component is rendering properly. | ||
|
||
You can also render pages if you need that level of visual testing. | ||
|
||
Storybook also gives you a library of addons provided by the team and the community to enhance the testing, including UX testing, A11y compliance, etc. | ||
|
||
Check out [Intro to Storybook](https://storybook.js.org/tutorials/intro-to-storybook/) to get an in-depth look on the workflow. | ||
|
||
## Spinning up the Storybook server | ||
|
||
It's as easy as running `yarn storybook` to boot up a dedicated localhost to see all the components that have stories. | ||
|
||
## Setting up a component's stories | ||
|
||
A Storybook "story" is an instance of a component in a certain state or with certain paramaters applied to show an alternative version of the component. | ||
|
||
Each component will only need one file containing all the stories, and should follow the naming convention of the component. | ||
|
||
So for the component `ExpandableCard.tsx`, the stories file will be named `ExpandableCard.stories.tsx`. | ||
|
||
The stories file will reside with each component. So the base folder structure in `src` will look like this: | ||
|
||
``` | ||
src/ | ||
└── components/ | ||
└── ComponentA/ | ||
├── index.tsx | ||
├── ComponentA.stories.tsx | ||
└── // Any other files as applicable (utils, child components, useHook, etc.) | ||
``` | ||
|
||
The initial structure of each story file will look something like this (in typescript): | ||
|
||
```tsx | ||
import ComponentA from "." | ||
|
||
export default { | ||
title: "ComponentA", // Generates the nav structure in the Storybook server | ||
} as ComponentMeta<typeof ComponentA> | ||
|
||
export const Basic = () => <ComponentA /> | ||
``` | ||
|
||
Should the component accept props on all or some renders, a template can be created. | ||
|
||
Let's say for a `Button` component with different style variants... | ||
|
||
```tsx | ||
import Button from "." | ||
|
||
export default { | ||
title: "Button", | ||
} as ComponentMeta<typeof Button> | ||
|
||
const Template: ComponentStory<typeof Button> = (args) => ( | ||
<ComponentA {...args} /> | ||
) | ||
|
||
export const Solid = Template.bind({}) | ||
Solid.args = { | ||
variant: "solid", | ||
children: "A Button", // Assuming the `children` prop takes text content only | ||
} | ||
|
||
export const Outline = Template.bind({}) | ||
Outline.args = { | ||
variant: "outline", | ||
children: "A Button", // Assuming the `children` prop takes text content only | ||
} | ||
|
||
/** | ||
* For practical purposes, if you are displaying different "variants", | ||
* they should be shown under one story, so they can be seen side-by-side in the GUI | ||
* for reviewers to easily compare. | ||
* This can be done for various sizes or other like alterations | ||
*/ | ||
|
||
// Assuming `solid` is the default variant in the Chakra theme config | ||
export const Variants = () => ( | ||
<VStack> | ||
<Button>A Solid Button</Button> | ||
<Button variant="outline">An Outline Button</Button> | ||
<Button variant="unstyled">An Unstyled Button</Button> | ||
</VStack> | ||
) | ||
``` | ||
|
||
As you go and make adjustments to the component itself or it's variant styles, Storybook will hot reload and those changes will appear in the stories that emphazise them. |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -75,7 +75,18 @@ | |
"unist-util-visit-parents": "^2.1.2" | ||
}, | ||
"devDependencies": { | ||
"@babel/core": "^7.19.6", | ||
"@chakra-ui/storybook-addon": "^4.0.12", | ||
"@netlify/functions": "^1.2.0", | ||
"@storybook/addon-a11y": "^6.5.13", | ||
"@storybook/addon-actions": "^6.5.13", | ||
"@storybook/addon-essentials": "^6.5.13", | ||
"@storybook/addon-interactions": "^6.5.13", | ||
"@storybook/addon-links": "^6.5.13", | ||
"@storybook/builder-webpack5": "^6.5.13", | ||
"@storybook/manager-webpack5": "^6.5.13", | ||
"@storybook/react": "^6.5.13", | ||
"@storybook/testing-library": "^0.0.13", | ||
"@types/browser-lang": "^0.1.0", | ||
"@types/github-slugger": "^1.3.0", | ||
"@types/luxon": "^2.3.2", | ||
|
@@ -85,6 +96,7 @@ | |
"@types/react-dom": "^18.0.6", | ||
"@types/react-instantsearch-core": "^6.26.2", | ||
"@types/styled-system": "^5.1.15", | ||
"babel-loader": "^8.3.0", | ||
"babel-preset-gatsby": "^2.23.0", | ||
"github-slugger": "^1.3.0", | ||
"gray-matter": "^4.0.3", | ||
|
@@ -110,7 +122,9 @@ | |
"start:lambda": "netlify-lambda serve src/lambda", | ||
"start:static": "gatsby build && gatsby serve", | ||
"serve": "gatsby serve", | ||
"type-check": "tsc --noEmit" | ||
"type-check": "tsc --noEmit", | ||
"storybook": "start-storybook -p 6006", | ||
"build-storybook": "build-storybook" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is the idea of this build command to have a new endpoint where we can access all the stories all the time for each new build? Thinking about our ci/cd pipeline, where do you see this applied? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think he mostly addressed this here: #8497 (comment) |
||
}, | ||
"husky": { | ||
"hooks": { | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
import { ComponentMeta, ComponentStory } from "@storybook/react" | ||
import React from "react" | ||
import BannerNotification from "." | ||
|
||
export default { | ||
component: BannerNotification, | ||
} as ComponentMeta<typeof BannerNotification> | ||
|
||
/** | ||
* Story taken from PostMergeBanner component | ||
* and content from `../../content/developers/tutorials/hello-world-smart-contract-fullstack/index.md` | ||
*/ | ||
export const PostMergeBanner: ComponentStory<typeof BannerNotification> = ( | ||
args | ||
) => <BannerNotification {...args} /> | ||
|
||
PostMergeBanner.args = { | ||
shouldShow: true, | ||
justify: "center", | ||
textAlign: "center", | ||
sx: { | ||
"& p": { | ||
maxWidth: "100ch", | ||
m: 0, | ||
p: 0, | ||
}, | ||
"& a": { | ||
textDecor: "underline", | ||
}, | ||
}, | ||
children: ( | ||
<p> | ||
This tutorial is out of date after the merge and may not work. Please | ||
raise a PR if you would like to contribute. | ||
</p> | ||
), | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
import { ComponentMeta, ComponentStory } from "@storybook/react" | ||
import React from "react" | ||
import Button from "." | ||
|
||
export default { | ||
component: Button, | ||
} as ComponentMeta<typeof Button> | ||
|
||
const Template: ComponentStory<typeof Button> = (args) => <Button {...args} /> | ||
|
||
export const Solid = Template.bind({}) | ||
Solid.args = { | ||
children: "What is Ethereum?", | ||
} | ||
|
||
export const Outline = Template.bind({}) | ||
Outline.args = { | ||
children: "More on digital money", | ||
variant: "outline", | ||
} | ||
|
||
export const OutlineColor = Template.bind({}) | ||
OutlineColor.args = { | ||
children: "More on digital money", | ||
variant: "outline-color", | ||
} | ||
|
||
export const Disabled = Template.bind({}) | ||
Disabled.args = { | ||
children: "I am disabled", | ||
disabled: true, | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
import { Box } from "@chakra-ui/react" | ||
import { ComponentMeta, ComponentStory } from "@storybook/react" | ||
import React from "react" | ||
import Card, { IProps } from "." | ||
import Button from "../Button" | ||
|
||
const Component = Card | ||
|
||
export default { | ||
component: Card, | ||
decorators: [ | ||
(Story) => ( | ||
<Box maxW="342px" margin="0 auto"> | ||
<Story /> | ||
</Box> | ||
), | ||
], | ||
} as ComponentMeta<typeof Component> | ||
|
||
const defaultProps: IProps = { | ||
emoji: ":woman_student:", | ||
title: "Learn Ethereum development", | ||
description: "Read up on core concepts and the Ethereum stack with our docs", | ||
} | ||
|
||
export const Default: ComponentStory<typeof Component> = (args) => ( | ||
<Component {...defaultProps} {...args}> | ||
<Button>Read the docs</Button> | ||
</Component> | ||
) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Loved that we can test the color mode and different breakpoints from the chakra theme.