@unicef/material-ui-currency-textfield
Version:
Currency input textfield for react with Material-ui style
222 lines (209 loc) • 7.72 kB
JavaScript
import React from "react"
import PropTypes from "prop-types"
import AutoNumeric from "autonumeric"
import { withStyles } from "@material-ui/styles"
import { TextField, InputAdornment } from "@material-ui/core"
const styles = theme => ({
textField: props => ({
textAlign: props.textAlign || "right",
}),
})
/**
* CurrencyTextField is a [react](https://reactjs.org/) component with automated currency and number format, and with [Material-ui](https://material-ui.com/) look and feel.
*
* CurrencyTextField is a wrapper component for <a href="https://github.com/autoNumeric/autoNumeric">autonumeric</a> and based on <a href="https://github.com/mkg0/react-numeric">react-numeric</a>.
*
* Main features:
* * Adds thousands separator automatically.
* * Adds automatically the decimals on blur.
* * Smart input. User can only type the accepted characters depending on the current value.
* * Lots of config options...
* * It accepts all the `props` and `classes` of Material-Ui <a href="https://material-ui.com/api/text-field/#textfield-api">TextField API</a> (Ex: classes, label, helperText, variant).
* * And also all the `options` from <a href="http://autonumeric.org/guide">AutoNumeric</a>
*/
class CurrencyTextField extends React.Component {
constructor(props) {
super(props)
this.getValue = this.getValue.bind(this)
this.callEventHandler = this.callEventHandler.bind(this)
}
componentDidMount() {
const { currencySymbol, ...others } = this.props
this.autonumeric = new AutoNumeric(this.input, this.props.value, {
...this.props.preDefined,
...others,
onChange: undefined,
onFocus: undefined,
onBlur: undefined,
onKeyPress: undefined,
onKeyUp: undefined,
onKeyDown: undefined,
watchExternalChanges: false,
})
}
componentWillUnmount() {
this.autonumeric.remove()
}
componentWillReceiveProps(newProps) {
const isValueChanged =
this.props.value !== newProps.value && this.getValue() !== newProps.value
if (isValueChanged) {
this.autonumeric.set(newProps.value)
}
}
getValue() {
if (!this.autonumeric) return
const valueMapper = {
string: numeric => numeric.getNumericString(),
number: numeric => numeric.getNumber(),
}
return valueMapper[this.props.outputFormat](this.autonumeric)
}
callEventHandler(event, eventName) {
if (!this.props[eventName]) return
this.props[eventName](event, this.getValue())
}
render() {
const {
classes,
currencySymbol,
inputProps,
InputProps,
...others
} = this.props
const otherProps = {}
;[
"id",
"label",
"className",
"autoFocus",
"variant",
"style",
"error",
"disabled",
"type",
"name",
"defaultValue",
"tabIndex",
"fullWidth",
"rows",
"rowsMax",
"select",
"required",
"helperText",
"unselectable",
"margin",
"SelectProps",
"multiline",
"size",
"FormHelperTextProps",
"placeholder",
].forEach(prop => (otherProps[prop] = this.props[prop]))
return (
<TextField
inputRef={ref => (this.input = ref)}
onChange={e => this.callEventHandler(e, "onChange")}
onFocus={e => this.callEventHandler(e, "onFocus")}
onBlur={e => this.callEventHandler(e, "onBlur")}
onKeyPress={e => this.callEventHandler(e, "onKeyPress")}
onKeyUp={e => this.callEventHandler(e, "onKeyUp")}
onKeyDown={e => this.callEventHandler(e, "onKeyDown")}
InputProps={{
startAdornment: (
<InputAdornment position="start">{currencySymbol}</InputAdornment>
),
...InputProps,
}}
inputProps={{
className: classes.textField,
...inputProps,
}}
{...otherProps}
/>
)
}
}
CurrencyTextField.propTypes = {
type: PropTypes.oneOf(["text", "tel", "hidden"]),
/** The variant to use. */
variant: PropTypes.string,
id: PropTypes.string,
/** The CSS class name of the wrapper element. */
className: PropTypes.string,
/** Inline styling for element */
style: PropTypes.object,
/** If true, the input element will be disabled. */
disabled: PropTypes.bool,
/** The label content. */
label: PropTypes.string,
/** Align the numbers in the textField.
* If you pass the `inputProps` from TextFieldAPI text align won't work.
* then, you have handle it by className with your own class inside inputProps.
*/
textAlign: PropTypes.oneOf(["right", "left", "center"]),
/** Tab index for the element */
tabIndex: PropTypes.number,
/** If true, the input element will be focused during the first mount. */
autoFocus: PropTypes.bool,
/** The short hint displayed in the input before the user enters a value. */
placeholder: PropTypes.string,
/** value to be enter and display in input */
value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
/** Callback fired when the value is changed. */
onChange: PropTypes.func,
/** Callback fired when focused on element. */
onFocus: PropTypes.func,
/** Callback fired on blur. */
onBlur: PropTypes.func,
/** Callback fired on key press. */
onKeyPress: PropTypes.func,
/** Callback fired on key press. */
onKeyUp: PropTypes.func,
/** Callback fired on key press. */
onKeyDown: PropTypes.func,
/** Defines the currency symbol string. */
currencySymbol: PropTypes.string,
/** Defines what decimal separator character is used. */
decimalCharacter: PropTypes.string,
/** Allow to declare an alternative decimal separator which is automatically replaced by `decimalCharacter` when typed. */
decimalCharacterAlternative: PropTypes.string,
/** Defines the default number of decimal places to show on the formatted value. */
decimalPlaces: PropTypes.number,
/** Defines how many decimal places should be visible when the element is unfocused null. */
decimalPlacesShownOnBlur: PropTypes.number,
/** Defines how many decimal places should be visible when the element has the focus. */
decimalPlacesShownOnFocus: PropTypes.number,
/** Defines the thousand grouping separator character */
digitGroupSeparator: PropTypes.string,
/** Controls the leading zero behavior */
leadingZero: PropTypes.oneOf(["allow", "deny", "keep"]),
/** maximum value that can be enter */
maximumValue: PropTypes.string,
/** minimum value that can be enter */
minimumValue: PropTypes.string,
/** placement of the negitive and possitive sign symbols */
negativePositiveSignPlacement: PropTypes.oneOf(["l", "r", "p", "s"]),
/** Defines the negative sign symbol to use */
negativeSignCharacter: PropTypes.string,
/** how the value should be formatted,before storing it */
outputFormat: PropTypes.oneOf(["string", "number"]),
/** Defines if the element value should be selected on focus. */
selectOnFocus: PropTypes.bool,
/** Defines the positive sign symbol to use. */
positiveSignCharacter: PropTypes.string,
/** Defines if the element should be set as read only on initialization. */
readOnly: PropTypes.bool,
/** predefined objects are available in <a href="https://www.nodenpm.com/autonumeric/4.5.1/detail.html#predefined-options">AutoNumeric</a>*/
preDefined: PropTypes.object,
}
CurrencyTextField.defaultProps = {
type: "text",
variant: "standard",
currencySymbol: "$",
outputFormat: "number",
textAlign: "right",
maximumValue: "10000000000000",
minimumValue: "-10000000000000",
}
export default withStyles(styles)(CurrencyTextField)
export const predefinedOptions = AutoNumeric.getPredefinedOptions()