Browse Source

Merge pull request #747 from mrfelton/feat/autofocus-forms

fix(ui): autofocus forms
renovate/lint-staged-8.x
JimmyMow 6 years ago
committed by GitHub
parent
commit
9cf677ddde
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 10
      app/components/AmountInput/AmountInput.js
  2. 249
      app/components/Contacts/AddChannel.js
  3. 12
      app/components/Contacts/SubmitChannelForm.js
  4. 17
      app/components/Form/Pay.js
  5. 197
      app/components/Form/Request.js
  6. 8
      test/unit/components/Form/Pay.spec.js
  7. 4
      test/unit/components/Form/Request.spec.js

10
app/components/AmountInput/AmountInput.js

@ -7,6 +7,7 @@ class AmountInput extends React.Component {
this.handleChange = this.handleChange.bind(this)
this.handleBlur = this.handleBlur.bind(this)
this.handleKeyDown = this.handleKeyDown.bind(this)
this.textInput = React.createRef()
}
setRules() {
@ -41,6 +42,14 @@ class AmountInput extends React.Component {
}
}
focusTextInput() {
this.textInput.current.focus()
}
clearTextInput() {
this.textInput.current.value = ''
}
parseNumber(_value) {
let value = _value || ''
if (typeof _value === 'string') {
@ -143,6 +152,7 @@ class AmountInput extends React.Component {
onBlur={this.handleBlur}
onKeyDown={this.handleKeyDown}
readOnly={readOnly}
ref={this.textInput}
type="text"
required
value={amount}

249
app/components/Contacts/AddChannel.js

@ -1,4 +1,4 @@
import React from 'react'
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import Isvg from 'react-inlinesvg'
@ -6,137 +6,150 @@ import x from 'icons/x.svg'
import styles from './AddChannel.scss'
const AddChannel = ({
contactsform,
closeContactsForm,
openSubmitChannelForm,
updateContactFormSearchQuery,
updateManualFormSearchQuery,
setNode,
activeChannelPubkeys,
nonActiveChannelPubkeys,
pendingOpenChannelPubkeys,
filteredNetworkNodes,
loadingChannelPubkeys,
showManualForm,
openManualForm
}) => {
const renderRightSide = node => {
if (loadingChannelPubkeys.includes(node.pub_key)) {
return (
<span className={styles.inactive}>
<div className={styles.loading}>
<div className={styles.spinner} />
</div>
</span>
)
}
class AddChannel extends Component {
constructor(props) {
super(props)
this.searchInput = React.createRef()
}
if (activeChannelPubkeys.includes(node.pub_key)) {
return (
<span className={`${styles.online} ${styles.inactive}`}>
<span>Online</span>
</span>
)
}
componentDidMount() {
// Focus the search input field.
this.searchInput.current.focus()
}
if (nonActiveChannelPubkeys.includes(node.pub_key)) {
return (
<span className={`${styles.offline} ${styles.inactive}`}>
<span>Offline</span>
</span>
)
}
render() {
const {
contactsform,
closeContactsForm,
openSubmitChannelForm,
updateContactFormSearchQuery,
updateManualFormSearchQuery,
setNode,
activeChannelPubkeys,
nonActiveChannelPubkeys,
pendingOpenChannelPubkeys,
filteredNetworkNodes,
loadingChannelPubkeys,
showManualForm,
openManualForm
} = this.props
const renderRightSide = node => {
if (loadingChannelPubkeys.includes(node.pub_key)) {
return (
<span className={styles.inactive}>
<div className={styles.loading}>
<div className={styles.spinner} />
</div>
</span>
)
}
if (activeChannelPubkeys.includes(node.pub_key)) {
return (
<span className={`${styles.online} ${styles.inactive}`}>
<span>Online</span>
</span>
)
}
if (nonActiveChannelPubkeys.includes(node.pub_key)) {
return (
<span className={`${styles.offline} ${styles.inactive}`}>
<span>Offline</span>
</span>
)
}
if (pendingOpenChannelPubkeys.includes(node.pub_key)) {
return (
<span className={`${styles.pending} ${styles.inactive}`}>
<span>Pending</span>
</span>
)
}
if (!node.addresses.length) {
return <span className={`${styles.private} ${styles.inactive}`}>Private</span>
}
if (pendingOpenChannelPubkeys.includes(node.pub_key)) {
return (
<span className={`${styles.pending} ${styles.inactive}`}>
<span>Pending</span>
<span
className={styles.connect}
onClick={() => {
// set the node public key for the submit form
setNode(node)
// open the submit form
openSubmitChannelForm()
}}
>
Connect
</span>
)
}
if (!node.addresses.length) {
return <span className={`${styles.private} ${styles.inactive}`}>Private</span>
const searchUpdated = search => {
updateContactFormSearchQuery(search)
if (search.includes('@') && search.split('@')[0].length === 66) {
updateManualFormSearchQuery(search)
}
}
return (
<span
className={styles.connect}
onClick={() => {
// set the node public key for the submit form
setNode(node)
// open the submit form
openSubmitChannelForm()
}}
>
Connect
</span>
)
}
<div className={styles.container}>
<header className={styles.header}>
<input
type="text"
placeholder="Search the network..."
className={styles.searchInput}
value={contactsform.searchQuery}
onChange={event => searchUpdated(event.target.value)}
ref={this.searchInput}
/>
<span onClick={closeContactsForm} className={styles.closeIcon}>
<Isvg src={x} />
</span>
</header>
const searchUpdated = search => {
updateContactFormSearchQuery(search)
<section className={styles.nodes}>
<ul className={styles.networkResults}>
{filteredNetworkNodes.map(node => (
<li key={node.pub_key}>
<section>
{node.alias.length > 0 ? (
<h2>
<span>{node.alias.trim()}</span>
<span>
({node.pub_key.substr(0, 10)}
...
{node.pub_key.substr(node.pub_key.length - 10)})
</span>
</h2>
) : (
<h2>
<span>{node.pub_key}</span>
</h2>
)}
</section>
<section>{renderRightSide(node)}</section>
</li>
))}
</ul>
</section>
if (search.includes('@') && search.split('@')[0].length === 66) {
updateManualFormSearchQuery(search)
}
{showManualForm && (
<section className={styles.manualForm}>
<p>
Hm, looks like we can&apos;t see that node from here, wanna try to manually connect?
</p>
<div className={styles.manualConnectButton} onClick={openManualForm}>
Connect Manually
</div>
</section>
)}
</div>
)
}
return (
<div className={styles.container}>
<header className={styles.header}>
<input
type="text"
placeholder="Search the network..."
className={styles.searchInput}
value={contactsform.searchQuery}
onChange={event => searchUpdated(event.target.value)}
// ref={input => input && input.focus()}
/>
<span onClick={closeContactsForm} className={styles.closeIcon}>
<Isvg src={x} />
</span>
</header>
<section className={styles.nodes}>
<ul className={styles.networkResults}>
{filteredNetworkNodes.map(node => (
<li key={node.pub_key}>
<section>
{node.alias.length > 0 ? (
<h2>
<span>{node.alias.trim()}</span>
<span>
({node.pub_key.substr(0, 10)}
...
{node.pub_key.substr(node.pub_key.length - 10)})
</span>
</h2>
) : (
<h2>
<span>{node.pub_key}</span>
</h2>
)}
</section>
<section>{renderRightSide(node)}</section>
</li>
))}
</ul>
</section>
{showManualForm && (
<section className={styles.manualForm}>
<p>
Hm, looks like we can&apos;t see that node from here, wanna try to manually connect?
</p>
<div className={styles.manualConnectButton} onClick={openManualForm}>
Connect Manually
</div>
</section>
)}
</div>
)
}
AddChannel.propTypes = {

12
app/components/Contacts/SubmitChannelForm.js

@ -8,6 +8,17 @@ import AmountInput from 'components/AmountInput'
import styles from './SubmitChannelForm.scss'
class SubmitChannelForm extends React.Component {
constructor(props) {
super(props)
this.amountInput = React.createRef()
}
componentDidMount() {
// Clear and Focus the amount input field.
this.amountInput.current.clearTextInput()
this.amountInput.current.focusTextInput()
}
render() {
const {
closeChannelForm,
@ -107,6 +118,7 @@ class SubmitChannelForm extends React.Component {
amount={contactCapacity}
currency={ticker.currency}
onChangeEvent={updateContactCapacity}
ref={this.amountInput}
/>
<div className={styles.currency}>
<section

17
app/components/Form/Pay.js

@ -12,6 +12,22 @@ import AmountInput from 'components/AmountInput'
import styles from './Pay.scss'
class Pay extends Component {
constructor(props) {
super(props)
this.paymentRequestInput = React.createRef()
}
componentDidMount() {
const { setPayInput, setPayAmount } = this.props
// Clear the form of any previous data.
setPayInput('')
setPayAmount('')
// Focus the payment request input field.
this.paymentRequestInput.current.focus()
}
componentDidUpdate(prevProps) {
const {
isLn,
@ -110,6 +126,7 @@ class Pay extends Component {
onBlur={onPayInputBlur}
id="paymentRequest"
rows="4"
ref={this.paymentRequestInput}
/>
<section
className={`${styles.errorMessage} ${

197
app/components/Form/Request.js

@ -1,4 +1,4 @@
import React from 'react'
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import Isvg from 'react-inlinesvg'
@ -9,100 +9,121 @@ import { btc } from 'lib/utils'
import AmountInput from 'components/AmountInput'
import styles from './Request.scss'
const Request = ({
requestform: { amount, memo, showCurrencyFilters },
ticker,
setRequestAmount,
setRequestMemo,
setCurrency,
setRequestCurrencyFilters,
currencyName,
requestFiatAmount,
class Request extends Component {
constructor(props) {
super(props)
this.amountInput = React.createRef()
}
currentCurrencyFilters,
componentDidMount() {
const { setRequestMemo, setRequestAmount } = this.props
onRequestSubmit
}) => {
const onCurrencyFilterClick = currency => {
// change the input amount
setRequestAmount(btc.convert(ticker.currency, currency, amount))
// Clear the form of any previous data.
setRequestMemo('')
setRequestAmount('')
setCurrency(currency)
setRequestCurrencyFilters(false)
// Focus the amount input field.
this.amountInput.current.focusTextInput()
}
return (
<div className={styles.container}>
<header className={styles.header}>
<Isvg src={hand} />
<h1>Request Payment</h1>
</header>
<div className={styles.content}>
<section className={styles.amount}>
<div className={styles.top}>
<label htmlFor="amount">Amount</label>
<span />
</div>
<div className={styles.bottom}>
<AmountInput
id="amount"
amount={amount}
currency={ticker.currency}
onChangeEvent={setRequestAmount}
/>
<div className={styles.currency}>
<section
className={styles.currentCurrency}
onClick={() => setRequestCurrencyFilters(!showCurrencyFilters)}
>
<span>{currencyName}</span>
<span>
<FaAngleDown />
</span>
</section>
<ul className={showCurrencyFilters ? styles.active : undefined}>
{currentCurrencyFilters.map(filter => (
<li key={filter.key} onClick={() => onCurrencyFilterClick(filter.key)}>
{filter.name}
</li>
))}
</ul>
render() {
const {
requestform: { amount, memo, showCurrencyFilters },
ticker,
setRequestAmount,
setRequestMemo,
setCurrency,
setRequestCurrencyFilters,
currencyName,
requestFiatAmount,
currentCurrencyFilters,
onRequestSubmit
} = this.props
const onCurrencyFilterClick = currency => {
// change the input amount
setRequestAmount(btc.convert(ticker.currency, currency, amount))
setCurrency(currency)
setRequestCurrencyFilters(false)
}
return (
<div className={styles.container}>
<header className={styles.header}>
<Isvg src={hand} />
<h1>Request Payment</h1>
</header>
<div className={styles.content}>
<section className={styles.amount}>
<div className={styles.top}>
<label htmlFor="amount">Amount</label>
<span />
</div>
<div className={styles.bottom}>
<AmountInput
id="amount"
amount={amount}
currency={ticker.currency}
onChangeEvent={setRequestAmount}
ref={this.amountInput}
/>
<div className={styles.currency}>
<section
className={styles.currentCurrency}
onClick={() => setRequestCurrencyFilters(!showCurrencyFilters)}
>
<span>{currencyName}</span>
<span>
<FaAngleDown />
</span>
</section>
<ul className={showCurrencyFilters ? styles.active : undefined}>
{currentCurrencyFilters.map(filter => (
<li key={filter.key} onClick={() => onCurrencyFilterClick(filter.key)}>
{filter.name}
</li>
))}
</ul>
</div>
</div>
</div>
<div className={styles.fiatAmount}>{`${requestFiatAmount || 0} ${
ticker.fiatTicker
}`}</div>
</section>
<section className={styles.memo}>
<div className={styles.top}>
<label htmlFor="memo">Memo</label>
</div>
<div className={styles.bottom}>
<input
type="text"
placeholder="Details about the request"
value={memo}
onChange={event => setRequestMemo(event.target.value)}
id="memo"
/>
</div>
</section>
<section className={styles.submit}>
<div
className={`${styles.button} ${amount > 0 ? styles.active : undefined}`}
onClick={onRequestSubmit}
>
Request
</div>
</section>
<div className={styles.fiatAmount}>{`${requestFiatAmount || 0} ${
ticker.fiatTicker
}`}</div>
</section>
<section className={styles.memo}>
<div className={styles.top}>
<label htmlFor="memo">Memo</label>
</div>
<div className={styles.bottom}>
<input
type="text"
placeholder="Details about the request"
value={memo}
onChange={event => setRequestMemo(event.target.value)}
id="memo"
/>
</div>
</section>
<section className={styles.submit}>
<div
className={`${styles.button} ${amount > 0 ? styles.active : undefined}`}
onClick={onRequestSubmit}
>
Request
</div>
</section>
</div>
</div>
</div>
)
)
}
}
Request.propTypes = {

8
test/unit/components/Form/Pay.spec.js

@ -1,5 +1,5 @@
import React from 'react'
import { configure, shallow } from 'enzyme'
import { configure, mount } from 'enzyme'
import Adapter from 'enzyme-adapter-react-16'
import Pay from 'components/Form/Pay'
@ -45,7 +45,7 @@ const defaultProps = {
describe('Form', () => {
describe('should show the form without an input', () => {
const el = shallow(<Pay {...defaultProps} />)
const el = mount(<Pay {...defaultProps} />)
it('should contain Pay', () => {
expect(el.find('input#paymentRequest').props.value).toBe(undefined)
@ -54,7 +54,7 @@ describe('Form', () => {
describe('should show lightning with a lightning input', () => {
const props = { ...defaultProps, isLn: true }
const el = shallow(<Pay {...props} />)
const el = mount(<Pay {...props} />)
it('should contain Pay', () => {
expect(el.find('input#paymentRequest').props.value).toBe(undefined)
@ -63,7 +63,7 @@ describe('Form', () => {
describe('should show on-chain with an on-chain input', () => {
const props = { ...defaultProps, isOnchain: true }
const el = shallow(<Pay {...props} />)
const el = mount(<Pay {...props} />)
it('should contain Pay', () => {
expect(el.find('input#paymentRequest').props.value).toBe(undefined)

4
test/unit/components/Form/Request.spec.js

@ -1,5 +1,5 @@
import React from 'react'
import { configure, shallow } from 'enzyme'
import { configure, mount } from 'enzyme'
import Adapter from 'enzyme-adapter-react-16'
import Request from 'components/Form/Request'
@ -28,7 +28,7 @@ const defaultProps = {
describe('Form', () => {
describe('should show request form when formType is REQUEST_FORM', () => {
const props = { ...defaultProps }
const el = shallow(<Request {...props} />)
const el = mount(<Request {...props} />)
it('should contain Request', () => {
expect(el.contains('Request')).toBe(true)
})

Loading…
Cancel
Save