diff --git a/src/components/Output.tsx b/src/components/Output.tsx index d1fd52c57..eaec706cb 100644 --- a/src/components/Output.tsx +++ b/src/components/Output.tsx @@ -84,6 +84,124 @@ type StyleProps = { withBaseFont?: boolean; }; +export const formatNumber = (params: ElementProps) => { + const { + value, + showSign = ShowSign.Negative, + useGrouping = true, + type, + locale = navigator.language || 'en-US', + fractionDigits, + roundingMode = BigNumber.ROUND_HALF_UP, + } = params; + const { decimal: LOCALE_DECIMAL_SEPARATOR, group: LOCALE_GROUP_SEPARATOR } = + useLocaleSeparators(); + + const valueBN = MustBigNumber(value).abs(); + const isNegative = MustBigNumber(value).isNegative(); + const isPositive = MustBigNumber(value).isPositive() && !MustBigNumber(value).isZero(); + + const sign: string | undefined = { + [ShowSign.Both]: isNegative ? UNICODE.MINUS : isPositive ? UNICODE.PLUS : undefined, + [ShowSign.Negative]: isNegative ? UNICODE.MINUS : undefined, + [ShowSign.None]: undefined, + }[showSign]; + + const format = { + decimalSeparator: LOCALE_DECIMAL_SEPARATOR, + ...(useGrouping + ? { + groupSeparator: LOCALE_GROUP_SEPARATOR, + groupSize: 3, + secondaryGroupSize: 0, + fractionGroupSeparator: ' ', + fractionGroupSize: 0, + } + : {}), + }; + + let formattedString: string = 'NaN'; + + switch (type) { + case OutputType.CompactNumber: + if (!isNumber(value)) { + throw new Error('value must be a number for compact number output'); + } + + formattedString = Intl.NumberFormat(locale, { + style: 'decimal', + notation: 'compact', + maximumSignificantDigits: 3, + }) + .format(Math.abs(value)) + .toLowerCase(); + break; + case OutputType.Number: + formattedString = valueBN.toFormat(fractionDigits ?? 0, roundingMode, { + ...format, + }); + break; + case OutputType.Fiat: + formattedString = valueBN.toFormat(fractionDigits ?? USD_DECIMALS, roundingMode, { + ...format, + prefix: '$', + }); + break; + case OutputType.SmallFiat: + formattedString = valueBN.toFormat(fractionDigits ?? SMALL_USD_DECIMALS, roundingMode, { + ...format, + prefix: '$', + }); + break; + case OutputType.CompactFiat: + if (!isNumber(value)) { + throw new Error('value must be a number for compact fiat output'); + } + formattedString = Intl.NumberFormat(locale, { + style: 'currency', + currency: 'USD', + notation: 'compact', + maximumSignificantDigits: 3, + }) + .format(Math.abs(value)) + .toLowerCase(); + break; + case OutputType.Asset: + formattedString = valueBN.toFormat(fractionDigits ?? TOKEN_DECIMALS, roundingMode, { + ...format, + }); + break; + case OutputType.Percent: + formattedString = valueBN + .times(100) + .toFormat(fractionDigits ?? PERCENT_DECIMALS, roundingMode, { + ...format, + suffix: '%', + }); + break; + case OutputType.SmallPercent: + formattedString = valueBN + .times(100) + .toFormat(fractionDigits ?? SMALL_PERCENT_DECIMALS, roundingMode, { + ...format, + suffix: '%', + }); + break; + case OutputType.Multiple: + formattedString = valueBN.toFormat(fractionDigits ?? LEVERAGE_DECIMALS, roundingMode, { + ...format, + suffix: '×', + }); + break; + } + + return { + sign, + format, + formattedString, + }; +}; + export type OutputProps = ElementProps & StyleProps; export const Output = ({ @@ -110,8 +228,6 @@ export const Output = ({ const selectedLocale = useSelector(getSelectedLocale); const stringGetter = useStringGetter(); const isDetailsLoading = useContext(LoadingContext); - const { decimal: LOCALE_DECIMAL_SEPARATOR, group: LOCALE_GROUP_SEPARATOR } = - useLocaleSeparators(); if (isLoading || isDetailsLoading) { return ; @@ -213,28 +329,20 @@ export const Output = ({ case OutputType.SmallPercent: case OutputType.Multiple: { const hasValue = value !== null && value !== undefined; - const valueBN = MustBigNumber(value).abs(); - const isNegative = MustBigNumber(value).isNegative(); - const isPositive = MustBigNumber(value).isPositive() && !MustBigNumber(value).isZero(); - - const sign: string | undefined = { - [ShowSign.Both]: isNegative ? UNICODE.MINUS : isPositive ? UNICODE.PLUS : undefined, - [ShowSign.Negative]: isNegative ? UNICODE.MINUS : undefined, - [ShowSign.None]: undefined, - }[showSign]; - - const format = { - decimalSeparator: LOCALE_DECIMAL_SEPARATOR, - ...(useGrouping - ? { - groupSeparator: LOCALE_GROUP_SEPARATOR, - groupSize: 3, - secondaryGroupSize: 0, - fractionGroupSeparator: ' ', - fractionGroupSize: 0, - } - : {}), - }; + + const { sign, formattedString } = formatNumber({ + type, + value, + isLoading, + fractionDigits, + showSign, + useGrouping, + roundingMode, + relativeTimeFormatOptions, + timeOptions, + withParentheses, + locale, + }); return ( <$Number @@ -260,102 +368,35 @@ export const Output = ({ throw new Error('value must be a number for compact number output'); } - return ( - - ); + return ; }, [OutputType.Number]: () => ( - + ), [OutputType.Fiat]: () => ( - + ), [OutputType.SmallFiat]: () => ( - + ), [OutputType.CompactFiat]: () => { if (!isNumber(value)) { throw new Error('value must be a number for compact fiat output'); } - return ( - - ); + return ; }, [OutputType.Asset]: () => ( - + ), [OutputType.Percent]: () => ( - + ), [OutputType.SmallPercent]: () => ( - + ), [OutputType.Multiple]: () => ( - + ), }[type]()} {slotRight} diff --git a/src/views/ExportHistoryDropdown.tsx b/src/views/ExportHistoryDropdown.tsx index 22c9f3ff0..bc104af89 100644 --- a/src/views/ExportHistoryDropdown.tsx +++ b/src/views/ExportHistoryDropdown.tsx @@ -8,13 +8,13 @@ import { AnalyticsEvent } from '@/constants/analytics'; import { ButtonAction, ButtonSize } from '@/constants/buttons'; import { STRING_KEYS } from '@/constants/localization'; -import { useLocaleSeparators, useStringGetter } from '@/hooks'; +import { useStringGetter } from '@/hooks/useStringGetter'; import { Button } from '@/components/Button'; import { Checkbox } from '@/components/Checkbox'; import { DropdownMenu } from '@/components/DropdownMenu'; import { Icon, IconName } from '@/components/Icon'; -import { OutputType, formatNumber, formatTimestamp } from '@/components/Output'; +import { OutputType, formatNumber } from '@/components/Output'; import { getSubaccountFills, getSubaccountTransfers } from '@/state/accountSelectors'; import { getSelectedLocale } from '@/state/localizationSelectors'; @@ -23,12 +23,15 @@ import { track } from '@/lib/analytics'; import { exportCSV } from '@/lib/csv'; import { MustBigNumber } from '@/lib/numbers'; -export const ExportHistoryDropdown = () => { +interface ExportHistoryDropdownProps { + className?: string; +} + +export const ExportHistoryDropdown = (props: ExportHistoryDropdownProps) => { const selectedLocale = useSelector(getSelectedLocale); const stringGetter = useStringGetter(); const allTransfers = useSelector(getSubaccountTransfers, shallowEqual) ?? []; const allFills = useSelector(getSubaccountFills, shallowEqual) ?? []; - const { decimal: localeDecimalSeparator, group: localeGroupSeparator } = useLocaleSeparators(); const [checkedTrades, setCheckedTrades] = useState(true); const [checkedTransfers, setCheckedTransfers] = useState(true); @@ -38,21 +41,11 @@ export const ExportHistoryDropdown = () => { const { sign: feeSign, formattedString: feeString } = formatNumber({ type: OutputType.Fiat, value: fill.fee, - localeDecimalSeparator, - localeGroupSeparator, }); const { sign: totalSign, formattedString: totalString } = formatNumber({ type: OutputType.Fiat, value: MustBigNumber(fill.price).times(fill.size), - localeDecimalSeparator, - localeGroupSeparator, - }); - - const { displayString } = formatTimestamp({ - type: OutputType.DateTime, - value: fill.createdAtMilliseconds, - locale: selectedLocale, }); const sideKey = { @@ -65,7 +58,10 @@ export const ExportHistoryDropdown = () => { liquidity: fill.resources.liquidityStringKey && stringGetter({ key: fill.resources.liquidityStringKey }), - time: displayString, + time: new Date(fill.createdAtMilliseconds).toLocaleString(selectedLocale, { + dateStyle: 'short', + timeStyle: 'short', + }), amount: fill.size, fee: feeSign ? `${feeSign}${feeString}` : feeString, total: totalSign ? `${totalSign}${totalString}` : totalString, @@ -77,7 +73,7 @@ export const ExportHistoryDropdown = () => { : '', }; }), - [allFills, stringGetter, localeDecimalSeparator, localeGroupSeparator] + [allFills, selectedLocale, stringGetter] ); const transfers = useMemo( @@ -86,18 +82,13 @@ export const ExportHistoryDropdown = () => { const { sign, formattedString } = formatNumber({ type: OutputType.Fiat, value: transfer.amount, - localeDecimalSeparator, - localeGroupSeparator, - }); - - const { displayString } = formatTimestamp({ - type: OutputType.DateTime, - value: transfer.updatedAtMilliseconds, - locale: selectedLocale, }); return { - time: displayString, + time: new Date(transfer.updatedAtMilliseconds).toLocaleString(selectedLocale, { + dateStyle: 'short', + timeStyle: 'short', + }), action: transfer.resources.typeStringKey && stringGetter({ key: transfer.resources.typeStringKey }), @@ -107,7 +98,7 @@ export const ExportHistoryDropdown = () => { transaction: transfer.transactionHash, }; }), - [allTransfers, stringGetter, localeDecimalSeparator, localeGroupSeparator] + [allTransfers, selectedLocale, stringGetter] ); const exportTrades = useCallback(() => { @@ -255,6 +246,7 @@ export const ExportHistoryDropdown = () => { onSelect: exportData, }, ]} + {...props} > {stringGetter({ key: STRING_KEYS.EXPORT })}