import { colors } from '@0xproject/react-shared'; import { BigNumber } from '@0xproject/utils'; import * as _ from 'lodash'; import TextField from 'material-ui/TextField'; import * as React from 'react'; import { RequiredLabel } from 'ts/components/ui/required_label'; import { ValidatedBigNumberCallback } from 'ts/types'; import { utils } from 'ts/utils/utils'; interface BalanceBoundedInputProps { label?: string; balance: BigNumber; amount?: BigNumber; hintText?: string; onChange: ValidatedBigNumberCallback; onErrorMsgChange?: (errorMsg: React.ReactNode) => void; shouldShowIncompleteErrs?: boolean; shouldCheckBalance: boolean; validate?: (amount: BigNumber) => React.ReactNode; isDisabled?: boolean; shouldShowErrs?: boolean; shouldShowUnderline?: boolean; inputStyle?: React.CSSProperties; inputHintStyle?: React.CSSProperties; } interface BalanceBoundedInputState { errMsg: React.ReactNode; amountString: string; } export class BalanceBoundedInput extends React.Component { public static defaultProps: Partial = { shouldShowIncompleteErrs: false, isDisabled: false, shouldShowErrs: true, hintText: 'amount', onErrorMsgChange: _.noop, shouldShowUnderline: true, }; constructor(props: BalanceBoundedInputProps) { super(props); const amountString = this.props.amount ? this.props.amount.toString() : ''; this.state = { errMsg: this._validate(amountString, props.balance), amountString, }; } public componentWillReceiveProps(nextProps: BalanceBoundedInputProps): void { if (nextProps === this.props) { return; } const isCurrentAmountNumeric = utils.isNumeric(this.state.amountString); if (!_.isUndefined(nextProps.amount)) { let shouldResetState = false; if (!isCurrentAmountNumeric) { shouldResetState = true; } else { const currentAmount = new BigNumber(this.state.amountString); if (!currentAmount.eq(nextProps.amount) || !nextProps.balance.eq(this.props.balance)) { shouldResetState = true; } } if (shouldResetState) { const amountString = nextProps.amount.toString(); this._setAmountState(amountString, nextProps.balance); } } else if (isCurrentAmountNumeric) { const amountString = ''; this._setAmountState(amountString, nextProps.balance); } } public render(): React.ReactNode { let errorText; if (this.props.shouldShowErrs) { errorText = this.props.shouldShowIncompleteErrs && this.state.amountString === '' ? 'This field is required' : this.state.errMsg; } let label: React.ReactNode | string = ''; if (!_.isUndefined(this.props.label)) { label = ; } return ( {this.props.hintText}} onChange={this._onValueChange.bind(this)} underlineStyle={{ width: 'calc(100% + 50px)' }} inputStyle={this.props.inputStyle} hintStyle={this.props.inputHintStyle} underlineShow={this.props.shouldShowUnderline} disabled={this.props.isDisabled} /> ); } private _onValueChange(_event: any, amountString: string): void { this._setAmountState(amountString, this.props.balance, () => { const isValid = _.isUndefined(this._validate(amountString, this.props.balance)); const isPositiveNumber = utils.isNumeric(amountString) && !_.includes(amountString, '-'); if (isPositiveNumber) { this.props.onChange(isValid, new BigNumber(amountString)); } else { this.props.onChange(isValid); } }); } private _validate(amountString: string, balance: BigNumber): React.ReactNode { if (!utils.isNumeric(amountString)) { return amountString !== '' ? 'Must be a number' : ''; } const amount = new BigNumber(amountString); if (amount.eq(0)) { return 'Cannot be zero'; } if (this.props.shouldCheckBalance && amount.gt(balance)) { return Insufficient balance.; } const errMsg = _.isUndefined(this.props.validate) ? undefined : this.props.validate(amount); return errMsg; } private _setAmountState(amount: string, balance: BigNumber, callback: () => void = _.noop): void { const errorMsg = this._validate(amount, balance); this.props.onErrorMsgChange(errorMsg); this.setState( { amountString: amount, errMsg: errorMsg, }, callback, ); } }