@totalsoft/rocket-ui
Version:
A set of reusable and composable React components built on top of Material UI core for developing fast and friendly web applications interfaces.
253 lines • 10.6 kB
JavaScript
import React, { useCallback, useLayoutEffect, useMemo, useState } from 'react';
import PropTypes from 'prop-types';
import { NumericFormat } from 'react-number-format';
import { TextField as MuiTextField, StepButton, classes } from './TextFieldStyles';
import InputAdornment from '@mui/material/InputAdornment';
import CloseIcon from '@mui/icons-material/Close';
import IconButton from '../../buttons/IconButton';
import { is, isNil } from 'ramda';
import i18n from 'i18next';
import useDebouncedCallback from '../../utils/useDebouncedCallback';
const NumberTextField = React.forwardRef(function NumberFormatCustom(props, ref) {
const { value, onChange, decimalScale = 2, fixedDecimalScale = true, thousandSeparator = true, decimalSeparator, language = i18n.language, currency, isStepper, minValue, maxValue, ...other } = props;
const isAllowed = useCallback(({ formattedValue, floatValue }) => {
if (floatValue && floatValue.toString().includes('e'))
return false;
if (isNil(floatValue)) {
return formattedValue === '';
}
else {
return floatValue <= maxValue && floatValue >= minValue;
}
}, [maxValue, minValue]);
const formatter = new Intl.NumberFormat(language);
const thousandSep = thousandSeparator === true ? formatter.format(1111).replace(/1/g, '') : thousandSeparator;
const decimalSep = decimalSeparator || formatter.format(1.1).replace(/1/g, '');
const currencyFormatter = currency && new Intl.NumberFormat(language, { style: 'currency', currency });
const currencySymbol = currencyFormatter?.format(1).replace(/[\d,.\s]/g, '');
const valueIsNumericString = is(String, value) && is(Number, Number(value));
const handleValueChange = useCallback((values, sourceInfo) => {
onChange(values.floatValue, sourceInfo.event);
}, [onChange]);
return (React.createElement(NumericFormat, { style: { textAlign: isStepper ? 'center' : 'right' }, value: value, getInputRef: ref, onValueChange: handleValueChange, isAllowed: isAllowed, decimalScale: decimalScale, fixedDecimalScale: fixedDecimalScale, thousandSeparator: thousandSep, decimalSeparator: decimalSep, prefix: currencySymbol, valueIsNumericString: valueIsNumericString, ...other }));
});
NumberTextField.propTypes = {
value: PropTypes.any,
onChange: PropTypes.func.isRequired,
decimalScale: PropTypes.number,
fixedDecimalScale: PropTypes.bool,
thousandSeparator: PropTypes.oneOfType([PropTypes.string, PropTypes.bool]),
decimalSeparator: PropTypes.string,
language: PropTypes.string,
currency: PropTypes.string,
isStepper: PropTypes.bool,
minValue: PropTypes.number,
maxValue: PropTypes.number,
format: PropTypes.string,
allowEmptyFormatting: PropTypes.bool
};
const ClearButton = ({ onClearInput, disabled }) => (React.createElement(IconButton, { disabled: disabled, "aria-label": "Clear", size: "tiny", color: "primary", variant: "text", onClick: onClearInput },
React.createElement(CloseIcon, { fontSize: "small" })));
ClearButton.propTypes = {
onClearInput: PropTypes.func,
disabled: PropTypes.bool
};
const AddButton = ({ onAdd }) => (React.createElement(InputAdornment, { position: "end" },
React.createElement(StepButton, { onClick: onAdd }, "+")));
AddButton.propTypes = {
onAdd: PropTypes.func
};
const SubtractButton = ({ onSubtract }) => (React.createElement(InputAdornment, { position: "start" },
React.createElement(StepButton, { onClick: onSubtract }, "-")));
SubtractButton.propTypes = {
onSubtract: PropTypes.func
};
/**
* Text Fields let users enter and edit text.
* At its core, it uses [Material-UI TextField](https://mui.com/material-ui/react-text-field/#basic-textfield).
*/
const TextField = ({ isNumeric: receivedIsNumeric, inputProps, InputProps, endAdornment, startAdornment, fullWidth, InputLabelProps, value, onChange = () => { }, debounceBy = 0, decimalScale = 2, fixedDecimalScale, thousandSeparator, decimalSeparator, language, currency, disabled, isClearable, isStepper = false, step = 1, minValue = -Infinity, maxValue = Infinity, variant = 'standard', ...rest }) => {
const isNumeric = receivedIsNumeric || isStepper;
const [liveValue, setLiveValue] = useState(value);
useLayoutEffect(() => {
setLiveValue(value);
}, [value]);
const debouncedOnChange = useDebouncedCallback(onChange, debounceBy);
const handleClearInput = useCallback(() => {
onChange('');
}, [onChange]);
const handleSubtract = useCallback(() => {
const nextValue = !value ? -step : Number(value) - Number(step);
if (nextValue >= minValue)
onChange(nextValue);
}, [minValue, onChange, step, value]);
const handleAdd = useCallback(() => {
const nextValue = !value ? Number(step) : Number(value) + Number(step);
if (nextValue <= maxValue)
onChange(nextValue);
}, [maxValue, onChange, step, value]);
const internalStartAdornment = useMemo(() => {
if (!isStepper && !startAdornment)
return null;
return (React.createElement(React.Fragment, null,
isStepper && (React.createElement(InputAdornment, { position: "start" },
React.createElement(SubtractButton, { onSubtract: handleSubtract }))),
startAdornment));
}, [handleSubtract, isStepper, startAdornment]);
const internalEndAdornment = useMemo(() => {
if (!isStepper && !isClearable && !endAdornment)
return null;
return (React.createElement(InputAdornment, { position: "end" },
isStepper && React.createElement(AddButton, { onAdd: handleAdd }),
isClearable && React.createElement(ClearButton, { onClearInput: handleClearInput, disabled: disabled }),
endAdornment));
}, [disabled, endAdornment, handleAdd, handleClearInput, isClearable, isStepper]);
const muiInputProps = {
className: `${isStepper && !fullWidth ? classes.stepperFixedWidth : ''}`,
...InputProps,
startAdornment: internalStartAdornment,
endAdornment: internalEndAdornment,
style: InputProps?.style
};
const numericProps = {
decimalScale,
fixedDecimalScale,
thousandSeparator,
decimalSeparator,
language,
currency,
isStepper,
minValue,
maxValue
};
// props applied to the Input element
const customMuiInputProps = isNumeric
? {
...muiInputProps,
inputComponent: NumberTextField
}
: muiInputProps;
// attributes applied to the input element
const customReactInputProps = {
...(isNumeric && numericProps),
...inputProps,
className: `${classes.input} ${inputProps?.className ? inputProps.className : ''}`
};
const handleChange = useCallback((valueOrEvent, origEvent) => {
const value = isNumeric
? valueOrEvent
: valueOrEvent?.target?.value;
const event = isNumeric ? origEvent : valueOrEvent;
setLiveValue(value);
debouncedOnChange(value, event);
}, [debouncedOnChange, isNumeric]);
return (React.createElement(MuiTextField, { onChange: handleChange, value: liveValue, fullWidth: fullWidth, disabled: disabled, variant: variant, ...rest, InputProps: customMuiInputProps, inputProps: customReactInputProps, InputLabelProps: {
className: classes.label,
...InputLabelProps
} }));
};
TextField.propTypes = {
/**
* @default false
* If `true`, the input will accept only numeric values.
*/
isNumeric: PropTypes.bool,
/**
* Attributes applied to the input element.
* For the numeric input, you can provide properties like thousandSeparator, decimalScale and allowNegative.
*/
inputProps: PropTypes.object,
/**
* Other properties you can provide to the Input component.
*/
InputProps: PropTypes.object,
/**
* End adornment of component. (Usually an InputAdornment from material-ui)
*/
endAdornment: PropTypes.node,
/**
* Start adornment of component. (Usually an InputAdornment from material-ui)
*/
startAdornment: PropTypes.node,
/**
* If `true`, the input will take up the full width of its container.
*/
fullWidth: PropTypes.bool,
/**
* Props applied to the InputLabel element.
*/
InputLabelProps: PropTypes.object,
/**
* The value of the `input` element, required for a controlled component.
*/
value: PropTypes.any,
/**
* @default '() => {}'
* Callback fired when the value is changed.
* @param {unknown} value The target value from the event source of the callback.
*/
onChange: PropTypes.func,
/**
* The delay of debouncing.
*/
debounceBy: PropTypes.number,
/**
* If defined, it limits to given decimal scale.
*/
decimalScale: PropTypes.number,
/**
* If `true`, it add 0s to match given decimalScale.
*/
fixedDecimalScale: PropTypes.bool,
/**
* Character that separates thousands from hundreds.
*/
thousandSeparator: PropTypes.oneOfType([PropTypes.string, PropTypes.bool]),
/**
* Character that separates decimals from integers.
*/
decimalSeparator: PropTypes.string,
/**
* The current language, preferably taken from the i18next (i18.language) or another internationalization library.
*/
language: PropTypes.string,
/**
* The currency that will be displayed as a pre-fix.
*/
currency: PropTypes.string,
/**
* If `true`, the component is disabled
*/
disabled: PropTypes.bool,
/**
* If `true`, a clear button is shown.
*/
isClearable: PropTypes.bool,
/**
* @default false
* If `true`, will display `+` and `-` buttons for increasing/decreasing the value.
*/
isStepper: PropTypes.bool,
/**
* @default 1
* Used together with `isStepper` prop; the value by which the current input increases.
*/
step: PropTypes.number,
/**
* @default -Infinity
* Lower limit for the input.
*/
minValue: PropTypes.number,
/**
* @default Infinity
* Upper limit for the input.
*/
maxValue: PropTypes.number,
/**
* @default 'standard'
* The variant to use.
*/
variant: PropTypes.oneOf(['filled', 'standard', 'outlined'])
};
export default TextField;
//# sourceMappingURL=TextField.js.map