diff options
| author | Andrej Mihajlov <and@mullvad.net> | 2018-08-02 21:14:20 +0200 |
|---|---|---|
| committer | Andrej Mihajlov <and@mullvad.net> | 2018-08-08 16:25:33 +0200 |
| commit | 7ae25a0835a27cb9bf46ed64d6c59a1147110bee (patch) | |
| tree | 4df13757e114bf9291b31f05305cb01e1513ed53 | |
| parent | 045e915f7de99f9a16519a453169b05140810e81 (diff) | |
| download | mullvadvpn-7ae25a0835a27cb9bf46ed64d6c59a1147110bee.tar.xz mullvadvpn-7ae25a0835a27cb9bf46ed64d6c59a1147110bee.zip | |
Make HeaderBar more composable
| -rw-r--r-- | app/components/Connect.js | 19 | ||||
| -rw-r--r-- | app/components/HeaderBar.js | 163 | ||||
| -rw-r--r-- | app/components/HeaderBarPlatformStyles.android.js | 2 | ||||
| -rw-r--r-- | app/components/HeaderBarPlatformStyles.js | 16 | ||||
| -rw-r--r-- | app/components/HeaderBarStyles.js | 53 | ||||
| -rw-r--r-- | app/components/Layout.js | 11 | ||||
| -rw-r--r-- | app/components/Login.js | 6 | ||||
| -rw-r--r-- | test/components/Connect.spec.js | 4 | ||||
| -rw-r--r-- | test/components/HeaderBar.spec.js | 44 |
9 files changed, 149 insertions, 169 deletions
diff --git a/app/components/Connect.js b/app/components/Connect.js index cd27b33719..1a6e21599a 100644 --- a/app/components/Connect.js +++ b/app/components/Connect.js @@ -3,6 +3,7 @@ import moment from 'moment'; import * as React from 'react'; import { Layout, Container, Header } from './Layout'; +import { SettingsBarButton, Brand } from './HeaderBar'; import { Component, Text, View, Types } from 'reactxp'; import * as AppButton from './AppButton'; import Img from './Img'; @@ -73,12 +74,10 @@ export default class Connect extends Component<Props, State> { return ( <Layout> - <Header - style={this.headerStyle()} - showSettings={true} - onSettings={this.props.onSettings} - testName="header" - /> + <Header barStyle={this.headerBarStyle()} testName="header"> + <Brand /> + <SettingsBarButton onPress={this.props.onSettings} /> + </Header> <Container>{child}</Container> </Layout> ); @@ -329,15 +328,17 @@ export default class Connect extends Component<Props, State> { // Private - headerStyle(): HeaderBarStyle { - switch (this.props.connection.status) { + headerBarStyle(): HeaderBarStyle { + const { status } = this.props.connection; + switch (status) { case 'disconnected': return 'error'; case 'connecting': case 'connected': return 'success'; + default: + throw new Error(`Invalid ConnectionState: ${status}`); } - throw new Error('Invalid ConnectionState'); } networkSecurityStyle(): Types.Style { diff --git a/app/components/HeaderBar.js b/app/components/HeaderBar.js index a46856eb46..02b1d86e31 100644 --- a/app/components/HeaderBar.js +++ b/app/components/HeaderBar.js @@ -1,55 +1,148 @@ // @flow import React from 'react'; -import { Component, Text, Button, View } from 'reactxp'; - +import { Component, Text, Button, View, Styles } from 'reactxp'; import Img from './Img'; - -import styles from './HeaderBarStyles'; -import platformStyles from './HeaderBarPlatformStyles'; +import { colors } from '../config'; export type HeaderBarStyle = 'default' | 'defaultDark' | 'error' | 'success'; -export type HeaderBarProps = { - style: HeaderBarStyle, - showSettings: boolean, - onSettings: ?() => void, +type HeaderBarProps = { + barStyle: HeaderBarStyle, +}; + +const headerBarStyles = { + container: { + base: Styles.createViewStyle({ + paddingTop: 12, + paddingBottom: 12, + paddingLeft: 12, + paddingRight: 12, + }), + platformOverride: { + darwin: Styles.createViewStyle({ + paddingTop: 24, + }), + linux: Styles.createViewStyle({ + WebkitAppRegion: 'drag', + }), + }, + }, + content: Styles.createViewStyle({ + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'flex-end', + // the size of "brand" logo + minHeight: 51, + }), + barStyle: { + default: Styles.createViewStyle({ + backgroundColor: colors.blue, + }), + defaultDark: Styles.createViewStyle({ + backgroundColor: colors.darkBlue, + }), + error: Styles.createViewStyle({ + backgroundColor: colors.red, + }), + success: Styles.createViewStyle({ + backgroundColor: colors.green, + }), + }, }; export default class HeaderBar extends Component<HeaderBarProps> { static defaultProps: HeaderBarProps = { - style: 'default', - showSettings: false, - onSettings: null, + barStyle: 'default', }; render() { - const containerClass = [ - styles['headerbar'], - platformStyles[process.platform], - styles['style_' + this.props.style], + const style = [ + headerBarStyles.container.base, + headerBarStyles.container.platformOverride[process.platform], + headerBarStyles.barStyle[this.props.barStyle], + this.props.style, ]; return ( - <View style={containerClass}> - <View style={styles.container} testName="headerbar__container"> - <Img height={50} width={50} source="logo-icon" /> - <Text style={styles.title}>MULLVAD VPN</Text> - </View> + <View style={style}> + <View style={headerBarStyles.content}>{this.props.children}</View> + </View> + ); + } +} - {this.props.showSettings ? ( - <Button - style={styles.settings} - onPress={this.props.onSettings} - testName="headerbar__settings"> - <Img - height={24} - width={24} - source="icon-settings" - style={[styles.settings_icon, platformStyles.settings_icon]} - hoverStyle={styles.settings_icon_hover} - /> - </Button> - ) : null} +const brandStyles = { + container: Styles.createViewStyle({ + flex: 1, + flexDirection: 'row', + alignItems: 'center', + }), + title: Styles.createTextStyle({ + fontFamily: 'DINPro', + fontSize: 24, + fontWeight: '900', + lineHeight: 30, + letterSpacing: -0.5, + color: colors.white60, + marginLeft: 8, + }), +}; + +export class Brand extends Component { + render() { + return ( + <View style={brandStyles.container} testName="headerbar__container"> + <Img width={50} height={50} source="logo-icon" /> + <Text style={brandStyles.title}>{'MULLVAD VPN'}</Text> </View> ); } } + +type SettingsButtonProps = { + onPress: ?() => void, +}; + +const settingsBarButtonStyles = { + container: { + base: Styles.createViewStyle({ + cursor: 'default', + padding: 0, + marginLeft: 8, + }), + platformOverride: { + linux: Styles.createViewStyle({ + WebkitAppRegion: 'no-drag', + }), + }, + }, + icon: { + normal: Styles.createViewStyle({ + color: colors.white60, + }), + hover: Styles.createViewStyle({ + color: colors.white, + }), + }, +}; + +export class SettingsBarButton extends Component<SettingsButtonProps> { + render() { + return ( + <Button + style={[ + settingsBarButtonStyles.container.base, + settingsBarButtonStyles.container.platformOverride[process.platform], + ]} + onPress={this.props.onPress} + testName="headerbar__settings"> + <Img + height={24} + width={24} + source="icon-settings" + style={settingsBarButtonStyles.icon.normal} + hoverStyle={settingsBarButtonStyles.icon.hover} + /> + </Button> + ); + } +} diff --git a/app/components/HeaderBarPlatformStyles.android.js b/app/components/HeaderBarPlatformStyles.android.js deleted file mode 100644 index f2f97beb65..0000000000 --- a/app/components/HeaderBarPlatformStyles.android.js +++ /dev/null @@ -1,2 +0,0 @@ -import { createViewStyles } from '../lib/styles'; -export default { ...createViewStyles({}) }; diff --git a/app/components/HeaderBarPlatformStyles.js b/app/components/HeaderBarPlatformStyles.js deleted file mode 100644 index c60967797a..0000000000 --- a/app/components/HeaderBarPlatformStyles.js +++ /dev/null @@ -1,16 +0,0 @@ -// @flow -import { createViewStyles } from '../lib/styles'; - -export default { - ...createViewStyles({ - darwin: { - paddingTop: 24, - }, - linux: { - WebkitAppRegion: 'drag', - }, - settings_icon: { - WebkitAppRegion: 'no-drag', - }, - }), -}; diff --git a/app/components/HeaderBarStyles.js b/app/components/HeaderBarStyles.js deleted file mode 100644 index 7d3c94cdc9..0000000000 --- a/app/components/HeaderBarStyles.js +++ /dev/null @@ -1,53 +0,0 @@ -// @flow -import { createTextStyles, createViewStyles } from '../lib/styles'; -import { colors } from '../config'; - -export default { - ...createViewStyles({ - headerbar: { - paddingTop: 12, - paddingBottom: 12, - paddingLeft: 12, - paddingRight: 12, - backgroundColor: colors.blue, - flexDirection: 'row', - justifyContent: 'space-between', - alignItems: 'center', - }, - style_defaultDark: { - backgroundColor: colors.darkBlue, - }, - style_error: { - backgroundColor: colors.red, - }, - style_success: { - backgroundColor: colors.green, - }, - container: { - display: 'flex', - flexDirection: 'row', - alignItems: 'center', - }, - settings: { - cursor: 'default', - padding: 0, - }, - settings_icon: { - color: colors.white60, - }, - settings_icon_hover: { - color: colors.white, - }, - }), - ...createTextStyles({ - title: { - fontFamily: 'DINPro', - fontSize: 24, - fontWeight: '900', - lineHeight: 30, - letterSpacing: -0.5, - color: colors.white60, - marginLeft: 8, - }, - }), -}; diff --git a/app/components/Layout.js b/app/components/Layout.js index 2699b3e43d..1eb6984797 100644 --- a/app/components/Layout.js +++ b/app/components/Layout.js @@ -1,19 +1,16 @@ // @flow import * as React from 'react'; -import HeaderBar from './HeaderBar'; import { View, Component } from 'reactxp'; - -import type { HeaderBarProps } from './HeaderBar'; - +import HeaderBar from './HeaderBar'; import styles from './LayoutStyles'; -export class Header extends Component<HeaderBarProps> { +export class Header extends Component<React.ElementProps<typeof HeaderBar>> { static defaultProps = HeaderBar.defaultProps; render() { return ( - <View style={styles.header}> - <HeaderBar {...this.props} /> + <View style={[styles.header, this.props.style]}> + <HeaderBar barStyle={this.props.barStyle}>{this.props.children}</HeaderBar> </View> ); } diff --git a/app/components/Login.js b/app/components/Login.js index d902bba1bc..b410f80553 100644 --- a/app/components/Login.js +++ b/app/components/Login.js @@ -2,6 +2,7 @@ import * as React from 'react'; import { Component, Text, View, Animated, Styles, UserInterface } from 'reactxp'; import { Layout, Container, Header } from './Layout'; +import { SettingsBarButton, Brand } from './HeaderBar'; import AccountInput from './AccountInput'; import Accordion from './Accordion'; import { formatAccount } from '../lib/formatters'; @@ -94,7 +95,10 @@ export default class Login extends Component<Props, State> { render() { return ( <Layout> - <Header showSettings={true} onSettings={this.props.openSettings} /> + <Header> + <Brand /> + <SettingsBarButton onPress={this.props.openSettings} /> + </Header> <Container> <View style={styles.login_form}> {this._getStatusIcon()} diff --git a/test/components/Connect.spec.js b/test/components/Connect.spec.js index 67dae303d8..e445b2c788 100644 --- a/test/components/Connect.spec.js +++ b/test/components/Connect.spec.js @@ -19,7 +19,7 @@ describe('components/Connect', () => { const header = getComponent(component, 'header'); const securityMessage = getComponent(component, 'networkSecurityMessage'); const connectButton = getComponent(component, 'secureConnection'); - expect(header.prop('style')).to.equal('error'); + expect(header.prop('barStyle')).to.equal('error'); expect(securityMessage.html()).to.contain('UNSECURED'); expect(connectButton.html()).to.contain('Secure my connection'); }); @@ -35,7 +35,7 @@ describe('components/Connect', () => { const header = getComponent(component, 'header'); const securityMessage = getComponent(component, 'networkSecurityMessage'); const disconnectButton = getComponent(component, 'disconnect'); - expect(header.prop('style')).to.equal('success'); + expect(header.prop('barStyle')).to.equal('success'); expect(securityMessage.html()).to.contain('SECURE '); expect(disconnectButton.html()).to.contain('Disconnect'); }); diff --git a/test/components/HeaderBar.spec.js b/test/components/HeaderBar.spec.js deleted file mode 100644 index 09eab134ad..0000000000 --- a/test/components/HeaderBar.spec.js +++ /dev/null @@ -1,44 +0,0 @@ -// @flow - -import React from 'react'; -import { shallow } from 'enzyme'; -import HeaderBar from '../../app/components/HeaderBar'; - -describe('components/HeaderBar', () => { - it('should display settings button', () => { - const component = render({ - showSettings: true, - }); - const hasChildMatching = hasChild(component, 'headerbar__settings'); - expect(hasChildMatching).to.be.true; - }); - - it('should not display settings button', () => { - const component = render({ - showSettings: false, - }); - const hasChildMatching = hasChild(component, 'headerbar__settings'); - expect(hasChildMatching).to.be.false; - }); - - it('should call settings callback', (done) => { - const component = render({ - showSettings: true, - onSettings: () => done(), - }); - const settingsButton = getComponent(component, 'headerbar__settings'); - settingsButton.simulate('press'); - }); -}); - -function render(props) { - return shallow(<HeaderBar {...props} />); -} - -function getComponent(container, testName) { - return container.findWhere((n) => n.prop('testName') === testName); -} - -function hasChild(container, testName) { - return getComponent(container, testName).length > 0; -} |
