Skip to content

Commit

Permalink
feat(icon-button): create component (#154)
Browse files Browse the repository at this point in the history
  • Loading branch information
Matt Goo authored Sep 21, 2018
1 parent cb477f5 commit c7b4cf6
Show file tree
Hide file tree
Showing 14 changed files with 440 additions and 0 deletions.
11 changes: 11 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@
"@material/chips": "^0.39.0",
"@material/fab": "^0.39.0",
"@material/floating-label": "^0.39.0",
"@material/icon-button": "^0.39.0",
"@material/line-ripple": "^0.39.0",
"@material/list": "^0.39.0",
"@material/notched-outline": "^0.39.0",
Expand Down
2 changes: 2 additions & 0 deletions packages/icon-button/.npmignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
index.js
IconToggle.js
31 changes: 31 additions & 0 deletions packages/icon-button/IconToggle.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import React, {Component} from 'react';
import classnames from 'classnames';
import PropTypes from 'prop-types';

export default class IconToggle extends Component {
render() {
const {isOn, className, children} = this.props;
const classes = classnames(
'mdc-icon-button__icon',
{'mdc-icon-button__icon--on': isOn},
className,
);
return (
<div className={classes}>
{children}
</div>
);
}
}

IconToggle.propTypes = {
children: PropTypes.node,
className: PropTypes.string,
isOn: PropTypes.bool,
};

IconToggle.defaultProps = {
children: '',
className: '',
isOn: false,
};
73 changes: 73 additions & 0 deletions packages/icon-button/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
# React Icon Button

A React version of an [MDC Icon Button](https://github.com/material-components/material-components-web/tree/master/packages/mdc-icon-button).

## Installation

```
npm install @material/react-icon-button
```

## Usage

```js
import React from 'react';
import IconButton from '@material/react-icon-button';
import MaterialIcon from '@material/react-material-icon';

class MyApp extends React.Component {
render() {
return (
<IconButton>
<MaterialIcon icon='favorite' />
</IconButton>
);
}
}
```

You can use any other icon here such as Font Awesome, which is documented more in detail [here](https://github.com/material-components/material-components-web/tree/master/packages/mdc-icon-button#icon-button-toggle-with-font-awesome). If you're using [Google Font's Material Icons](https://material.io/tools/icons/?style=baseline), we recommend using our [Material Icon Component](../material-icon) as shown in the example above.

## Variants

### Icon Button Toggle

If you need to use this component as an Icon Button Toggle, please read [this documentation](https://github.com/material-components/material-components-web/blob/master/packages/mdc-icon-button/README.md#icon-button-toggle). The following is an example using the `<IconToggle />` component as children of `<IconButton>`. One component with the `isOn` prop, and one without.

```js
import React from 'react';
import IconButton, {IconToggle} from '@material/react-icon-button';
import MaterialIcon from '@material/react-material-icon';

class MyApp extends React.Component {
render() {
return (
<IconButton>
<IconToggle isOn>
<MaterialIcon icon='favorite' />
</IconToggle>
<IconToggle>
<MaterialIcon icon='favorite_border' />
</IconToggle>
</IconButton>
);
}
}
```

## Props

Prop Name | Type | Description
--- | --- | ---
children | Element | Icon element or text to be displayed within root element.
className | String | Classes to be applied to the root element.
disabled | Boolean | Disables button if true.
isLink | Boolean | Changes root element to an anchor tag (default button).
onClick | Function | Click event handler. Event is passed as an argument

## Sass Mixins

Sass mixins may be available to customize various aspects of the Components. Please refer to the
MDC Web repository for more information on what mixins are available, and how to use them.

[Advanced Sass Mixins](https://github.com/material-components/material-components-web/blob/master/packages/mdc-icon-button/README.md#sass-mixins)
107 changes: 107 additions & 0 deletions packages/icon-button/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
import React, {Component} from 'react';
import classnames from 'classnames';
import PropTypes from 'prop-types';
import withRipple from '@material/react-ripple';
import {MDCIconButtonToggleFoundation} from '@material/icon-button/dist/mdc.iconButton';
import IconToggle from './IconToggle';

const {strings} = MDCIconButtonToggleFoundation;

class IconButtonBase extends Component {

constructor(props) {
super(props);
this.state = {
classList: new Set(),
[strings.ARIA_PRESSED]: props[strings.ARIA_PRESSED],
};
}

componentDidMount() {
this.foundation_ = new MDCIconButtonToggleFoundation(this.adapter);
this.foundation_.init();
}

get classes() {
const {classList} = this.state;
const {className} = this.props;
return classnames('mdc-icon-button', Array.from(classList), className);
}

get adapter() {
return {
addClass: (className) =>
this.setState({classList: this.state.classList.add(className)}),
removeClass: (className) => {
const classList = new Set(this.state.classList);
classList.delete(className);
this.setState({classList});
},
hasClass: (className) => this.classes.split(' ').includes(className),
setAttr: (attr, value) => this.setState({[attr]: value}),
};
}

handleClick_ = (e) => {
this.props.onClick(e);
this.foundation_.handleClick();
}

render() {
const {
children,
initRipple,
isLink,
/* eslint-disable no-unused-vars */
className,
onClick,
unbounded,
[strings.ARIA_PRESSED]: ariaPressed,
/* eslint-enable no-unused-vars */
...otherProps
} = this.props;

const props = {
className: this.classes,
ref: initRipple,
[strings.ARIA_PRESSED]: this.state[strings.ARIA_PRESSED],
onClick: this.handleClick_,
...otherProps,
};

if (isLink) {
return (
<a {...props}>
{children}
</a>
);
}

return (
<button {...props}>
{children}
</button>
);
}
}

IconButtonBase.propTypes = {
children: PropTypes.node,
className: PropTypes.string,
initRipple: PropTypes.func,
isLink: PropTypes.bool,
onClick: PropTypes.func,
unbounded: PropTypes.bool,
};

IconButtonBase.defaultProps = {
children: '',
className: '',
initRipple: () => {},
isLink: false,
onClick: () => {},
unbounded: true,
};

export default withRipple(IconButtonBase);
export {IconToggle, IconButtonBase};
1 change: 1 addition & 0 deletions packages/icon-button/index.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
@import "@material/icon-button/mdc-icon-button";
27 changes: 27 additions & 0 deletions packages/icon-button/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
{
"name": "@material/react-icon-button",
"version": "0.0.0",
"description": "Material Components React Icon Button",
"license": "Apache-2.0",
"keywords": [
"mdc web react",
"material components react",
"material design",
"material icon button",
"materialiconbutton"
],
"repository": {
"type": "git",
"url": "https://github.com/material-components/material-components-web-react.git"
},
"dependencies": {
"@material/icon-button": "^0.39.0",
"@material/react-ripple": "^0.4.2",
"classnames": "^2.2.5",
"prop-types": "^15.6.1",
"react": "^16.3.2"
},
"publishConfig": {
"access": "public"
}
}
1 change: 1 addition & 0 deletions test/screenshot/golden.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
"line-ripple": "56b136db2dc7e09260849447e6bde9b55a837af332a05d9f52506ab1c95e2e57",
"fab": "db36f52195c420062d91dd5ebe5432ad87247b3c1146fd547b0a195079bbce2f",
"floating-label": "1d4d4f2e57e1769b14fc84985d1e6f53410c49aef41c9cf4fde94f938adefe57",
"icon-button": "5ffb1f7fbd06d2c0533f6ba8d4d9ea170cec1a248a61de1cc1bb626cb58ebcd2",
"material-icon": "442b39fb22d2c7a74efb23ca098429b471501ce21df8662327bbf9871fe0bcb0",
"notched-outline": "7770dd381c27608a1f43b6f83da92507fe53963f5e4409bd73184b86275538fe",
"select": "ed9c021c8347f18e6ece48d55a8a3b30bb2380ec94276b002e9c064e7674e1c8",
Expand Down
43 changes: 43 additions & 0 deletions test/screenshot/icon-button/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import React from 'react';
import MaterialIcon from '../../../packages/material-icon/index';
import '../../../packages/icon-button/index.scss';
import './index.scss';

import IconButton, {IconToggle} from '../../../packages/icon-button/index';

class IconButtonTest extends React.Component {
render() {
return (
<div>
<IconButton>
<MaterialIcon icon='favorite' />
</IconButton>

<span className='demo-custom-color'>
<IconButton>
<MaterialIcon icon='favorite' />
</IconButton>
</span>

<IconButton isLink>
<MaterialIcon icon='favorite' />
</IconButton>

<IconButton disabled>
<MaterialIcon icon='favorite' />
</IconButton>

<IconButton>
<IconToggle>
<MaterialIcon icon='favorite_border' />
</IconToggle>
<IconToggle isOn>
<MaterialIcon icon='favorite' />
</IconToggle>
</IconButton>
</div>
);
}
}

export default IconButtonTest;
5 changes: 5 additions & 0 deletions test/screenshot/icon-button/index.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
@import "../../../packages/icon-button/index.scss";

.demo-custom-color .mdc-icon-button {
@include mdc-icon-button-ink-color(primary);
}
1 change: 1 addition & 0 deletions test/screenshot/screenshot-test-urls.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ const urls = [
'line-ripple',
'fab',
'floating-label',
'icon-button',
'material-icon',
'notched-outline',
'select',
Expand Down
42 changes: 42 additions & 0 deletions test/unit/icon-button/IconToggle.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import React from 'react';
import {assert} from 'chai';
import {shallow} from 'enzyme';
import {IconToggle} from '../../../packages/icon-button/index';

suite('IconButtonIconToggle');

test('classNames adds classes', () => {
const wrapper = shallow(<IconToggle className='test-class-name' />);
assert.isTrue(wrapper.hasClass('test-class-name'));
});

test('has icon button icon class', () => {
const wrapper = shallow(<IconToggle className='test-class-name' />);
assert.isTrue(wrapper.hasClass('mdc-icon-button__icon'));
});

test('has icon button on icon class if props.isOn is true', () => {
const wrapper = shallow(<IconToggle isOn className='test-class-name' />);
assert.isTrue(wrapper.hasClass('mdc-icon-button__icon--on'));
});

test('renders icon', () => {
const wrapper = shallow(<IconToggle>
<i className='test-icon' />
</IconToggle>);
assert.equal(wrapper.children().first().type(), 'i');
});

test('renders svg', () => {
const wrapper = shallow(<IconToggle>
<svg className='test-svg' />
</IconToggle>);
assert.equal(wrapper.children().first().type(), 'svg');
});

test('renders img', () => {
const wrapper = shallow(<IconToggle>
<img className='test-img' />
</IconToggle>);
assert.equal(wrapper.children().first().type(), 'img');
});
Loading

0 comments on commit c7b4cf6

Please sign in to comment.