Skip to content

Commit de10ed9

Browse files
authored
Merge pull request #8497 from TylerAPfledderer/feat/demo-implement-storybook
Implement StorybookJS
2 parents 7a0abfe + 941d5b3 commit de10ed9

File tree

21 files changed

+5725
-1018
lines changed

21 files changed

+5725
-1018
lines changed

.storybook/main.js

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
const { propNames } = require("@chakra-ui/react")
2+
3+
module.exports = {
4+
stories: [
5+
{
6+
directory: "../src/components",
7+
titlePrefix: "Components",
8+
files: "**/*.stories.tsx",
9+
},
10+
],
11+
addons: [
12+
"@storybook/addon-links",
13+
"@storybook/addon-essentials",
14+
"@storybook/addon-interactions",
15+
// https://storybook.js.org/addons/@storybook/addon-a11y/
16+
"@storybook/addon-a11y",
17+
"@chakra-ui/storybook-addon",
18+
],
19+
framework: "@storybook/react",
20+
refs: {
21+
"@chakra-ui/react": {
22+
disable: true,
23+
},
24+
},
25+
core: {
26+
builder: "webpack5",
27+
},
28+
features: {
29+
storyStoreV7: true,
30+
emotionAlias: false,
31+
},
32+
webpackFinal: async (config) => {
33+
// Transpile Gatsby module because Gatsby includes un-transpiled ES6 code.
34+
config.module.rules[0].exclude = [
35+
/node_modules\/(?!(gatsby|gatsby-script)\/)/,
36+
]
37+
// Remove core-js to prevent issues with Storybook
38+
config.module.rules[0].exclude = [/core-js/]
39+
// Use babel-plugin-remove-graphql-queries to remove static queries from components when rendering in storybook
40+
config.module.rules[0].use[0].options.plugins.push(
41+
require.resolve("babel-plugin-remove-graphql-queries")
42+
)
43+
config.resolve.mainFields = ["browser", "module", "main"]
44+
return config
45+
},
46+
typescript: {
47+
check: false,
48+
checkOptions: {},
49+
reactDocgen: "react-docgen-typescript",
50+
reactDocgenTypescriptOptions: {
51+
compilerOptions: {
52+
allowSyntheticDefaultImports: false,
53+
esModuleInterop: false,
54+
},
55+
shouldExtractLiteralValuesFromEnum: true,
56+
/**
57+
* For handling bloated controls table of Chakra Props
58+
*
59+
* https://github.com/chakra-ui/chakra-ui/issues/2009#issuecomment-852793946
60+
*/
61+
propFilter: (prop) => {
62+
const excludedPropNames = propNames.concat([
63+
"as",
64+
"apply",
65+
"sx",
66+
"__css",
67+
])
68+
const isStyledSystemProp = excludedPropNames.includes(prop.name)
69+
const isHTMLElementProp =
70+
prop.parent?.fileName.includes("node_modules") ?? false
71+
return !(isStyledSystemProp || isHTMLElementProp)
72+
},
73+
},
74+
},
75+
}

.storybook/preview.js

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import { action } from "@storybook/addon-actions"
2+
import theme from "../src/@chakra-ui/gatsby-plugin/theme"
3+
import { theme as DefaultChakraTheme } from "@chakra-ui/react"
4+
5+
const chakraBreakpointArray = Object.entries(DefaultChakraTheme.breakpoints)
6+
7+
// Gatsby's Link overrides:
8+
// Gatsby Link calls the `enqueue` & `hovering` methods on the global variable ___loader.
9+
// This global object isn't set in storybook context, requiring you to override it to empty functions (no-op),
10+
// so Gatsby Link doesn't throw errors.
11+
global.___loader = {
12+
enqueue: () => {},
13+
hovering: () => {},
14+
}
15+
// This global variable prevents the "__BASE_PATH__ is not defined" error inside Storybook.
16+
global.__BASE_PATH__ = "/"
17+
18+
// Navigating through a gatsby app using gatsby-link or any other gatsby component will use the `___navigate` method.
19+
// 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
20+
21+
// @ts-ignore
22+
window.___navigate = (pathname) => {
23+
action("NavigateTo:")(pathname)
24+
}
25+
26+
export const parameters = {
27+
actions: { argTypesRegex: "^on[A-Z].*" },
28+
controls: {
29+
matchers: {
30+
color: /(background|color)$/i,
31+
date: /Date$/,
32+
},
33+
},
34+
chakra: {
35+
theme,
36+
},
37+
// Modify viewport selection to match Chakra breakpoints (or custom breakpoints)
38+
viewport: {
39+
viewports: chakraBreakpointArray.reduce((prevVal, currVal) => {
40+
const [token, key] = currVal
41+
42+
// Unnecessary breakpoint
43+
if (token === "base") return { ...prevVal }
44+
45+
return {
46+
...prevVal,
47+
[token]: {
48+
name: token,
49+
styles: {
50+
width: key,
51+
height: "600px",
52+
},
53+
},
54+
}
55+
}, {}),
56+
},
57+
}

