woocommerce/plugins/woocommerce-blocks/packages/components/formatted-monetary-amount/index.tsx

124 lines
3.3 KiB
TypeScript
Raw Normal View History

/**
* External dependencies
*/
import NumberFormat from 'react-number-format';
import type {
NumberFormatValues,
NumberFormatProps,
} from 'react-number-format';
import clsx from 'clsx';
import type { ReactElement } from 'react';
import type { Currency } from '@woocommerce/types';
import { SITE_CURRENCY } from '@woocommerce/settings';
/**
* Internal dependencies
*/
import './style.scss';
export interface FormattedMonetaryAmountProps
extends Omit< NumberFormatProps, 'onValueChange' | 'displayType' > {
className?: string;
displayType?: NumberFormatProps[ 'displayType' ] | undefined;
allowNegative?: boolean;
isAllowed?: ( formattedValue: NumberFormatValues ) => boolean;
value: number | string; // Value of money amount.
currency?: Currency | undefined; // Currency configuration object. Defaults to site currency.
onValueChange?: ( unit: number ) => void; // Function to call when value changes.
Convert product-elements/price to TypeScript (https://github.com/woocommerce/woocommerce-blocks/pull/7683) * Convert product-element/price to TypeScript * Apply feedback from woocommerce/woocommerce-blocks#7095 to this PR * Export block due to Cross-Sells dependency * Update assets/js/atomic/blocks/product-elements/price/edit.tsx Co-authored-by: Manish Menaria <the.manish.menaria@gmail.com> * bot: update checkstyle.xml * Apply review feedback * Outsource supports section * bot: update checkstyle.xml * Resolve merge conflicts * bot: update checkstyle.xml * Solve TS error in cart-cross-sells-product.tsx * bot: update checkstyle.xml * Solve TS error regarding min_amount and max_amount * bot: update checkstyle.xml * Empty-Commit * Fix TS problems in product-elements/price/block.tsx * bot: update checkstyle.xml * Solve TS errors * bot: update checkstyle.xml * Solve TS errors * bot: update checkstyle.xml * Solve TS errors * bot: update checkstyle.xml * bot: update checkstyle.xml * Resolve merge conflicts * Convert product-element/price to TypeScript * Apply feedback from woocommerce/woocommerce-blocks#7095 to this PR * Export block due to Cross-Sells dependency * Apply review feedback * Update assets/js/atomic/blocks/product-elements/price/edit.tsx Co-authored-by: Manish Menaria <the.manish.menaria@gmail.com> * bot: update checkstyle.xml * bot: update checkstyle.xml * Solve TS error in cart-cross-sells-product.tsx * bot: update checkstyle.xml * bot: update checkstyle.xml * Solve TS error regarding min_amount and max_amount * Empty-Commit * bot: update checkstyle.xml * Fix TS problems in product-elements/price/block.tsx * bot: update checkstyle.xml * bot: update checkstyle.xml * Solve TS errors * Solve TS errors * bot: update checkstyle.xml * Solve TS errors * bot: update checkstyle.xml * bot: update checkstyle.xml * bot: update checkstyle.xml * Empty checkstyle.xml * bot: update checkstyle.xml * Solve TS errors * bot: update checkstyle.xml * Solve TS errors * bot: update checkstyle.xml * Use BlockAttributes from @wordpress/blocks * Fix TS error * Fix TS errors * Fix TS error Co-authored-by: Manish Menaria <the.manish.menaria@gmail.com> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2022-12-23 15:30:10 +00:00
style?: React.CSSProperties | undefined;
renderText?: ( value: string ) => JSX.Element;
}
/**
* Formats currency data into the expected format for NumberFormat.
*/
const currencyToNumberFormat = ( currency: Currency ) => {
const { prefix, suffix, thousandSeparator, decimalSeparator } = currency;
const hasDuplicateSeparator = thousandSeparator === decimalSeparator;
if ( hasDuplicateSeparator ) {
Prevent checkout blocks breakage for stores using similar number separators (#46241) * Add a safety check for the numbers with the same value for the separators This case breaks the library we are using for number formatting. This is not documented on their end and can't be fixed by passing a different configuration. We are fixing it on our end by overwriting the thousand separator. This change will only surface in the checkout blocks, at they are the only blocks doing formatting via React (the products blocks use php). This will not apply to the order confirmation. This change is preventing a fatal error thrown by the library and allowing users to see the content of the cart. It's an edge case, because it happens only why people have the same values for the separators, which is an non-standard way to format. * Add unit tests and changelog * Add a safety check for the numbers with the same value for the separators This case breaks the library we are using for number formatting. This is not documented on their end and can't be fixed by passing a different configuration. We are fixing it on our end by overwriting the thousand separator. This change will only surface in the checkout blocks, at they are the only blocks doing formatting via React (the products blocks use php). This will not apply to the order confirmation. This change is preventing a fatal error thrown by the library and allowing users to see the content of the cart. It's an edge case, because it happens only why people have the same values for the separators, which is an non-standard way to format. * Add unit tests and changelog * Improve tests * "Improve tests"
2024-04-18 12:17:30 +00:00
// eslint-disable-next-line no-console
console.warn(
'Thousand separator and decimal separator are the same. This may cause formatting issues.'
);
}
return {
thousandSeparator: hasDuplicateSeparator ? '' : thousandSeparator,
decimalSeparator,
fixedDecimalScale: true,
prefix,
suffix,
isNumericString: true,
};
};
/**
* FormattedMonetaryAmount component.
*
* Takes a price and returns a formatted price using the NumberFormat component.
*
* More detailed docs on the additional props can be found here:https://s-yadav.github.io/react-number-format/docs/intro
*/
const FormattedMonetaryAmount = ( {
className,
value: rawValue,
currency: rawCurrency = SITE_CURRENCY,
onValueChange,
displayType = 'text',
...props
}: FormattedMonetaryAmountProps ): ReactElement | null => {
// Merge currency configuration with site currency.
const currency = {
...SITE_CURRENCY,
...rawCurrency,
};
// Convert values to int.
const value =
typeof rawValue === 'string' ? parseInt( rawValue, 10 ) : rawValue;
if ( ! Number.isFinite( value ) ) {
return null;
}
const priceValue = value / 10 ** currency.minorUnit;
if ( ! Number.isFinite( priceValue ) ) {
return null;
}
const classes = clsx(
'wc-block-formatted-money-amount',
'wc-block-components-formatted-money-amount',
className
);
const decimalScale = props.decimalScale ?? currency?.minorUnit;
const numberFormatProps = {
...props,
...currencyToNumberFormat( currency ),
decimalScale,
value: undefined,
currency: undefined,
onValueChange: undefined,
};
// Wrapper for NumberFormat onValueChange which handles subunit conversion.
const onValueChangeWrapper = onValueChange
? ( values: NumberFormatValues ) => {
const minorUnitValue = +values.value * 10 ** currency.minorUnit;
onValueChange( minorUnitValue );
}
: () => void 0;
return (
<NumberFormat
className={ classes }
displayType={ displayType }
{ ...numberFormatProps }
value={ priceValue }
onValueChange={ onValueChangeWrapper }
/>
);
};
export default FormattedMonetaryAmount;