Skip to content

Commit f359dce

Browse files
committed
Separate Icons from Button component
1 parent 6d74961 commit f359dce

12 files changed

+135
-137
lines changed

Diff for: .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

Diff for: .storybook/main.js

+14
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
const path = require('path');
2+
13
module.exports = {
24
stories: ['../client/**/*.stories.(jsx|mdx)'],
35
addons: [
@@ -10,6 +12,18 @@ module.exports = {
1012
webpackFinal: async config => {
1113
// do mutation to the config
1214

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+
1327
return config;
1428
},
1529
};

Diff for: client/common/Button.jsx

+18-36
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import styled from 'styled-components';
44
import { Link } from 'react-router';
55

66
import { remSize, prop } from '../theme';
7-
import Icon, { ValidIconNameType } from './Icon';
87

98
const kinds = {
109
block: 'block',
@@ -151,14 +150,8 @@ const StyledIconButton = styled.button`
151150
* A Button performs an primary action
152151
*/
153152
const Button = ({
154-
children, href, iconAfterName, iconBeforeName, kind, label, to, type, ...props
153+
children, href, kind, 'aria-label': ariaLabel, to, type, ...props
155154
}) => {
156-
const IconAfter = Icon[iconAfterName];
157-
const IconBefore = Icon[iconBeforeName];
158-
const hasChildren = React.Children.count(children) > 0;
159-
160-
const content = <>{IconBefore}{hasChildren && <span>{children}</span>}{IconAfter}</>;
161-
162155
let StyledComponent = StyledButton;
163156

164157
if (kind === kinds.inline) {
@@ -168,69 +161,58 @@ const Button = ({
168161
}
169162

170163
if (href) {
171-
return <StyledComponent kind={kind} as="a" aria-label={label} href={href} {...props}>{content}</StyledComponent>;
164+
return <StyledComponent kind={kind} as="a" aria-label={ariaLabel} href={href} {...props}>{children}</StyledComponent>;
172165
}
173166

174167
if (to) {
175-
return <StyledComponent kind={kind} as={Link} aria-label={label} to={to} {...props}>{content}</StyledComponent>;
168+
return <StyledComponent kind={kind} as={Link} aria-label={ariaLabel} to={to} {...props}>{children}</StyledComponent>;
176169
}
177170

178-
return <StyledComponent kind={kind} aria-label={label} type={type} {...props}>{content}</StyledComponent>;
171+
return <StyledComponent kind={kind} aria-label={ariaLabel} type={type} {...props}>{children}</StyledComponent>;
179172
};
180173

181174
Button.defaultProps = {
182-
children: null,
183-
disabled: false,
184-
iconAfterName: null,
185-
iconBeforeName: null,
186-
kind: kinds.block,
187-
href: null,
188-
label: null,
189-
to: null,
190-
type: 'button',
175+
'children': null,
176+
'disabled': false,
177+
'kind': kinds.block,
178+
'href': null,
179+
'aria-label': null,
180+
'to': null,
181+
'type': 'button',
191182
};
192183

193-
Button.iconNames = Object.keys(Icon);
194184
Button.kinds = kinds;
195185

196186
Button.propTypes = {
197187
/**
198188
* The visible part of the button, telling the user what
199189
* the action is
200190
*/
201-
children: PropTypes.element,
191+
'children': PropTypes.element,
202192
/**
203193
If the button can be activated or not
204194
*/
205-
disabled: PropTypes.bool,
206-
/**
207-
* Name of icon to place before child content
208-
*/
209-
iconAfterName: ValidIconNameType,
210-
/**
211-
* Name of icon to place after child content
212-
*/
213-
iconBeforeName: ValidIconNameType,
195+
'disabled': PropTypes.bool,
214196
/**
215197
* The kind of button - determines how it appears visually
216198
*/
217-
kind: PropTypes.oneOf(Object.values(kinds)),
199+
'kind': PropTypes.oneOf(Object.values(kinds)),
218200
/**
219201
* Specifying an href will use an <a> to link to the URL
220202
*/
221-
href: PropTypes.string,
203+
'href': PropTypes.string,
222204
/*
223205
* An ARIA Label used for accessibility
224206
*/
225-
label: PropTypes.string,
207+
'aria-label': PropTypes.string,
226208
/**
227209
* Specifying a to URL will use a react-router Link
228210
*/
229-
to: PropTypes.string,
211+
'to': PropTypes.string,
230212
/**
231213
* If using a button, then type is defines the type of button
232214
*/
233-
type: PropTypes.oneOf(['button', 'submit']),
215+
'type': PropTypes.oneOf(['button', 'submit']),
234216
};
235217

236218
export default Button;

Diff for: client/common/Button.stories.jsx

+18-4
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@ import { action } from '@storybook/addon-actions';
33
import { boolean, text } from '@storybook/addon-knobs';
44

55
import Button from './Button';
6+
import Icons from './Icons';
7+
8+
const { Github, DropdownArrow, Plus } = Icons;
69

710
export default {
811
title: 'Common/Button',
@@ -36,17 +39,28 @@ export const ReactRouterLink = () => (
3639
);
3740

3841
export const ButtonWithIconBefore = () => (
39-
<Button iconBeforeName={Button.iconNames.Github}>Create</Button>
42+
<Button>
43+
<Github aria-label="Github logo" />
44+
<span>Create</span>
45+
</Button>
4046
);
4147

4248
export const ButtonWithIconAfter = () => (
43-
<Button iconAfterName={Button.iconNames.Github}>Create</Button>
49+
<Button>
50+
<span>Create</span>
51+
<Github aria-label="Github logo" />
52+
</Button>
4453
);
4554

4655
export const InlineButtonWithIconAfter = () => (
47-
<Button kind={Button.kinds.inline} iconAfterName={Button.iconNames.SortArrowDown}>File name</Button>
56+
<Button kind={Button.kinds.inline}>
57+
<span>File name</span>
58+
<DropdownArrow />
59+
</Button>
4860
);
4961

5062
export const InlineIconOnlyButton = () => (
51-
<Button kind={Button.kinds.inline} iconAfterName={Button.iconNames.Plus} label="Add to collection" />
63+
<Button kind={Button.kinds.inline} aria-label="Add to collection">
64+
<Plus />
65+
</Button>
5266
);

Diff for: client/common/Icon.jsx

-25
This file was deleted.

Diff for: client/common/Icons.jsx

+55
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import React from 'react';
2+
import PropTypes from 'prop-types';
3+
import SortArrowUp from '../images/sort-arrow-up.svg';
4+
import SortArrowDown from '../images/sort-arrow-down.svg';
5+
import Github from '../images/github.svg';
6+
import Google from '../images/google.svg';
7+
import Plus from '../images/plus-icon.svg';
8+
import Close from '../images/close.svg';
9+
import DropdownArrow from '../images/down-filled-triangle.svg';
10+
11+
// HOC that adds the right web accessibility props
12+
// https://www.scottohara.me/blog/2019/05/22/contextual-images-svgs-and-a11y.html
13+
14+
// could also give these a default size, color, etc. based on the theme
15+
// Need to add size to these - like small icon, medium icon, large icon. etc.
16+
function withLabel(Icon) {
17+
const render = (props) => {
18+
const { 'aria-label': ariaLabel } = props;
19+
if (ariaLabel) {
20+
return (<Icon
21+
{...props}
22+
aria-label={ariaLabel}
23+
role="img"
24+
focusable="false"
25+
/>);
26+
}
27+
return (<Icon
28+
{...props}
29+
aria-hidden
30+
focusable="false"
31+
/>);
32+
};
33+
34+
render.propTypes = {
35+
'aria-label': PropTypes.string
36+
};
37+
38+
render.defaultProps = {
39+
'aria-label': null
40+
};
41+
42+
return render;
43+
}
44+
45+
const Icons = {
46+
SortArrowUp: withLabel(SortArrowUp),
47+
SortArrowDown: withLabel(SortArrowDown),
48+
Github: withLabel(Github),
49+
Google: withLabel(Google),
50+
Plus: withLabel(Plus),
51+
Close: withLabel(Close),
52+
DropdownArrow: withLabel(DropdownArrow)
53+
};
54+
55+
export default Icons;

Diff for: client/common/Icon.stories.jsx renamed to client/common/Icons.stories.jsx

+5-5
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,17 @@
11
import React from 'react';
22
import { select } from '@storybook/addon-knobs';
33

4-
import Icon from './Icon';
4+
import Icons from './Icons';
55

66
export default {
7-
title: 'Common/Icon',
8-
component: Icon
7+
title: 'Common/Icons',
8+
component: Icons
99
};
1010

1111
export const AllIcons = () => {
12-
const names = Object.keys(Icon);
12+
const names = Object.keys(Icons);
1313

14-
const SelectedIcon = Icon[select('name', names, names[0])];
14+
const SelectedIcon = Icons[select('name', names, names[0])];
1515
return (
1616
<SelectedIcon />
1717
);

Diff for: client/modules/User/components/APIKeyForm.jsx

+3-4
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import PropTypes from 'prop-types';
22
import React from 'react';
33

44
import Button from '../../../common/Button';
5+
import Icons from '../../../common/Icons';
56
import CopyableInput from '../../IDE/components/CopyableInput';
67

78
import APIKeyList from './APIKeyList';
@@ -80,14 +81,12 @@ class APIKeyForm extends React.Component {
8081
value={this.state.keyLabel}
8182
/>
8283
<Button
83-
iconBeforeName={Button.iconNames.Plus}
8484
disabled={this.state.keyLabel === ''}
8585
type="submit"
8686
label="Create new key"
8787
>
88-
{/* TODO make sure this aria label is right for the button */}
89-
{/* <PlusIcon className="api-key-form__create-icon" focusable="false" aria-hidden="true" /> */}
90-
Create
88+
<Icons.Plus />
89+
<span>Create</span>
9190
</Button>
9291
</form>
9392

Diff for: client/modules/User/components/Collection.jsx

+4-10
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { bindActionCreators } from 'redux';
88
import classNames from 'classnames';
99

1010
import Button from '../../../common/Button';
11+
import Icons from '../../../common/Icons';
1112
import * as ProjectActions from '../../IDE/actions/project';
1213
import * as ProjectsActions from '../../IDE/actions/projects';
1314
import * as CollectionsActions from '../../IDE/actions/collections';
@@ -22,7 +23,6 @@ import AddToCollectionSketchList from '../../IDE/components/AddToCollectionSketc
2223
import CopyableInput from '../../IDE/components/CopyableInput';
2324
import { SketchSearchbar } from '../../IDE/components/Searchbar';
2425

25-
import DropdownArrowIcon from '../../../images/down-arrow.svg';
2626
import ArrowUpIcon from '../../../images/sort-arrow-up.svg';
2727
import ArrowDownIcon from '../../../images/sort-arrow-down.svg';
2828
import RemoveIcon from '../../../images/close.svg';
@@ -53,17 +53,11 @@ const ShareURL = ({ value }) => {
5353
return (
5454
<div className="collection-share" ref={node}>
5555
<Button
56-
iconAfterName={Button.iconNames.SortArrowDown}
5756
onClick={() => setShowURL(!showURL)}
58-
>Share
59-
</Button>
60-
{/* TODO make sure this has the right aria-label and SVG attributes */}
61-
{/* <button>
62-
aria-label="Show collection share URL"
6357
>
64-
<span>Share</span>
65-
<DropdownArrowIcon className="collection-share__arrow" focusable="false" aria-hidden="true" />
66-
</button> */}
58+
Share
59+
<Icons.DropdownArrow />
60+
</Button>
6761
{ showURL &&
6862
<div className="collection__share-dropdown">
6963
<CopyableInput value={value} label="Link to Collection" />

Diff for: client/modules/User/components/GithubButton.jsx

-22
This file was deleted.

Diff for: client/modules/User/components/GoogleButton.jsx

-22
This file was deleted.

0 commit comments

Comments
 (0)