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.handleChange = this.handleChange.bind(this)
this.handleBlur = this.handleBlur.bind(this) this.handleBlur = this.handleBlur.bind(this)
this.handleKeyDown = this.handleKeyDown.bind(this) this.handleKeyDown = this.handleKeyDown.bind(this)
this.textInput = React.createRef()
} }
setRules() { setRules() {
@ -41,6 +42,14 @@ class AmountInput extends React.Component {
} }
} }
focusTextInput() {
this.textInput.current.focus()
}
clearTextInput() {
this.textInput.current.value = ''
}
parseNumber(_value) { parseNumber(_value) {
let value = _value || '' let value = _value || ''
if (typeof _value === 'string') { if (typeof _value === 'string') {
@ -143,6 +152,7 @@ class AmountInput extends React.Component {
onBlur={this.handleBlur} onBlur={this.handleBlur}
onKeyDown={this.handleKeyDown} onKeyDown={this.handleKeyDown}
readOnly={readOnly} readOnly={readOnly}
ref={this.textInput}
type="text" type="text"
required required
value={amount} 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 PropTypes from 'prop-types'
import Isvg from 'react-inlinesvg' import Isvg from 'react-inlinesvg'
@ -6,137 +6,150 @@ import x from 'icons/x.svg'
import styles from './AddChannel.scss' import styles from './AddChannel.scss'
const AddChannel = ({ class AddChannel extends Component {
contactsform, constructor(props) {
closeContactsForm, super(props)
openSubmitChannelForm, this.searchInput = React.createRef()
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>
)
}
if (activeChannelPubkeys.includes(node.pub_key)) { componentDidMount() {
return ( // Focus the search input field.
<span className={`${styles.online} ${styles.inactive}`}> this.searchInput.current.focus()
<span>Online</span> }
</span>
)
}
if (nonActiveChannelPubkeys.includes(node.pub_key)) { render() {
return ( const {
<span className={`${styles.offline} ${styles.inactive}`}> contactsform,
<span>Offline</span> closeContactsForm,
</span> 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 ( return (
<span className={`${styles.pending} ${styles.inactive}`}> <span
<span>Pending</span> className={styles.connect}
onClick={() => {
// set the node public key for the submit form
setNode(node)
// open the submit form
openSubmitChannelForm()
}}
>
Connect
</span> </span>
) )
} }
if (!node.addresses.length) { const searchUpdated = search => {
return <span className={`${styles.private} ${styles.inactive}`}>Private</span> updateContactFormSearchQuery(search)
if (search.includes('@') && search.split('@')[0].length === 66) {
updateManualFormSearchQuery(search)
}
} }
return ( return (
<span <div className={styles.container}>
className={styles.connect} <header className={styles.header}>
onClick={() => { <input
// set the node public key for the submit form type="text"
setNode(node) placeholder="Search the network..."
// open the submit form className={styles.searchInput}
openSubmitChannelForm() value={contactsform.searchQuery}
}} onChange={event => searchUpdated(event.target.value)}
> ref={this.searchInput}
Connect />
</span> <span onClick={closeContactsForm} className={styles.closeIcon}>
) <Isvg src={x} />
} </span>
</header>
const searchUpdated = search => { <section className={styles.nodes}>
updateContactFormSearchQuery(search) <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) { {showManualForm && (
updateManualFormSearchQuery(search) <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 = { AddChannel.propTypes = {

12
app/components/Contacts/SubmitChannelForm.js

@ -8,6 +8,17 @@ import AmountInput from 'components/AmountInput'
import styles from './SubmitChannelForm.scss' import styles from './SubmitChannelForm.scss'
class SubmitChannelForm extends React.Component { 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() { render() {
const { const {
closeChannelForm, closeChannelForm,
@ -107,6 +118,7 @@ class SubmitChannelForm extends React.Component {
amount={contactCapacity} amount={contactCapacity}
currency={ticker.currency} currency={ticker.currency}
onChangeEvent={updateContactCapacity} onChangeEvent={updateContactCapacity}
ref={this.amountInput}
/> />
<div className={styles.currency}> <div className={styles.currency}>
<section <section

17
app/components/Form/Pay.js

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

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

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

Loading…
Cancel
Save