Skip to content

Storybook for component development #1244

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

Merged
merged 29 commits into from
May 26, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
4ee7548
Add styled-components to project
andrewn Jan 19, 2020
30e280a
Add storybook
andrewn Jan 19, 2020
b2a509b
Add action, knobs and docs add-ons
andrewn Jan 19, 2020
71d011d
Storp eslint complaining about deps for stories files
andrewn Jan 19, 2020
d34ddb3
Example Button component
andrewn Jan 19, 2020
000f6d0
Adds theme
andrewn Jan 19, 2020
499c17e
Merge branch 'master' into feature/storybook
andrewn Apr 19, 2020
bda1ff1
Convert LogIn to use shared Button
andrewn Apr 19, 2020
af03620
Show first part of Storybook title as root in the UI
andrewn Apr 19, 2020
1e5e47a
Babel plugin must be loaded first
andrewn Apr 19, 2020
c2734ab
Send current theme variables directly into provider
andrewn Apr 19, 2020
b61bd69
Add Icon component for icons
andrewn Apr 19, 2020
96ecb3e
Replace User form buttons with shared Button component
andrewn Apr 19, 2020
e134051
Extract ThemeProvider and connect to redux store
andrewn Apr 19, 2020
428fee2
Pad internal button elements to correctly space out icons, etc.
andrewn Apr 26, 2020
0d8aeed
Convert user account pages to use Button
andrewn Apr 26, 2020
6465a30
Convert New File/Folder to use Button
andrewn Apr 26, 2020
5686504
Collection primary buttons
andrewn Apr 26, 2020
7cf3a0b
<Button /> supports icons directly to style hover states
andrewn Apr 26, 2020
a2145ad
Inline button style (with/without icons)
andrewn Apr 27, 2020
ec1c821
SVG Icon grows to container size
andrewn Apr 27, 2020
e22f8b8
Restructure theme file
andrewn May 3, 2020
70fe492
Merge branch 'master' into feature/storybook-components
andrewn May 3, 2020
148ab78
Resolve merge conflicts with master
catarak May 7, 2020
6d74961
Start integrating accessibility changes to storybook feature
catarak May 9, 2020
f359dce
Separate Icons from Button component
catarak May 11, 2020
9d68de8
Update Button component with iconBefore and iconAfter, clean up Icons…
catarak May 19, 2020
161ac5b
Remove duplicate aria-labels
andrewn May 26, 2020
7dabbc5
Change commmon/Icons to common/icons
catarak May 26, 2020
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .babelrc
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
"env": {
"production": {
"plugins": [
"babel-plugin-styled-components",
"transform-react-remove-prop-types",
"@babel/plugin-transform-react-constant-elements",
"@babel/plugin-transform-react-inline-elements",
Expand Down Expand Up @@ -48,6 +49,7 @@
},
"development": {
"plugins": [
"babel-plugin-styled-components",
"react-hot-loader/babel"
]
}
Expand Down
10 changes: 9 additions & 1 deletion .eslintrc
Original file line number Diff line number Diff line change
Expand Up @@ -77,5 +77,13 @@
"__SERVER__": true,
"__DISABLE_SSR__": true,
"__DEVTOOLS__": true
}
},
"overrides": [
{
"files": ["*.stories.jsx"],
"rules": {
"import/no-extraneous-dependencies": "off"
}
}
]
}
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,5 @@ cert_chain.crt
localhost.crt
localhost.key
privkey.pem

storybook-static
29 changes: 29 additions & 0 deletions .storybook/main.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
const path = require('path');

module.exports = {
stories: ['../client/**/*.stories.(jsx|mdx)'],
addons: [
'@storybook/addon-actions',
'@storybook/addon-docs',
'@storybook/addon-knobs',
'@storybook/addon-links',
'storybook-addon-theme-playground/dist/register'
],
webpackFinal: async config => {
// do mutation to the config

const rules = config.module.rules;

// modify storybook's file-loader rule to avoid conflicts with svgr
const fileLoaderRule = rules.find(rule => rule.test.test('.svg'));
fileLoaderRule.exclude = path.resolve(__dirname, '../client');

// use svgr for svg files
rules.push({
test: /\.svg$/,
use: ["@svgr/webpack"],
})

return config;
},
};
31 changes: 31 additions & 0 deletions .storybook/preview.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import React from 'react';
import { addDecorator, addParameters } from '@storybook/react';
import { withKnobs } from "@storybook/addon-knobs";
import { withThemePlayground } from 'storybook-addon-theme-playground';
import { ThemeProvider } from "styled-components";

import theme, { Theme } from '../client/theme';

addDecorator(withKnobs);

const themeConfigs = Object.values(Theme).map(
name => {
return { name, theme: theme[name] };
}
);

addDecorator(withThemePlayground({
theme: themeConfigs,
provider: ThemeProvider
}));

addParameters({
options: {
/**
* display the top-level grouping as a "root" in the sidebar
*/
showRoots: true,
},
})

// addDecorator(storyFn => <ThemeProvider theme={theme}>{storyFn()}</ThemeProvider>);
240 changes: 240 additions & 0 deletions client/common/Button.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,240 @@
import React from 'react';
import PropTypes from 'prop-types';
import styled from 'styled-components';
import { Link } from 'react-router';

