Add Swap feature to CurrencyInput (#6091)

* Add Swap feature to CurrencyInput

* Fix linter error

* Fix and Add unit tests
feature/default_network_editable
Chi Kei Chan 6 years ago committed by Whymarrh Whitby
parent 83109c3dc7
commit 798930afba
  1. 1
      app/images/icons/swap.svg
  2. 55
      ui/app/components/currency-input/currency-input.component.js
  3. 5
      ui/app/components/currency-input/currency-input.container.js
  4. 19
      ui/app/components/currency-input/index.scss
  5. 60
      ui/app/components/currency-input/tests/currency-input.component.test.js
  6. 6
      ui/app/components/currency-input/tests/currency-input.container.test.js
  7. 7
      ui/app/components/unit-input/index.scss
  8. 44
      ui/app/components/unit-input/unit-input.component.js

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M16 17.01V10h-2v7.01h-3L15 21l4-3.99h-3zM9 3L5 6.99h3V14h2V6.99h3L9 3z"/><path d="M0 0h24v24H0z" fill="none"/></svg>

After

Width:  |  Height:  |  Size: 209 B

@ -17,9 +17,10 @@ export default class CurrencyInput extends PureComponent {
nativeCurrency: PropTypes.string,
onChange: PropTypes.func,
onBlur: PropTypes.func,
suffix: PropTypes.string,
useFiat: PropTypes.bool,
value: PropTypes.string,
fiatSuffix: PropTypes.string,
nativeSuffix: PropTypes.string,
}
constructor (props) {
@ -31,6 +32,7 @@ export default class CurrencyInput extends PureComponent {
this.state = {
decimalValue,
hexValue,
isSwapped: false,
}
}
@ -46,8 +48,8 @@ export default class CurrencyInput extends PureComponent {
}
getDecimalValue (props) {
const { value: hexValue, useFiat, currentCurrency, conversionRate } = props
const decimalValueString = useFiat
const { value: hexValue, currentCurrency, conversionRate } = props
const decimalValueString = this.shouldUseFiat()
? getValueFromWeiHex({
value: hexValue, toCurrency: currentCurrency, conversionRate, numberOfDecimals: 2,
})
@ -58,10 +60,23 @@ export default class CurrencyInput extends PureComponent {
return Number(decimalValueString) || 0
}
shouldUseFiat = () => {
const { useFiat } = this.props
const { isSwapped } = this.state || {}
return isSwapped ? !useFiat : useFiat
}
swap = () => {
const { isSwapped, decimalValue } = this.state
this.setState({isSwapped: !isSwapped}, () => {
this.handleChange(decimalValue)
})
}
handleChange = decimalValue => {
const { useFiat, currentCurrency: fromCurrency, conversionRate, onChange } = this.props
const { currentCurrency: fromCurrency, conversionRate, onChange } = this.props
const hexValue = useFiat
const hexValue = this.shouldUseFiat()
? getWeiHexFromDecimalValue({
value: decimalValue, fromCurrency, conversionRate, invertConversionRate: true,
})
@ -78,11 +93,11 @@ export default class CurrencyInput extends PureComponent {
}
renderConversionComponent () {
const { useFiat, currentCurrency, nativeCurrency } = this.props
const { currentCurrency, nativeCurrency } = this.props
const { hexValue } = this.state
let currency, numberOfDecimals
if (useFiat) {
if (this.shouldUseFiat()) {
// Display ETH
currency = nativeCurrency || ETH
numberOfDecimals = 6
@ -103,19 +118,25 @@ export default class CurrencyInput extends PureComponent {
}
render () {
const { suffix, ...restProps } = this.props
const { fiatSuffix, nativeSuffix, ...restProps } = this.props
const { decimalValue } = this.state
return (
<UnitInput
{...restProps}
suffix={suffix}
onChange={this.handleChange}
onBlur={this.handleBlur}
value={decimalValue}
>
{ this.renderConversionComponent() }
</UnitInput>
<UnitInput
{...restProps}
suffix={this.shouldUseFiat() ? fiatSuffix : nativeSuffix}
onChange={this.handleChange}
onBlur={this.handleBlur}
value={decimalValue}
actionComponent={(
<div
className="currency-input__swap-component"
onClick={this.swap}
/>
)}
>
{ this.renderConversionComponent() }
</UnitInput>
)
}
}

@ -14,14 +14,13 @@ const mapStateToProps = state => {
const mergeProps = (stateProps, dispatchProps, ownProps) => {
const { nativeCurrency, currentCurrency } = stateProps
const { useFiat } = ownProps
const suffix = useFiat ? currentCurrency.toUpperCase() : nativeCurrency || ETH
return {
...stateProps,
...dispatchProps,
...ownProps,
suffix,
nativeSuffix: nativeCurrency || ETH,
fiatSuffix: currentCurrency.toUpperCase(),
}
}

@ -4,4 +4,23 @@
line-height: 12px;
padding-left: 1px;
}
&__swap-component {
flex: 0 0 auto;
height: 24px;
width: 24px;
background-image: url("images/icons/swap.svg");
background-size: contain;
background-repeat: no-repeat;
cursor: pointer;
opacity: .4;
&:hover {
opacity: .3;
}
&:active {
opacity: .5;
}
}
}

@ -32,7 +32,8 @@ describe('CurrencyInput Component', () => {
const wrapper = mount(
<Provider store={store}>
<CurrencyInput
suffix="ETH"
nativeSuffix="ETH"
fiatSuffix="USD"
nativeCurrency="ETH"
/>
</Provider>
@ -58,7 +59,8 @@ describe('CurrencyInput Component', () => {
<Provider store={store}>
<CurrencyInput
value="de0b6b3a7640000"
suffix="ETH"
fiatSuffix="USD"
nativeSuffix="ETH"
nativeCurrency="ETH"
currentCurrency="usd"
conversionRate={231.06}
@ -90,7 +92,8 @@ describe('CurrencyInput Component', () => {
<Provider store={store}>
<CurrencyInput
value="f602f2234d0ea"
suffix="USD"
fiatSuffix="USD"
nativeSuffix="ETH"
useFiat
nativeCurrency="ETH"
currentCurrency="usd"
@ -247,5 +250,56 @@ describe('CurrencyInput Component', () => {
assert.equal(currencyInputInstance.state('hexValue'), '1ec05e43e72400')
assert.equal(currencyInputInstance.find(UnitInput).props().value, 2)
})
it('should swap selected currency when swap icon is clicked', () => {
const mockStore = {
metamask: {
nativeCurrency: 'ETH',
currentCurrency: 'usd',
conversionRate: 231.06,
},
}
const store = configureMockStore()(mockStore)
const wrapper = mount(
<Provider store={store}>
<CurrencyInput
onChange={handleChangeSpy}
onBlur={handleBlurSpy}
nativeSuffix="ETH"
fiatSuffix="USD"
nativeCurrency="ETH"
currentCurrency="usd"
conversionRate={231.06}
/>
</Provider>
)
assert.ok(wrapper)
assert.equal(handleChangeSpy.callCount, 0)
assert.equal(handleBlurSpy.callCount, 0)
const currencyInputInstance = wrapper.find(CurrencyInput).at(0).instance()
assert.equal(currencyInputInstance.state.decimalValue, 0)
assert.equal(currencyInputInstance.state.hexValue, undefined)
assert.equal(wrapper.find('.currency-display-component').text(), '$0.00USD')
const input = wrapper.find('input')
assert.equal(input.props().value, 0)
input.simulate('change', { target: { value: 1 } })
assert.equal(handleChangeSpy.callCount, 1)
assert.ok(handleChangeSpy.calledWith('de0b6b3a7640000'))
assert.equal(wrapper.find('.currency-display-component').text(), '$231.06USD')
assert.equal(currencyInputInstance.state.decimalValue, 1)
assert.equal(currencyInputInstance.state.hexValue, 'de0b6b3a7640000')
assert.equal(handleBlurSpy.callCount, 0)
input.simulate('blur')
assert.equal(handleBlurSpy.callCount, 1)
assert.ok(handleBlurSpy.calledWith('de0b6b3a7640000'))
const swap = wrapper.find('.currency-input__swap-component')
swap.simulate('click')
assert.equal(wrapper.find('.currency-display-component').text(), '0.004328ETH')
})
})
})

@ -46,14 +46,16 @@ describe('CurrencyInput container', () => {
currentCurrency: 'usd',
nativeCurrency: 'ETH',
useFiat: true,
suffix: 'USD',
nativeSuffix: 'ETH',
fiatSuffix: 'USD',
})
assert.deepEqual(mergeProps(mockStateProps, mockDispatchProps, {}), {
conversionRate: 280.45,
currentCurrency: 'usd',
nativeCurrency: 'ETH',
suffix: 'ETH',
nativeSuffix: 'ETH',
fiatSuffix: 'USD',
})
})
})

@ -1,4 +1,7 @@
.unit-input {
display: flex;
flex-flow: row nowrap;
align-items: center;
min-height: 54px;
border: 1px solid #dedede;
border-radius: 4px;
@ -24,6 +27,10 @@
display: none;
}
&__inputs {
flex: 1 0 auto;
}
&__input {
color: #4d4d4d;
font-size: 1rem;

@ -11,6 +11,7 @@ import { removeLeadingZeroes } from '../send/send.utils'
export default class UnitInput extends PureComponent {
static propTypes = {
children: PropTypes.node,
actionComponent: PropTypes.node,
error: PropTypes.bool,
onBlur: PropTypes.func,
onChange: PropTypes.func,
@ -70,7 +71,7 @@ export default class UnitInput extends PureComponent {
}
render () {
const { error, placeholder, suffix, children } = this.props
const { error, placeholder, suffix, actionComponent, children } = this.props
const { value } = this.state
return (
@ -78,26 +79,29 @@ export default class UnitInput extends PureComponent {
className={classnames('unit-input', { 'unit-input--error': error })}
onClick={this.handleFocus}
>
<div className="unit-input__input-container">
<input
type="number"
className="unit-input__input"
value={value}
placeholder={placeholder}
onChange={this.handleChange}
onBlur={this.handleBlur}
style={{ width: this.getInputWidth(value) }}
ref={ref => { this.unitInput = ref }}
/>
{
suffix && (
<div className="unit-input__suffix">
{ suffix }
</div>
)
}
<div className="unit-input__inputs">
<div className="unit-input__input-container">
<input
type="number"
className="unit-input__input"
value={value}
placeholder={placeholder}
onChange={this.handleChange}
onBlur={this.handleBlur}
style={{ width: this.getInputWidth(value) }}
ref={ref => { this.unitInput = ref }}
/>
{
suffix && (
<div className="unit-input__suffix">
{ suffix }
</div>
)
}
</div>
{ children }
</div>
{ children }
{actionComponent}
</div>
)
}

Loading…
Cancel
Save