diff --git a/website/.storybook/main.js b/website/.storybook/main.js
new file mode 100644
index 0000000000..c96c218255
--- /dev/null
+++ b/website/.storybook/main.js
@@ -0,0 +1,12 @@
+module.exports = {
+ "stories": [
+ "../src/**/*.stories.mdx",
+ "../src/**/*.stories.@(js|jsx|ts|tsx)"
+ ],
+ "addons": [
+ "@storybook/addon-links",
+ "@storybook/addon-essentials",
+ "@storybook/addon-interactions"
+ ],
+ "framework": "@storybook/react"
+}
\ No newline at end of file
diff --git a/website/.storybook/preview.js b/website/.storybook/preview.js
new file mode 100644
index 0000000000..798e1cd558
--- /dev/null
+++ b/website/.storybook/preview.js
@@ -0,0 +1,11 @@
+import '../src/css/custom.css';
+
+export const parameters = {
+ actions: { argTypesRegex: '^on[A-Z].*' },
+ controls: {
+ matchers: {
+ color: /(background|color)$/i,
+ date: /Date$/,
+ },
+ },
+};
diff --git a/website/package.json b/website/package.json
index a67a482584..b984119878 100644
--- a/website/package.json
+++ b/website/package.json
@@ -11,7 +11,9 @@
"clear": "docusaurus clear",
"serve": "docusaurus serve",
"write-translations": "docusaurus write-translations",
- "write-heading-ids": "docusaurus write-heading-ids"
+ "write-heading-ids": "docusaurus write-heading-ids",
+ "storybook": "start-storybook -p 6006",
+ "build-storybook": "build-storybook"
},
"dependencies": {
"@docusaurus/core": "2.0.0-beta.15",
@@ -47,5 +49,15 @@
"last 1 firefox version",
"last 1 safari version"
]
+ },
+ "devDependencies": {
+ "@babel/core": "^7.17.4",
+ "@storybook/addon-actions": "^6.4.19",
+ "@storybook/addon-essentials": "^6.4.19",
+ "@storybook/addon-interactions": "^6.4.19",
+ "@storybook/addon-links": "^6.4.19",
+ "@storybook/react": "^6.4.19",
+ "@storybook/testing-library": "^0.0.9",
+ "babel-loader": "^8.2.3"
}
}
diff --git a/website/src/stories/Button.jsx b/website/src/stories/Button.jsx
new file mode 100644
index 0000000000..15dde39209
--- /dev/null
+++ b/website/src/stories/Button.jsx
@@ -0,0 +1,50 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import './button.css';
+
+/**
+ * Primary UI component for user interaction
+ */
+export const Button = ({ primary, backgroundColor, size, label, ...props }) => {
+ const mode = primary ? 'storybook-button--primary' : 'storybook-button--secondary';
+ return (
+
+ {label}
+
+ );
+};
+
+Button.propTypes = {
+ /**
+ * Is this the principal call to action on the page?
+ */
+ primary: PropTypes.bool,
+ /**
+ * What background color to use
+ */
+ backgroundColor: PropTypes.string,
+ /**
+ * How large should the button be?
+ */
+ size: PropTypes.oneOf(['small', 'medium', 'large']),
+ /**
+ * Button contents
+ */
+ label: PropTypes.string.isRequired,
+ /**
+ * Optional click handler
+ */
+ onClick: PropTypes.func,
+};
+
+Button.defaultProps = {
+ backgroundColor: null,
+ primary: false,
+ size: 'medium',
+ onClick: undefined,
+};
diff --git a/website/src/stories/Button.stories.jsx b/website/src/stories/Button.stories.jsx
new file mode 100644
index 0000000000..61f6e19e14
--- /dev/null
+++ b/website/src/stories/Button.stories.jsx
@@ -0,0 +1,40 @@
+import React from 'react';
+
+import { Button } from './Button';
+
+// More on default export: https://storybook.js.org/docs/react/writing-stories/introduction#default-export
+export default {
+ title: 'Example/Button',
+ component: Button,
+ // More on argTypes: https://storybook.js.org/docs/react/api/argtypes
+ argTypes: {
+ backgroundColor: { control: 'color' },
+ },
+};
+
+// More on component templates: https://storybook.js.org/docs/react/writing-stories/introduction#using-args
+const Template = (args) => ;
+
+export const Primary = Template.bind({});
+// More on args: https://storybook.js.org/docs/react/writing-stories/args
+Primary.args = {
+ primary: true,
+ label: 'Button',
+};
+
+export const Secondary = Template.bind({});
+Secondary.args = {
+ label: 'Button',
+};
+
+export const Large = Template.bind({});
+Large.args = {
+ size: 'large',
+ label: 'Button',
+};
+
+export const Small = Template.bind({});
+Small.args = {
+ size: 'small',
+ label: 'Button',
+};
diff --git a/website/src/stories/Header.jsx b/website/src/stories/Header.jsx
new file mode 100644
index 0000000000..59787aed89
--- /dev/null
+++ b/website/src/stories/Header.jsx
@@ -0,0 +1,57 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+
+import { Button } from './Button';
+import './header.css';
+
+export const Header = ({ user, onLogin, onLogout, onCreateAccount }) => (
+
+);
+
+Header.propTypes = {
+ user: PropTypes.shape({}),
+ onLogin: PropTypes.func.isRequired,
+ onLogout: PropTypes.func.isRequired,
+ onCreateAccount: PropTypes.func.isRequired,
+};
+
+Header.defaultProps = {
+ user: null,
+};
diff --git a/website/src/stories/Header.stories.jsx b/website/src/stories/Header.stories.jsx
new file mode 100644
index 0000000000..e4850002ca
--- /dev/null
+++ b/website/src/stories/Header.stories.jsx
@@ -0,0 +1,24 @@
+import React from 'react';
+
+import { Header } from './Header';
+
+export default {
+ title: 'Example/Header',
+ component: Header,
+ parameters: {
+ // More on Story layout: https://storybook.js.org/docs/react/configure/story-layout
+ layout: 'fullscreen',
+ },
+};
+
+const Template = (args) => ;
+
+export const LoggedIn = Template.bind({});
+LoggedIn.args = {
+ user: {
+ name: 'Jane Doe',
+ },
+};
+
+export const LoggedOut = Template.bind({});
+LoggedOut.args = {};
diff --git a/website/src/stories/Introduction.stories.mdx b/website/src/stories/Introduction.stories.mdx
new file mode 100644
index 0000000000..42c4a8714e
--- /dev/null
+++ b/website/src/stories/Introduction.stories.mdx
@@ -0,0 +1,211 @@
+import { Meta } from '@storybook/addon-docs';
+import Code from './assets/code-brackets.svg';
+import Colors from './assets/colors.svg';
+import Comments from './assets/comments.svg';
+import Direction from './assets/direction.svg';
+import Flow from './assets/flow.svg';
+import Plugin from './assets/plugin.svg';
+import Repo from './assets/repo.svg';
+import StackAlt from './assets/stackalt.svg';
+
+
+
+
+
+# Welcome to Storybook
+
+Storybook helps you build UI components in isolation from your app's business logic, data, and context.
+That makes it easy to develop hard-to-reach states. Save these UI states as **stories** to revisit during development, testing, or QA.
+
+Browse example stories now by navigating to them in the sidebar.
+View their code in the `src/stories` directory to learn how they work.
+We recommend building UIs with a [**component-driven**](https://componentdriven.org) process starting with atomic components and ending with pages.
+
+
Configure
+
+
+
+Learn
+
+
+
+
+ Tip Edit the Markdown in{' '}
+ src/stories/Introduction.stories.mdx
+
diff --git a/website/src/stories/Page.jsx b/website/src/stories/Page.jsx
new file mode 100644
index 0000000000..c5fffe953b
--- /dev/null
+++ b/website/src/stories/Page.jsx
@@ -0,0 +1,69 @@
+import React from 'react';
+
+import { Header } from './Header';
+import './page.css';
+
+export const Page = () => {
+ const [user, setUser] = React.useState();
+
+ return (
+
+ setUser({ name: 'Jane Doe' })}
+ onLogout={() => setUser(undefined)}
+ onCreateAccount={() => setUser({ name: 'Jane Doe' })}
+ />
+
+
+ Pages in Storybook
+
+ We recommend building UIs with a{' '}
+
+ component-driven
+ {' '}
+ process starting with atomic components and ending with pages.
+
+
+ Render pages with mock data. This makes it easy to build and review page states without
+ needing to navigate to them in your app. Here are some handy patterns for managing page
+ data in Storybook:
+
+
+
+ Use a higher-level connected component. Storybook helps you compose such data from the
+ "args" of child component stories
+
+
+ Assemble data in the page component from your services. You can mock these services out
+ using Storybook.
+
+
+
+ Get a guided tutorial on component-driven development at{' '}
+
+ Storybook tutorials
+
+ . Read more in the{' '}
+
+ docs
+
+ .
+
+
+
Tip Adjust the width of the canvas with the{' '}
+
+
+
+
+
+ Viewports addon in the toolbar
+
+
+
+ );
+};
diff --git a/website/src/stories/Page.stories.jsx b/website/src/stories/Page.stories.jsx
new file mode 100644
index 0000000000..0174fdb881
--- /dev/null
+++ b/website/src/stories/Page.stories.jsx
@@ -0,0 +1,25 @@
+import React from 'react';
+import { within, userEvent } from '@storybook/testing-library';
+
+import { Page } from './Page';
+
+export default {
+ title: 'Example/Page',
+ component: Page,
+ parameters: {
+ // More on Story layout: https://storybook.js.org/docs/react/configure/story-layout
+ layout: 'fullscreen',
+ },
+};
+
+const Template = (args) => ;
+
+// More on interaction testing: https://storybook.js.org/docs/react/writing-tests/interaction-testing
+export const LoggedOut = Template.bind({});
+
+export const LoggedIn = Template.bind({});
+LoggedIn.play = async ({ canvasElement }) => {
+ const canvas = within(canvasElement);
+ const loginButton = await canvas.getByRole('button', { name: /Log in/i });
+ await userEvent.click(loginButton);
+};
diff --git a/website/src/stories/assets/code-brackets.svg b/website/src/stories/assets/code-brackets.svg
new file mode 100644
index 0000000000..73de947760
--- /dev/null
+++ b/website/src/stories/assets/code-brackets.svg
@@ -0,0 +1 @@
+illustration/code-brackets
\ No newline at end of file
diff --git a/website/src/stories/assets/colors.svg b/website/src/stories/assets/colors.svg
new file mode 100644
index 0000000000..17d58d516e
--- /dev/null
+++ b/website/src/stories/assets/colors.svg
@@ -0,0 +1 @@
+illustration/colors
\ No newline at end of file
diff --git a/website/src/stories/assets/comments.svg b/website/src/stories/assets/comments.svg
new file mode 100644
index 0000000000..6493a139f5
--- /dev/null
+++ b/website/src/stories/assets/comments.svg
@@ -0,0 +1 @@
+illustration/comments
\ No newline at end of file
diff --git a/website/src/stories/assets/direction.svg b/website/src/stories/assets/direction.svg
new file mode 100644
index 0000000000..65676ac272
--- /dev/null
+++ b/website/src/stories/assets/direction.svg
@@ -0,0 +1 @@
+illustration/direction
\ No newline at end of file
diff --git a/website/src/stories/assets/flow.svg b/website/src/stories/assets/flow.svg
new file mode 100644
index 0000000000..8ac27db403
--- /dev/null
+++ b/website/src/stories/assets/flow.svg
@@ -0,0 +1 @@
+illustration/flow
\ No newline at end of file
diff --git a/website/src/stories/assets/plugin.svg b/website/src/stories/assets/plugin.svg
new file mode 100644
index 0000000000..29e5c690c0
--- /dev/null
+++ b/website/src/stories/assets/plugin.svg
@@ -0,0 +1 @@
+illustration/plugin
\ No newline at end of file
diff --git a/website/src/stories/assets/repo.svg b/website/src/stories/assets/repo.svg
new file mode 100644
index 0000000000..f386ee902c
--- /dev/null
+++ b/website/src/stories/assets/repo.svg
@@ -0,0 +1 @@
+illustration/repo
\ No newline at end of file
diff --git a/website/src/stories/assets/stackalt.svg b/website/src/stories/assets/stackalt.svg
new file mode 100644
index 0000000000..9b7ad27435
--- /dev/null
+++ b/website/src/stories/assets/stackalt.svg
@@ -0,0 +1 @@
+illustration/stackalt
\ No newline at end of file
diff --git a/website/src/stories/button.css b/website/src/stories/button.css
new file mode 100644
index 0000000000..dc91dc7637
--- /dev/null
+++ b/website/src/stories/button.css
@@ -0,0 +1,30 @@
+.storybook-button {
+ font-family: 'Nunito Sans', 'Helvetica Neue', Helvetica, Arial, sans-serif;
+ font-weight: 700;
+ border: 0;
+ border-radius: 3em;
+ cursor: pointer;
+ display: inline-block;
+ line-height: 1;
+}
+.storybook-button--primary {
+ color: white;
+ background-color: #1ea7fd;
+}
+.storybook-button--secondary {
+ color: #333;
+ background-color: transparent;
+ box-shadow: rgba(0, 0, 0, 0.15) 0px 0px 0px 1px inset;
+}
+.storybook-button--small {
+ font-size: 12px;
+ padding: 10px 16px;
+}
+.storybook-button--medium {
+ font-size: 14px;
+ padding: 11px 20px;
+}
+.storybook-button--large {
+ font-size: 16px;
+ padding: 12px 24px;
+}
diff --git a/website/src/stories/header.css b/website/src/stories/header.css
new file mode 100644
index 0000000000..830610e6f2
--- /dev/null
+++ b/website/src/stories/header.css
@@ -0,0 +1,32 @@
+.wrapper {
+ font-family: 'Nunito Sans', 'Helvetica Neue', Helvetica, Arial, sans-serif;
+ border-bottom: 1px solid rgba(0, 0, 0, 0.1);
+ padding: 15px 20px;
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+}
+
+svg {
+ display: inline-block;
+ vertical-align: top;
+}
+
+h1 {
+ font-weight: 900;
+ font-size: 20px;
+ line-height: 1;
+ margin: 6px 0 6px 10px;
+ display: inline-block;
+ vertical-align: top;
+}
+
+button + button {
+ margin-left: 10px;
+}
+
+.welcome {
+ color: #333;
+ font-size: 14px;
+ margin-right: 10px;
+}
diff --git a/website/src/stories/page.css b/website/src/stories/page.css
new file mode 100644
index 0000000000..fbc32aea2e
--- /dev/null
+++ b/website/src/stories/page.css
@@ -0,0 +1,69 @@
+section {
+ font-family: 'Nunito Sans', 'Helvetica Neue', Helvetica, Arial, sans-serif;
+ font-size: 14px;
+ line-height: 24px;
+ padding: 48px 20px;
+ margin: 0 auto;
+ max-width: 600px;
+ color: #333;
+}
+
+section h2 {
+ font-weight: 900;
+ font-size: 32px;
+ line-height: 1;
+ margin: 0 0 4px;
+ display: inline-block;
+ vertical-align: top;
+}
+
+section p {
+ margin: 1em 0;
+}
+
+section a {
+ text-decoration: none;
+ color: #1ea7fd;
+}
+
+section ul {
+ padding-left: 30px;
+ margin: 1em 0;
+}
+
+section li {
+ margin-bottom: 8px;
+}
+
+section .tip {
+ display: inline-block;
+ border-radius: 1em;
+ font-size: 11px;
+ line-height: 12px;
+ font-weight: 700;
+ background: #e7fdd8;
+ color: #66bf3c;
+ padding: 4px 12px;
+ margin-right: 10px;
+ vertical-align: top;
+}
+
+section .tip-wrapper {
+ font-size: 13px;
+ line-height: 20px;
+ margin-top: 40px;
+ margin-bottom: 40px;
+}
+
+section .tip-wrapper svg {
+ display: inline-block;
+ height: 12px;
+ width: 12px;
+ margin-right: 4px;
+ vertical-align: top;
+ margin-top: 3px;
+}
+
+section .tip-wrapper svg path {
+ fill: #1ea7fd;
+}