Skip to content
This repository was archived by the owner on Nov 10, 2021. It is now read-only.

Commit 28abe4f

Browse files
author
Evan Willhite
authored
Merge pull request #5 from PublicRadioInternational/feature/PRIAPI-73-icons
PRIAPI-73: SVG spriting, icons
2 parents b8193c7 + 403c83e commit 28abe4f

15 files changed

+274
-26
lines changed

.storybook/webpack.config.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,10 @@ module.exports = {
1414
}
1515
}
1616
]
17+
},
18+
{
19+
test: /\.svg$/,
20+
loader: 'svg-sprite-loader'
1721
}
1822
]
1923
}

index.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,5 +6,6 @@
66
import Button from './src/components/Atoms/Button/Button.component';
77
import Dropdown from './src/components/Atoms/Dropdown/Dropdown.component';
88
import DropdownItem from './src/components/Atoms/DropdownItem/DropdownItem.component';
9+
import Icon from './src/components/Atoms/Icon/Icon.component';
910

10-
export { Button, Dropdown, DropdownItem }; // eslint-disable-line
11+
export { Button, Dropdown, DropdownItem, Icon }; // eslint-disable-line

package.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@
3535
"^.+\\.jsx?$": "babel-jest"
3636
},
3737
"moduleNameMapper": {
38-
"\\.(css|less)$": "identity-obj-proxy"
38+
"\\.(css|less|svg)$": "identity-obj-proxy"
3939
},
4040
"setupFiles": ["<rootDir>/jest.setup.js"],
4141
"testPathIgnorePatterns": ["<rootDir>/.next/", "<rootDir>/node_modules/"]
@@ -77,7 +77,8 @@
7777
"lint-staged": "^7.0.0",
7878
"prettier": "^1.11.1",
7979
"react-test-renderer": "^16.2.0",
80-
"semantic-release": "^15.0.0"
80+
"semantic-release": "^15.0.0",
81+
"svg-sprite-loader": "^3.7.1"
8182
},
8283
"publishConfig": {
8384
"access": "restricted"

src/components/Atoms/Button/Button.component.js

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,13 @@
66
import React from 'react';
77
import PropTypes from 'prop-types';
88
import styles from './Button.css';
9+
import Icon from '../Icon/Icon.component';
910

1011
/**
1112
* Component that renders a link, or a button with a click handler.
1213
*/
13-
function Button(props) {
14-
const { url, onClick, className, children, color } = props;
14+
const Button = props => {
15+
const { url, onClick, className, children, color, icon } = props;
1516
// Generate a class name based on the color.
1617
const buttonClass = `btn${color}`;
1718

@@ -23,6 +24,7 @@ function Button(props) {
2324
className={`${styles[buttonClass]} ${className}`}
2425
onClick={onClick}
2526
>
27+
{icon && <Icon svg={icon} inline />}
2628
<span className="text-label">{children}</span>
2729
</a>
2830
);
@@ -38,30 +40,34 @@ function Button(props) {
3840
aria-label={props['aria-label']}
3941
aria-haspopup={props['aria-haspopup']}
4042
>
43+
{icon ? <Icon svg={icon} inline /> : null}
4144
{children}
4245
</button>
4346
);
44-
}
47+
};
4548

4649
Button.propTypes = {
4750
url: PropTypes.string,
4851
onClick: PropTypes.func,
4952
children: PropTypes.node,
50-
color: PropTypes.string,
51-
className: PropTypes.string.isRequired,
53+
color: PropTypes.oneOf(['Orange', 'White']),
54+
className: PropTypes.string,
5255
'aria-expanded': PropTypes.bool,
5356
'aria-label': PropTypes.string,
54-
'aria-haspopup': PropTypes.bool
57+
'aria-haspopup': PropTypes.bool,
58+
icon: PropTypes.string
5559
};
5660

5761
Button.defaultProps = {
5862
url: null,
5963
color: 'White',
64+
className: null,
6065
children: null,
6166
onClick: () => {},
6267
'aria-expanded': false,
6368
'aria-label': null,
64-
'aria-haspopup': false
69+
'aria-haspopup': false,
70+
icon: null
6571
};
6672

6773
export default Button;

src/components/Atoms/Dropdown/Dropdown.component.js

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,18 +18,20 @@ export default class Dropdown extends Component {
1818
children: PropTypes.node,
1919
onClick: PropTypes.func,
2020
url: PropTypes.string,
21-
color: PropTypes.oneOf(['Orange', 'White'])
21+
color: PropTypes.oneOf(['Orange', 'White']),
22+
icon: PropTypes.string
2223
};
2324

2425
static defaultProps = {
2526
children: [],
2627
color: 'White',
2728
url: null,
28-
onClick: () => {}
29+
onClick: () => {},
30+
icon: null
2931
};
3032

3133
render() {
32-
const { title, onClick, children, color, url } = this.props;
34+
const { title, onClick, children, color, url, icon } = this.props;
3335

3436
return (
3537
<Downshift>
@@ -40,6 +42,7 @@ export default class Dropdown extends Component {
4042
url={url}
4143
color={color}
4244
onClick={onClick}
45+
icon={icon}
4346
>
4447
{title}
4548
</Button>
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
/**
2+
* @file Icon.component.js
3+
* Exports an icon component.
4+
*/
5+
6+
import React from 'react';
7+
import PropTypes from 'prop-types';
8+
import styles from './Icon.css';
9+
10+
/**
11+
* Component that renders a link, or a button with a click handler.
12+
*/
13+
const Icon = props => {
14+
const { svg, className, inline } = props;
15+
const icon = require(`./svg/${svg}.svg`); // eslint-disable-line
16+
return (
17+
<svg
18+
viewBox={icon.default.viewBox}
19+
className={inline ? styles.inlineSvg : className}
20+
fill="currentcolor"
21+
>
22+
<use xlinkHref={`#${icon.default.id}`} />
23+
</svg>
24+
);
25+
};
26+
27+
Icon.propTypes = {
28+
// Worth automatically creating these from the files in ./svg sometime?
29+
svg: PropTypes.oneOf(['heart', 'envelope', 'search', 'volume']).isRequired,
30+
className: PropTypes.string,
31+
inline: PropTypes.bool
32+
};
33+
34+
Icon.defaultProps = {
35+
className: null,
36+
inline: false
37+
};
38+
39+
export default Icon;

src/components/Atoms/Icon/Icon.css

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
.inlineSvg {
2+
display: inline-block;
3+
margin-right: 8px;
4+
position: relative;
5+
top: 2px;
6+
width: 16px;
7+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
/**
2+
* @file Button.test.js
3+
* Contains tests for Button.component.js.
4+
*/
5+
6+
import React from 'react';
7+
import renderer from 'react-test-renderer';
8+
9+
import Icon from './Icon.component';
10+
11+
describe('<Icon />', () => {
12+
it('Matches the Icon snapshot', () => {
13+
const component = renderer.create(<Icon svg="heart" />).toJSON();
14+
expect(component).toMatchSnapshot();
15+
});
16+
});
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
// Jest Snapshot v1, https://goo.gl/fbAQLP
2+
3+
exports[`<Icon /> Matches the Icon snapshot 1`] = `
4+
<svg
5+
className={null}
6+
fill="currentcolor"
7+
viewBox={undefined}
8+
>
9+
<use
10+
xlinkHref="#undefined"
11+
/>
12+
</svg>
13+
`;
Lines changed: 5 additions & 0 deletions
Loading
Lines changed: 5 additions & 0 deletions
Loading
Lines changed: 5 additions & 0 deletions
Loading
Lines changed: 5 additions & 0 deletions
Loading

src/components/Atoms/atoms.story.js

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,11 @@ storiesOf('Atoms/Buttons', module)
2222
<Button onClick={action('button-clicked')} color="Orange">
2323
Donate
2424
</Button>
25+
))
26+
.add('Icon', () => (
27+
<Button onClick={action('button-clicked')} icon="envelope">
28+
Newsletters
29+
</Button>
2530
));
2631

2732
/**
@@ -42,9 +47,10 @@ storiesOf('Atoms/Dropdown', module)
4247
))
4348
.add('Orange', () => (
4449
<Dropdown
45-
title="Listen"
50+
title="Donate"
4651
color="Orange"
4752
onClick={action('drowndown-button-clicked')}
53+
icon="heart"
4854
>
4955
<DropdownItem url="https://google.com">Google</DropdownItem>
5056
<DropdownItem onClick={action('dropdown-button-item-clicked')}>

0 commit comments

Comments
 (0)