summaryrefslogtreecommitdiffhomepage
path: root/app/components
diff options
context:
space:
mode:
Diffstat (limited to 'app/components')
-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
6 files changed, 412 insertions, 394 deletions
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>;
}
})