Skip to content

Commit 435f404

Browse files
committed
Merge branch 'release-1.0.2' into release
2 parents c9f5c19 + b2877f4 commit 435f404

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

56 files changed

+12346
-3530
lines changed

.babelrc

+2
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
"env": {
77
"production": {
88
"plugins": [
9+
"babel-plugin-styled-components",
910
"transform-react-remove-prop-types",
1011
"@babel/plugin-transform-react-constant-elements",
1112
"@babel/plugin-transform-react-inline-elements",
@@ -48,6 +49,7 @@
4849
},
4950
"development": {
5051
"plugins": [
52+
"babel-plugin-styled-components",
5153
"react-hot-loader/babel"
5254
]
5355
}

.env.example

+1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ API_URL=/editor
22
AWS_ACCESS_KEY=<your-aws-access-key>
33
AWS_REGION=<your-aws-region>
44
AWS_SECRET_KEY=<your-aws-secret-key>
5+
CORS_ALLOW_LOCALHOST=true
56
EMAIL_SENDER=<transactional-email-sender>
67
EMAIL_VERIFY_SECRET_TOKEN=whatever_you_want_this_to_be_it_only_matters_for_production
78
EXAMPLE_USER_EMAIL=[email protected]

.eslintrc

+9-1
Original file line numberDiff line numberDiff line change
@@ -77,5 +77,13 @@
7777
"__SERVER__": true,
7878
"__DISABLE_SSR__": true,
7979
"__DEVTOOLS__": true
80-
}
80+
},
81+
"overrides": [
82+
{
83+
"files": ["*.stories.jsx"],
84+
"rules": {
85+
"import/no-extraneous-dependencies": "off"
86+
}
87+
}
88+
]
8189
}

.gitignore

+2
Original file line numberDiff line numberDiff line change
@@ -17,3 +17,5 @@ cert_chain.crt
1717
localhost.crt
1818
localhost.key
1919
privkey.pem
20+
21+
storybook-static

.storybook/main.js

+29
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
const path = require('path');
2+
3+
module.exports = {
4+
stories: ['../client/**/*.stories.(jsx|mdx)'],
5+
addons: [
6+
'@storybook/addon-actions',
7+
'@storybook/addon-docs',
8+
'@storybook/addon-knobs',
9+
'@storybook/addon-links',
10+
'storybook-addon-theme-playground/dist/register'
11+
],
12+
webpackFinal: async config => {
13+
// do mutation to the config
14+
15+
const rules = config.module.rules;
16+
17+
// modify storybook's file-loader rule to avoid conflicts with svgr
18+
const fileLoaderRule = rules.find(rule => rule.test.test('.svg'));
19+
fileLoaderRule.exclude = path.resolve(__dirname, '../client');
20+
21+
// use svgr for svg files
22+
rules.push({
23+
test: /\.svg$/,
24+
use: ["@svgr/webpack"],
25+
})
26+
27+
return config;
28+
},
29+
};

.storybook/preview.js

+31
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import React from 'react';
2+
import { addDecorator, addParameters } from '@storybook/react';
3+
import { withKnobs } from "@storybook/addon-knobs";
4+
import { withThemePlayground } from 'storybook-addon-theme-playground';
5+
import { ThemeProvider } from "styled-components";
6+
7+
import theme, { Theme } from '../client/theme';
8+
9+
addDecorator(withKnobs);
10+
11+
const themeConfigs = Object.values(Theme).map(
12+
name => {
13+
return { name, theme: theme[name] };
14+
}
15+
);
16+
17+
addDecorator(withThemePlayground({
18+
theme: themeConfigs,
19+
provider: ThemeProvider
20+
}));
21+
22+
addParameters({
23+
options: {
24+
/**
25+
* display the top-level grouping as a "root" in the sidebar
26+
*/
27+
showRoots: true,
28+
},
29+
})
30+
31+
// addDecorator(storyFn => <ThemeProvider theme={theme}>{storyFn()}</ThemeProvider>);

client/common/Button.jsx

