aboutsummaryrefslogtreecommitdiffstats
path: root/packages/instant
diff options
context:
space:
mode:
Diffstat (limited to 'packages/instant')
-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
21 files changed, 415 insertions, 148 deletions
diff --git a/packages/instant/package.json b/packages/instant/package.json
index 4a3add2ec..977126867 100644
--- a/packages/instant/package.json
+++ b/packages/instant/package.json
@@ -60,7 +60,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": {
@@ -75,6 +75,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 d7e87eebb..630103c7b 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: (
@@ -46,6 +46,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';