summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authoranderklander <anderklander@gmail.com>2018-03-23 08:41:31 +0100
committeranderklander <anderklander@gmail.com>2018-04-11 13:45:41 +0200
commitd0dcf31199cc3efa23645312fb9b5d2eb41a1d56 (patch)
tree7934d818fe7e5ca50721a875c89c00aea7ac1637
parent70fb24b51b159195fb5ba5e854459ae47ab01f90 (diff)
downloadmullvadvpn-d0dcf31199cc3efa23645312fb9b5d2eb41a1d56.tar.xz
mullvadvpn-d0dcf31199cc3efa23645312fb9b5d2eb41a1d56.zip
SelectLocation in reactxp
-rw-r--r--app/components/SelectLocation.css146
-rw-r--r--app/components/SelectLocation.js134
-rw-r--r--app/components/SelectLocationStyles.js88
-rw-r--r--test/components/SelectLocation.spec.js27
4 files changed, 164 insertions, 231 deletions
diff --git a/app/components/SelectLocation.css b/app/components/SelectLocation.css
deleted file mode 100644
index 7e107f237f..0000000000
--- a/app/components/SelectLocation.css
+++ /dev/null
@@ -1,146 +0,0 @@
-.select-location {
- background: #192E45;
- height: 100%;
-}
-
-.select-location__container {
- display: flex;
- flex-direction: column;
- height: 100%;
-}
-
-.select-location__header {
- flex: 0 0 auto;
- padding: 40px 24px 16px;
- position: relative; /* anchor for close button */
-}
-
-.select-location__close {
- position: absolute;
- display: block;
- border: 0;
- padding: 0;
- margin: 0;
- width: 24px;
- height: 24px;
- top: 24px;
- left: 12px;
- background-color: transparent;
- background-image: url(../assets/images/icon-close.svg);
- opacity: 0.6;
- z-index: 1; /* part of .select-location__container covers the button */
-}
-
-.select-location__title {
- font-family: DINPro;
- font-size: 32px;
- font-weight: 900;
- line-height: 1.25em;
- color: #FFFFFF;
-}
-
-.select-location__subtitle {
- font-family: "Open Sans";
- font-size: 13px;
- font-weight: 600;
- line-height: normal;
- color: rgba(255,255,255,0.8);
- padding: 0 24px 24px;
-}
-
-.select-location__cell {
- background-color: rgba(41,71,115,1);
- display: flex;
- flex-direction: row;
- align-items: stretch;
-}
-
-.select-location__cell-content {
- padding: 15px 24px;
- display: flex;
- flex: 1 1 auto;
- flex-direction: row;
- align-items: center;
-}
-
-.select-location__cell--selectable:hover {
- background-color:rgba(41,71,115,0.9);
-}
-
-.select-location__cell--selected,
-.select-location__cell--selected:hover {
- background-color: #44AD4D;
-}
-
-.select-location__country + .select-location__country {
- margin-top: 1px;
-}
-
-.select-location__cell-label {
- font-family: DINPro;
- font-size: 20px;
- font-weight: 900;
- line-height: 26px;
- color: #FFFFFF;
-}
-
-.select-location__cell-label--inactive {
- color: rgba(255, 255, 255, 0.2);
-}
-
-.select-location__cell-icon {
- width: 24px;
- height: 24px;
- flex: 0 0 auto;
- margin-right: 8px;
- align-items: center;
- justify-content: center;
- display: flex;
- color: #fff;
-}
-
-.select-location__collapse-button {
- border: 0;
- background: transparent;
- padding: 0;
- margin: 0 0 0 auto;
- display: flex;
- align-items: stretch;
- padding: 12px;
-}
-
-.select-location__collapse-icon {
- color: #fff;
-}
-
-.select-location__sub-cell {
- background-color: rgb(36, 57, 84);
- padding: 15px 24px 15px 40px;
- display: flex;
- flex-direction: row;
- align-items: center;
- margin-top: 1px;
-}
-
-.select-location__sub-cell--selectable:hover {
- background-color: rgba(41,71,115,0.9);
-}
-
-.select-location__sub-cell--selected,
-.select-location__sub-cell--selected:hover {
- background-color: #44AD4D;
-}
-
-.select-location-relay-status {
- width: 16px;
- height: 16px;
- border-radius: 8px;
-}
-
-.select-location-relay-status--inactive {
- background: rgba(208, 2, 27, 0.95);
-}
-
-.select-location-relay-status--active {
- background: rgba(68, 173, 77, 0.9);
-} \ No newline at end of file
diff --git a/app/components/SelectLocation.js b/app/components/SelectLocation.js
index 88f1415b01..2f040763ef 100644
--- a/app/components/SelectLocation.js
+++ b/app/components/SelectLocation.js
@@ -1,12 +1,13 @@
// @flow
import * as React from 'react';
-import { Layout, Container, Header } from './Layout';
+import { Layout, Container } from './Layout';
import CustomScrollbars from './CustomScrollbars';
+import { Text, View } from 'reactxp';
+import { Button, CellButton, Label } from './styled';
+import styles from './SelectLocationStyles';
+import Img from './Img';
import Accordion from './Accordion';
-import ChevronDownSVG from '../assets/images/icon-chevron-down.svg';
-import ChevronUpSVG from '../assets/images/icon-chevron-up.svg';
-import TickSVG from '../assets/images/icon-tick.svg';
import type { SettingsReduxState, RelayLocationRedux, RelayLocationCityRedux } from '../redux/settings/reducers';
import type { RelayLocation } from '../lib/ipc-facade';
@@ -22,7 +23,7 @@ type State = {
};
export default class SelectLocation extends React.Component<SelectLocationProps, State> {
- _selectedCell: ?HTMLElement;
+ _selectedCell: ?CellButton;
_scrollView: ?CustomScrollbars;
state = {
@@ -50,6 +51,8 @@ export default class SelectLocation extends React.Component<SelectLocationProps,
// restore scroll to selected cell
const cell = this._selectedCell;
const scrollView = this._scrollView;
+ console.log(scrollView);
+ console.log(cell);
if(scrollView && cell) {
scrollView.scrollToElement(cell, 'middle');
@@ -59,29 +62,30 @@ export default class SelectLocation extends React.Component<SelectLocationProps,
render() {
return (
<Layout>
- <Header hidden={ true } style={ 'defaultDark' } />
<Container>
- <div className="select-location">
- <button className="select-location__close" onClick={ this.props.onClose } />
- <div className="select-location__container">
- <div className="select-location__header">
- <h2 className="select-location__title">Select location</h2>
- </div>
+ <View style={styles.select_location}>
+ <Button style={styles.close} onPress={ this.props.onClose } testName='close'>
+ <Img style={styles.close_icon} source='icon-close'/>
+ </Button>
+ <View style={styles.container}>
+ <View style={styles.header}>
+ <Text style={styles.title}>Select location</Text>
+ </View>
- <CustomScrollbars autoHide={ true } ref={ (ref) => this._scrollView = ref }>
- <div>
- <div className="select-location__subtitle">
+ <CustomScrollbars autoHide={ true } ref={ (ref) => this._scrollView = ref }>
+ <View>
+ <Text style={styles.subtitle}>
While connected, your real location is masked with a private and secure location in the selected region
- </div>
+ </Text>
{ this.props.settings.relayLocations.map((relayCountry) => {
return this._renderCountry(relayCountry);
}) }
- </div>
+ </View>
</CustomScrollbars>
- </div>
- </div>
+ </View>
+ </View>
</Container>
</Layout>
);
@@ -122,9 +126,9 @@ export default class SelectLocation extends React.Component<SelectLocationProps,
}
_relayStatusIndicator(active: boolean) {
- const statusClass = active ? 'select-location-relay-status--active' : 'select-location-relay-status--inactive';
+ const statusClass = active ? styles.relay_status__active : styles.relay_status__inactive;
- return (<div className={ 'select-location-relay-status ' + statusClass }></div>);
+ return (<View style={[ styles.relay_status, statusClass ]}></View>);
}
_renderCountry(relayCountry: RelayLocationRedux) {
@@ -142,47 +146,35 @@ export default class SelectLocation extends React.Component<SelectLocationProps,
e.stopPropagation();
};
- const countryClass = 'select-location__cell' +
- (isSelected ? ' select-location__cell--selected' : '') +
- (relayCountry.hasActiveRelays ? ' select-location__cell--selectable' : '');
-
- const onRef = isSelected ? (element) => {
- this._selectedCell = element;
- } : undefined;
-
return (
- <div key={ relayCountry.code } className="select-location__country">
- <div className={ countryClass }
- onClick={ handleSelect }
- ref={ onRef }>
- <div className="select-location__cell-content">
-
- <div className="select-location__cell-icon">
- { isSelected ?
- <TickSVG /> :
- this._relayStatusIndicator(relayCountry.hasActiveRelays) }
- </div>
+ <View key={ relayCountry.code } style={styles.country}>
+ <CellButton
+ style={ isSelected ? styles.cell_selected : null }
+ onPress={ handleSelect }
+ disabled={!relayCountry.hasActiveRelays}
+ testName='country'>
- <div className={ 'select-location__cell-label' +
- (relayCountry.hasActiveRelays ? '' : ' select-location__cell-label--inactive') }>
- { relayCountry.name }
- </div>
- </div>
+ { isSelected ?
+ <Img source='icon-tick' height='24' width='24' /> :
+ this._relayStatusIndicator(relayCountry.hasActiveRelays) }
- { relayCountry.cities.length > 1 && <button type="button" className="select-location__collapse-button" onClick={ handleCollapse }>
- { isExpanded ?
- <ChevronUpSVG className="select-location__collapse-icon" /> :
- <ChevronDownSVG className="select-location__collapse-icon" /> }
- </button> }
+ <Label>
+ { relayCountry.name }
+ </Label>
- </div>
+ { relayCountry.cities.length > 1 ?
+ isExpanded ?
+ <Img style={styles.collapse_button} onPress={ handleCollapse } source='icon-chevron-up' height='24' width='24' /> :
+ <Img style={styles.collapse_button} onPress={ handleCollapse } source='icon-chevron-down' height='24' width='24' />
+ : null }
+ </CellButton>
{ relayCountry.cities.length > 1 &&
- (<Accordion className="select-location__cities" height={ isExpanded ? 'auto' : 0 }>
+ (<Accordion height={ isExpanded ? 'auto' : 0 }>
{ relayCountry.cities.map((relayCity) => this._renderCity(relayCountry.code, relayCity)) }
</Accordion>)
}
- </div>
+ </View>
);
}
@@ -191,36 +183,30 @@ export default class SelectLocation extends React.Component<SelectLocationProps,
const isSelected = this._isSelected(relayLocation);
- const cityClass = 'select-location__sub-cell' +
- (isSelected ? ' select-location__sub-cell--selected' : '') +
- (relayCity.hasActiveRelays ? ' select-location__sub-cell--selectable' : '');
+ const onRef = isSelected ? (element) => {
+ this._selectedCell = element;
+ } : undefined;
const handleSelect = (relayCity.hasActiveRelays && !isSelected) ? () => {
this.props.onSelect(relayLocation);
} : undefined;
- const onRef = isSelected ? (element) => {
- this._selectedCell = element;
- } : undefined;
-
return (
- <div key={ `${countryCode}_${relayCity.code}` }
- className={ cityClass }
- onClick={ handleSelect }
- ref={ onRef }>
+ <CellButton key={ `${countryCode}_${relayCity.code}` }
+ onPress={ handleSelect }
+ disabled={!relayCity.hasActiveRelays}
+ style={isSelected ? styles.sub_cell__selected : styles.sub_cell}
+ testName='city'
+ ref={onRef}>
- <div className="select-location__cell-icon">
- { isSelected ?
- <TickSVG /> :
- this._relayStatusIndicator(relayCity.hasActiveRelays) }
- </div>
+ { isSelected ?
+ <Img source='icon-tick' height='24' width='24' /> :
+ this._relayStatusIndicator(relayCity.hasActiveRelays) }
- <div className={ 'select-location__cell-label' +
- (relayCity.hasActiveRelays ? '' : ' select-location__cell-label--inactive') }>
+ <Label>
{ relayCity.name }
- </div>
- </div>
+ </Label>
+ </CellButton>
);
}
-
}
diff --git a/app/components/SelectLocationStyles.js b/app/components/SelectLocationStyles.js
new file mode 100644
index 0000000000..52f17a58c1
--- /dev/null
+++ b/app/components/SelectLocationStyles.js
@@ -0,0 +1,88 @@
+// @flow
+import { createViewStyles, createTextStyles } from '../lib/styles';
+import { colors } from '../config';
+
+export default {
+ ...createViewStyles({
+ select_location: {
+ backgroundColor: colors.darkBlue,
+ height: '100%',
+ },
+ container: {
+ flexDirection: 'column',
+ flex: 1,
+ },
+ header:{
+ flex: 0,
+ marginBottom: 16,
+ },
+ close: {
+ marginLeft: 12,
+ marginTop: 12,
+ cursor: 'default',
+ },
+ close_icon:{
+ width: 24,
+ height: 24,
+ flex: 0,
+ opacity: 0.6,
+ },
+ relay_status: {
+ width: 16,
+ height: 16,
+ borderRadius: 8,
+ marginLeft: 8,
+ marginRight: 8,
+ },
+ relay_status__inactive: {
+ backgroundColor: colors.red95,
+ },
+ relay_status__active: {
+ backgroundColor: colors.green90,
+ },
+ country: {
+ flexDirection: 'column',
+ flex: 0,
+ },
+ collapse_button: {
+ height: 24,
+ width: 24,
+ alignSelf: 'flex-end',
+ },
+ sub_cell: {
+ paddingLeft: 40,
+ backgroundColor: colors.blue40,
+ },
+ sub_cell__selected: {
+ paddingLeft: 40,
+ backgroundColor: colors.green,
+ },
+ cell_selected: {
+ backgroundColor: colors.green,
+ },
+ }),
+ ...createTextStyles({
+ title: {
+ fontFamily: 'DINPro',
+ fontSize: 32,
+ fontWeight: '900',
+ lineHeight: 40,
+ color: colors.white,
+ paddingTop: 16,
+ paddingLeft: 24,
+ paddingRight: 24,
+ },
+ subtitle: {
+ fontFamily: 'Open Sans',
+ fontSize: 13,
+ fontWeight: '600',
+ color: colors.white60,
+ letterSpacing: -0.2,
+ paddingTop: 0,
+ paddingLeft: 24,
+ paddingRight: 24,
+ paddingBottom: 24,
+ flex: 0,
+ },
+ }),
+}; \ No newline at end of file
diff --git a/test/components/SelectLocation.spec.js b/test/components/SelectLocation.spec.js
index 59a8dc9770..4e2265c6dd 100644
--- a/test/components/SelectLocation.spec.js
+++ b/test/components/SelectLocation.spec.js
@@ -2,7 +2,7 @@
import { expect } from 'chai';
import * as React from 'react';
-import ReactTestUtils, { Simulate } from 'react-dom/test-utils';
+import { shallow } from 'enzyme';
import SelectLocation from '../../app/components/SelectLocation';
import type { SettingsReduxState } from '../../app/redux/settings/reducers';
@@ -48,7 +48,7 @@ describe('components/SelectLocation', () => {
};
const render = (props: SelectLocationProps) => {
- return ReactTestUtils.renderIntoDocument(
+ return shallow(
<SelectLocation { ...props } />
);
};
@@ -57,11 +57,8 @@ describe('components/SelectLocation', () => {
const props = makeProps(state, {
onClose: () => done()
});
- const domNode = ReactTestUtils.findRenderedDOMComponentWithClass(render(props), 'select-location__close');
- // See: https://github.com/facebook/flow/pull/5841
- if(domNode) {
- Simulate.click(domNode);
- }
+ const node = getComponent(render(props), 'close');
+ click(node);
});
it('should call select callback for country', (done) => {
@@ -77,9 +74,9 @@ describe('components/SelectLocation', () => {
}
}
});
- const elements = ReactTestUtils.scryRenderedDOMComponentsWithClass(render(props), 'select-location__cell');
+ const elements = getComponent(render(props), 'country');
expect(elements).to.have.length(1);
- Simulate.click(elements[0]);
+ click(elements.at(0));
});
it('should call select callback for city', (done) => {
@@ -95,9 +92,17 @@ describe('components/SelectLocation', () => {
}
}
});
- const elements = ReactTestUtils.scryRenderedDOMComponentsWithClass(render(props), 'select-location__sub-cell');
+ const elements = getComponent(render(props), 'city');
expect(elements).to.have.length(2);
- Simulate.click(elements[0]);
+ click(elements.at(0));
});
});
+
+function getComponent(container, testName) {
+ return container.findWhere( n => n.prop('testName') === testName);
+}
+
+function click(component) {
+ component.prop('onPress')();
+}