1
0
mirror of https://github.com/kremalicious/metamask-extension.git synced 2024-12-23 09:52:26 +01:00

Add Swap feature to CurrencyInput (#6091)

* Add Swap feature to CurrencyInput

* Fix linter error

* Fix and Add unit tests
This commit is contained in:
Chi Kei Chan 2019-02-06 23:14:17 +08:00 committed by Whymarrh Whitby
parent 83109c3dc7
commit 798930afba
8 changed files with 153 additions and 46 deletions

View File

@ -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

View File

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

View File

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

View File

@ -4,4 +4,23 @@
line-height: 12px; line-height: 12px;
padding-left: 1px; 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;
}
}
} }

View File

@ -32,7 +32,8 @@ describe('CurrencyInput Component', () => {
const wrapper = mount( const wrapper = mount(
<Provider store={store}> <Provider store={store}>
<CurrencyInput <CurrencyInput
suffix="ETH" nativeSuffix="ETH"
fiatSuffix="USD"
nativeCurrency="ETH" nativeCurrency="ETH"
/> />
</Provider> </Provider>
@ -58,7 +59,8 @@ describe('CurrencyInput Component', () => {
<Provider store={store}> <Provider store={store}>
<CurrencyInput <CurrencyInput
value="de0b6b3a7640000" value="de0b6b3a7640000"
suffix="ETH" fiatSuffix="USD"
nativeSuffix="ETH"
nativeCurrency="ETH" nativeCurrency="ETH"
currentCurrency="usd" currentCurrency="usd"
conversionRate={231.06} conversionRate={231.06}
@ -90,7 +92,8 @@ describe('CurrencyInput Component', () => {
<Provider store={store}> <Provider store={store}>
<CurrencyInput <CurrencyInput
value="f602f2234d0ea" value="f602f2234d0ea"
suffix="USD" fiatSuffix="USD"
nativeSuffix="ETH"
useFiat useFiat
nativeCurrency="ETH" nativeCurrency="ETH"
currentCurrency="usd" currentCurrency="usd"
@ -247,5 +250,56 @@ describe('CurrencyInput Component', () => {
assert.equal(currencyInputInstance.state('hexValue'), '1ec05e43e72400') assert.equal(currencyInputInstance.state('hexValue'), '1ec05e43e72400')
assert.equal(currencyInputInstance.find(UnitInput).props().value, 2) 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')
})
}) })
}) })

View File

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

View File

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

View File

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