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:
parent
83109c3dc7
commit
798930afba
1
app/images/icons/swap.svg
Normal file
1
app/images/icons/swap.svg
Normal 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 |
@ -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>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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')
|
||||||
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@ -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',
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@ -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;
|
||||||
|
@ -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>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user