Skip to content

Commit

Permalink
feat(top-app-bar): Add top app bar fixed variant (#68)
Browse files Browse the repository at this point in the history
  • Loading branch information
Matt Goo authored May 30, 2018
1 parent f612388 commit fd5790c
Show file tree
Hide file tree
Showing 15 changed files with 149 additions and 25 deletions.
2 changes: 1 addition & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,5 +31,5 @@ matrix:
install: npm i
script:
- ./test/screenshot/start.sh
- sleep 190s
- sleep 200s
- npm run test:image-diff
57 changes: 44 additions & 13 deletions packages/top-app-bar/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import React from 'react';
import PropTypes from 'prop-types';
import classnames from 'classnames';
import {
MDCFixedTopAppBarFoundation,
MDCTopAppBarFoundation,
MDCShortTopAppBarFoundation,
} from '@material/top-app-bar';
Expand All @@ -14,16 +15,23 @@ export default class TopAppBar extends React.Component {
classList: new Set(),
};

constructor(props) {
super(props);
this.topAppBarElement = React.createRef();
}

get classes() {
const {classList} = this.state;
const {
shortCollapsed,
className,
short,
fixed,
prominent,
short,
shortCollapsed,
} = this.props;

return classnames('mdc-top-app-bar', Array.from(classList), className, {
'mdc-top-app-bar--fixed': fixed,
'mdc-top-app-bar--short': shortCollapsed || short,
'mdc-top-app-bar--short-collapsed': shortCollapsed,
'mdc-top-app-bar--prominent': prominent,
Expand All @@ -41,6 +49,8 @@ export default class TopAppBar extends React.Component {
initializeFoundation = () => {
if (this.props.short) {
this.foundation_ = new MDCShortTopAppBarFoundation(this.adapter);
} else if (this.props.fixed) {
this.foundation_ = new MDCFixedTopAppBarFoundation(this.adapter);
} else {
this.foundation_ = new MDCTopAppBarFoundation(this.adapter);
}
Expand All @@ -67,6 +77,11 @@ export default class TopAppBar extends React.Component {
return React.cloneElement(element, {hasRipple: true});
}

getMergedStyles = () => {
const {style} = this.state;
return Object.assign({}, style, this.props.style);
}

get adapter() {
const {actionItems} = this.props;

Expand All @@ -79,6 +94,16 @@ export default class TopAppBar extends React.Component {
this.setState({classList});
},
hasClass: (className) => this.classes.split(' ').includes(className),
setStyle: (varName, value) => {
const updatedStyle = Object.assign({}, this.state.style);
updatedStyle[varName] = value;
this.setState({style: updatedStyle});
},
getTopAppBarHeight: () => {
if (this.topAppBarElement && this.topAppBarElement.current) {
return this.topAppBarElement.current.clientHeight;
}
},
registerScrollHandler: (handler) =>
window.addEventListener('scroll', handler),
deregisterScrollHandler: (handler) =>
Expand All @@ -90,7 +115,9 @@ export default class TopAppBar extends React.Component {

render() {
return (
<header className={this.classes}>
<header className={this.classes}
style={this.getMergedStyles()}
ref={this.topAppBarElement}>
<div className='mdc-top-app-bar__row'>
{this.renderTitleAndNavigationSection()}
{this.renderActionItems()}
Expand Down Expand Up @@ -150,21 +177,25 @@ export default class TopAppBar extends React.Component {
}

TopAppBar.propTypes = {
shortCollapsed: PropTypes.bool,
short: PropTypes.bool,
prominent: PropTypes.bool,
title: PropTypes.string,
actionItems: PropTypes.arrayOf(PropTypes.element),
navigationIcon: PropTypes.element,
className: PropTypes.string,
fixed: PropTypes.bool,
navigationIcon: PropTypes.element,
prominent: PropTypes.bool,
short: PropTypes.bool,
shortCollapsed: PropTypes.bool,
style: PropTypes.object,
title: PropTypes.string,
};

TopAppBar.defaultProps = {
shortCollapsed: false,
short: false,
prominent: false,
title: '',
actionItems: null,
navigationIcon: null,
className: '',
fixed: false,
navigationIcon: null,
prominent: false,
short: false,
shortCollapsed: false,
style: {},
title: '',
};
13 changes: 7 additions & 6 deletions test/screenshot/golden.json
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
{
"top-app-bar/short.html": "1f9d8f2787d327f81c8fd9c84e5c6f7c770bbd6d6413f4686bcd1d3a9b4b9fcd",
"top-app-bar/prominent.html": "9e9dabb99071f011aba7c5aaa705a28ee15160df79582207c47c65052c120660",
"top-app-bar/standard.html": "637994cfd34328fa5d739285a8d845af920cf1e49a894d5f7053a9869b1c3cd6",
"top-app-bar/standardNoActionItems.html": "a419f4894b23d92aeaec1dabec5cc5a494f3aef60d4dbe263c63eb2b65fd69f0",
"top-app-bar/short.html": "728c9a355129f68dc19b653593dabbc8538c65a356fc12ad78cd463e5a0f38f7",
"top-app-bar/prominent.html": "e637647594906569c0048195870bb6f7b2b370120a97d26cb99ed6b1254b2423",
"top-app-bar/standard.html": "7b2c178a191d2d405a8552644254974fc66739b30ad20193b0d1bc5546958d40",
"top-app-bar/standardNoActionItems.html": "cd62247a9bdd613adb405f99e58030faf2c17e26ba4365f84fd50b3d735cc702",
"top-app-bar/shortCollapsed.html": "d95df9e94851ffc70ccc38daed159e274209c54f3d1d18961f4512504defda6f",
"top-app-bar/standardWithNavigationIconElement.html": "0caa3fe75f9f26c81a95d3848359462a154bde0c758af73024d1ffaa5571a7c9",
"top-app-bar/standardWithNavigationIconElement.html": "5e789c63e175c3fa87551ff3ecfe65e66854ba1aee4d1d7ccb44fdffa0e622c7",
"material-icon/menu.html": "790b2076760612c6c91ee0f6bb42aa3603b30c2a60f302de22429c2572645640",
"fab/standard.html": "c0a68de7fa657ac62622e4f541c8e1eaef7544d12afb26b3421c3b04d58effa6",
"card/index.html": "fe449cbe9e766c68386704b6cbfc3f3f508fbc947c6cdec97fa1b05183d0e61d",
Expand All @@ -15,5 +15,6 @@
"notched-outline/index.html": "73394b6d7dcd94bc930dc740db8199000b94d365d7d703381ef01b3318b97d8c",
"text-field/icon/index.html": "ce761ca24e2d5ca425ad1783c9e2d476b84f65232d86c4fdc0e8c3e756311c8f",
"text-field/helper-text/index.html": "722a8eb27cbde5e32fc92c70e6c107f8ef75bff1abb3914fd2ef58fd49b678bb",
"text-field/index.html": "8cac264e77fe54efcc659edeb8ef1e530963f9efbe8a26edd2e54780beafcb71"
"text-field/index.html": "8cac264e77fe54efcc659edeb8ef1e530963f9efbe8a26edd2e54780beafcb71",
"top-app-bar/fixed.html": "7b2c178a191d2d405a8552644254974fc66739b30ad20193b0d1bc5546958d40"
}
16 changes: 16 additions & 0 deletions test/screenshot/top-app-bar/fixed.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500">
<link rel="stylesheet" href="fixed.css">
<style>
body {
font-family: 'Roboto', Arial, sans-serif;
}
</style>
<script src="fixed.js" async></script>
</head>
<body>
<div id="app"></div>
</body>
</html>
22 changes: 22 additions & 0 deletions test/screenshot/top-app-bar/fixed.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import React from 'react';
import ReactDOM from 'react-dom';
import TopAppBar from '../../../packages/top-app-bar';
import '../../../packages/top-app-bar/index.scss';
import '../../../packages/material-icon/index.scss';
import './index.scss';

import MaterialIcon from '../../../packages/material-icon';

ReactDOM.render((
<div className='top-app-bar-container'>
<TopAppBar
fixed
title='Miami, FL'
navigationIcon={<MaterialIcon
icon='menu'
onClick={() => console.log('fixed click')}
/>}
actionItems={[<MaterialIcon key='item' icon='bookmark' />]}
/>
</div>
), document.getElementById('app'));
6 changes: 6 additions & 0 deletions test/screenshot/top-app-bar/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,12 @@
</style>
</head>
<body>
<div>
<a href='./fixed.html'>
Fixed
</a>
</div>

<div>
<a href='./shortCollapsed.html'>
Short Collapsed
Expand Down
4 changes: 4 additions & 0 deletions test/screenshot/top-app-bar/index.scss
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
body {
margin: 0;
}

.top-app-bar-container {
height: 200vh;
}
2 changes: 1 addition & 1 deletion test/screenshot/top-app-bar/prominent.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import './index.scss';
import MaterialIcon from '../../../packages/material-icon';

ReactDOM.render((
<div>
<div className='top-app-bar-container'>
<TopAppBar
prominent
title='Miami, FL'
Expand Down
1 change: 1 addition & 0 deletions test/screenshot/top-app-bar/screenshot-suite.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import ScreenshotSuite from '../screenshot-suite';
import Screenshot from '../screenshot';

const screenshots = [
new Screenshot('top-app-bar/fixed.html'),
new Screenshot('top-app-bar/prominent.html'),
new Screenshot('top-app-bar/short.html'),
new Screenshot('top-app-bar/shortCollapsed.html'),
Expand Down
2 changes: 1 addition & 1 deletion test/screenshot/top-app-bar/short.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import './index.scss';
import MaterialIcon from '../../../packages/material-icon';

ReactDOM.render((
<div>
<div className='top-app-bar-container'>
<TopAppBar
short
title='Miami, FL'
Expand Down
2 changes: 1 addition & 1 deletion test/screenshot/top-app-bar/standard.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import './index.scss';
import MaterialIcon from '../../../packages/material-icon';

ReactDOM.render((
<div>
<div className='top-app-bar-container'>
<TopAppBar
title='Miami, FL'
navigationIcon={<MaterialIcon
Expand Down
2 changes: 1 addition & 1 deletion test/screenshot/top-app-bar/standardNoActionItems.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import '../../../packages/top-app-bar/index.scss';
import './index.scss';

ReactDOM.render((
<div>
<div className='top-app-bar-container'>
<TopAppBar
title='Miami, FL'
navigationIcon={<MaterialIcon
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ const NavigationIcon = ({
const NavigationIconWithRipple = withRipple(NavigationIcon);

ReactDOM.render((
<div>
<div className='top-app-bar-container'>
<TopAppBar
title='Miami, FL'
navigationIcon={<NavigationIconWithRipple unbounded />}
Expand Down
1 change: 1 addition & 0 deletions test/screenshot/top-app-bar/webpack.config.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
const {bundle} = require('../webpack-bundles');

module.exports = [
bundle('top-app-bar/fixed.js', 'top-app-bar/fixed'),
bundle('top-app-bar/short.js', 'top-app-bar/short'),
bundle('top-app-bar/prominent.js', 'top-app-bar/prominent'),
bundle('top-app-bar/standard.js', 'top-app-bar/standard'),
Expand Down
42 changes: 42 additions & 0 deletions test/unit/top-app-bar/index.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,11 @@ test('has correct prominent class', () => {
assert.isTrue(wrapper.hasClass('mdc-top-app-bar--prominent'));
});

test('has correct fixed class', () => {
const wrapper = shallow(<TopAppBar fixed />);
assert.isTrue(wrapper.hasClass('mdc-top-app-bar--fixed'));
});

test('navigationIcon is rendered with navigation icon class', () => {
const wrapper = mount(
<TopAppBar
Expand Down Expand Up @@ -108,6 +113,12 @@ test('actionItems are rendered as a custom component that accepts a className pr
assert.isTrue(actionItem.hasClass('mdc-top-app-bar__action-item'));
});

test('top app bar style should be set by state', () => {
const wrapper = mount(<TopAppBar/>);
wrapper.setState({style: {color: 'blue'}});
assert.equal(wrapper.getDOMNode().style.color, 'blue');
});

test('#adapter.addClass adds a class to state', () => {
const wrapper = shallow(<TopAppBar />);
wrapper.instance().adapter.addClass('test-class-1');
Expand Down Expand Up @@ -162,6 +173,37 @@ test('#adapter.getTotalActionItems returns false with no actionItem ' +
assert.isFalse(wrapper.instance().adapter.getTotalActionItems());
});

test('#adapter.setStyle should update state', () => {
const wrapper = shallow(<TopAppBar />);
wrapper.instance().adapter.setStyle('display', 'block');
assert.equal(wrapper.state().style.display, 'block');
});

test('#adapter.getTopAppBarHeight should return clientHeight', () => {
const div = document.createElement('div');
// needs to be attached to real DOM to get width
// https://github.com/airbnb/enzyme/issues/1525
document.body.append(div);
const options = {attachTo: div};
const wrapper = mount(<TopAppBar title='Hi' />, options);
const topAppBarHeight = wrapper.instance().adapter.getTopAppBarHeight();
// 18 is the rendered height in pixels. It won't have the actual
// top app bar height since the css is not applied. 18 is the height
// of the title element.
assert.equal(topAppBarHeight, 18);
div.remove();
});

test('#enableRippleOnElement throws error if a native element', () => {
const wrapper = mount(<TopAppBar title='Hi' />);
assert.throws(
() => wrapper.instance().enableRippleOnElement({type: 'test'}),
Error,
'Material Design requires all Top App Bar Icons to have ripple. '
+ 'Please use @material/react-ripple HOC with your icons.'
);
});

test('#componentWillUnmount destroys foundation', () => {
const wrapper = shallow(<TopAppBar />);
const foundation = wrapper.instance().foundation_;
Expand Down

0 comments on commit fd5790c

Please sign in to comment.