summaryrefslogtreecommitdiffhomepage
path: root/app
diff options
context:
space:
mode:
Diffstat (limited to 'app')
-rw-r--r--app/assets/css/global.css4
-rw-r--r--app/assets/css/style.css1
-rw-r--r--app/assets/images/icon-close-sml.svg2
-rw-r--r--app/components/AccountInput.js63
-rw-r--r--app/components/Img.js29
-rw-r--r--app/components/Login.css211
-rw-r--r--app/components/Login.js297
-rw-r--r--app/components/LoginStyles.js165
-rw-r--r--app/components/styled/CellButton.js41
9 files changed, 417 insertions, 396 deletions
diff --git a/app/assets/css/global.css b/app/assets/css/global.css
index 355ddd1bde..cf35bc2643 100644
--- a/app/assets/css/global.css
+++ b/app/assets/css/global.css
@@ -28,3 +28,7 @@ body {
width: 100%;
display: flex;
}
+
+::-webkit-input-placeholder {
+ color: rgba(41, 77, 115, 0.4);
+} \ No newline at end of file
diff --git a/app/assets/css/style.css b/app/assets/css/style.css
index 7d667f67e3..27403aaedf 100644
--- a/app/assets/css/style.css
+++ b/app/assets/css/style.css
@@ -7,7 +7,6 @@
/* app */
@import '../../components/PlatformWindow.css';
@import '../../components/CustomScrollbars.css';
-@import '../../components/Login.css';
@import '../../components/Connect.css';
@import '../../components/SelectLocation.css';
@import '../../components/Layout.css';
diff --git a/app/assets/images/icon-close-sml.svg b/app/assets/images/icon-close-sml.svg
index d06479241b..a379068d03 100644
--- a/app/assets/images/icon-close-sml.svg
+++ b/app/assets/images/icon-close-sml.svg
@@ -1 +1 @@
-<svg width="16" height="16" viewBox="0 0 16 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><title>icon close sml</title><desc>Created with Sketch.</desc><g id="Symbols" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" fill-opacity=".2"><g id="Views/Login-Inputted-filltered-prev-IDs" transform="translate(-268.000000, -403.000000)" fill="#294D73"><g id="Input" transform="translate(24.000000, 338.000000)"><g id="suggestions" transform="translate(0.000000, 49.000000)"><g id="1"><path d="M253,24 L255.290281,21.7097195 C255.681035,21.3189651 255.684193,20.6841926 255.294624,20.2946243 L255.705376,20.7053757 C255.316406,20.3164062 254.682248,20.3177522 254.290281,20.7097195 L252,23 L249.709719,20.7097195 C249.317752,20.3177522 248.683594,20.3164062 248.294624,20.7053757 L248.705376,20.2946243 C248.315807,20.6841926 248.318965,21.3189651 248.709719,21.7097195 L251,24 L248.709719,26.2902805 C248.318965,26.6810349 248.315807,27.3158074 248.705376,27.7053757 L248.294624,27.2946243 C248.683594,27.6835938 249.317752,27.6822478 249.709719,27.2902805 L252,25 L254.290281,27.2902805 C254.682248,27.6822478 255.316406,27.6835938 255.705376,27.2946243 L255.294624,27.7053757 C255.684193,27.3158074 255.681035,26.6810349 255.290281,26.2902805 L253,24 Z M252,32 C247.58208,32 244,28.41792 244,24 C244,19.58208 247.58208,16 252,16 C256.41792,16 260,19.58208 260,24 C260,28.41792 256.41792,32 252,32 Z" id="icon-close-sml"/></g></g></g></g></g></svg> \ No newline at end of file
+<svg width="16" height="16" viewBox="0 0 16 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><title>icon close sml</title><desc>Created with Sketch.</desc><g id="Symbols" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" fill-opacity="1"><g id="Views/Login-Inputted-filltered-prev-IDs" transform="translate(-268.000000, -403.000000)" fill="#294D73"><g id="Input" transform="translate(24.000000, 338.000000)"><g id="suggestions" transform="translate(0.000000, 49.000000)"><g id="1"><path d="M253,24 L255.290281,21.7097195 C255.681035,21.3189651 255.684193,20.6841926 255.294624,20.2946243 L255.705376,20.7053757 C255.316406,20.3164062 254.682248,20.3177522 254.290281,20.7097195 L252,23 L249.709719,20.7097195 C249.317752,20.3177522 248.683594,20.3164062 248.294624,20.7053757 L248.705376,20.2946243 C248.315807,20.6841926 248.318965,21.3189651 248.709719,21.7097195 L251,24 L248.709719,26.2902805 C248.318965,26.6810349 248.315807,27.3158074 248.705376,27.7053757 L248.294624,27.2946243 C248.683594,27.6835938 249.317752,27.6822478 249.709719,27.2902805 L252,25 L254.290281,27.2902805 C254.682248,27.6822478 255.316406,27.6835938 255.705376,27.2946243 L255.294624,27.7053757 C255.684193,27.3158074 255.681035,26.6810349 255.290281,26.2902805 L253,24 Z M252,32 C247.58208,32 244,28.41792 244,24 C244,19.58208 247.58208,16 252,16 C256.41792,16 260,19.58208 260,24 C260,28.41792 256.41792,32 252,32 Z" id="icon-close-sml"/></g></g></g></g></g></svg> \ No newline at end of file
diff --git a/app/components/AccountInput.js b/app/components/AccountInput.js
index 418cb3b77e..dd2d3c7621 100644
--- a/app/components/AccountInput.js
+++ b/app/components/AccountInput.js
@@ -1,6 +1,8 @@
// @flow
import * as React from 'react';
import { formatAccount } from '../lib/formatters';
+import { TextInput } from 'reactxp';
+import { colors } from '../config';
// @TODO: move it into types.js
@@ -39,8 +41,7 @@ export default class AccountInput extends React.Component<AccountInputProps, Acc
selectionRange: [0, 0]
};
- _ref: ?HTMLInputElement;
- _ignoreSelect = false;
+ _ref: ?TextInput;
constructor(props: AccountInputProps) {
super(props);
@@ -76,16 +77,20 @@ export default class AccountInput extends React.Component<AccountInputProps, Acc
const displayString = formatAccount(this.state.value || '');
const { value, onChange, onEnter, ...otherProps } = this.props; // eslint-disable-line no-unused-vars
return (
- <input { ...otherProps }
- type="text"
+ <TextInput { ...otherProps }
value={ displayString }
- onChange={ () => {} }
- onSelect={ this.onSelect }
- onKeyUp={ this.onKeyUp }
- onKeyDown={ this.onKeyDown }
+ onSelectionChange={ this.onSelect }
onPaste={ this.onPaste }
onCut={ this.onCut }
- ref={ (ref) => this.onRef(ref) } />
+ ref={ (ref) => this.onRef(ref) }
+ autoCorrect={ false }
+ onChangeText={ () => {} }
+ onKeyPress={ this.onKeyPress }
+ returnKeyType="done"
+ keyboardType="numeric"
+ placeholderTextColor={colors.blue20}
+ testName='AccountInput'
+ />
);
}
@@ -203,56 +208,42 @@ export default class AccountInput extends React.Component<AccountInputProps, Acc
}
// Events
-
- onKeyDown = (e: KeyboardEvent) => {
+ _ignoreSelect: boolean;
+ onKeyPress = (e: KeyboardEvent) => {
const { value, selectionRange } = this.state;
if(e.which === 8) { // backspace
+ this._ignoreSelect = true;
const result = this.remove(value, selectionRange);
e.preventDefault();
- this._ignoreSelect = true;
-
this.setState(result, () => {
+ this._ignoreSelect = false;
if(this.props.onChange) {
this.props.onChange(result.value);
}
});
} else if(/^[0-9]$/.test(e.key)) { // digits or cmd+v
+ this._ignoreSelect = true;
const result = this.insert(value, e.key, selectionRange);
e.preventDefault();
- this._ignoreSelect = true;
-
this.setState(result, () => {
+ this._ignoreSelect = false;
if(this.props.onChange) {
this.props.onChange(result.value);
}
});
- }
- }
-
- onKeyUp = (e: KeyboardEvent) => {
- this._ignoreSelect = false;
-
- if(e.which === 13 && this.props.onEnter) {
+ } else if(e.which === 13 && this.props.onEnter) {
this.props.onEnter();
}
}
- onSelect = (e: Event) => {
- const ref = e.target;
- if(!(ref instanceof HTMLInputElement)) {
- throw new Error('ref must be an instance of HTMLInputElement');
- }
-
- if(this._ignoreSelect) {
+ onSelect = (start: number, end: number) => {
+ if (this._ignoreSelect){
return;
}
-
- const start = ref.selectionStart;
- const end = ref.selectionEnd;
- const selRange = this.toInternalSelectionRange(this.sanitize(ref.value), [start, end]);
+ const selRange = this.toInternalSelectionRange(this.sanitize(this.state.value), [start, end]);
this.setState({ selectionRange: selRange });
}
@@ -295,15 +286,14 @@ export default class AccountInput extends React.Component<AccountInputProps, Acc
}
}
- onRef(ref: ?HTMLInputElement) {
+ onRef = (ref: ?TextInput) => {
this._ref = ref;
if(!ref) { return; }
const { value, selectionRange } = this.state;
const domRange = this.toDomSelection(value, selectionRange);
- ref.selectionStart = domRange[0];
- ref.selectionEnd = domRange[1];
+ ref.selectRange(domRange[0], domRange[1]);
}
focus() {
@@ -311,5 +301,4 @@ export default class AccountInput extends React.Component<AccountInputProps, Acc
this._ref.focus();
}
}
-
} \ No newline at end of file
diff --git a/app/components/Img.js b/app/components/Img.js
index c4fb7637d5..d124eb7f49 100644
--- a/app/components/Img.js
+++ b/app/components/Img.js
@@ -1,15 +1,27 @@
// @flow
-import React from 'react';
-import { View, Component } from 'reactxp';
+import * as React from 'react';
+import { View, Component, Types } from 'reactxp';
-export default class Img extends Component {
- props: {
+type ImgProps = {
source: string,
- tintColor?: string
+ tintColor?: string,
+ hoverStyle?: Types.ViewStyle,
+ disabled?: boolean,
};
+type State = { hovered: boolean };
+
+export default class Img extends Component<ImgProps, State> {
+
+ state = { hovered: false };
+
+ onHoverStart = () => !this.props.disabled ? this.setState({ hovered: true }) : null;
+ onHoverEnd = () => !this.props.disabled ? this.setState({ hovered: false }) : null;
+
+ getHoverStyle = () => this.state.hovered ? this.props.hoverStyle || null : null;
+
render() {
- const { source, ...otherProps } = this.props;
+ const { source, style, onMouseEnter, onMouseLeave, ...otherProps } = this.props;
const tintColor = this.props.tintColor;
const url = './assets/images/' + source + '.svg';
let image;
@@ -34,7 +46,10 @@ export default class Img extends Component {
}
return (
- <View { ...otherProps }>
+ <View { ...otherProps }
+ onMouseEnter={onMouseEnter || this.onHoverStart}
+ onMouseLeave={onMouseLeave || this.onHoverEnd}
+ style={[style, this.getHoverStyle()]}>
{ image }
</View>);
}
diff --git a/app/components/Login.css b/app/components/Login.css
deleted file mode 100644
index 9311212ec9..0000000000
--- a/app/components/Login.css
+++ /dev/null
@@ -1,211 +0,0 @@
-.login {
- height: 100%;
- display: flex;
- flex-direction: column;
-}
-
-.login-footer {
- background-color: #192E45;
- padding: 18px 24px 24px;
- flex: 0 0 auto;
- transition: transform 0.25s ease-in-out;
-}
-
-.login-footer--invisible {
- transform: translateY(100%);
-}
-
-.login-form__status-icon {
- flex: 0 0 auto; /* never collapse or grow */
- text-align: center;
- margin-bottom: 44px;
-
- /* use fixed size to make space and avoid jitter when changing <img> visibility */
- height: 60px;
-}
-
-.login-footer__prompt {
- font-family: "Open Sans";
- font-size: 13px;
- line-height: 18px;
- font-weight: 600;
- color: rgba(255,255,255,0.8);
- margin-bottom: 8px;
-}
-
-.login-form {
- display: flex;
- flex-direction: column;
- padding: 0 24px;
- margin: 83px 0 auto;
-}
-
-.login-form__title {
- font-family: DINPro;
- font-size: 32px;
- font-weight: 900;
- line-height: 1.25em;
- color: #FFFFFF;
- margin-bottom: 7px;
-}
-
-.login-form__subtitle {
- font-family: "Open Sans";
- font-size: 13px;
- font-weight: 600;
- line-height: normal;
- color: rgba(255,255,255,0.8);
- margin-bottom: 8px;
-}
-
-.login-form__account-input-container {
- /*
- Provides an anchor for input-container
- to be properly positioned when it gains absolute
- position to overlap the footer view.
- */
- position: relative;
-
- /* push border to the outer side of the layout grid */
- margin-left: -2px;
- margin-right: -2px;
-}
-
-.login-form__account-input-group {
- border: 2px solid transparent;
- border-radius: 8px;
- transition: border 0.25s ease-in-out;
- overflow: hidden;
-}
-
-.login-form__account-input-group--active {
- position: absolute;
- border-color: rgba(25,46,69,0.4);
-}
-
-.login-form__account-input-group--inactive {
- opacity: 0.6;
-}
-
-.login-form__account-input-group--error {
- border-color: rgba(208,2,27,0.4);
-}
-
-.login-form__account-input-group--error .login-form__account-input-textfield {
- color: #D0021B;
-}
-
-.login-form__account-input-backdrop {
- background-color: #FFFFFF;
- display: flex;
- flex-direction: row;
- transition: background-color 0.25s ease-in-out;
-}
-
-.login-form__account-input-textfield::-webkit-input-placeholder {
- color: rgba(41,77,115,0.4);
-}
-
-.login-form__account-input-textfield {
- width: 100%;
- border: 0;
- padding: 10px 12px 12px 12px;
- font-family: DINPro;
- font-size: 20px;
- font-weight: 900;
- line-height: 26px;
- color: #294D73;
- background-color: transparent;
- flex: 1 1 auto;
- transition: 0.3s color ease-in-out;
-}
-
-.login-form__account-input-textfield--inactive {
- background-color: rgba(255,255,255,0.6);
-}
-
-.login-form__account-input-button {
- flex: 0 0 auto;
- border: 0;
- width: 48px;
- background-color: transparent;
- transition-duration: 0.3s;
- transition-property: background-color, opacity;
- transition-timing-function: ease-in-out;
-}
-
-.login-form__account-input-button-icon {
- display: inline-block;
- vertical-align: middle;
-}
-
-.login-form__account-input-button-icon path {
- fill: rgba(41,77,115,0.2);
- transition: fill 0.3s ease-in-out;
-}
-
-.login-form__account-input-button--active {
- background-color: #44ad4d;
-}
-
-.login-form__account-input-button--active .login-form__account-input-button-icon path {
- fill: #fff;
-}
-
-.login-form__account-input-button--active:hover {
- background-color: rgba(68, 173, 76, 0.9);
-}
-
-.login-form__account-input-button--invisible {
- visibility: hidden;
- opacity: 0;
-}
-
-.login-form__account-dropdown-container {
- overflow: hidden;
- transition: height 0.25s ease-in-out;
-}
-
-.login-form__account-dropdown__item {
- display: flex;
- flex-direction: row;
- border-top: 1px solid rgba(25,46,69,0.4);
- background-color: rgba(255,255,255,0.6);
- background-clip: padding-box;
- transition: background-color 0.25s ease-in-out;
-}
-
-.login-form__account-dropdown__item:hover {
- background-color: rgba(255,255,255,0.65);
-}
-
-.login-form__account-dropdown__label {
- flex: 1 1 auto;
- font-family: DINPro;
- font-size: 20px;
- font-weight: 900;
- line-height: 26px;
- color: #294D73;
- border: 0;
- background: transparent;
- padding: 10px 12px 12px 12px;
- text-align: left;
-}
-
-.login-form__account-dropdown__remove {
- background: transparent;
- border: 0;
- padding: 10px 12px 12px 12px;
-
- /* center SVG within button */
- display: flex;
- justify-content: center;
-}
-
-.login-form__account-dropdown__remove path {
- transition: fill-opacity 0.25s ease-in-out;
-}
-
-.login-form__account-dropdown__remove:hover path {
- fill-opacity: 1;
-} \ No newline at end of file
diff --git a/app/components/Login.js b/app/components/Login.js
index 3231fc7710..d510f96d7b 100644
--- a/app/components/Login.js
+++ b/app/components/Login.js
@@ -1,11 +1,14 @@
// @flow
import * as React from 'react';
+import { Component, Text, View, Animated, Styles } from 'reactxp';
import { Layout, Container, Header } from './Layout';
import AccountInput from './AccountInput';
+import Accordion from './Accordion';
import { formatAccount } from '../lib/formatters';
-import ExternalLinkSVG from '../assets/images/icon-extLink.svg';
-import LoginArrowSVG from '../assets/images/icon-arrow.svg';
-import RemoveAccountSVG from '../assets/images/icon-close-sml.svg';
+import Img from './Img';
+import { BlueButton, Label, CellButton } from './styled';
+import styles from './LoginStyles';
+import { colors } from '../config';
import type { AccountReduxState } from '../redux/account/reducers';
import type { AccountToken } from '../lib/ipc-facade';
@@ -23,14 +26,24 @@ export type LoginPropTypes = {
type State = {
notifyOnFirstChangeAfterFailure: boolean,
isActive: boolean,
- dropdownHeight: number
+ footerHeight: number,
+ animatedFooterValue: Animated.Value,
+ animatedLoginButtonValue: Animated.Value,
+ animation: ?Animated.CompositeAnimation,
+ footerAnimationStyle: ?Animated.Style,
+ loginButtonAnimationStyle: ?Animated.Style,
};
-export default class Login extends React.Component<LoginPropTypes, State> {
+export default class Login extends Component<LoginPropTypes, State> {
state = {
notifyOnFirstChangeAfterFailure: false,
- isActive: false,
- dropdownHeight: 0
+ isActive: true,
+ footerHeight: 0,
+ animatedFooterValue: Animated.createValue(0),
+ animatedLoginButtonValue: Animated.createValue(0),
+ animation: null,
+ footerAnimationStyle: {},
+ loginButtonAnimationStyle: {},
};
constructor(props: LoginPropTypes) {
@@ -38,14 +51,12 @@ export default class Login extends React.Component<LoginPropTypes, State> {
if(props.account.status === 'failed') {
this.state.notifyOnFirstChangeAfterFailure = true;
}
- }
-
- componentDidMount() {
- this._updateDropdownHeight();
- }
-
- componentDidUpdate() {
- this._updateDropdownHeight();
+ this.state.footerAnimationStyle = Styles.createAnimatedViewStyle({
+ transform: [{translateY: this.state.animatedFooterValue }]
+ });
+ this.state.loginButtonAnimationStyle = Styles.createAnimatedViewStyle({
+ backgroundColor: Animated.interpolate(this.state.animatedLoginButtonValue, [0.0, 1.0], [colors.white, colors.green]),
+ });
}
componentWillReceiveProps(nextProps: LoginPropTypes) {
@@ -55,46 +66,73 @@ export default class Login extends React.Component<LoginPropTypes, State> {
if(prev.status !== next.status && next.status === 'failed') {
this.setState({ notifyOnFirstChangeAfterFailure: true });
}
+
+ this._animate(nextProps);
}
render() {
- const footerClass = this._shouldShowFooter() ? '' : 'login-footer--invisible';
return (
<Layout>
<Header showSettings={ true } onSettings={ this.props.onSettings } />
<Container>
- <div className="login">
- <div className="login-form">
+ <View style={styles.login}>
+ <View style={styles.login_form}>
{ this._getStatusIcon() }
- <div className="login-form__title">{ this._formTitle() }</div>
+ <Text style={styles.title}>{ this._formTitle() }</Text>
- {this._shouldShowLoginForm() && <div className='login-form__fields'>
+ {this._shouldShowLoginForm() && <View>
{ this._createLoginForm() }
- </div>}
- </div>
+ </View>}
+ </View>
- <div className={ 'login-footer ' + footerClass }>
+ <Animated.View onLayout={this._onFooterLayout} style={[styles.login_footer, this.state.footerAnimationStyle]} testName={'footerVisibility ' + this._shouldShowFooter(this.props).toString()}>
{ this._createFooter() }
- </div>
- </div>
+ </Animated.View>
+ </View>
</Container>
</Layout>
);
}
- _onCreateAccount = () => this.props.onExternalLink('createAccount');
- _onFocus = () => this.setState({ isActive: true });
+ _onCreateAccount = () => this.props.onExternalLink('createAccount')
+
+ _onFocus = () => this.setState({ isActive: true }, () => {
+ this._animate(this.props);
+ })
+
_onBlur = (e) => {
const relatedTarget = e.relatedTarget;
// restore focus if click happened within dropdown
- if(relatedTarget && this._isWithinDropdown(relatedTarget)) {
+ if(relatedTarget) {
e.target.focus();
return;
}
- this.setState({ isActive: false });
+ this.setState({ isActive: false }, () => {
+ this._animate(this.props);
+ });
+ }
+
+ _animate = (props: LoginPropTypes) => {
+ if (this.state.animation) {
+ this.state.animation.stop();
+ }
+ const accountToken = props.account.accountToken || [];
+
+ const footerPosition = this._shouldShowFooter(props) ? 0 : this.state.footerHeight;
+ const loginButtonValue = (accountToken.length) > 0 ? 1 : 0;
+ this._setAnimation(this._getFooterAnimation(footerPosition), this._getLoginButtonAnimation(loginButtonValue));
+ }
+
+ _setAnimation = (footerAnimation: Animated.CompositeAnimation, loginButtonAnimation: Animated.CompositeAnimation) => {
+ let compositeAnimation = Animated.parallel([ footerAnimation, loginButtonAnimation]);
+ this.setState({animation: compositeAnimation}, () => {
+ compositeAnimation.start(() => this.setState({
+ animation: null
+ }));
+ });
}
_onLogin = () => {
@@ -140,99 +178,113 @@ export default class Login extends React.Component<LoginPropTypes, State> {
_getStatusIcon() {
const statusIconPath = this._getStatusIconPath();
- return <div className="login-form__status-icon">
+ return <View style={ styles.status_icon}>
{ statusIconPath ?
- <img src={ statusIconPath } alt="" /> :
+ <Img source={ statusIconPath } height='48' width='48' alt="" /> :
null }
- </div>;
+ </View>;
}
_getStatusIconPath(): ?string {
switch(this.props.account.status) {
case 'logging in':
- return './assets/images/icon-spinner.svg';
+ return 'icon-spinner';
case 'failed':
- return './assets/images/icon-fail.svg';
+ return 'icon-fail';
case 'ok':
- return './assets/images/icon-success.svg';
+ return 'icon-success';
default:
return undefined;
}
}
- _accountInputGroupClass(): string {
- const classes = ['login-form__account-input-group'];
+ _accountInputGroupStyles(): Array<Object> {
+ const classes = [styles.account_input_group];
if(this.state.isActive) {
- classes.push('login-form__account-input-group--active');
+ classes.push(styles.account_input_group__active);
}
switch(this.props.account.status) {
case 'logging in':
- classes.push('login-form__account-input-group--inactive');
+ classes.push(styles.account_input_group__inactive);
break;
case 'failed':
- classes.push('login-form__account-input-group--error');
+ classes.push(styles.account_input_group__error);
break;
}
- return classes.join(' ');
+ return classes;
}
- _accountInputButtonClass(): string {
+ _accountInputButtonStyles(): Array<Object> {
+ const { status } = this.props.account;
+ const classes = [styles.account_input_button];
+
+ if(status === 'logging in') {
+ classes.push(styles.account_input_button__invisible);
+ }
+
+ classes.push(this.state.loginButtonAnimationStyle);
+
+ return classes;
+ }
+
+ _accountInputArrowStyles(): Array<Object> {
const { accountToken, status } = this.props.account;
- const classes = ['login-form__account-input-button'];
+ const classes = [styles.account_input_button];
if(accountToken && accountToken.length > 0) {
- classes.push('login-form__account-input-button--active');
+ classes.push(styles.account_input_button__active);
}
if(status === 'logging in') {
- classes.push('login-form__account-input-button--invisible');
+ classes.push(styles.account_input_button__invisible);
}
- return classes.join(' ');
+ return classes;
}
- _shouldEnableAccountInput() {
+ _shouldEnableAccountInput(props: LoginPropTypes) {
// enable account input always except when "logging in"
- return this.props.account.status !== 'logging in';
+ return props.account.status !== 'logging in';
}
- _shouldShowAccountHistory() {
- return this._shouldEnableAccountInput() &&
+ _shouldShowAccountHistory(props: LoginPropTypes) {
+ return this._shouldEnableAccountInput(props) &&
this.state.isActive &&
- this.props.account.accountHistory.length > 0;
+ props.account.accountHistory.length > 0;
}
_shouldShowLoginForm() {
return this.props.account.status !== 'ok';
}
- _shouldShowFooter() {
- const { status } = this.props.account;
- return (status === 'none' || status === 'failed') && !this._shouldShowAccountHistory();
+ _shouldShowFooter(props: LoginPropTypes) {
+ const { status } = props.account;
+ return (status === 'none' || status === 'failed') && !this._shouldShowAccountHistory(props);
}
- // helper function to calculate and save dropdown element's height
- // this is a no-op of the height didn't change since last update
- _updateDropdownHeight() {
- const element = this._accountDropdownElement;
- if(element && this.state.dropdownHeight !== element.clientHeight) {
- this.setState({
- dropdownHeight: element.clientHeight
- });
- }
+ _getFooterAnimation(toValue: number){
+ return Animated.timing(this.state.animatedFooterValue, {
+ toValue: toValue,
+ easing: Animated.Easing.InOut(),
+ duration: 250,
+ useNativeDriver: true,
+ });
}
- // returns true if DOM node is within dropdown hierarchy
- _isWithinDropdown(relatedTarget) {
- const dropdownElement = this._accountDropdownElement;
- return dropdownElement && dropdownElement.contains(relatedTarget);
+ _onFooterLayout = (layout) => {
+ this.setState({footerHeight: layout.height});
}
- // container element used for measuring the height of the accounts dropdown
- _accountDropdownElement: ?HTMLElement;
- _onAccountDropdownContainerRef = ref => this._accountDropdownElement = ref;
+ _getLoginButtonAnimation(toValue: number){
+ return Animated.timing(this.state.animatedLoginButtonValue, {
+ toValue: toValue,
+ easing: Animated.Easing.Linear(),
+ duration: 250,
+ useNativeDriver: true,
+ });
+ }
_onSelectAccountFromHistory = (accountToken) => {
this.props.onAccountTokenChange(accountToken);
@@ -241,9 +293,6 @@ export default class Login extends React.Component<LoginPropTypes, State> {
_createLoginForm() {
const { accountHistory, accountToken } = this.props.account;
- const dropdownStyles = {
- height: this._shouldShowAccountHistory() ? this.state.dropdownHeight : 0
- };
// auto-focus on account input when failed to log in
// do not refactor this into instance method,
@@ -254,47 +303,45 @@ export default class Login extends React.Component<LoginPropTypes, State> {
}
};
- return <div>
- <div className="login-form__subtitle">{ this._formSubtitle() }</div>
- <div className="login-form__account-input-container">
- <div className={ this._accountInputGroupClass() }>
- <div className="login-form__account-input-backdrop">
- <AccountInput className="login-form__account-input-textfield"
- type="text"
- placeholder="e.g 0000 0000 0000"
- onFocus={ this._onFocus }
- onBlur={ this._onBlur }
- onChange={ this._onInputChange }
- onEnter={ this._onLogin }
- value={ accountToken || '' }
- disabled={ !this._shouldEnableAccountInput() }
- autoFocus={ true }
- ref={ autoFocusOnFailure } />
- <button className={ this._accountInputButtonClass() } onClick={ this._onLogin }>
- <LoginArrowSVG className="login-form__account-input-button-icon" />
- </button>
- </div>
- <div style={ dropdownStyles } className="login-form__account-dropdown-container">
- <div ref={ this._onAccountDropdownContainerRef }>
- { <AccountDropdown
- items={ accountHistory.slice().reverse() }
- onSelect={ this._onSelectAccountFromHistory }
- onRemove={ this.props.onRemoveAccountTokenFromHistory } /> }
- </div>
- </div>
- </div>
- </div>
- </div>;
+ return <View style= {styles.login}>
+ <Text style={ styles.subtitle }>{ this._formSubtitle() }</Text>
+ <View style={ this._accountInputGroupStyles() }>
+ <View style={ styles.account_input_backdrop}>
+ <AccountInput style={styles.account_input_textfield}
+ type="text"
+ placeholder="e.g 0000 0000 0000"
+ placeholderTextColor={colors.blue40}
+ onFocus={ this._onFocus }
+ onBlur={ this._onBlur }
+ onChange={ this._onInputChange }
+ onEnter={ this._onLogin }
+ value={ accountToken || '' }
+ disabled={ !this._shouldEnableAccountInput(this.props) }
+ autoFocus={ true }
+ ref={ autoFocusOnFailure }
+ testName='AccountInput'/>
+ <Animated.View style={this._accountInputButtonStyles()} onPress={ this._onLogin } testName='account-input-button'>
+ <Img style={this._accountInputArrowStyles()} source='icon-arrow' height='16' width='24' tintColor='currentColor' />
+ </Animated.View>
+ </View>
+ <Accordion height={ this._shouldShowAccountHistory(this.props) ? 'auto' : 0 }>
+ { <AccountDropdown
+ items={ accountHistory.slice().reverse() }
+ onSelect={ this._onSelectAccountFromHistory }
+ onRemove={ this.props.onRemoveAccountTokenFromHistory } /> }
+ </Accordion>
+ </View>
+ </View>;
}
_createFooter() {
- return <div>
- <div className="login-footer__prompt">{ 'Don\'t have an account number?' }</div>
- <button className="button button--primary" onClick={ this._onCreateAccount }>
- <span className="button-label">Create account</span>
- <ExternalLinkSVG className="button-icon button-icon--16" />
- </button>
- </div>;
+ return <View>
+ <Text style={ styles.login_footer__prompt}>{ 'Don\'t have an account number?' }</Text>
+ <BlueButton onPress={ this._onCreateAccount }>
+ <Label>Create account</Label>
+ <Img source='icon-extLink' height='16' width='16' />
+ </BlueButton>
+ </View>;
}
}
@@ -308,7 +355,7 @@ class AccountDropdown extends React.Component<AccountDropdownProps> {
render() {
const uniqueItems = [...new Set(this.props.items)];
return (
- <div className="login-form__account-dropdown">
+ <View>
{ uniqueItems.map(token => (
<AccountDropdownItem key={ token }
value={ token }
@@ -316,7 +363,7 @@ class AccountDropdown extends React.Component<AccountDropdownProps> {
onSelect={ this.props.onSelect }
onRemove={ this.props.onRemove } />
)) }
- </div>
+ </View>
);
}
}
@@ -330,15 +377,19 @@ type AccountDropdownItemProps = {
class AccountDropdownItem extends React.Component<AccountDropdownItemProps> {
render() {
- return (
- <div className="login-form__account-dropdown__item">
- <button className="login-form__account-dropdown__label"
- onClick={ () => this.props.onSelect(this.props.value) }>{ this.props.label }</button>
- <button className="login-form__account-dropdown__remove"
- onClick={ () => this.props.onRemove(this.props.value) }>
- <RemoveAccountSVG />
- </button>
- </div>
- );
+ return (<View>
+ <View style={ styles.account_dropdown__spacer }/>
+ <CellButton style={ styles.account_dropdown__item } cellHoverStyle={ styles.account_dropdown__item_hover }>
+ <Label style={styles.account_dropdown__label} cellHoverStyle={ styles.account_dropdown__label_hover } onPress={ () => this.props.onSelect(this.props.value) }>
+ { this.props.label }
+ </Label>
+ <Img style={styles.account_dropdown__remove}
+ cellHoverStyle={ styles.account_dropdown__remove_cell_hover }
+ hoverStyle={ styles.account_dropdown__remove_hover }
+ source='icon-close-sml'
+ height='16' width='16'
+ onPress={ () => this.props.onRemove(this.props.value) }/>
+ </CellButton>
+ </View>);
}
}
diff --git a/app/components/LoginStyles.js b/app/components/LoginStyles.js
new file mode 100644
index 0000000000..471ab045cc
--- /dev/null
+++ b/app/components/LoginStyles.js
@@ -0,0 +1,165 @@
+// @flow
+import { createViewStyles, createTextStyles } from '../lib/styles';
+import { colors } from '../config';
+
+export default {
+ ...createViewStyles({
+ login: {
+ height: '100%',
+ },
+ login_footer: {
+ backgroundColor: colors.darkBlue,
+ paddingTop: 18,
+ paddingBottom:16,
+ flex: 0,
+ },
+ status_icon: {
+ flex: 0,
+ height: 48,
+ marginBottom: 30,
+ justifyContent: 'center',
+ },
+ login_form:{
+ flex:1,
+ flexDirection: 'column',
+ justifyContent: 'flex_end',
+ overflow:'visible',
+ paddingTop: 0,
+ paddingBottom: 0,
+ paddingLeft: 24,
+ paddingRight: 24,
+ marginTop: 83,
+ marginBottom:0,
+ marginRight: 0,
+ marginLeft:0,
+ },
+ account_input_group: {
+ borderWidth: 2,
+ borderRadius: 8,
+ borderColor: 'transparent',
+ },
+ account_input_group__active: {
+ borderColor: colors.darkBlue,
+ },
+ account_input_group__inactive: {
+ opacity: 0.6,
+ },
+ account_input_group__error: {
+ borderColor: colors.red40,
+ color: colors.red,
+ },
+ account_input_textfield: {
+ color: colors.blue,
+ },
+ account_input_backdrop: {
+ backgroundColor: colors.white,
+ borderColor: colors.darkBlue,
+ flexDirection: 'row',
+ },
+ account_input_textfield__inactive: {
+ backgroundColor: colors.white60,
+ },
+ account_input_button: {
+ flex: 0,
+ border: 0,
+ width: 48,
+ alignItems: 'center',
+ justifyContent: 'center',
+ color: colors.blue20,
+ },
+ account_input_button__active: {
+ color: colors.white,
+ },
+ account_input_button__invisible: {
+ color: colors.white,
+ backgroundColor: colors.white,
+ visibility: 'hidden',
+ opacity: 0,
+ },
+ account_dropdown__spacer: {
+ height: 1,
+ backgroundColor: colors.darkBlue,
+ },
+ account_dropdown__item: {
+ paddingTop: 10,
+ paddingRight: 12,
+ paddingLeft: 12,
+ paddingBottom: 12,
+ marginBottom: 0,
+ flexDirection: 'row',
+ backgroundColor: colors.white60,
+ },
+ account_dropdown__item_hover: {
+ backgroundColor: colors.white40,
+ },
+ account_dropdown__remove: {
+ justifyContent: 'center',
+ color: colors.blue40,
+ },
+ account_dropdown__remove_cell_hover: {
+ color: colors.blue60,
+ },
+ account_dropdown__remove_hover: {
+ color: colors.blue,
+ },
+ account_dropdown__label_hover: {
+ color: colors.blue,
+ },
+ }),
+ ...createTextStyles({
+ login_footer__prompt: {
+ color: colors.white80,
+ fontFamily: 'Open Sans',
+ fontSize: 13,
+ fontWeight: '600',
+ lineHeight: 18,
+ letterSpacing: -0.2,
+ marginLeft: 24,
+ marginRight: 24,
+ },
+ title: {
+ fontFamily: 'DINPro',
+ fontSize: 32,
+ fontWeight: '900',
+ lineHeight: 44,
+ letterSpacing: -0.7,
+ color: colors.white,
+ marginBottom: 7,
+ flex:0,
+ },
+ subtitle: {
+ fontFamily: 'Open Sans',
+ fontSize: 13,
+ fontWeight: '600',
+
+ letterSpaceing: -0.2,
+ color: colors.white80,
+ marginBottom: 8,
+ },
+ account_input_textfield: {
+ border: 0,
+ paddingTop: 10,
+ paddingRight: 12,
+ paddingLeft: 12,
+ paddingBottom: 12,
+ fontFamily: 'DINPro',
+ fontSize: 20,
+ fontWeight: 900,
+ lineHeight: 26,
+ color: colors.blue,
+ backgroundColor: 'transparent',
+ flex: 1,
+ },
+ account_dropdown__label: {
+ flex: 1,
+ fontFamily: 'DINPro',
+ fontSize: 20,
+ fontWeight: '900',
+ lineHeight: 26,
+ color: colors.blue80,
+ border: 0,
+ textAlign: 'left',
+ marginLeft: 0,
+ },
+ }),
+};
diff --git a/app/components/styled/CellButton.js b/app/components/styled/CellButton.js
index 064c28ca2c..400a7ac6de 100644
--- a/app/components/styled/CellButton.js
+++ b/app/components/styled/CellButton.js
@@ -1,7 +1,7 @@
// @flow
import * as React from 'react';
-import { Text, Component } from 'reactxp';
+import { Text, Component, Types } from 'reactxp';
import { Button } from './Button';
import { colors } from '../../config';
@@ -10,6 +10,7 @@ import { createViewStyles, createTextStyles } from '../../lib/styles';
const styles = {
...createViewStyles({
cell:{
+ backgroundColor: colors.blue80,
paddingTop: 14,
paddingBottom: 14,
paddingLeft: 16,
@@ -39,11 +40,13 @@ const styles = {
color: colors.white,
},
icon: {
+ color: colors.white60,
marginLeft: 8,
},
}),
...createTextStyles({
label:{
+ color: colors.white,
alignSelf: 'center',
fontFamily: 'DINPro',
fontSize: 20,
@@ -53,6 +56,7 @@ const styles = {
marginLeft: 8,
},
subtext:{
+ color: colors.white60,
fontFamily: 'Open Sans',
fontSize: 13,
fontWeight: '800',
@@ -65,26 +69,31 @@ const styles = {
export class SubText extends Text {}
export class Label extends Text {}
-export default class CellButton extends Component {
- props: {
- children?: React.Node,
- disabled: boolean,
- };
+type CellButtonProps = {
+ children?: React.Node,
+ disabled?: boolean,
+ cellHoverStyle?: Types.ViewStyle,
+ style?: Types.ViewStyle
+};
+
+type State = { hovered: boolean };
+
+export default class CellButton extends Component<CellButtonProps, State> {
state = { hovered: false };
- textStyle = () => this.state.hovered ? styles.white80 : styles.white;
- iconStyle = () => this.state.hovered ? styles.white40 : styles.white60;
- subtextStyle = () => this.state.hovered ? styles.white40 : styles.white60;
- backgroundStyle = () => this.state.hovered ? styles.blueHover : styles.blue;
+ textStyle = (cellHoverStyle?: Types.ViewStyle) => this.state.hovered ? cellHoverStyle || styles.white80 : null;
+ iconStyle = (cellHoverStyle?: Types.ViewStyle) => this.state.hovered ? cellHoverStyle || styles.white40 : null;
+ subtextStyle = (cellHoverStyle?: Types.ViewStyle) => this.state.hovered ? cellHoverStyle || styles.white40 : null;
+ backgroundStyle = (cellHoverStyle?: Types.ViewStyle) => this.state.hovered ? cellHoverStyle || styles.blueHover : null;
onHoverStart = () => !this.props.disabled ? this.setState({ hovered: true }) : null;
onHoverEnd = () => !this.props.disabled ? this.setState({ hovered: false }) : null;
render() {
- const { children, ...otherProps } = this.props;
+ const { children, style, cellHoverStyle, ...otherProps } = this.props;
return (
- <Button style={[ styles.cell, this.backgroundStyle() ]}
+ <Button style={[ styles.cell, style, this.backgroundStyle(cellHoverStyle) ]}
onHoverStart={this.onHoverStart}
onHoverEnd={this.onHoverEnd}
{...otherProps}>
@@ -94,19 +103,19 @@ export default class CellButton extends Component {
let updatedProps = {};
if(node.type.name === 'Label') {
- updatedProps = { style: [styles.label, this.textStyle(), node.props.style]};
+ updatedProps = { style: [styles.label, node.props.style, this.textStyle(node.props.cellHoverStyle)]};
}
if(node.type.name === 'Img') {
- updatedProps = { tintColor:'currentColor', style: [styles.icon, this.iconStyle(), node.props.style]};
+ updatedProps = { tintColor:'currentColor', style: [styles.icon, node.props.style, this.iconStyle(node.props.cellHoverStyle)]};
}
if(node.type.name === 'SubText') {
- updatedProps = { style: [styles.subtext, this.subtextStyle(), node.props.style]};
+ updatedProps = { style: [styles.subtext, node.props.style, this.subtextStyle(node.props.cellHoverStyle)]};
}
return React.cloneElement(node, updatedProps);
- } else {
+ } else if (node){
return <Label style={[styles.label, this.textStyle()]}>{children}</Label>;
}
})