import { remSize, prop } from '../theme';

const kinds = {
block: 'block',
icon: 'icon',
inline: 'inline',
};

// The '&&&' will increase the specificity of the
// component's CSS so that it overrides the more
// general global styles
const StyledButton = styled.button`
&&& {
display: flex;
justify-content: center;
align-items: center;

width: max-content;
text-decoration: none;

color: ${prop('Button.default.foreground')};
background-color: ${prop('Button.default.background')};
cursor: pointer;
border: 2px solid ${prop('Button.default.border')};
border-radius: 2px;
padding: ${remSize(8)} ${remSize(25)};
line-height: 1;

svg * {
fill: ${prop('Button.default.foreground')};
}

&:hover:not(:disabled) {
color: ${prop('Button.hover.foreground')};
background-color: ${prop('Button.hover.background')};
border-color: ${prop('Button.hover.border')};

svg * {
fill: ${prop('Button.hover.foreground')};
}
}

&:active:not(:disabled) {
color: ${prop('Button.active.foreground')};
background-color: ${prop('Button.active.background')};

svg * {
fill: ${prop('Button.active.foreground')};
}
}

&:disabled {
color: ${prop('Button.disabled.foreground')};
background-color: ${prop('Button.disabled.background')};
border-color: ${prop('Button.disabled.border')};
cursor: not-allowed;

svg * {
fill: ${prop('Button.disabled.foreground')};
}
}

> * + * {
margin-left: ${remSize(8)};
}
}
`;

const StyledInlineButton = styled.button`
&&& {
display: flex;
justify-content: center;
align-items: center;

text-decoration: none;

color: ${prop('primaryTextColor')};
cursor: pointer;
border: none;
line-height: 1;

svg * {
fill: ${prop('primaryTextColor')};
}

&:disabled {
cursor: not-allowed;
}

> * + * {
margin-left: ${remSize(8)};
}
}
`;

const StyledIconButton = styled.button`
&&& {
display: flex;
justify-content: center;
align-items: center;

width: ${remSize(32)}px;
height: ${remSize(32)}px;
text-decoration: none;

color: ${prop('Button.default.foreground')};
background-color: ${prop('Button.hover.background')};
cursor: pointer;
border: 1px solid transparent;
border-radius: 50%;
padding: ${remSize(8)} ${remSize(25)};
line-height: 1;

&:hover:not(:disabled) {
color: ${prop('Button.hover.foreground')};
background-color: ${prop('Button.hover.background')};

svg * {
fill: ${prop('Button.hover.foreground')};
}
}

&:active:not(:disabled) {
color: ${prop('Button.active.foreground')};
background-color: ${prop('Button.active.background')};

svg * {
fill: ${prop('Button.active.foreground')};
}
}

&:disabled {
color: ${prop('Button.disabled.foreground')};
background-color: ${prop('Button.disabled.background')};
cursor: not-allowed;
}

> * + * {
margin-left: ${remSize(8)};
}
}
`;

/**
* A Button performs an primary action
*/
const Button = ({
children, href, kind, iconBefore, iconAfter, 'aria-label': ariaLabel, to, type, ...props
}) => {
const hasChildren = React.Children.count(children) > 0;
const content = <>{iconBefore}{hasChildren && <span>{children}</span>}{iconAfter}</>;
let StyledComponent = StyledButton;

if (kind === kinds.inline) {
StyledComponent = StyledInlineButton;
} else if (kind === kinds.icon) {
StyledComponent = StyledIconButton;
}

if (href) {
return (
<StyledComponent
kind={kind}
as="a"
aria-label={ariaLabel}
href={href}
{...props}
>
{content}
</StyledComponent>
);
}

if (to) {
return <StyledComponent kind={kind} as={Link} aria-label={ariaLabel} to={to} {...props}>{content}</StyledComponent>;
}

return <StyledComponent kind={kind} aria-label={ariaLabel} type={type} {...props}>{content}</StyledComponent>;
};

Button.defaultProps = {
'children': null,
'disabled': false,
'iconAfter': null,
'iconBefore': null,
'kind': kinds.block,
'href': null,
'aria-label': null,
'to': null,
'type': 'button',
};

Button.kinds = kinds;

Button.propTypes = {
/**
* The visible part of the button, telling the user what
* the action is
*/
'children': PropTypes.element,
/**
If the button can be activated or not
*/
'disabled': PropTypes.bool,
/**
* SVG icon to place after child content
*/
'iconAfter': PropTypes.element,
/**
* SVG icon to place before child content
*/
'iconBefore': PropTypes.element,
/**
* The kind of button - determines how it appears visually
*/
'kind': PropTypes.oneOf(Object.values(kinds)),
/**
* Specifying an href will use an <a> to link to the URL
*/
'href': PropTypes.string,
/*
* An ARIA Label used for accessibility
*/
'aria-label': PropTypes.string,
/**
* Specifying a to URL will use a react-router Link
*/
'to': PropTypes.string,
/**
* If using a button, then type is defines the type of button
*/
'type': PropTypes.oneOf(['button', 'submit']),
};

export default Button;
Loading