diff options
Merge branch 'i3725-refactor-send-component-' of github.com:MetaMask/metamask-extension into i3725-refactor-send-component-
-rw-r--r-- | .eslintrc | 1 | ||||
-rw-r--r-- | package-lock.json | 12 | ||||
-rw-r--r-- | test/integration/lib/send-new-ui.js | 6 | ||||
-rw-r--r-- | ui/app/components/currency-input.js | 113 | ||||
-rw-r--r-- | ui/app/components/input-number.js | 8 | ||||
-rw-r--r-- | ui/app/components/send/currency-display.js | 58 | ||||
-rw-r--r-- | ui/app/components/send_/send-content/send-amount-row/send-amount-row.component.js | 6 | ||||
-rw-r--r-- | ui/app/components/send_/send-content/send-amount-row/tests/send-amount-row-component.test.js | 38 | ||||
-rw-r--r-- | ui/app/css/itcss/components/currency-display.scss | 22 |
9 files changed, 108 insertions, 156 deletions
@@ -142,6 +142,7 @@ "operator-linebreak": [1, "after", { "overrides": { "?": "ignore", ":": "ignore" } }], "padded-blocks": "off", "quotes": [2, "single", {"avoidEscape": true, "allowTemplateLiterals": true}], + "react/no-deprecated": 0, "semi": [2, "never"], "semi-spacing": [2, { "before": false, "after": true }], "space-before-blocks": [1, "always"], diff --git a/package-lock.json b/package-lock.json index 490a621f0..943213eb2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -26512,6 +26512,18 @@ "object-assign": "4.1.1" } }, + "react": { + "version": "15.6.2", + "resolved": "https://registry.npmjs.org/react/-/react-15.6.2.tgz", + "integrity": "sha1-26BDSrQ5z+gvEI8PURZjkIF5qnI=", + "requires": { + "create-react-class": "15.6.2", + "fbjs": "0.8.16", + "loose-envify": "1.3.1", + "object-assign": "4.1.1", + "prop-types": "15.6.1" + } + }, "react-hyperscript": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/react-hyperscript/-/react-hyperscript-2.4.2.tgz", diff --git a/test/integration/lib/send-new-ui.js b/test/integration/lib/send-new-ui.js index 176907926..f787eafe8 100644 --- a/test/integration/lib/send-new-ui.js +++ b/test/integration/lib/send-new-ui.js @@ -101,7 +101,7 @@ async function runSendFlowTest(assert, done) { const sendAmountField = await queryAsync($, '.send-v2__form-row:eq(2)') sendAmountField.find('.currency-display')[0].click() - const sendAmountFieldInput = await findAsync(sendAmountField, 'input:text') + const sendAmountFieldInput = await findAsync(sendAmountField, '.currency-display__input') sendAmountFieldInput.val('5.1') reactTriggerChange(sendAmountField.find('input')[0]) @@ -127,7 +127,7 @@ async function runSendFlowTest(assert, done) { ) await customizeGas(assert, 0, 21000, '0', '$0.00 USD') - await customizeGas(assert, 500, 60000, '0.003', '$3.60 USD') + await customizeGas(assert, 500, 60000, '0.03', '$36.03 USD') const sendButton = await queryAsync($, 'button.btn-primary.btn--large.page-container__footer-button') assert.equal(sendButton[0].textContent, 'Next', 'next button rendered') @@ -165,7 +165,7 @@ async function runSendFlowTest(assert, done) { const sendAmountFieldInEdit = await queryAsync($, '.send-v2__form-row:eq(2)') sendAmountFieldInEdit.find('.currency-display')[0].click() - const sendAmountFieldInputInEdit = sendAmountFieldInEdit.find('input:text') + const sendAmountFieldInputInEdit = sendAmountFieldInEdit.find('.currency-display__input') sendAmountFieldInputInEdit.val('1.0') reactTriggerChange(sendAmountFieldInputInEdit[0]) diff --git a/ui/app/components/currency-input.js b/ui/app/components/currency-input.js deleted file mode 100644 index ece3eb43d..000000000 --- a/ui/app/components/currency-input.js +++ /dev/null @@ -1,113 +0,0 @@ -const Component = require('react').Component -const h = require('react-hyperscript') -const inherits = require('util').inherits - -module.exports = CurrencyInput - -inherits(CurrencyInput, Component) -function CurrencyInput (props) { - Component.call(this) - - const sanitizedValue = sanitizeValue(props.value) - - this.state = { - value: sanitizedValue, - emptyState: false, - focused: false, - } -} - -function removeNonDigits (str) { - return str.match(/\d|$/g).join('') -} - -// Removes characters that are not digits, then removes leading zeros -function sanitizeInteger (val) { - return String(parseInt(removeNonDigits(val) || '0', 10)) -} - -function sanitizeDecimal (val) { - return removeNonDigits(val) -} - -// Take a single string param and returns a non-negative integer or float as a string. -// Breaks the input into three parts: the integer, the decimal point, and the decimal/fractional part. -// Removes leading zeros from the integer, and non-digits from the integer and decimal -// The integer is returned as '0' in cases where it would be empty. A decimal point is -// included in the returned string if one is included in the param -// Examples: -// sanitizeValue('0') -> '0' -// sanitizeValue('a') -> '0' -// sanitizeValue('010.') -> '10.' -// sanitizeValue('0.005') -> '0.005' -// sanitizeValue('22.200') -> '22.200' -// sanitizeValue('.200') -> '0.200' -// sanitizeValue('a.b.1.c,89.123') -> '0.189123' -function sanitizeValue (value) { - let [ , integer, point, decimal] = (/([^.]*)([.]?)([^.]*)/).exec(value) - - integer = sanitizeInteger(integer) || '0' - decimal = sanitizeDecimal(decimal) - - return `${integer}${point}${decimal}` -} - -CurrencyInput.prototype.handleChange = function (newValue) { - const { onInputChange } = this.props - const { value } = this.state - - let parsedValue = newValue - const newValueLastIndex = newValue.length - 1 - - if (value === '0' && newValue[newValueLastIndex] === '0') { - parsedValue = parsedValue.slice(0, newValueLastIndex) - } - const sanitizedValue = sanitizeValue(parsedValue) - this.setState({ - value: sanitizedValue, - emptyState: newValue === '' && sanitizedValue === '0', - }) - onInputChange(sanitizedValue) -} - -// If state.value === props.value plus a decimal point, or at least one -// zero or a decimal point and at least one zero, then this returns state.value -// after it is sanitized with getValueParts -CurrencyInput.prototype.getValueToRender = function () { - const { value } = this.props - const { value: stateValue } = this.state - - const trailingStateString = (new RegExp(`^${value}(.+)`)).exec(stateValue) - const trailingDecimalAndZeroes = trailingStateString && (/^[.0]0*/).test(trailingStateString[1]) - - return sanitizeValue(trailingDecimalAndZeroes - ? stateValue - : value) -} - -CurrencyInput.prototype.render = function () { - const { - className, - placeholder, - readOnly, - inputRef, - type, - } = this.props - const { emptyState, focused } = this.state - - const inputSizeMultiplier = readOnly ? 1 : 1.2 - - const valueToRender = this.getValueToRender() - return h('input', { - className, - type, - value: emptyState ? '' : valueToRender, - placeholder: focused ? '' : placeholder, - size: valueToRender.length * inputSizeMultiplier, - readOnly, - onFocus: () => this.setState({ focused: true, emptyState: valueToRender === '0' }), - onBlur: () => this.setState({ focused: false, emptyState: false }), - onChange: e => this.handleChange(e.target.value), - ref: inputRef, - }) -} diff --git a/ui/app/components/input-number.js b/ui/app/components/input-number.js index 5600e35ee..de5fcca54 100644 --- a/ui/app/components/input-number.js +++ b/ui/app/components/input-number.js @@ -1,7 +1,6 @@ const Component = require('react').Component const h = require('react-hyperscript') const inherits = require('util').inherits -const CurrencyInput = require('./currency-input') const { addCurrencies, conversionGTE, @@ -51,14 +50,15 @@ InputNumber.prototype.render = function () { const { unitLabel, step = 1, placeholder, value = 0 } = this.props return h('div.customize-gas-input-wrapper', {}, [ - h(CurrencyInput, { + h('input', { className: 'customize-gas-input', value, placeholder, type: 'number', - onInputChange: newValue => { - this.setValue(newValue) + onChange: e => { + this.setValue(e.target.value) }, + min: 0, }), h('span.gas-tooltip-input-detail', {}, [unitLabel]), h('div.gas-tooltip-input-arrows', {}, [ diff --git a/ui/app/components/send/currency-display.js b/ui/app/components/send/currency-display.js index 90fb2b66c..360dd15d4 100644 --- a/ui/app/components/send/currency-display.js +++ b/ui/app/components/send/currency-display.js @@ -1,7 +1,6 @@ const Component = require('react').Component const h = require('react-hyperscript') const inherits = require('util').inherits -const CurrencyInput = require('../currency-input') const { conversionUtil, multiplyCurrencies } = require('../../conversion-util') const currencyFormatter = require('currency-formatter') const currencies = require('currency-formatter/currencies') @@ -21,21 +20,36 @@ function toHexWei (value) { }) } +CurrencyDisplay.prototype.componentWillMount = function () { + this.setState({ + valueToRender: this.getValueToRender(this.props), + }) +} + +CurrencyDisplay.prototype.componentWillReceiveProps = function (nextProps) { + const currentValueToRender = this.getValueToRender(this.props) + const newValueToRender = this.getValueToRender(nextProps) + if (currentValueToRender !== newValueToRender) { + this.setState({ + valueToRender: newValueToRender, + }) + } +} + CurrencyDisplay.prototype.getAmount = function (value) { const { selectedToken } = this.props const { decimals } = selectedToken || {} const multiplier = Math.pow(10, Number(decimals || 0)) - const sendAmount = multiplyCurrencies(value, multiplier, {toNumericBase: 'hex'}) + const sendAmount = multiplyCurrencies(value || '0', multiplier, {toNumericBase: 'hex'}) return selectedToken ? sendAmount : toHexWei(value) } -CurrencyDisplay.prototype.getValueToRender = function () { - const { selectedToken, conversionRate, value } = this.props - +CurrencyDisplay.prototype.getValueToRender = function ({ selectedToken, conversionRate, value }) { + if (value === '0x0') return '0' const { decimals, symbol } = selectedToken || {} const multiplier = Math.pow(10, Number(decimals || 0)) @@ -76,6 +90,18 @@ CurrencyDisplay.prototype.getConvertedValueToRender = function (nonFormattedValu : convertedValue } +CurrencyDisplay.prototype.handleChange = function (newVal) { + this.setState({ valueToRender: newVal }) + this.props.onChange(this.getAmount(newVal)) +} + +CurrencyDisplay.prototype.getInputWidth = function (valueToRender, readOnly) { + const valueString = String(valueToRender) + const valueLength = valueString.length || 1 + const decimalPointDeficit = valueString.match(/\./) ? -0.5 : 0 + return (valueLength + decimalPointDeficit + 0.75) + 'ch' +} + CurrencyDisplay.prototype.render = function () { const { className = 'currency-display', @@ -85,10 +111,10 @@ CurrencyDisplay.prototype.render = function () { convertedCurrency, readOnly = false, inError = false, - handleChange, + onBlur, } = this.props + const { valueToRender } = this.state - const valueToRender = this.getValueToRender() const convertedValueToRender = this.getConvertedValueToRender(valueToRender) return h('div', { @@ -96,24 +122,30 @@ CurrencyDisplay.prototype.render = function () { style: { borderColor: inError ? 'red' : null, }, - onClick: () => this.currencyInput && this.currencyInput.focus(), + onClick: () => { + this.currencyInput && this.currencyInput.focus() + }, }, [ h('div.currency-display__primary-row', [ h('div.currency-display__input-wrapper', [ - h(readOnly ? 'input' : CurrencyInput, { + h('input', { className: primaryBalanceClassName, value: `${valueToRender}`, placeholder: '0', + type: 'number', readOnly, ...(!readOnly ? { - onInputChange: newValue => { - handleChange(this.getAmount(newValue)) - }, - inputRef: input => { this.currencyInput = input }, + onChange: e => this.handleChange(e.target.value), + onBlur: () => onBlur(this.getAmount(valueToRender)), } : {}), + ref: input => { this.currencyInput = input }, + style: { + width: this.getInputWidth(valueToRender, readOnly), + }, + min: 0, }), h('span.currency-display__currency-symbol', primaryCurrency), diff --git a/ui/app/components/send_/send-content/send-amount-row/send-amount-row.component.js b/ui/app/components/send_/send-content/send-amount-row/send-amount-row.component.js index b094d0cd5..8aefeed4a 100644 --- a/ui/app/components/send_/send-content/send-amount-row/send-amount-row.component.js +++ b/ui/app/components/send_/send-content/send-amount-row/send-amount-row.component.js @@ -49,11 +49,10 @@ export default class SendAmountRow extends Component { }) } - handleAmountChange (amount) { + updateAmount (amount) { const { updateSendAmount, setMaxModeTo } = this.props setMaxModeTo(false) - this.validateAmount(amount) updateSendAmount(amount) } @@ -78,7 +77,8 @@ export default class SendAmountRow extends Component { <CurrencyDisplay conversionRate={amountConversionRate} convertedCurrency={convertedCurrency} - handleChange={newAmount => this.handleAmountChange(newAmount)} + onBlur={newAmount => this.updateAmount(newAmount)} + onChange={newAmount => this.validateAmount(newAmount)} inError={inError} primaryCurrency={primaryCurrency || 'ETH'} selectedToken={selectedToken} diff --git a/ui/app/components/send_/send-content/send-amount-row/tests/send-amount-row-component.test.js b/ui/app/components/send_/send-content/send-amount-row/tests/send-amount-row-component.test.js index 31d2e2515..2205579ca 100644 --- a/ui/app/components/send_/send-content/send-amount-row/tests/send-amount-row-component.test.js +++ b/ui/app/components/send_/send-content/send-amount-row/tests/send-amount-row-component.test.js @@ -14,7 +14,7 @@ const propsMethodSpies = { updateSendAmountError: sinon.spy(), } -sinon.spy(SendAmountRow.prototype, 'handleAmountChange') +sinon.spy(SendAmountRow.prototype, 'updateAmount') sinon.spy(SendAmountRow.prototype, 'validateAmount') describe('SendAmountRow Component', function () { @@ -45,7 +45,7 @@ describe('SendAmountRow Component', function () { propsMethodSpies.updateSendAmount.resetHistory() propsMethodSpies.updateSendAmountError.resetHistory() SendAmountRow.prototype.validateAmount.resetHistory() - SendAmountRow.prototype.handleAmountChange.resetHistory() + SendAmountRow.prototype.updateAmount.resetHistory() }) describe('validateAmount', () => { @@ -71,11 +71,11 @@ describe('SendAmountRow Component', function () { }) - describe('handleAmountChange', () => { + describe('updateAmount', () => { it('should call setMaxModeTo', () => { assert.equal(propsMethodSpies.setMaxModeTo.callCount, 0) - instance.handleAmountChange('someAmount') + instance.updateAmount('someAmount') assert.equal(propsMethodSpies.setMaxModeTo.callCount, 1) assert.deepEqual( propsMethodSpies.setMaxModeTo.getCall(0).args, @@ -83,19 +83,9 @@ describe('SendAmountRow Component', function () { ) }) - it('should call this.validateAmount', () => { - assert.equal(SendAmountRow.prototype.validateAmount.callCount, 0) - instance.handleAmountChange('someAmount') - assert.equal(SendAmountRow.prototype.validateAmount.callCount, 1) - assert.deepEqual( - propsMethodSpies.updateSendAmount.getCall(0).args, - ['someAmount'] - ) - }) - it('should call updateSendAmount', () => { assert.equal(propsMethodSpies.updateSendAmount.callCount, 0) - instance.handleAmountChange('someAmount') + instance.updateAmount('someAmount') assert.equal(propsMethodSpies.updateSendAmount.callCount, 1) assert.deepEqual( propsMethodSpies.updateSendAmount.getCall(0).args, @@ -136,7 +126,8 @@ describe('SendAmountRow Component', function () { const { conversionRate, convertedCurrency, - handleChange, + onBlur, + onChange, inError, primaryCurrency, selectedToken, @@ -148,11 +139,18 @@ describe('SendAmountRow Component', function () { assert.equal(primaryCurrency, 'mockPrimaryCurrency') assert.deepEqual(selectedToken, { address: 'mockTokenAddress' }) assert.equal(value, 'mockAmount') - assert.equal(SendAmountRow.prototype.handleAmountChange.callCount, 0) - handleChange('mockNewAmount') - assert.equal(SendAmountRow.prototype.handleAmountChange.callCount, 1) + assert.equal(SendAmountRow.prototype.updateAmount.callCount, 0) + onBlur('mockNewAmount') + assert.equal(SendAmountRow.prototype.updateAmount.callCount, 1) + assert.deepEqual( + SendAmountRow.prototype.updateAmount.getCall(0).args, + ['mockNewAmount'] + ) + assert.equal(SendAmountRow.prototype.validateAmount.callCount, 0) + onChange('mockNewAmount') + assert.equal(SendAmountRow.prototype.validateAmount.callCount, 1) assert.deepEqual( - SendAmountRow.prototype.handleAmountChange.getCall(0).args, + SendAmountRow.prototype.validateAmount.getCall(0).args, ['mockNewAmount'] ) }) diff --git a/ui/app/css/itcss/components/currency-display.scss b/ui/app/css/itcss/components/currency-display.scss index 36d843c79..3560b0b0c 100644 --- a/ui/app/css/itcss/components/currency-display.scss +++ b/ui/app/css/itcss/components/currency-display.scss @@ -47,10 +47,32 @@ &__input-wrapper { position: relative; display: flex; + + input[type="number"]::-webkit-inner-spin-button { + -webkit-appearance: none; + display: none; + } + + input[type="number"]:hover::-webkit-inner-spin-button { + -webkit-appearance: none; + display: none; + } } &__currency-symbol { margin-top: 1px; color: $scorpion; } + + .react-numeric-input { + input[type="number"]::-webkit-inner-spin-button { + -webkit-appearance: none; + display: none; + } + + input[type="number"]:hover::-webkit-inner-spin-button { + -webkit-appearance: none; + display: none; + } + } }
\ No newline at end of file |