docs/applying-storybook.md

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
# Applying Storybook to Components and Pages
2+
3+
## Overview
4+
5+
StorybookJS is a UI tool for isolating UI components to visually test their styles and states.
6+
7+
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.
8+
9+
You can also render pages if you need that level of visual testing.
10+
11+
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.
12+
13+
Check out [Intro to Storybook](https://storybook.js.org/tutorials/intro-to-storybook/) to get an in-depth look on the workflow.
14+
15+
## Spinning up the Storybook server
16+
17+
It's as easy as running `yarn storybook` to boot up a dedicated localhost to see all the components that have stories.
18+
19+
## Setting up a component's stories
20+
21+
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.
22+
23+
Each component will only need one file containing all the stories, and should follow the naming convention of the component.
24+
25+
So for the component `ExpandableCard.tsx`, the stories file will be named `ExpandableCard.stories.tsx`.
26+
27+
The stories file will reside with each component. So the base folder structure in `src` will look like this:
28+
29+
```
30+
src/
31+
└── components/
32+
└── ComponentA/
33+
├── index.tsx
34+
├── ComponentA.stories.tsx
35+
└── // Any other files as applicable (utils, child components, useHook, etc.)
36+
```
37+
38+
The initial structure of each story file will look something like this (in typescript):
39+
40+
```tsx
41+
import ComponentA from "."
42+
43+
export default {
44+
title: "ComponentA", // Generates the nav structure in the Storybook server
45+
} as ComponentMeta<typeof ComponentA>
46+
47+
export const Basic = () => <ComponentA />
48+
```
49+
50+
Should the component accept props on all or some renders, a template can be created.
51+
52+
Let's say for a `Button` component with different style variants...
53+
54+
```tsx
55+
import Button from "."
56+
57+
export default {
58+
title: "Button",
59+
} as ComponentMeta<typeof Button>
60+
61+
const Template: ComponentStory<typeof Button> = (args) => (
62+
<ComponentA {...args} />
63+
)
64+
65+
export const Solid = Template.bind({})
66+
Solid.args = {
67+
variant: "solid",
68+
children: "A Button", // Assuming the `children` prop takes text content only
69+
}
70+
71+
export const Outline = Template.bind({})
72+
Outline.args = {
73+
variant: "outline",
74+
children: "A Button", // Assuming the `children` prop takes text content only
75+
}
76+
77+
/**
78+
* For practical purposes, if you are displaying different "variants",
79+
* they should be shown under one story, so they can be seen side-by-side in the GUI
80+
* for reviewers to easily compare.
81+
* This can be done for various sizes or other like alterations
82+
*/
83+
84+
// Assuming `solid` is the default variant in the Chakra theme config
85+
export const Variants = () => (
86+
<VStack>
87+
<Button>A Solid Button</Button>
88+
<Button variant="outline">An Outline Button</Button>
89+
<Button variant="unstyled">An Unstyled Button</Button>
90+
</VStack>
91+
)
92+
```
93+
94+
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.

package.json

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,18 @@
7575
"unist-util-visit-parents": "^2.1.2"
7676
},
7777
"devDependencies": {
78+
"@babel/core": "^7.19.6",
79+
"@chakra-ui/storybook-addon": "^4.0.12",
7880
"@netlify/functions": "^1.2.0",
81+
"@storybook/addon-a11y": "^6.5.13",
82+
"@storybook/addon-actions": "^6.5.13",
83+
"@storybook/addon-essentials": "^6.5.13",
84+
"@storybook/addon-interactions": "^6.5.13",
85+
"@storybook/addon-links": "^6.5.13",
86+
"@storybook/builder-webpack5": "^6.5.13",
87+
"@storybook/manager-webpack5": "^6.5.13",
88+
"@storybook/react": "^6.5.13",
89+
"@storybook/testing-library": "^0.0.13",
7990
"@types/browser-lang": "^0.1.0",
8091
"@types/github-slugger": "^1.3.0",
8192
"@types/luxon": "^2.3.2",
@@ -85,6 +96,7 @@
8596
"@types/react-dom": "^18.0.6",
8697
"@types/react-instantsearch-core": "^6.26.2",
8798
"@types/styled-system": "^5.1.15",
99+
"babel-loader": "^8.3.0",
88100
"babel-preset-gatsby": "^2.23.0",
89101
"github-slugger": "^1.3.0",
90102
"gray-matter": "^4.0.3",
@@ -110,7 +122,9 @@
110122
"start:lambda": "netlify-lambda serve src/lambda",
111123
"start:static": "gatsby build && gatsby serve",
112124
"serve": "gatsby serve",
113-
"type-check": "tsc --noEmit"
125+
"type-check": "tsc --noEmit",
126+
"storybook": "start-storybook -p 6006",
127+
"build-storybook": "build-storybook"
114128
},
115129
"husky": {
116130
"hooks": {
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import { ComponentMeta, ComponentStory } from "@storybook/react"
2+
import React from "react"
3+
import BannerNotification from "."
4+
5+
export default {
6+
component: BannerNotification,
7+
} as ComponentMeta<typeof BannerNotification>
8+
9+
/**
10+
* Story taken from PostMergeBanner component
11+
* and content from `../../content/developers/tutorials/hello-world-smart-contract-fullstack/index.md`
12+
*/
13+
export const PostMergeBanner: ComponentStory<typeof BannerNotification> = (
14+
args
15+
) => <BannerNotification {...args} />
16+
17+
PostMergeBanner.args = {
18+
shouldShow: true,
19+
justify: "center",
20+
textAlign: "center",
21+
sx: {
22+
"& p": {
23+
maxWidth: "100ch",
24+
m: 0,
25+
p: 0,
26+
},
27+
"& a": {
28+
textDecor: "underline",
29+
},
30+
},
31+
children: (
32+
<p>
33+
This tutorial is out of date after the merge and may not work. Please
34+
raise a PR if you would like to contribute.
35+
</p>
36+
),
37+
}

src/components/BannerNotification.tsx renamed to src/components/BannerNotification/index.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import React from "react"
22
import { Flex, FlexProps, useMediaQuery } from "@chakra-ui/react"
3-
import { lightTheme as oldTheme } from "../theme"
3+
import { lightTheme as oldTheme } from "../../theme"
44

55
export interface IProps extends FlexProps {
66
shouldShow?: boolean
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import { ComponentMeta, ComponentStory } from "@storybook/react"
2+
import React from "react"
3+
import Button from "."
4+
5+
export default {
6+
component: Button,
7+
} as ComponentMeta<typeof Button>
8+
9+
const Template: ComponentStory<typeof Button> = (args) => <Button {...args} />
10+
11+
export const Solid = Template.bind({})
12+
Solid.args = {
13+
children: "What is Ethereum?",
14+
}
15+
16+
export const Outline = Template.bind({})
17+
Outline.args = {
18+
children: "More on digital money",
19+
variant: "outline",
20+
}
21+
22+
export const OutlineColor = Template.bind({})
23+
OutlineColor.args = {
24+
children: "More on digital money",
25+
variant: "outline-color",
26+
}
27+
28+
export const Disabled = Template.bind({})
29+
Disabled.args = {
30+
children: "I am disabled",
31+
disabled: true,
32+
}

src/components/Button.tsx renamed to src/components/Button/index.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import React from "react"
22
import { Button as ChakraButton, ButtonProps } from "@chakra-ui/react"
33

4-
import { scrollIntoView } from "../utils/scrollIntoView"
4+
import { scrollIntoView } from "../../utils/scrollIntoView"
55

66
export interface IProps extends ButtonProps {
77
toId?: string

src/components/Card/Card.stories.tsx

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import { Box } from "@chakra-ui/react"
2+
import { ComponentMeta, ComponentStory } from "@storybook/react"
3+
import React from "react"
4+
import Card, { IProps } from "."
5+
import Button from "../Button"
6+
7+
const Component = Card
8+
9+
export default {
10+
component: Card,
11+
decorators: [
12+
(Story) => (
13+
<Box maxW="342px" margin="0 auto">
14+
<Story />
15+
</Box>
16+
),
17+
],
18+
} as ComponentMeta<typeof Component>
19+
20+
const defaultProps: IProps = {
21+
emoji: ":woman_student:",
22+
title: "Learn Ethereum development",
23+
description: "Read up on core concepts and the Ethereum stack with our docs",
24+
}
25+
26+
export const Default: ComponentStory<typeof Component> = (args) => (
27+
<Component {...defaultProps} {...args}>
28+
<Button>Read the docs</Button>
29+
</Component>
30+
)

src/components/Card.tsx renamed to src/components/Card/index.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import React, { ReactNode } from "react"
22
import { ChakraProps, Flex, Heading, Text } from "@chakra-ui/react"
3-
import Emoji from "./Emoji"
3+
import Emoji from "../Emoji"
44

55
export interface IProps extends ChakraProps {
66
children?: React.ReactNode

0 commit comments

Comments
 (0)