Skip to content

feat: i18nify SDK integrated for Amount component #2003

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 18 commits into from
Feb 12, 2024
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 7 additions & 3 deletions packages/blade/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -269,7 +269,8 @@
"typescript": "4.9",
"typescript-transform-paths": "3.4.6",
"@types/body-scroll-lock": "3.1.0",
"ramda": "0.29.1"
"ramda": "0.29.1",
"@razorpay/i18nify-js": "^1.3.1"
},
"peerDependencies": {
"react": ">=18",
Expand All @@ -283,7 +284,8 @@
"react-native-svg": "^12.3.0",
"react-native-gesture-handler": "^2.9.0",
"@gorhom/bottom-sheet": "^4.4.6",
"@gorhom/portal": "^1.0.14"
"@gorhom/portal": "^1.0.14",
"@razorpay/i18nify-js": "^1.3.1"
},
"peerDependenciesMeta": {
"react-native": {
Expand All @@ -298,7 +300,9 @@
"@storybook/**/react": "18.2.0",
"react-dom": "18.2.0",
"react": "18.2.0",
"styled-components": "^5"
"styled-components": "^5",
"@types/react": "17.0.38",
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why do we have types react as 17 here while our actual react version is 18?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Had added this while testing i18nify-react in Blade/examples/basic app.
As Blade uses @types/react: 17.0.38 and i18nify-react uses "@types/react": "^18.0.0",. It was leading to some conflicts in the types.
Let me recheck this, will remove this if not required.

"@types/react-native": "0.72.2"
},
"bundlemon": {
"files": [
Expand Down
4 changes: 2 additions & 2 deletions packages/blade/src/components/Amount/Amount.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import type { StoryFn, Meta } from '@storybook/react';
import { Title } from '@storybook/addon-docs';
import { getCurrencyList } from '@razorpay/i18nify-js';
import type { AmountProps } from './Amount';
import { Amount as AmountComponent } from './Amount';
import type { AmountHeadingProps, AmountDisplayProps, AmountBodyProps } from './amountTokens';
import { currencyIndicatorMapping } from './amountTokens';
import { getStyledPropsArgTypes } from '~components/Box/BaseBox/storybookArgTypes';
import BaseBox from '~components/Box/BaseBox';
import { Sandbox } from '~utils/storybook/Sandbox';
Expand Down Expand Up @@ -176,7 +176,7 @@ HumanizeSuffix.args = {
HumanizeSuffix.storyName = 'Humanize Suffix';

const AmountCurrencyTemplate: StoryFn<typeof AmountComponent> = (args) => {
const values = Object.keys(currencyIndicatorMapping);
const values = Object.keys(getCurrencyList());

return (
<BaseBox justifyContent="flex-start" maxHeight="300px" overflowY="auto">
Expand Down
176 changes: 90 additions & 86 deletions packages/blade/src/components/Amount/Amount.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,9 @@
import type { ReactElement } from 'react';
import React from 'react';
import type { AmountTypeProps, Currency } from './amountTokens';
import {
normalAmountSizes,
getCurrencyAbbreviations,
currencyIndicatorMapping,
subtleFontSizes,
amountLineHeights,
currencyPositionMapping,
} from './amountTokens';
import type { CurrencyCodeType } from '@razorpay/i18nify-js';
import { formatNumber, formatNumberByParts } from '@razorpay/i18nify-js';
import type { AmountTypeProps } from './amountTokens';
import { normalAmountSizes, subtleFontSizes, amountLineHeights } from './amountTokens';
import type { BaseTextProps } from '~components/Typography/BaseText/types';
import BaseBox from '~components/Box/BaseBox';
import type { TestID } from '~utils/types';
Expand Down Expand Up @@ -60,7 +55,7 @@ type AmountCommonProps = {
*
* @default 'INR'
* */
currency?: Currency;
currency?: CurrencyCodeType;
/**
* If true, the amount text will have a line through it.
*
Expand All @@ -85,14 +80,16 @@ const getTextColorProps = ({ color }: { color: AmountProps['color'] }): ColorPro
return props;
};

type AmountType = Partial<ReturnType<typeof formatNumberByParts>> & { formatted: string };

interface AmountValue extends Omit<AmountProps, 'value'> {
amountValueColor: BaseTextProps['color'];
value: string;
amount: AmountType;
size: Exclude<AmountProps['size'], undefined>;
}

const AmountValue = ({
value,
amount,
size = 'medium',
type = 'body',
weight = 'regular',
Expand All @@ -104,9 +101,6 @@ const AmountValue = ({
const affixFontSize = isAffixSubtle ? subtleFontSizes[type][size] : normalAmountSizes[type][size];
const numberFontFamily: keyof FontFamily = type === 'body' ? 'text' : 'heading';
if (suffix === 'decimals' && isAffixSubtle) {
const integer = value.split('.')[0];
const decimal = value.split('.')[1];

// Native does not support alignItems of Text inside a div, instead we need to wrap is in a Text
const AmountWrapper = isReactNative ? Text : React.Fragment;

Expand All @@ -120,7 +114,7 @@ const AmountValue = ({
fontFamily={numberFontFamily}
as={isReactNative ? undefined : 'span'}
>
{integer}
{amount.integer}
</BaseText>
<BaseText
fontWeight={weight}
Expand All @@ -130,11 +124,13 @@ const AmountValue = ({
as={isReactNative ? undefined : 'span'}
opacity={isAffixSubtle ? opacity[8] : 1}
>
.{decimal || '00'}
{amount.decimal}
{amount.fraction}
</BaseText>
</AmountWrapper>
);
}

return (
<BaseText
fontSize={normalAmountSizes[type][size]}
Expand All @@ -143,83 +139,78 @@ const AmountValue = ({
color={amountValueColor}
lineHeight={amountLineHeights[type][size]}
>
{value}
{amount.formatted}
</BaseText>
);
};

// This function rounds a number to a specified number of decimal places
// and floors the result.
export const getFlooredFixed = (value: number, decimalPlaces: number): number => {
const factor = 100 ** decimalPlaces;
const roundedValue = Math.floor(value * factor) / factor;
return Number(roundedValue.toFixed(decimalPlaces));
};

export const addCommas = (amountValue: number, currency: Currency, decimalPlaces = 0): string => {
// If the currency is 'INR', set the locale to 'en-IN' (Indian English).
// Otherwise, set the locale to 'en-US' (U.S. English).
const locale = currency === 'INR' ? 'en-IN' : 'en-US';
return amountValue.toLocaleString(locale, { minimumFractionDigits: decimalPlaces });
};
/**
* This function returns the humanized amount
* ie: for INR 2000 => 2K
* for MYR 2000000 => 2M
*/
export const getHumanizedAmount = ({
value,
currency,
denominationPosition = 'right',
}: {
value: number;
currency: Currency;
denominationPosition?: 'left' | 'right';
}): string => {
let amountValue = value;
const abbreviations = getCurrencyAbbreviations(currency);
const abbreviation = abbreviations.find((abbr) => amountValue >= abbr.value);

if (abbreviation) {
amountValue = amountValue / abbreviation.value;
const formattedAmountValue = getFlooredFixed(amountValue, 2);

if (denominationPosition === 'right') {
return `${addCommas(formattedAmountValue, currency)}${abbreviation.symbol}`;
}

return `${abbreviation.symbol}${addCommas(formattedAmountValue, currency)}`;
}

return amountValue.toString();
};

type FormatAmountWithSuffixType = {
suffix: AmountProps['suffix'];
value: number;
currency: Currency;
denominationPosition?: 'left' | 'right';
};

/**
* Returns a parsed object based on the suffix passed in parameters
* === Logic ===
* value = 12500.45
* if suffix === 'decimals' => {
"formatted": "12,500.45",
"integer": "12,500",
"decimal": ".",
"fraction": "45",
"isPrefixSymbol": false,
"rawParts": [{"type": "integer","value": "12"},{"type": "group","value": ","},{"type": "integer","value": "500"},{"type": "decimal","value": "."},{"type": "fraction","value": "45"}]
}
* else if suffix === 'humanize' => { formatted: "1.2T" }
* else => { formatted: "1,23,456" }
* @returns {AmountType}
*/
export const formatAmountWithSuffix = ({
suffix,
value,
currency,
denominationPosition,
}: FormatAmountWithSuffixType): string => {
switch (suffix) {
case 'decimals': {
const decimalNumber = getFlooredFixed(value, 2);
return addCommas(decimalNumber, currency, 2);
}
case 'humanize': {
return getHumanizedAmount({ value, currency, denominationPosition });
}
case 'none': {
return addCommas(getFlooredFixed(value, 0), currency);
}: FormatAmountWithSuffixType): AmountType => {
try {
switch (suffix) {
case 'decimals': {
const options = {
intlOptions: {
maximumFractionDigits: 2,
minimumFractionDigits: 2,
},
};
return {
...formatNumberByParts(value, options),
formatted: formatNumber(value, options),
};
}
case 'humanize': {
const formatted = formatNumber(value, {
intlOptions: {
notation: 'compact',
roundingMode: 'floor',
},
});
return {
formatted,
};
}

default: {
const formatted = formatNumber(value, {
intlOptions: {
maximumFractionDigits: 0,
roundingMode: 'floor',
},
});
return {
formatted,
};
}
}
default:
return addCommas(getFlooredFixed(value, 0), currency);
} catch (err: unknown) {
return {
formatted: `${value}`,
};
}
};

Expand Down Expand Up @@ -277,14 +268,26 @@ const _Amount = ({
}
}

const currencySymbolOrCode = currencyIndicatorMapping[currency][currencyIndicator];
const currencyPosition = currencyPositionMapping[currency] || 'left';
const denominationPosition = currencyPosition === 'left' ? 'right' : 'left';
const renderedValue = formatAmountWithSuffix({ suffix, value, currency, denominationPosition });
const { amountValueColor } = getTextColorProps({
color,
});

let isPrefixSymbol, currencySymbol;
try {
const byParts = formatNumberByParts(value, {
currency,
});
isPrefixSymbol = byParts.isPrefixSymbol;
currencySymbol = byParts.currency;
} catch (err: unknown) {
isPrefixSymbol = true;
currencySymbol = currency;
}

const currencyPosition = isPrefixSymbol ? 'left' : 'right';
const renderedValue = formatAmountWithSuffix({ suffix, value });
const currencySymbolOrCode = currencyIndicator === 'currency-symbol' ? currencySymbol : currency;

const currencyFontSize = isAffixSubtle
? subtleFontSizes[type][size]
: normalAmountSizes[type][size];
Expand Down Expand Up @@ -316,13 +319,14 @@ const _Amount = ({
</BaseText>
)}
<AmountValue
value={renderedValue}
amount={renderedValue}
amountValueColor={amountValueColor}
type={type}
weight={weight}
size={size}
isAffixSubtle={isAffixSubtle}
suffix={suffix}
currency={currency}
/>
{currencyPosition === 'right' && (
<BaseText
Expand Down
Loading