2019-12-18 11:29:20 +00:00
|
|
|
/**
|
|
|
|
* External dependencies
|
|
|
|
*/
|
2022-04-08 13:47:19 +00:00
|
|
|
import NumberFormat from 'react-number-format';
|
|
|
|
import type {
|
2021-03-09 10:55:24 +00:00
|
|
|
NumberFormatValues,
|
|
|
|
NumberFormatProps,
|
|
|
|
} from 'react-number-format';
|
2024-05-31 03:49:36 +00:00
|
|
|
import clsx from 'clsx';
|
2021-03-09 10:55:24 +00:00
|
|
|
import type { ReactElement } from 'react';
|
2021-11-26 17:03:12 +00:00
|
|
|
import type { Currency } from '@woocommerce/types';
|
2020-01-14 20:52:42 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Internal dependencies
|
|
|
|
*/
|
|
|
|
import './style.scss';
|
2019-12-18 11:29:20 +00:00
|
|
|
|
2023-10-30 09:31:54 +00:00
|
|
|
export interface FormattedMonetaryAmountProps
|
|
|
|
extends Omit< NumberFormatProps, 'onValueChange' | 'displayType' > {
|
2021-03-09 10:55:24 +00:00
|
|
|
className?: string;
|
2023-10-30 09:31:54 +00:00
|
|
|
displayType?: NumberFormatProps[ 'displayType' ] | undefined;
|
2022-01-06 14:30:34 +00:00
|
|
|
allowNegative?: boolean;
|
|
|
|
isAllowed?: ( formattedValue: NumberFormatValues ) => boolean;
|
2021-03-15 15:03:47 +00:00
|
|
|
value: number | string; // Value of money amount.
|
2021-03-09 10:55:24 +00:00
|
|
|
currency: Currency | Record< string, never >; // Currency configuration object.
|
|
|
|
onValueChange?: ( unit: number ) => void; // Function to call when value changes.
|
2022-12-23 15:30:10 +00:00
|
|
|
style?: React.CSSProperties | undefined;
|
2021-11-26 17:03:12 +00:00
|
|
|
renderText?: ( value: string ) => JSX.Element;
|
2021-03-09 10:55:24 +00:00
|
|
|
}
|
|
|
|
|
2019-12-18 11:29:20 +00:00
|
|
|
/**
|
|
|
|
* Formats currency data into the expected format for NumberFormat.
|
|
|
|
*/
|
2021-03-09 10:55:24 +00:00
|
|
|
const currencyToNumberFormat = (
|
|
|
|
currency: FormattedMonetaryAmountProps[ 'currency' ]
|
|
|
|
) => {
|
2024-04-18 12:17:30 +00:00
|
|
|
const hasSimiliarSeparators =
|
|
|
|
currency?.thousandSeparator === currency?.decimalSeparator;
|
|
|
|
if ( hasSimiliarSeparators ) {
|
|
|
|
// eslint-disable-next-line no-console
|
|
|
|
console.warn(
|
|
|
|
'Thousand separator and decimal separator are the same. This may cause formatting issues.'
|
|
|
|
);
|
|
|
|
}
|
2019-12-18 11:29:20 +00:00
|
|
|
return {
|
2024-04-18 12:17:30 +00:00
|
|
|
thousandSeparator: hasSimiliarSeparators
|
|
|
|
? ''
|
|
|
|
: currency?.thousandSeparator,
|
2022-12-23 15:30:10 +00:00
|
|
|
decimalSeparator: currency?.decimalSeparator,
|
2020-01-08 20:10:29 +00:00
|
|
|
fixedDecimalScale: true,
|
2022-12-23 15:30:10 +00:00
|
|
|
prefix: currency?.prefix,
|
|
|
|
suffix: currency?.suffix,
|
2019-12-18 11:29:20 +00:00
|
|
|
isNumericString: true,
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
2022-12-23 15:30:10 +00:00
|
|
|
type CustomFormattedMonetaryAmountProps = Omit<
|
|
|
|
FormattedMonetaryAmountProps,
|
|
|
|
'currency'
|
|
|
|
> & {
|
|
|
|
currency: Currency | Record< string, never >;
|
|
|
|
};
|
|
|
|
|
2019-12-18 11:29:20 +00:00
|
|
|
/**
|
2021-03-09 10:55:24 +00:00
|
|
|
* FormattedMonetaryAmount component.
|
2019-12-18 11:29:20 +00:00
|
|
|
*
|
|
|
|
* Takes a price and returns a formatted price using the NumberFormat component.
|
2023-10-30 09:31:54 +00:00
|
|
|
*
|
|
|
|
* More detailed docs on the additional props can be found here:https://s-yadav.github.io/react-number-format/docs/intro
|
2019-12-18 11:29:20 +00:00
|
|
|
*/
|
|
|
|
const FormattedMonetaryAmount = ( {
|
2021-03-09 10:55:24 +00:00
|
|
|
className,
|
2021-03-15 15:03:47 +00:00
|
|
|
value: rawValue,
|
2019-12-18 11:29:20 +00:00
|
|
|
currency,
|
2021-03-09 10:55:24 +00:00
|
|
|
onValueChange,
|
2021-03-05 14:03:48 +00:00
|
|
|
displayType = 'text',
|
2019-12-18 11:29:20 +00:00
|
|
|
...props
|
2022-12-23 15:30:10 +00:00
|
|
|
}: CustomFormattedMonetaryAmountProps ): ReactElement | null => {
|
2021-03-15 15:03:47 +00:00
|
|
|
const value =
|
|
|
|
typeof rawValue === 'string' ? parseInt( rawValue, 10 ) : rawValue;
|
|
|
|
|
2021-03-05 14:03:48 +00:00
|
|
|
if ( ! Number.isFinite( value ) ) {
|
2020-05-01 09:59:27 +00:00
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
2019-12-18 11:29:20 +00:00
|
|
|
const priceValue = value / 10 ** currency.minorUnit;
|
|
|
|
|
2020-05-01 09:59:27 +00:00
|
|
|
if ( ! Number.isFinite( priceValue ) ) {
|
2019-12-18 11:29:20 +00:00
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
2024-05-31 03:49:36 +00:00
|
|
|
const classes = clsx(
|
2020-06-11 17:38:31 +00:00
|
|
|
'wc-block-formatted-money-amount',
|
|
|
|
'wc-block-components-formatted-money-amount',
|
|
|
|
className
|
|
|
|
);
|
2023-04-06 12:58:07 +00:00
|
|
|
const decimalScale = props.decimalScale ?? currency?.minorUnit;
|
2019-12-18 11:29:20 +00:00
|
|
|
const numberFormatProps = {
|
|
|
|
...props,
|
|
|
|
...currencyToNumberFormat( currency ),
|
2023-04-06 12:58:07 +00:00
|
|
|
decimalScale,
|
2019-12-18 11:29:20 +00:00
|
|
|
value: undefined,
|
|
|
|
currency: undefined,
|
|
|
|
onValueChange: undefined,
|
|
|
|
};
|
|
|
|
|
|
|
|
// Wrapper for NumberFormat onValueChange which handles subunit conversion.
|
|
|
|
const onValueChangeWrapper = onValueChange
|
2021-03-09 10:55:24 +00:00
|
|
|
? ( values: NumberFormatValues ) => {
|
2021-12-15 16:54:49 +00:00
|
|
|
const minorUnitValue = +values.value * 10 ** currency.minorUnit;
|
2019-12-18 11:29:20 +00:00
|
|
|
onValueChange( minorUnitValue );
|
|
|
|
}
|
2021-12-15 16:54:49 +00:00
|
|
|
: () => void 0;
|
2019-12-18 11:29:20 +00:00
|
|
|
|
|
|
|
return (
|
|
|
|
<NumberFormat
|
2020-01-14 20:52:42 +00:00
|
|
|
className={ classes }
|
2021-03-09 10:55:24 +00:00
|
|
|
displayType={ displayType }
|
2019-12-18 11:29:20 +00:00
|
|
|
{ ...numberFormatProps }
|
|
|
|
value={ priceValue }
|
|
|
|
onValueChange={ onValueChangeWrapper }
|
|
|
|
/>
|
|
|
|
);
|
|
|
|
};
|
|
|
|
|
|
|
|
export default FormattedMonetaryAmount;
|