diff options
Diffstat (limited to 'packages')
18 files changed, 352 insertions, 108 deletions
diff --git a/packages/instant/package.json b/packages/instant/package.json index 1a6e9d2e9..be85b5062 100644 --- a/packages/instant/package.json +++ b/packages/instant/package.json @@ -58,12 +58,12 @@ "react-redux": "^5.0.7", "redux": "^4.0.0", "redux-devtools-extension": "^2.13.5", - "styled-components": "^3.4.9", + "styled-components": "^4.0.2", "ts-optchain": "^0.1.1" }, "devDependencies": { - "@static/discharge": "^1.2.2", "@0x/tslint-config": "^1.0.9", + "@static/discharge": "^1.2.2", "@types/enzyme": "^3.1.14", "@types/enzyme-adapter-react-16": "^1.0.3", "@types/jest": "^23.3.5", @@ -73,6 +73,7 @@ "@types/react-dom": "^16.0.8", "@types/react-redux": "^6.0.9", "@types/redux": "^3.6.0", + "@types/styled-components": "^4.0.1", "awesome-typescript-loader": "^5.2.1", "enzyme": "^3.6.0", "enzyme-adapter-react-16": "^1.5.0", diff --git a/packages/instant/src/components/amount_input.tsx b/packages/instant/src/components/amount_input.tsx deleted file mode 100644 index c89fb05ad..000000000 --- a/packages/instant/src/components/amount_input.tsx +++ /dev/null @@ -1,49 +0,0 @@ -import { BigNumber } from '@0x/utils'; -import * as _ from 'lodash'; -import * as React from 'react'; - -import { ColorOption } from '../style/theme'; -import { util } from '../util/util'; - -import { Container, Input } from './ui'; - -export interface AmountInputProps { - fontColor?: ColorOption; - fontSize?: string; - value?: BigNumber; - onChange: (value?: BigNumber) => void; -} - -export class AmountInput extends React.Component<AmountInputProps> { - public static defaultProps = { - onChange: util.boundNoop, - }; - public render(): React.ReactNode { - const { fontColor, fontSize, value } = this.props; - return ( - <Container borderBottom="1px solid rgba(255,255,255,0.3)" display="inline-block"> - <Input - fontColor={fontColor} - fontSize={fontSize} - onChange={this._handleChange} - value={!_.isUndefined(value) ? value.toString() : ''} - placeholder="0.00" - width="2.2em" - /> - </Container> - ); - } - private readonly _handleChange = (event: React.ChangeEvent<HTMLInputElement>): void => { - const value = event.target.value; - let bigNumberValue; - if (!_.isEmpty(value)) { - try { - bigNumberValue = new BigNumber(event.target.value); - } catch { - // We don't want to allow values that can't be a BigNumber, so don't even call onChange. - return; - } - } - this.props.onChange(bigNumberValue); - }; -} diff --git a/packages/instant/src/components/animations/slide_animations.tsx b/packages/instant/src/components/animations/slide_animations.tsx index 1f10a2ed6..7c8666861 100644 --- a/packages/instant/src/components/animations/slide_animations.tsx +++ b/packages/instant/src/components/animations/slide_animations.tsx @@ -1,6 +1,7 @@ import * as React from 'react'; +import { Keyframes } from 'styled-components'; -import { keyframes, styled } from '../../style/theme'; +import { css, keyframes, styled } from '../../style/theme'; const slideKeyframeGenerator = (fromY: string, toY: string) => keyframes` from { @@ -15,16 +16,16 @@ const slideKeyframeGenerator = (fromY: string, toY: string) => keyframes` `; export interface SlideAnimationProps { - keyframes: string; + keyframes: Keyframes; animationType: string; animationDirection?: string; } -export const SlideAnimation = - styled.div < - SlideAnimationProps > - ` - animation-name: ${props => props.keyframes}; +export const SlideAnimation = styled<SlideAnimationProps, 'div'>('div')` + animation-name: ${props => + css` + ${props.keyframes}; + `}; animation-duration: 0.3s; animation-timing-function: ${props => props.animationType}; animation-delay: 0s; diff --git a/packages/instant/src/components/asset_amount_input.tsx b/packages/instant/src/components/asset_amount_input.tsx index c03ef1cf3..960b8bc95 100644 --- a/packages/instant/src/components/asset_amount_input.tsx +++ b/packages/instant/src/components/asset_amount_input.tsx @@ -7,27 +7,51 @@ import { ERC20Asset } from '../types'; import { assetUtils } from '../util/asset'; import { util } from '../util/util'; -import { AmountInput, AmountInputProps } from './amount_input'; -import { Container, Text } from './ui'; +import { ScalingAmountInput } from './scaling_amount_input'; +import { Container, Flex, Text } from './ui'; // Asset amounts only apply to ERC20 assets -export interface AssetAmountInputProps extends AmountInputProps { +export interface AssetAmountInputProps { asset?: ERC20Asset; onChange: (value?: BigNumber, asset?: ERC20Asset) => void; + startingFontSizePx: number; + fontColor?: ColorOption; } -export class AssetAmountInput extends React.Component<AssetAmountInputProps> { +export interface AssetAmountInputState { + currentFontSizePx: number; +} + +export class AssetAmountInput extends React.Component<AssetAmountInputProps, AssetAmountInputState> { public static defaultProps = { onChange: util.boundNoop, }; + constructor(props: AssetAmountInputProps) { + super(props); + this.state = { + currentFontSizePx: props.startingFontSizePx, + }; + } public render(): React.ReactNode { const { asset, onChange, ...rest } = this.props; return ( - <Container> - <AmountInput {...rest} onChange={this._handleChange} /> - <Container display="inline-block" marginLeft="10px"> - <Text fontSize={rest.fontSize} fontColor={ColorOption.white} textTransform="uppercase"> - {assetUtils.bestNameForAsset(asset)} + <Container whiteSpace="nowrap"> + <Container borderBottom="1px solid rgba(255,255,255,0.3)" display="inline-block"> + <ScalingAmountInput + {...rest} + textLengthThreshold={this._textLengthThresholdForAsset(asset)} + maxFontSizePx={this.props.startingFontSizePx} + onChange={this._handleChange} + onFontSizeChange={this._handleFontSizeChange} + /> + </Container> + <Container display="inline-flex" marginLeft="10px" title={assetUtils.bestNameForAsset(asset)}> + <Text + fontSize={`${this.state.currentFontSizePx}px`} + fontColor={ColorOption.white} + textTransform="uppercase" + > + {assetUtils.formattedSymbolForAsset(asset)} </Text> </Container> </Container> @@ -36,4 +60,22 @@ export class AssetAmountInput extends React.Component<AssetAmountInputProps> { private readonly _handleChange = (value?: BigNumber): void => { this.props.onChange(value, this.props.asset); }; + private readonly _handleFontSizeChange = (fontSizePx: number): void => { + this.setState({ + currentFontSizePx: fontSizePx, + }); + }; + private readonly _textLengthThresholdForAsset = (asset?: ERC20Asset): number => { + if (_.isUndefined(asset)) { + return 3; + } + const symbol = asset.metaData.symbol; + if (symbol.length <= 3) { + return 5; + } + if (symbol.length === 5) { + return 3; + } + return 4; + }; } diff --git a/packages/instant/src/components/instant_heading.tsx b/packages/instant/src/components/instant_heading.tsx index 856e4d43e..0efb58360 100644 --- a/packages/instant/src/components/instant_heading.tsx +++ b/packages/instant/src/components/instant_heading.tsx @@ -43,7 +43,9 @@ export class InstantHeading extends React.Component<InstantHeadingProps, {}> { </Text> </Container> <Flex direction="row" justify="space-between"> - <SelectedAssetAmountInput fontSize="45px" /> + <Flex height="60px"> + <SelectedAssetAmountInput startingFontSizePx={38} /> + </Flex> <Flex direction="column" justify="space-between"> {iconOrAmounts} </Flex> diff --git a/packages/instant/src/components/scaling_amount_input.tsx b/packages/instant/src/components/scaling_amount_input.tsx new file mode 100644 index 000000000..23a15305a --- /dev/null +++ b/packages/instant/src/components/scaling_amount_input.tsx @@ -0,0 +1,53 @@ +import { BigNumber } from '@0x/utils'; +import * as _ from 'lodash'; +import * as React from 'react'; + +import { ColorOption } from '../style/theme'; +import { BigNumberInput } from '../util/big_number'; +import { util } from '../util/util'; + +import { ScalingInput } from './scaling_input'; + +export interface ScalingAmountInputProps { + maxFontSizePx: number; + textLengthThreshold: number; + fontColor?: ColorOption; + value?: BigNumber; + onChange: (value?: BigNumber) => void; + onFontSizeChange: (fontSizePx: number) => void; +} + +export class ScalingAmountInput extends React.Component<ScalingAmountInputProps> { + public static defaultProps = { + onChange: util.boundNoop, + onFontSizeChange: util.boundNoop, + }; + public render(): React.ReactNode { + const { textLengthThreshold, fontColor, maxFontSizePx, value, onFontSizeChange } = this.props; + return ( + <ScalingInput + maxFontSizePx={maxFontSizePx} + textLengthThreshold={textLengthThreshold} + onFontSizeChange={onFontSizeChange} + fontColor={fontColor} + onChange={this._handleChange} + value={!_.isUndefined(value) ? value.toString() : ''} + placeholder="0.00" + emptyInputWidthCh={3.5} + /> + ); + } + private readonly _handleChange = (event: React.ChangeEvent<HTMLInputElement>): void => { + const value = event.target.value; + let bigNumberValue; + if (!_.isEmpty(value)) { + try { + bigNumberValue = new BigNumberInput(event.target.value); + } catch { + // We don't want to allow values that can't be a BigNumber, so don't even call onChange. + return; + } + } + this.props.onChange(bigNumberValue); + }; +} diff --git a/packages/instant/src/components/scaling_input.tsx b/packages/instant/src/components/scaling_input.tsx new file mode 100644 index 000000000..6948c86c1 --- /dev/null +++ b/packages/instant/src/components/scaling_input.tsx @@ -0,0 +1,171 @@ +import * as _ from 'lodash'; +import * as React from 'react'; + +import { ColorOption } from '../style/theme'; +import { util } from '../util/util'; + +import { Input } from './ui'; + +export enum ScalingInputPhase { + FixedFontSize, + ScalingFontSize, +} + +export interface ScalingSettings { + percentageToReduceFontSizePerCharacter: number; + percentageToIncreaseWidthPerCharacter: number; +} + +export interface ScalingInputProps { + textLengthThreshold: number; + maxFontSizePx: number; + value: string; + emptyInputWidthCh: number; + onChange: (event: React.ChangeEvent<HTMLInputElement>) => void; + onFontSizeChange: (fontSizePx: number) => void; + fontColor?: ColorOption; + placeholder?: string; + maxLength?: number; + scalingSettings: ScalingSettings; +} + +export interface ScalingInputState { + inputWidthPxAtPhaseChange?: number; +} + +export interface ScalingInputSnapshot { + inputWidthPx: number; +} + +// These are magic numbers that were determined experimentally. +const defaultScalingSettings: ScalingSettings = { + percentageToReduceFontSizePerCharacter: 0.125, + percentageToIncreaseWidthPerCharacter: 0.06, +}; + +export class ScalingInput extends React.Component<ScalingInputProps, ScalingInputState> { + public static defaultProps = { + onChange: util.boundNoop, + onFontSizeChange: util.boundNoop, + maxLength: 7, + scalingSettings: defaultScalingSettings, + }; + public state = { + inputWidthPxAtPhaseChange: undefined, + }; + private readonly _inputRef = React.createRef<HTMLInputElement>(); + public static getPhase(textLengthThreshold: number, value: string): ScalingInputPhase { + if (value.length <= textLengthThreshold) { + return ScalingInputPhase.FixedFontSize; + } + return ScalingInputPhase.ScalingFontSize; + } + public static getPhaseFromProps(props: ScalingInputProps): ScalingInputPhase { + const { value, textLengthThreshold } = props; + return ScalingInput.getPhase(textLengthThreshold, value); + } + public static calculateFontSize( + textLengthThreshold: number, + maxFontSizePx: number, + phase: ScalingInputPhase, + value: string, + percentageToReduceFontSizePerCharacter: number, + ): number { + if (phase !== ScalingInputPhase.ScalingFontSize) { + return maxFontSizePx; + } + const charactersOverMax = value.length - textLengthThreshold; + const scalingFactor = (1 - percentageToReduceFontSizePerCharacter) ** charactersOverMax; + const fontSize = scalingFactor * maxFontSizePx; + return fontSize; + } + public static calculateFontSizeFromProps(props: ScalingInputProps, phase: ScalingInputPhase): number { + const { textLengthThreshold, value, maxFontSizePx, scalingSettings } = props; + return ScalingInput.calculateFontSize( + textLengthThreshold, + maxFontSizePx, + phase, + value, + scalingSettings.percentageToReduceFontSizePerCharacter, + ); + } + public getSnapshotBeforeUpdate(): ScalingInputSnapshot { + return { + inputWidthPx: this._getInputWidthInPx(), + }; + } + public componentDidUpdate( + prevProps: ScalingInputProps, + prevState: ScalingInputState, + snapshot: ScalingInputSnapshot, + ): void { + const prevPhase = ScalingInput.getPhaseFromProps(prevProps); + const curPhase = ScalingInput.getPhaseFromProps(this.props); + // if we went from anything else to end, fix to the current width as it shouldn't change as we grow + if (prevPhase !== ScalingInputPhase.ScalingFontSize && curPhase === ScalingInputPhase.ScalingFontSize) { + this.setState({ + inputWidthPxAtPhaseChange: snapshot.inputWidthPx, + }); + } + // if we end from end to to anything else, un-fix the width + if (prevPhase === ScalingInputPhase.ScalingFontSize && curPhase !== ScalingInputPhase.ScalingFontSize) { + this.setState({ + inputWidthPxAtPhaseChange: undefined, + }); + } + const prevFontSize = ScalingInput.calculateFontSizeFromProps(prevProps, prevPhase); + const curFontSize = ScalingInput.calculateFontSizeFromProps(this.props, curPhase); + // If font size has changed, notify. + if (prevFontSize !== curFontSize) { + this.props.onFontSizeChange(curFontSize); + } + } + public render(): React.ReactNode { + const { fontColor, onChange, placeholder, value, maxLength } = this.props; + const phase = ScalingInput.getPhaseFromProps(this.props); + return ( + <Input + ref={this._inputRef as any} + fontColor={fontColor} + onChange={onChange} + value={value} + placeholder={placeholder} + fontSize={`${this._calculateFontSize(phase)}px`} + width={this._calculateWidth(phase)} + maxLength={maxLength} + /> + ); + } + private readonly _calculateWidth = (phase: ScalingInputPhase): string => { + const { value, textLengthThreshold, scalingSettings } = this.props; + if (_.isEmpty(value)) { + return `${this.props.emptyInputWidthCh}ch`; + } + switch (phase) { + case ScalingInputPhase.FixedFontSize: + return `${value.length}ch`; + case ScalingInputPhase.ScalingFontSize: + const { inputWidthPxAtPhaseChange } = this.state; + if (!_.isUndefined(inputWidthPxAtPhaseChange)) { + const charactersOverMax = value.length - textLengthThreshold; + const scalingFactor = + (scalingSettings.percentageToIncreaseWidthPerCharacter + 1) ** charactersOverMax; + const width = scalingFactor * inputWidthPxAtPhaseChange; + return `${width}px`; + } + return `${textLengthThreshold}ch`; + default: + return '1ch'; + } + }; + private readonly _calculateFontSize = (phase: ScalingInputPhase): number => { + return ScalingInput.calculateFontSizeFromProps(this.props, phase); + }; + private readonly _getInputWidthInPx = (): number => { + const ref = this._inputRef.current; + if (!ref) { + return 0; + } + return ref.getBoundingClientRect().width; + }; +} diff --git a/packages/instant/src/components/ui/container.tsx b/packages/instant/src/components/ui/container.tsx index 5e2218c68..7077d0aa3 100644 --- a/packages/instant/src/components/ui/container.tsx +++ b/packages/instant/src/components/ui/container.tsx @@ -1,5 +1,3 @@ -import * as React from 'react'; - import { ColorOption, styled } from '../../style/theme'; import { cssRuleIfExists } from '../../style/util'; @@ -11,6 +9,7 @@ export interface ContainerProps { bottom?: string; left?: string; width?: string; + height?: string; maxWidth?: string; margin?: string; marginTop?: string; @@ -27,14 +26,11 @@ export interface ContainerProps { backgroundColor?: ColorOption; hasBoxShadow?: boolean; zIndex?: number; + whiteSpace?: string; opacity?: number; } -const PlainContainer: React.StatelessComponent<ContainerProps> = ({ children, className }) => ( - <div className={className}>{children}</div> -); - -export const Container = styled(PlainContainer)` +export const Container = styled<ContainerProps, 'div'>('div')` box-sizing: border-box; ${props => cssRuleIfExists(props, 'display')} ${props => cssRuleIfExists(props, 'position')} @@ -43,6 +39,7 @@ export const Container = styled(PlainContainer)` ${props => cssRuleIfExists(props, 'bottom')} ${props => cssRuleIfExists(props, 'left')} ${props => cssRuleIfExists(props, 'width')} + ${props => cssRuleIfExists(props, 'height')} ${props => cssRuleIfExists(props, 'max-width')} ${props => cssRuleIfExists(props, 'margin')} ${props => cssRuleIfExists(props, 'margin-top')} @@ -55,6 +52,7 @@ export const Container = styled(PlainContainer)` ${props => cssRuleIfExists(props, 'border-top')} ${props => cssRuleIfExists(props, 'border-bottom')} ${props => cssRuleIfExists(props, 'z-index')} + ${props => cssRuleIfExists(props, 'white-space')} ${props => cssRuleIfExists(props, 'opacity')} ${props => (props.hasBoxShadow ? `box-shadow: 0px 2px 10px rgba(0, 0, 0, 0.1)` : '')}; background-color: ${props => (props.backgroundColor ? props.theme[props.backgroundColor] : 'none')}; diff --git a/packages/instant/src/components/ui/flex.tsx b/packages/instant/src/components/ui/flex.tsx index 327e91926..d4e14d979 100644 --- a/packages/instant/src/components/ui/flex.tsx +++ b/packages/instant/src/components/ui/flex.tsx @@ -1,5 +1,3 @@ -import * as React from 'react'; - import { ColorOption, styled } from '../../style/theme'; import { cssRuleIfExists } from '../../style/util'; @@ -9,21 +7,19 @@ export interface FlexProps { justify?: 'flex-start' | 'center' | 'space-around' | 'space-between' | 'space-evenly' | 'flex-end'; align?: 'flex-start' | 'center' | 'space-around' | 'space-between' | 'space-evenly' | 'flex-end'; width?: string; + height?: string; backgroundColor?: ColorOption; className?: string; } -const PlainFlex: React.StatelessComponent<FlexProps> = ({ children, className }) => ( - <div className={className}>{children}</div> -); - -export const Flex = styled(PlainFlex)` +export const Flex = styled<FlexProps, 'div'>('div')` display: flex; flex-direction: ${props => props.direction}; flex-wrap: ${props => props.flexWrap}; justify-content: ${props => props.justify}; align-items: ${props => props.align}; ${props => cssRuleIfExists(props, 'width')} + ${props => cssRuleIfExists(props, 'height')} background-color: ${props => (props.backgroundColor ? props.theme[props.backgroundColor] : 'none')}; `; diff --git a/packages/instant/src/components/ui/input.tsx b/packages/instant/src/components/ui/input.tsx index f8c6b6ef6..9db7d1c4c 100644 --- a/packages/instant/src/components/ui/input.tsx +++ b/packages/instant/src/components/ui/input.tsx @@ -12,11 +12,7 @@ export interface InputProps { onChange?: (event: React.ChangeEvent<HTMLInputElement>) => void; } -const PlainInput: React.StatelessComponent<InputProps> = ({ value, className, placeholder, onChange }) => ( - <input className={className} value={value} onChange={onChange} placeholder={placeholder} /> -); - -export const Input = styled(PlainInput)` +export const Input = styled<InputProps, 'input'>('input')` font-size: ${props => props.fontSize}; width: ${props => props.width}; padding: 0.1em 0em; diff --git a/packages/instant/src/components/ui/text.tsx b/packages/instant/src/components/ui/text.tsx index 9fb8ea26f..d23b0034d 100644 --- a/packages/instant/src/components/ui/text.tsx +++ b/packages/instant/src/components/ui/text.tsx @@ -23,14 +23,8 @@ export interface TextProps { display?: string; } -const PlainText: React.StatelessComponent<TextProps> = ({ children, className, onClick }) => ( - <div className={className} onClick={onClick}> - {children} - </div> -); - const darkenOnHoverAmount = 0.3; -export const Text = styled(PlainText)` +export const Text = styled<TextProps, 'div'>('div')` font-family: ${props => props.fontFamily}; font-style: ${props => props.fontStyle}; font-weight: ${props => props.fontWeight}; diff --git a/packages/instant/src/containers/selected_asset_amount_input.ts b/packages/instant/src/containers/selected_asset_amount_input.ts index 0d847cf02..e1438e920 100644 --- a/packages/instant/src/containers/selected_asset_amount_input.ts +++ b/packages/instant/src/containers/selected_asset_amount_input.ts @@ -17,7 +17,7 @@ import { AssetAmountInput } from '../components/asset_amount_input'; export interface SelectedAssetAmountInputProps { fontColor?: ColorOption; - fontSize?: string; + startingFontSizePx: number; } interface ConnectedState { diff --git a/packages/instant/src/style/fonts.ts b/packages/instant/src/style/fonts.ts index 975a30a61..92450502d 100644 --- a/packages/instant/src/style/fonts.ts +++ b/packages/instant/src/style/fonts.ts @@ -1,10 +1,10 @@ -import { injectGlobal } from './theme'; - export const fonts = { include: () => { // Inject the inter-ui font into the page - return injectGlobal` - @import url('https://rsms.me/inter/inter-ui.css'); - `; + const appendTo = document.head || document.getElementsByTagName('head')[0] || document.body; + const style = document.createElement('style'); + style.type = 'text/css'; + style.appendChild(document.createTextNode(`@import url('https://rsms.me/inter/inter-ui.css')`)); + appendTo.appendChild(style); }, }; diff --git a/packages/instant/src/style/theme.ts b/packages/instant/src/style/theme.ts index d26c816c1..be527a12c 100644 --- a/packages/instant/src/style/theme.ts +++ b/packages/instant/src/style/theme.ts @@ -1,6 +1,6 @@ import * as styledComponents from 'styled-components'; -const { default: styled, css, injectGlobal, keyframes, ThemeProvider } = styledComponents; +const { default: styled, css, keyframes, ThemeProvider } = styledComponents; export type Theme = { [key in ColorOption]: string }; @@ -28,4 +28,4 @@ export const theme: Theme = { darkOrange: '#F2994C', }; -export { styled, css, injectGlobal, keyframes, ThemeProvider }; +export { styled, css, keyframes, ThemeProvider }; diff --git a/packages/instant/src/util/asset.ts b/packages/instant/src/util/asset.ts index 4e3b2b946..ebeba948e 100644 --- a/packages/instant/src/util/asset.ts +++ b/packages/instant/src/util/asset.ts @@ -2,7 +2,7 @@ import { AssetProxyId, ObjectMap } from '@0x/types'; import * as _ from 'lodash'; import { assetDataNetworkMapping } from '../data/asset_data_network_mapping'; -import { Asset, AssetMetaData, Network, ZeroExInstantError } from '../types'; +import { Asset, AssetMetaData, Network, ZeroExInstantError, ERC20Asset } from '../types'; export const assetUtils = { createAssetFromAssetData: ( @@ -43,6 +43,16 @@ export const assetUtils = { return defaultName; } }, + formattedSymbolForAsset: (asset?: ERC20Asset, defaultName: string = '???'): string => { + if (_.isUndefined(asset)) { + return defaultName; + } + const symbol = asset.metaData.symbol; + if (symbol.length <= 5) { + return symbol; + } + return `${symbol.slice(0, 3)}...`; + }, getAssociatedAssetDataIfExists: (assetData: string, network: Network): string | undefined => { const assetDataGroupIfExists = _.find(assetDataNetworkMapping, value => value[network] === assetData); if (_.isUndefined(assetDataGroupIfExists)) { diff --git a/packages/instant/src/util/big_number.ts b/packages/instant/src/util/big_number.ts new file mode 100644 index 000000000..773eb0cb4 --- /dev/null +++ b/packages/instant/src/util/big_number.ts @@ -0,0 +1,29 @@ +import { BigNumber } from '@0x/utils'; +import * as _ from 'lodash'; + +/** + * A BigNumber extension that is more flexible about decimal strings. + * Such as allowing: + * new BigNumberInput(0.) => 0 + * new BigNumberInput(1.) => 1 + * new BigNumberInput(1..) => still throws + */ +export class BigNumberInput extends BigNumber { + private _hasDecimalPeriod: boolean; + constructor(bigNumberString: string) { + const hasDecimalPeriod = _.endsWith(bigNumberString, '.'); + let internalString = bigNumberString; + if (hasDecimalPeriod) { + internalString = bigNumberString.slice(0, bigNumberString.length - 1); + } + super(internalString); + this._hasDecimalPeriod = hasDecimalPeriod; + } + public toString(): string { + const internalString = super.toString(); + if (this._hasDecimalPeriod) { + return `${internalString}.`; + } + return internalString; + } +} diff --git a/packages/instant/src/util/format.ts b/packages/instant/src/util/format.ts index 8482b1526..ca7c01359 100644 --- a/packages/instant/src/util/format.ts +++ b/packages/instant/src/util/format.ts @@ -24,7 +24,7 @@ export const format = { if (_.isUndefined(ethUnitAmount)) { return defaultText; } - const roundedAmount = ethUnitAmount.round(decimalPlaces); + const roundedAmount = ethUnitAmount.round(decimalPlaces).toDigits(decimalPlaces); return `${roundedAmount} ETH`; }, ethBaseAmountInUsd: ( diff --git a/packages/instant/test/util/format.test.ts b/packages/instant/test/util/format.test.ts index 141df9275..2c9294c78 100644 --- a/packages/instant/test/util/format.test.ts +++ b/packages/instant/test/util/format.test.ts @@ -20,8 +20,8 @@ describe('format', () => { it('converts .432414 ETH in base units to the string `.4324 ETH`', () => { expect(format.ethBaseAmount(DECIMAL_ETH_IN_BASE_UNITS)).toBe('0.4324 ETH'); }); - it('converts 5.3014059295032 ETH in base units to the string `5.3014 ETH`', () => { - expect(format.ethBaseAmount(IRRATIONAL_ETH_IN_BASE_UNITS)).toBe('5.3014 ETH'); + it('converts 5.3014059295032 ETH in base units to the string `5.301 ETH`', () => { + expect(format.ethBaseAmount(IRRATIONAL_ETH_IN_BASE_UNITS)).toBe('5.301 ETH'); }); it('returns defaultText param when ethBaseAmount is not defined', () => { const defaultText = 'defaultText'; @@ -38,8 +38,8 @@ describe('format', () => { it('converts BigNumer(.432414) to the string `.4324 ETH`', () => { expect(format.ethUnitAmount(BIG_NUMBER_DECIMAL)).toBe('0.4324 ETH'); }); - it('converts BigNumber(5.3014059295032) to the string `5.3014 ETH`', () => { - expect(format.ethUnitAmount(BIG_NUMBER_IRRATIONAL)).toBe('5.3014 ETH'); + it('converts BigNumber(5.3014059295032) to the string `5.301 ETH`', () => { + expect(format.ethUnitAmount(BIG_NUMBER_IRRATIONAL)).toBe('5.301 ETH'); }); it('returns defaultText param when ethUnitAmount is not defined', () => { const defaultText = 'defaultText'; |