summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorAndrej Mihajlov <and@mullvad.net>2018-08-02 21:14:20 +0200
committerAndrej Mihajlov <and@mullvad.net>2018-08-08 16:25:33 +0200
commit7ae25a0835a27cb9bf46ed64d6c59a1147110bee (patch)
tree4df13757e114bf9291b31f05305cb01e1513ed53
parent045e915f7de99f9a16519a453169b05140810e81 (diff)
downloadmullvadvpn-7ae25a0835a27cb9bf46ed64d6c59a1147110bee.tar.xz
mullvadvpn-7ae25a0835a27cb9bf46ed64d6c59a1147110bee.zip
Make HeaderBar more composable
-rw-r--r--app/components/Connect.js19
-rw-r--r--app/components/HeaderBar.js163
-rw-r--r--app/components/HeaderBarPlatformStyles.android.js2
-rw-r--r--app/components/HeaderBarPlatformStyles.js16
-rw-r--r--app/components/HeaderBarStyles.js53
-rw-r--r--app/components/Layout.js11
-rw-r--r--app/components/Login.js6
-rw-r--r--test/components/Connect.spec.js4
-rw-r--r--test/components/HeaderBar.spec.js44
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;
-}