diff options
Diffstat (limited to 'app/components')
| -rw-r--r-- | app/components/AccountInput.js | 63 | ||||
| -rw-r--r-- | app/components/Img.js | 29 | ||||
| -rw-r--r-- | app/components/Login.css | 211 | ||||
| -rw-r--r-- | app/components/Login.js | 297 | ||||
| -rw-r--r-- | app/components/LoginStyles.js | 165 | ||||
| -rw-r--r-- | app/components/styled/CellButton.js | 41 |
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>; } }) |
