Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP: demo code with react #1

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
5 changes: 3 additions & 2 deletions src/components/Form.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import * as FormActions from '../libs/actions/FormActions';
import * as ErrorUtils from '../libs/ErrorUtils';
import styles from '../styles/styles';
import FormAlertWithSubmitButton from './FormAlertWithSubmitButton';
import AutofillProvider from '../vendors/AutofillProvider';

const propTypes = {
/** A unique Onyx key identifying the form */
Expand Down Expand Up @@ -212,7 +213,7 @@ class Form extends React.Component {

render() {
return (
<>
<AutofillProvider>
<ScrollView
style={[styles.w100, styles.flex1]}
contentContainerStyle={styles.flexGrow1}
Expand All @@ -236,7 +237,7 @@ class Form extends React.Component {
)}
</View>
</ScrollView>
</>
</AutofillProvider>
);
}
}
Expand Down
13 changes: 8 additions & 5 deletions src/components/SignInPageForm/index.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import React from 'react';
import FormElement from '../FormElement';
import AutofillProvider from '../../vendors/AutofillProvider';

class Form extends React.Component {
componentDidMount() {
Expand All @@ -14,11 +15,13 @@ class Form extends React.Component {

render() {
return (
<FormElement
ref={el => this.form = el}
// eslint-disable-next-line react/jsx-props-no-spreading
{...this.props}
/>
<AutofillProvider>
<FormElement
ref={el => this.form = el}
// eslint-disable-next-line react/jsx-props-no-spreading
{...this.props}
/>
</AutofillProvider>
);
}
}
Expand Down
11 changes: 11 additions & 0 deletions src/components/TextInput/BaseTextInput.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import variables from '../../styles/variables';
import Checkbox from '../Checkbox';
import getSecureEntryKeyboardType from '../../libs/getSecureEntryKeyboardType';
import CONST from '../../CONST';
import AutofillContext from '../../vendors/AutofillContext';

class BaseTextInput extends Component {
constructor(props) {
Expand Down Expand Up @@ -52,6 +53,9 @@ class BaseTextInput extends Component {
}

componentDidMount() {
if (this.context) {
this.context.addInput(this, 'input', 'state.value', this.setValue);
}
if (this.props.disableKeyboard) {
this.appStateSubscription = AppState.addEventListener(
'change',
Expand Down Expand Up @@ -112,6 +116,9 @@ class BaseTextInput extends Component {
if (!event.isDefaultPrevented()) {
this.input.focus();
}
if (this.context) {
this.context.showDropdown(this);
}
}

onFocus(event) {
Expand All @@ -124,6 +131,9 @@ class BaseTextInput extends Component {
if (this.props.onBlur) { this.props.onBlur(event); }
this.setState({isFocused: false});
this.deactivateLabel();
if (this.context) {
this.context.hideDropdown(this);
}
}

/**
Expand Down Expand Up @@ -333,5 +343,6 @@ class BaseTextInput extends Component {

BaseTextInput.propTypes = baseTextInputPropTypes.propTypes;
BaseTextInput.defaultProps = baseTextInputPropTypes.defaultProps;
BaseTextInput.contextType = AutofillContext;

export default BaseTextInput;
5 changes: 5 additions & 0 deletions src/pages/settings/Payments/AddDebitCardPage.js
Original file line number Diff line number Diff line change
Expand Up @@ -123,10 +123,12 @@ class DebitCardPage extends Component {
>
<TextInput
inputID="nameOnCard"
name="nameOnCard"
label={this.props.translate('addDebitCardPage.nameOnCard')}
/>
<TextInput
inputID="cardNumber"
name="cardNumber"
label={this.props.translate('addDebitCardPage.debitCardNumber')}
containerStyles={[styles.mt4]}
keyboardType={CONST.KEYBOARD_TYPE.NUMBER_PAD}
Expand All @@ -135,6 +137,7 @@ class DebitCardPage extends Component {
<View style={[styles.flex1, styles.mr2]}>
<TextInput
inputID="expirationDate"
name="expirationDate"
label={this.props.translate('addDebitCardPage.expiration')}
placeholder={this.props.translate('addDebitCardPage.expirationDate')}
keyboardType={CONST.KEYBOARD_TYPE.NUMBER_PAD}
Expand All @@ -143,6 +146,7 @@ class DebitCardPage extends Component {
<View style={[styles.flex1]}>
<TextInput
inputID="securityCode"
name="securityCode"
label={this.props.translate('addDebitCardPage.cvv')}
maxLength={4}
keyboardType={CONST.KEYBOARD_TYPE.NUMBER_PAD}
Expand Down Expand Up @@ -174,6 +178,7 @@ class DebitCardPage extends Component {
<View style={[styles.mt4]}>
<TextInput
inputID="password"
name="password"
label={this.props.translate('addDebitCardPage.expensifyPassword')}
textContentType="password"
autoCompleteType={ComponentUtils.PASSWORD_AUTOCOMPLETE_TYPE}
Expand Down
6 changes: 6 additions & 0 deletions src/vendors/AutofillContext.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import React from 'react';
import AutofillManager from './AutofillManager';

export default React.createContext(
new AutofillManager(false),
);
199 changes: 199 additions & 0 deletions src/vendors/AutofillDropdown.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@
import React from 'react';
import PropTypes from 'prop-types';
import Onyx, {withOnyx} from 'react-native-onyx';
import ReactDOM from 'react-dom';
import {Pressable, View} from 'react-native';
import lodashGet from 'lodash/get';
import _ from 'underscore';
import Text from '../components/Text';
import withLocalize from '../components/withLocalize';
import compose from '../libs/compose';
import AutofillContext from './AutofillContext';

const propTypes = {
/** Rendered child component */
type: PropTypes.string.isRequired,

candidates: PropTypes.arrayOf(PropTypes.shape({})),
};

const defaultProps = {
candidates: [],
};

const styles = {
container: {
position: 'fixed',
paddingVertical: 5,
borderRadius: 5,
width: 200,
minWidth: 100,
maxWidth: 300,
minHeight: 28,
maxHeight: 300,
lineHeight: 28,
fontSize: 14,
background: 'rgba(255, 255, 255, 1)',
boxShadow: '0 0 5px 1px rgba(0, 0, 0, .3)',
cursor: 'default',
},
li: {
paddingHorizontal: 10,
},
hovered: {
backgroundColor: 'rgba(0, 0, 0, 0.1)',
},
title: {
fontSize: 14,
lineHeight: 28,
},
description: {
fontSize: 14,
lineHeight: 28,
},
line: {
marginVertical: 5,
borderTopWidth: 1,
borderTopColor: '#ddd',
},
admin: {
paddingHorizontal: 10,
fontSize: 14,
lineHeight: 28,
},
};

class AutofillDropdown extends React.Component {
constructor(props) {
super(props);
this.setItem = this.setItem.bind(this);
this.calculatePosition = this.calculatePosition.bind(this);
this.renderPassword = this.renderPassword.bind(this);
this.renderCard = this.renderCard.bind(this);
this.renderContainer = this.renderContainer.bind(this);
}

setItem(item) {
this.context.select(item);
}

calculatePosition() {
const input = this.context.input;
if (!input) {
return;
}
const el = lodashGet(input.node, input.domPath);
if (!el) {
return;
}
const rect = el.getBoundingClientRect();
const bodyRect = document.body.getBoundingClientRect();
if ((bodyRect.width - rect.right) < 200) {
return {
top: rect.top - 10,
left: 'auto',
right: (bodyRect.width - rect.left) + 20,
};
}
return {
top: rect.top - 10,
left: rect.right + 20,
right: 'auto',
};
}

renderPassword() {
return _.map(this.props.candidates, (item, index) => {
const maskLength = item.password.length > 10 ? 10 : item.password.length;
const desc = '*'.repeat(maskLength);
return (
<Pressable key={index} onMouseDown={() => this.setItem(item)}>
{({hovered}) => (
<View style={[styles.li, hovered ? styles.hovered : undefined]}>
<Text style={styles.title}>{item.username}</Text>
<Text style={styles.description}>{desc}</Text>
</View>
)}
</Pressable>
);
});
}

renderCard() {
return _.map(this.props.candidates, (item, index) => {
const desc = `**** ${item.cardNumber.slice(-4, -1)}, expires on ${item.expirationDate}`;
return (
<Pressable key={index} onMouseDown={() => this.setItem(item)}>
{({hovered}) => (
<View style={[styles.li, hovered ? styles.hovered : undefined]}>
<Text style={styles.title}>{item.nameOnCard}</Text>
<Text style={styles.description}>{desc}</Text>
</View>
)}
</Pressable>
);
});
}

renderContainer() {
const text = this.props.type === 'password' ? 'Manage Passwords...' : 'Manage Cards...';
return (
<View style={[styles.container, this.calculatePosition()]}>
{this.props.type === 'password' ? this.renderPassword() : this.renderCard()}
<View style={styles.line} />
<Pressable onMouseDown={() => window.alert('development')}>
{({hovered}) => (
<Text style={[styles.admin, hovered ? styles.hovered : undefined]}>{text}</Text>
)}
</Pressable>
</View>
);
}

render() {
if (!this.props.candidates || this.props.candidates.length < 1) {
return (<></>);
}
return ReactDOM.createPortal(this.renderContainer(), document.body);
}
}

AutofillDropdown.propTypes = propTypes;
AutofillDropdown.contextType = AutofillContext;
AutofillDropdown.defaultProps = defaultProps;

// TODO:delete
Onyx.set('password', [
{
username: '2471314@gmail.com',
password: '1111111111',
},
{
username: '2471314+1@gmail.com',
password: '22222',
},
]);

Onyx.set('card', [
{
nameOnCard: 'Randy Mayoral',
cardNumber: '4261 5790 7998 2970',
expirationDate: '10/26',
securityCode: '333',
},
{
nameOnCard: 'Laine Catt',
cardNumber: '4416 8240 4467 0787',
expirationDate: '07/23',
securityCode: '496',
},
]);

export default compose(
withLocalize,
withOnyx({
candidates: {
key: props => (props.type === 'password' ? 'password' : 'card'),
},
}),
)(AutofillDropdown);