+240
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,240 @@
1+
import React from 'react';
2+
import PropTypes from 'prop-types';
3+
import styled from 'styled-components';
4+
import { Link } from 'react-router';
5+
6+
import { remSize, prop } from '../theme';
7+
8+
const kinds = {
9+
block: 'block',
10+
icon: 'icon',
11+
inline: 'inline',
12+
};
13+
14+
// The '&&&' will increase the specificity of the
15+
// component's CSS so that it overrides the more
16+
// general global styles
17+
const StyledButton = styled.button`
18+
&&& {
19+
display: flex;
20+
justify-content: center;
21+
align-items: center;
22+
23+
width: max-content;
24+
text-decoration: none;
25+
26+
color: ${prop('Button.default.foreground')};
27+
background-color: ${prop('Button.default.background')};
28+
cursor: pointer;
29+
border: 2px solid ${prop('Button.default.border')};
30+
border-radius: 2px;
31+
padding: ${remSize(8)} ${remSize(25)};
32+
line-height: 1;
33+
34+
svg * {
35+
fill: ${prop('Button.default.foreground')};
36+
}
37+
38+
&:hover:not(:disabled) {
39+
color: ${prop('Button.hover.foreground')};
40+
background-color: ${prop('Button.hover.background')};
41+
border-color: ${prop('Button.hover.border')};
42+
43+
svg * {
44+
fill: ${prop('Button.hover.foreground')};
45+
}
46+
}
47+
48+
&:active:not(:disabled) {
49+
color: ${prop('Button.active.foreground')};
50+
background-color: ${prop('Button.active.background')};
51+
52+
svg * {
53+
fill: ${prop('Button.active.foreground')};
54+
}
55+
}
56+
57+
&:disabled {
58+
color: ${prop('Button.disabled.foreground')};
59+
background-color: ${prop('Button.disabled.background')};
60+
border-color: ${prop('Button.disabled.border')};
61+
cursor: not-allowed;
62+
63+
svg * {
64+
fill: ${prop('Button.disabled.foreground')};
65+
}
66+
}
67+
68+
> * + * {
69+
margin-left: ${remSize(8)};
70+
}
71+
}
72+
`;
73+
74+
const StyledInlineButton = styled.button`
75+
&&& {
76+
display: flex;
77+
justify-content: center;
78+
align-items: center;
79+
80+
text-decoration: none;
81+
82+
color: ${prop('primaryTextColor')};
83+
cursor: pointer;
84+
border: none;
85+
line-height: 1;
86+
87+
svg * {
88+
fill: ${prop('primaryTextColor')};
89+
}
90+
91+
&:disabled {
92+
cursor: not-allowed;
93+
}
94+
95+
> * + * {
96+
margin-left: ${remSize(8)};
97+
}
98+
}
99+
`;
100+
101+
const StyledIconButton = styled.button`
102+
&&& {
103+
display: flex;
104+
justify-content: center;
105+
align-items: center;
106+
107+
width: ${remSize(32)}px;
108+
height: ${remSize(32)}px;
109+
text-decoration: none;
110+
111+
color: ${prop('Button.default.foreground')};
112+
background-color: ${prop('Button.hover.background')};
113+
cursor: pointer;
114+
border: 1px solid transparent;
115+
border-radius: 50%;
116+
padding: ${remSize(8)} ${remSize(25)};
117+
line-height: 1;
118+
119+
&:hover:not(:disabled) {
120+
color: ${prop('Button.hover.foreground')};
121+
background-color: ${prop('Button.hover.background')};
122+
123+
svg * {
124+
fill: ${prop('Button.hover.foreground')};
125+
}
126+
}
127+
128+
&:active:not(:disabled) {
129+
color: ${prop('Button.active.foreground')};
130+
background-color: ${prop('Button.active.background')};
131+
132+
svg * {
133+
fill: ${prop('Button.active.foreground')};
134+
}
135+
}
136+
137+
&:disabled {
138+
color: ${prop('Button.disabled.foreground')};
139+
background-color: ${prop('Button.disabled.background')};
140+
cursor: not-allowed;
141+
}
142+
143+
> * + * {
144+
margin-left: ${remSize(8)};
145+
}
146+
}
147+
`;
148+
149+
/**
150+
* A Button performs an primary action
151+
*/
152+
const Button = ({
153+
children, href, kind, iconBefore, iconAfter, 'aria-label': ariaLabel, to, type, ...props
154+
}) => {
155+
const hasChildren = React.Children.count(children) > 0;
156+
const content = <>{iconBefore}{hasChildren && <span>{children}</span>}{iconAfter}</>;
157+
let StyledComponent = StyledButton;
158+
159+
if (kind === kinds.inline) {
160+
StyledComponent = StyledInlineButton;
161+
} else if (kind === kinds.icon) {
162+
StyledComponent = StyledIconButton;
163+
}
164+
165+
if (href) {
166+
return (
167+
<StyledComponent
168+
kind={kind}
169+
as="a"
170+
aria-label={ariaLabel}
171+
href={href}
172+
{...props}
173+
>
174+
{content}
175+
</StyledComponent>
176+
);
177+
}
178+
179+
if (to) {
180+
return <StyledComponent kind={kind} as={Link} aria-label={ariaLabel} to={to} {...props}>{content}</StyledComponent>;
181+
}
182+
183+
return <StyledComponent kind={kind} aria-label={ariaLabel} type={type} {...props}>{content}</StyledComponent>;
184+
};
185+
186+
Button.defaultProps = {
187+
'children': null,
188+
'disabled': false,
189+
'iconAfter': null,
190+
'iconBefore': null,
191+
'kind': kinds.block,
192+
'href': null,
193+
'aria-label': null,
194+
'to': null,
195+
'type': 'button',
196+
};
197+
198+
Button.kinds = kinds;
199+
200+
Button.propTypes = {
201+
/**
202+
* The visible part of the button, telling the user what
203+
* the action is
204+
*/
205+
'children': PropTypes.element,
206+
/**
207+
If the button can be activated or not
208+
*/
209+
'disabled': PropTypes.bool,
210+
/**
211+
* SVG icon to place after child content
212+
*/
213+
'iconAfter': PropTypes.element,
214+
/**
215+
* SVG icon to place before child content
216+
*/
217+
'iconBefore': PropTypes.element,
218+
/**
219+
* The kind of button - determines how it appears visually
220+
*/
221+
'kind': PropTypes.oneOf(Object.values(kinds)),
222+
/**
223+
* Specifying an href will use an <a> to link to the URL
224+
*/
225+
'href': PropTypes.string,
226+
/*
227+
* An ARIA Label used for accessibility
228+
*/
229+
'aria-label': PropTypes.string,
230+
/**
231+
* Specifying a to URL will use a react-router Link
232+
*/
233+
'to': PropTypes.string,
234+
/**
235+
* If using a button, then type is defines the type of button
236+
*/
237+
'type': PropTypes.oneOf(['button', 'submit']),
238+
};
239+
240+
export default Button;

0 commit comments

Comments
 (0)