aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorFrancesco Agosti <francesco.agosti93@gmail.com>2018-10-27 02:14:00 +0800
committerGitHub <noreply@github.com>2018-10-27 02:14:00 +0800
commit4c5b26db183f8f9163e043f5dd4bb0ce60e3aa7f (patch)
treedbfc6614e901c44d32775b1fe69bb957706c9cf0
parentd2bf23de71bc2bdfaca14bd7026daa59ca5ef386 (diff)
parent30809e646be02025d6f9e9ed0ff214d9ace681c8 (diff)
downloaddexon-sol-tools-4c5b26db183f8f9163e043f5dd4bb0ce60e3aa7f.tar
dexon-sol-tools-4c5b26db183f8f9163e043f5dd4bb0ce60e3aa7f.tar.gz
dexon-sol-tools-4c5b26db183f8f9163e043f5dd4bb0ce60e3aa7f.tar.bz2
dexon-sol-tools-4c5b26db183f8f9163e043f5dd4bb0ce60e3aa7f.tar.lz
dexon-sol-tools-4c5b26db183f8f9163e043f5dd4bb0ce60e3aa7f.tar.xz
dexon-sol-tools-4c5b26db183f8f9163e043f5dd4bb0ce60e3aa7f.tar.zst
dexon-sol-tools-4c5b26db183f8f9163e043f5dd4bb0ce60e3aa7f.zip
Merge pull request #1175 from 0xProject/feature/instant/input-fees-rounding
[instant] Create a ScalingInput component and use it in the amount input and upgrade to styled-components v4
-rw-r--r--packages/instant/package.json3
-rw-r--r--packages/instant/src/components/amount_input.tsx49
-rw-r--r--packages/instant/src/components/animations/slide_animations.tsx10
-rw-r--r--packages/instant/src/components/asset_amount_input.tsx39
-rw-r--r--packages/instant/src/components/erc20_asset_amount_input.tsx84
-rw-r--r--packages/instant/src/components/instant_heading.tsx6
-rw-r--r--packages/instant/src/components/scaling_amount_input.tsx52
-rw-r--r--packages/instant/src/components/scaling_input.tsx170
-rw-r--r--packages/instant/src/components/ui/container.tsx15
-rw-r--r--packages/instant/src/components/ui/flex.tsx13
-rw-r--r--packages/instant/src/components/ui/input.tsx9
-rw-r--r--packages/instant/src/components/ui/text.tsx11
-rw-r--r--packages/instant/src/containers/selected_erc20_asset_amount_input.ts (renamed from packages/instant/src/containers/selected_asset_amount_input.ts)27
-rw-r--r--packages/instant/src/redux/actions.ts5
-rw-r--r--packages/instant/src/redux/reducer.ts3
-rw-r--r--packages/instant/src/style/fonts.ts10
-rw-r--r--packages/instant/src/style/theme.ts6
-rw-r--r--packages/instant/src/util/asset.ts12
-rw-r--r--packages/instant/src/util/big_number_input.ts29
-rw-r--r--packages/instant/src/util/format.ts2
-rw-r--r--packages/instant/test/util/format.test.ts8
-rw-r--r--yarn.lock67
22 files changed, 473 insertions, 157 deletions
diff --git a/packages/instant/package.json b/packages/instant/package.json
index 421802530..be85b5062 100644
--- a/packages/instant/package.json
+++ b/packages/instant/package.json
@@ -58,7 +58,7 @@
"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": {
@@ -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..84280372b 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,7 +16,7 @@ const slideKeyframeGenerator = (fromY: string, toY: string) => keyframes`
`;
export interface SlideAnimationProps {
- keyframes: string;
+ keyframes: Keyframes;
animationType: string;
animationDirection?: string;
}
@@ -24,7 +25,10 @@ export const SlideAnimation =
styled.div <
SlideAnimationProps >
`
- animation-name: ${props => props.keyframes};
+ 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
deleted file mode 100644
index c03ef1cf3..000000000
--- a/packages/instant/src/components/asset_amount_input.tsx
+++ /dev/null
@@ -1,39 +0,0 @@
-import { BigNumber } from '@0x/utils';
-import * as _ from 'lodash';
-import * as React from 'react';
-
-import { ColorOption } from '../style/theme';
-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';
-
-// Asset amounts only apply to ERC20 assets
-export interface AssetAmountInputProps extends AmountInputProps {
- asset?: ERC20Asset;
- onChange: (value?: BigNumber, asset?: ERC20Asset) => void;
-}
-
-export class AssetAmountInput extends React.Component<AssetAmountInputProps> {
- public static defaultProps = {
- onChange: util.boundNoop,
- };
- 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)}
- </Text>
- </Container>
- </Container>
- );
- }
- private readonly _handleChange = (value?: BigNumber): void => {
- this.props.onChange(value, this.props.asset);
- };
-}
diff --git a/packages/instant/src/components/erc20_asset_amount_input.tsx b/packages/instant/src/components/erc20_asset_amount_input.tsx
new file mode 100644
index 000000000..583fad28b
--- /dev/null
+++ b/packages/instant/src/components/erc20_asset_amount_input.tsx
@@ -0,0 +1,84 @@
+import * as _ from 'lodash';
+import * as React from 'react';
+
+import { ColorOption, transparentWhite } from '../style/theme';
+import { ERC20Asset } from '../types';
+import { assetUtils } from '../util/asset';
+import { BigNumberInput } from '../util/big_number_input';
+import { util } from '../util/util';
+
+import { ScalingAmountInput } from './scaling_amount_input';
+import { Container, Text } from './ui';
+
+// Asset amounts only apply to ERC20 assets
+export interface ERC20AssetAmountInputProps {
+ asset?: ERC20Asset;
+ value?: BigNumberInput;
+ onChange: (value?: BigNumberInput, asset?: ERC20Asset) => void;
+ startingFontSizePx: number;
+ fontColor?: ColorOption;
+}
+
+export interface ERC20AssetAmountInputState {
+ currentFontSizePx: number;
+}
+
+export class ERC20AssetAmountInput extends React.Component<ERC20AssetAmountInputProps, ERC20AssetAmountInputState> {
+ public static defaultProps = {
+ onChange: util.boundNoop,
+ };
+ constructor(props: ERC20AssetAmountInputProps) {
+ super(props);
+ this.state = {
+ currentFontSizePx: props.startingFontSizePx,
+ };
+ }
+ public render(): React.ReactNode {
+ const { asset, onChange, ...rest } = this.props;
+ return (
+ <Container whiteSpace="nowrap">
+ <Container borderBottom={`1px solid ${transparentWhite}`} 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>
+ );
+ }
+ private readonly _handleChange = (value?: BigNumberInput): void => {
+ this.props.onChange(value, this.props.asset);
+ };
+ private readonly _handleFontSizeChange = (fontSizePx: number): void => {
+ this.setState({
+ currentFontSizePx: fontSizePx,
+ });
+ };
+ // For assets with symbols of different length,
+ // start scaling the input at different character lengths
+ 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 17ac65429..1ef276ff3 100644
--- a/packages/instant/src/components/instant_heading.tsx
+++ b/packages/instant/src/components/instant_heading.tsx
@@ -2,7 +2,7 @@ import { BigNumber } from '@0x/utils';
import * as _ from 'lodash';
import * as React from 'react';
-import { SelectedAssetAmountInput } from '../containers/selected_asset_amount_input';
+import { SelectedERC20AssetAmountInput } from '../containers/selected_erc20_asset_amount_input';
import { ColorOption } from '../style/theme';
import { AsyncProcessState, OrderProcessState, OrderState } from '../types';
import { format } from '../util/format';
@@ -48,7 +48,9 @@ export class InstantHeading extends React.Component<InstantHeadingProps, {}> {
</Text>
</Container>
<Flex direction="row" justify="space-between">
- <SelectedAssetAmountInput fontSize="45px" />
+ <Flex height="60px">
+ <SelectedERC20AssetAmountInput 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..655ae2b74
--- /dev/null
+++ b/packages/instant/src/components/scaling_amount_input.tsx
@@ -0,0 +1,52 @@
+import * as _ from 'lodash';
+import * as React from 'react';
+
+import { ColorOption } from '../style/theme';
+import { BigNumberInput } from '../util/big_number_input';
+import { util } from '../util/util';
+
+import { ScalingInput } from './scaling_input';
+
+export interface ScalingAmountInputProps {
+ maxFontSizePx: number;
+ textLengthThreshold: number;
+ fontColor?: ColorOption;
+ value?: BigNumberInput;
+ onChange: (value?: BigNumberInput) => 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.toDisplayString() : ''}
+ 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(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..34cb0b5fd
--- /dev/null
+++ b/packages/instant/src/components/scaling_input.tsx
@@ -0,0 +1,170 @@
+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;
+ constantPxToIncreaseWidthPerCharacter: 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,
+ constantPxToIncreaseWidthPerCharacter: 4,
+};
+
+export class ScalingInput extends React.Component<ScalingInputProps, ScalingInputState> {
+ public static defaultProps = {
+ onChange: util.boundNoop,
+ onFontSizeChange: util.boundNoop,
+ maxLength: 7,
+ scalingSettings: defaultScalingSettings,
+ };
+ public state: ScalingInputState = {
+ 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 fixed to scaling, save the width from the transition
+ if (prevPhase !== ScalingInputPhase.ScalingFontSize && curPhase === ScalingInputPhase.ScalingFontSize) {
+ this.setState({
+ inputWidthPxAtPhaseChange: snapshot.inputWidthPx,
+ });
+ }
+ // if we went from scaling to fixed, revert back to scaling using `ch`
+ 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 scalingAmount = scalingSettings.constantPxToIncreaseWidthPerCharacter * charactersOverMax;
+ const width = inputWidthPxAtPhaseChange + scalingAmount;
+ 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..76b570de7 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,14 @@ 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.div <
+ ContainerProps >
+ `
box-sizing: border-box;
${props => cssRuleIfExists(props, 'display')}
${props => cssRuleIfExists(props, 'position')}
@@ -43,6 +42,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 +55,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..5fa3fc95b 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,22 @@ 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.div <
+ FlexProps >
+ `
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..a884ff7cb 100644
--- a/packages/instant/src/components/ui/input.tsx
+++ b/packages/instant/src/components/ui/input.tsx
@@ -12,11 +12,10 @@ 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.input <
+ InputProps >
+ `
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..fd72f6cc8 100644
--- a/packages/instant/src/components/ui/text.tsx
+++ b/packages/instant/src/components/ui/text.tsx
@@ -23,14 +23,11 @@ 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.div <
+ TextProps >
+ `
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_erc20_asset_amount_input.ts
index e9dbc61ce..ee76e9d66 100644
--- a/packages/instant/src/containers/selected_asset_amount_input.ts
+++ b/packages/instant/src/containers/selected_erc20_asset_amount_input.ts
@@ -11,34 +11,35 @@ import { Action, actions } from '../redux/actions';
import { State } from '../redux/reducer';
import { ColorOption } from '../style/theme';
import { ERC20Asset, OrderProcessState } from '../types';
+import { BigNumberInput } from '../util/big_number_input';
import { errorUtil } from '../util/error';
-import { AssetAmountInput } from '../components/asset_amount_input';
+import { ERC20AssetAmountInput } from '../components/erc20_asset_amount_input';
-export interface SelectedAssetAmountInputProps {
+export interface SelectedERC20AssetAmountInputProps {
fontColor?: ColorOption;
- fontSize?: string;
+ startingFontSizePx: number;
}
interface ConnectedState {
assetBuyer?: AssetBuyer;
- value?: BigNumber;
+ value?: BigNumberInput;
asset?: ERC20Asset;
}
interface ConnectedDispatch {
- updateBuyQuote: (assetBuyer?: AssetBuyer, value?: BigNumber, asset?: ERC20Asset) => void;
+ updateBuyQuote: (assetBuyer?: AssetBuyer, value?: BigNumberInput, asset?: ERC20Asset) => void;
}
interface ConnectedProps {
- value?: BigNumber;
+ value?: BigNumberInput;
asset?: ERC20Asset;
- onChange: (value?: BigNumber, asset?: ERC20Asset) => void;
+ onChange: (value?: BigNumberInput, asset?: ERC20Asset) => void;
}
-type FinalProps = ConnectedProps & SelectedAssetAmountInputProps;
+type FinalProps = ConnectedProps & SelectedERC20AssetAmountInputProps;
-const mapStateToProps = (state: State, _ownProps: SelectedAssetAmountInputProps): ConnectedState => {
+const mapStateToProps = (state: State, _ownProps: SelectedERC20AssetAmountInputProps): ConnectedState => {
const selectedAsset = state.selectedAsset;
if (_.isUndefined(selectedAsset) || selectedAsset.metaData.assetProxyId !== AssetProxyId.ERC20) {
return {
@@ -82,7 +83,7 @@ const debouncedUpdateBuyQuoteAsync = _.debounce(updateBuyQuoteAsync, 200, { trai
const mapDispatchToProps = (
dispatch: Dispatch<Action>,
- _ownProps: SelectedAssetAmountInputProps,
+ _ownProps: SelectedERC20AssetAmountInputProps,
): ConnectedDispatch => ({
updateBuyQuote: (assetBuyer, value, asset) => {
// Update the input
@@ -104,7 +105,7 @@ const mapDispatchToProps = (
const mergeProps = (
connectedState: ConnectedState,
connectedDispatch: ConnectedDispatch,
- ownProps: SelectedAssetAmountInputProps,
+ ownProps: SelectedERC20AssetAmountInputProps,
): FinalProps => {
return {
...ownProps,
@@ -116,8 +117,8 @@ const mergeProps = (
};
};
-export const SelectedAssetAmountInput: React.ComponentClass<SelectedAssetAmountInputProps> = connect(
+export const SelectedERC20AssetAmountInput: React.ComponentClass<SelectedERC20AssetAmountInputProps> = connect(
mapStateToProps,
mapDispatchToProps,
mergeProps,
-)(AssetAmountInput);
+)(ERC20AssetAmountInput);
diff --git a/packages/instant/src/redux/actions.ts b/packages/instant/src/redux/actions.ts
index 5a4099f15..46045024b 100644
--- a/packages/instant/src/redux/actions.ts
+++ b/packages/instant/src/redux/actions.ts
@@ -2,6 +2,8 @@ import { BuyQuote } from '@0x/asset-buyer';
import { BigNumber } from '@0x/utils';
import * as _ from 'lodash';
+import { BigNumberInput } from '../util/big_number_input';
+
import { ActionsUnion, OrderState } from '../types';
export interface PlainAction<T extends string> {
@@ -36,7 +38,8 @@ export enum ActionTypes {
export const actions = {
updateEthUsdPrice: (price?: BigNumber) => createAction(ActionTypes.UPDATE_ETH_USD_PRICE, price),
- updateSelectedAssetAmount: (amount?: BigNumber) => createAction(ActionTypes.UPDATE_SELECTED_ASSET_AMOUNT, amount),
+ updateSelectedAssetAmount: (amount?: BigNumberInput) =>
+ createAction(ActionTypes.UPDATE_SELECTED_ASSET_AMOUNT, amount),
updateBuyOrderState: (orderState: OrderState) => createAction(ActionTypes.UPDATE_BUY_ORDER_STATE, orderState),
updateLatestBuyQuote: (buyQuote?: BuyQuote) => createAction(ActionTypes.UPDATE_LATEST_BUY_QUOTE, buyQuote),
updateSelectedAsset: (assetData?: string) => createAction(ActionTypes.UPDATE_SELECTED_ASSET, assetData),
diff --git a/packages/instant/src/redux/reducer.ts b/packages/instant/src/redux/reducer.ts
index 25d0092b2..d7e5bdfb5 100644
--- a/packages/instant/src/redux/reducer.ts
+++ b/packages/instant/src/redux/reducer.ts
@@ -14,6 +14,7 @@ import {
OrderState,
} from '../types';
import { assetUtils } from '../util/asset';
+import { BigNumberInput } from '../util/big_number_input';
import { Action, ActionTypes } from './actions';
@@ -22,7 +23,7 @@ export interface State {
assetBuyer?: AssetBuyer;
assetMetaDataMap: ObjectMap<AssetMetaData>;
selectedAsset?: Asset;
- selectedAssetAmount?: BigNumber;
+ selectedAssetAmount?: BigNumberInput;
buyOrderState: OrderState;
ethUsdPrice?: BigNumber;
latestBuyQuote?: BuyQuote;
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..6575ff9f4 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,6 @@ export const theme: Theme = {
darkOrange: '#F2994C',
};
-export { styled, css, injectGlobal, keyframes, ThemeProvider };
+export const transparentWhite = 'rgba(255,255,255,0.3)';
+
+export { styled, css, keyframes, ThemeProvider };
diff --git a/packages/instant/src/util/asset.ts b/packages/instant/src/util/asset.ts
index 4e3b2b946..2c5b6325d 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, ERC20Asset, Network, ZeroExInstantError } 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_input.ts b/packages/instant/src/util/big_number_input.ts
new file mode 100644
index 000000000..d2a9a8dc5
--- /dev/null
+++ b/packages/instant/src/util/big_number_input.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 readonly _isEndingWithDecimal: boolean;
+ constructor(bigNumberString: string) {
+ const hasDecimalPeriod = _.endsWith(bigNumberString, '.');
+ let internalString = bigNumberString;
+ if (hasDecimalPeriod) {
+ internalString = bigNumberString.slice(0, -1);
+ }
+ super(internalString);
+ this._isEndingWithDecimal = hasDecimalPeriod;
+ }
+ public toDisplayString(): string {
+ const internalString = super.toString();
+ if (this._isEndingWithDecimal) {
+ 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';
diff --git a/yarn.lock b/yarn.lock
index 8b9fd53cb..1ea799433 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -484,6 +484,12 @@
dependencies:
"@babel/highlight" "^7.0.0"
+"@babel/helper-annotate-as-pure@^7.0.0":
+ version "7.0.0"
+ resolved "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.0.0.tgz#323d39dd0b50e10c7c06ca7d7638e6864d8c5c32"
+ dependencies:
+ "@babel/types" "^7.0.0"
+
"@babel/highlight@^7.0.0":
version "7.0.0"
resolved "https://registry.npmjs.org/@babel/highlight/-/highlight-7.0.0.tgz#f710c38c8d458e6dd9a201afb637fcb781ce99e4"
@@ -504,6 +510,24 @@
dependencies:
regenerator-runtime "^0.12.0"
+"@babel/types@^7.0.0":
+ version "7.1.3"
+ resolved "https://registry.npmjs.org/@babel/types/-/types-7.1.3.tgz#3a767004567060c2f40fca49a304712c525ee37d"
+ dependencies:
+ esutils "^2.0.2"
+ lodash "^4.17.10"
+ to-fast-properties "^2.0.0"
+
+"@emotion/is-prop-valid@^0.6.8":
+ version "0.6.8"
+ resolved "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-0.6.8.tgz#68ad02831da41213a2089d2cab4e8ac8b30cbd85"
+ dependencies:
+ "@emotion/memoize" "^0.6.6"
+
+"@emotion/memoize@^0.6.6":
+ version "0.6.6"
+ resolved "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.6.6.tgz#004b98298d04c7ca3b4f50ca2035d4f60d2eed1b"
+
"@ledgerhq/hw-app-eth@^4.3.0":
version "4.7.3"
resolved "https://registry.yarnpkg.com/@ledgerhq/hw-app-eth/-/hw-app-eth-4.7.3.tgz#d352e19658ae296532e522c53c8ec2a1a77b64e5"
@@ -1579,6 +1603,13 @@
"@types/node" "*"
"@types/react" "*"
+"@types/styled-components@^4.0.1":
+ version "4.0.1"
+ resolved "https://registry.npmjs.org/@types/styled-components/-/styled-components-4.0.1.tgz#5eb9a5474dbde3becab2bcc8f04e0b8e8dcf8c06"
+ dependencies:
+ "@types/node" "*"
+ "@types/react" "*"
+
"@types/tmp@^0.0.33":
version "0.0.33"
resolved "https://registry.yarnpkg.com/@types/tmp/-/tmp-0.0.33.tgz#1073c4bc824754ae3d10cfab88ab0237ba964e4d"
@@ -2532,6 +2563,13 @@ babel-plugin-jest-hoist@^23.2.0:
version "23.2.0"
resolved "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-23.2.0.tgz#e61fae05a1ca8801aadee57a6d66b8cefaf44167"
+"babel-plugin-styled-components@>= 1":
+ version "1.8.0"
+ resolved "https://registry.npmjs.org/babel-plugin-styled-components/-/babel-plugin-styled-components-1.8.0.tgz#9dd054c8e86825203449a852a5746f29f2dab857"
+ dependencies:
+ "@babel/helper-annotate-as-pure" "^7.0.0"
+ lodash "^4.17.10"
+
babel-plugin-syntax-async-functions@^6.8.0:
version "6.13.0"
resolved "https://registry.yarnpkg.com/babel-plugin-syntax-async-functions/-/babel-plugin-syntax-async-functions-6.13.0.tgz#cad9cad1191b5ad634bf30ae0872391e0647be95"
@@ -4581,6 +4619,14 @@ css-to-react-native@^2.0.3:
fbjs "^0.8.5"
postcss-value-parser "^3.3.0"
+css-to-react-native@^2.2.2:
+ version "2.2.2"
+ resolved "https://registry.npmjs.org/css-to-react-native/-/css-to-react-native-2.2.2.tgz#c077d0f7bf3e6c915a539e7325821c9dd01f9965"
+ dependencies:
+ css-color-keywords "^1.0.0"
+ fbjs "^0.8.5"
+ postcss-value-parser "^3.3.0"
+
css-vendor@^0.3.8:
version "0.3.8"
resolved "https://registry.npmjs.org/css-vendor/-/css-vendor-0.3.8.tgz#6421cfd3034ce664fe7673972fd0119fc28941fa"
@@ -6705,7 +6751,7 @@ ganache-core@0xProject/ganache-core#monorepo-dep:
ethereumjs-tx "0xProject/ethereumjs-tx#fake-tx-include-signature-by-default"
ethereumjs-util "^5.2.0"
ethereumjs-vm "2.3.5"
- ethereumjs-wallet "0.6.0"
+ ethereumjs-wallet "~0.6.0"
fake-merkle-patricia-tree "~1.0.1"
heap "~0.2.6"
js-scrypt "^0.2.0"
@@ -14394,19 +14440,18 @@ styled-components@^3.3.3:
stylis-rule-sheet "^0.0.10"
supports-color "^3.2.3"
-styled-components@^3.4.9:
- version "3.4.10"
- resolved "https://registry.npmjs.org/styled-components/-/styled-components-3.4.10.tgz#9a654c50ea2b516c36ade57ddcfa296bf85c96e1"
+styled-components@^4.0.2:
+ version "4.0.2"
+ resolved "https://registry.npmjs.org/styled-components/-/styled-components-4.0.2.tgz#7d4409ada019cdd34c25ba68c4577ea95dbcf0c5"
dependencies:
- buffer "^5.0.3"
- css-to-react-native "^2.0.3"
- fbjs "^0.8.16"
- hoist-non-react-statics "^2.5.0"
+ "@emotion/is-prop-valid" "^0.6.8"
+ babel-plugin-styled-components ">= 1"
+ css-to-react-native "^2.2.2"
+ memoize-one "^4.0.0"
prop-types "^15.5.4"
react-is "^16.3.1"
stylis "^3.5.0"
stylis-rule-sheet "^0.0.10"
- supports-color "^3.2.3"
stylis-rule-sheet@^0.0.10:
version "0.0.10"
@@ -14849,6 +14894,10 @@ to-fast-properties@^1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-1.0.3.tgz#b83571fa4d8c25b82e231b06e3a3055de4ca1a47"
+to-fast-properties@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e"
+
to-no-case@^1.0.0:
version "1.0.2"
resolved "https://registry.yarnpkg.com/to-no-case/-/to-no-case-1.0.2.tgz#c722907164ef6b178132c8e69930212d1b4aa16a"