Skip to content

fix(amount): added bigInt and hideCurrency support in amount component#793

Open
Shreyag02 wants to merge 1 commit intomainfrom
fix/amount/bigInt-support
Open

fix(amount): added bigInt and hideCurrency support in amount component#793
Shreyag02 wants to merge 1 commit intomainfrom
fix/amount/bigInt-support

Conversation

@Shreyag02
Copy link
Copy Markdown
Contributor

Description

Adds two capabilities to the Amount component and fixes one latent bug:

  1. BigInt support. value now accepts bigint in addition to number | string. BigInt is treated as integer-only and always in major units, so valueInMinorUnits is ignored when value is a BigInt. This restores precision for amounts beyond Number.MAX_SAFE_INTEGER (≈ 9 × 10¹⁵) — the previous workaround was string-only, which is awkward for backends that already produce BigInt. Intl.NumberFormat.prototype.format() accepts BigInt natively, so the integration is direct.
  2. hideCurrency prop. A boolean that switches the formatter from style: 'currency' to style: 'decimal', rendering only the formatted number with locale-driven separators/grouping/fraction-digits intact. Useful for places where the currency is displayed elsewhere (column headers, line-item subtotals, token counters in the SDK Tokens view). When hideCurrency is true, currencyDisplay is ignored.
  3. Negative-string bug fix. The previous string-path minor-units conversion (value.slice(0, -decimals) + '.' + value.slice(-decimals)) mangled negative inputs like "-1" into ".-1". Replaced with a sign-aware padStart-based path that handles short and negative strings correctly.

Behavior contract documented in JSDoc on both source and docs props.ts. Backwards compatible — hideCurrency defaults to false, the BigInt branch is purely additive, and the negative-string path was previously buggy (no test ever passed against it).

Type of Change

  • Bug fix (non-breaking change that fixes an issue)
  • New feature (non-breaking change that adds functionality)
  • Documentation update

How Has This Been Tested?

  • Unit tests: added 11 new cases covering BigInt (6), hideCurrency (4), and the negative-string regression (1). Full suite at 41/41 passing.
    • BigInt: defaults, valueInMinorUnits=false parity, precision beyond MAX_SAFE_INTEGER, negative BigInts, zero-decimal currency (JPY), no MAX_SAFE_INTEGER warning for BigInt.
    • hideCurrency: basic case, BigInt input, override of currencyDisplay, currency-driven decimals still respected.
    • Negative string: value="-1299" → -$12.99.
  • Type checks: zero TS diagnostics across all five files.
  • Manual trace: walked through edge-case combinations not directly tested — all produce correct output.
    <Amount  value="-1" /> (padStart-padded to -$0.01)
    <Amount value={0n} />
    <Amount value={1299n} currency='BHD' /> 
    
  • Docs preview: added hideCurrencyDemo and a BigInt entry in largeNumbersDemo. The console-warning demo value was bumped from 15 digits (which never tripped Math.abs > MAX_SAFE_INTEGER) to 17 digits, so the warning now actually fires when the section is rendered.

Checklist:

  • My code follows the style guidelines of this project
  • I have performed a self-review of my own code
  • I have commented my code, particularly in hard-to-understand areas
  • I have made corresponding changes to the documentation (.mdx files)
  • My changes generate no new warnings
  • I have added tests that prove my fix is effective or that my feature works

Screenshots (if appropriate):

N/A

Related Issues

N/A

@vercel
Copy link
Copy Markdown

vercel Bot commented May 7, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
apsara Ready Ready Preview, Comment May 7, 2026 2:10pm

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 7, 2026

Review Change Stack

📝 Walkthrough

Walkthrough

This PR extends the Amount component with two complementary features. First, it broadens the value prop to accept bigint in addition to number and string, treating bigint values as already in major units (ignoring valueInMinorUnits). Second, it introduces a hideCurrency prop that renders only the formatted numeric portion while preserving locale-specific grouping and decimal separators. The implementation refactors the value-handling logic into explicit type branches and reworks the NumberFormat options to dynamically choose between decimal and currency styles. Tests validate edge cases including very large bigint values, negative inputs, and interactions with zero-decimal currencies.

Suggested reviewers

  • rohilsurana
  • paanSinghCoder
  • rohanchkrabrty
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the two main features added (bigInt and hideCurrency support) and correctly identifies the target component.
Description check ✅ Passed The description provides comprehensive detail on all changes, testing approach, backwards compatibility, and bug fixes, all directly related to the changeset.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/raystack/components/amount/__tests__/amount.test.tsx`:
- Around line 251-271: The hideCurrency test suite only covers non-round
amounts; add tests that assert behavior for round amounts (e.g., render <Amount
value={1200} hideCurrency /> and expect "12.00") and for bigint round values
(e.g., value={1200n} hideCurrency expecting "1,200") to ensure fraction digits
are preserved, and duplicate the override and zero-decimal (JPY) cases with
round values (e.g., currencyDisplay='code' and currency='JPY' with value=1200)
so the suite catches the fraction-digit regression in the Amount component.

In `@packages/raystack/components/amount/amount.tsx`:
- Around line 223-234: The current formatOptions object (in Amount component)
leaves minimumFractionDigits/maximumFractionDigits undefined when hideCurrency
is true, causing decimal digits to be stripped for round values; update the
hideCurrency branch inside formatOptions so it sets style: 'decimal' and also
explicitly applies the currency-driven fraction digits (use
minimumFractionDigits and maximumFractionDigits or the computed decimals value
used elsewhere) so decimals are preserved when hideCurrency=true; adjust tests
for bigint expectations and add a new test case asserting round amounts (e.g.,
value=1200) still render the expected trailing zeros.
- Around line 239-240: The code calls Intl.NumberFormat.format(finalBaseValue)
with finalBaseValue as a string to preserve precision, but environments may not
support V3 string-precision; either ensure your supported browsers include V3
(update browserslist/README) or add a runtime feature-detection and fallback:
implement the suggested supportsStringFormat check (new
Intl.NumberFormat('und').format('11111111111111111112').indexOf('2') !== -1)
and, if false, convert finalBaseValue to a safe fallback (e.g.,
Number(finalBaseValue) or a BigInt-to-string grouping formatter) before calling
Intl.NumberFormat.format; update the code around the
Intl.NumberFormat.format(...) call in amount.tsx that uses finalBaseValue
accordingly.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 9bb66c33-bcb5-4f9f-8b88-800f3a0c6b4e

📥 Commits

Reviewing files that changed from the base of the PR and between 3eba1bb and fe87575.

📒 Files selected for processing (5)
  • apps/www/src/content/docs/components/amount/demo.ts
  • apps/www/src/content/docs/components/amount/index.mdx
  • apps/www/src/content/docs/components/amount/props.ts
  • packages/raystack/components/amount/__tests__/amount.test.tsx
  • packages/raystack/components/amount/amount.tsx

Comment on lines +251 to 271
describe('hideCurrency', () => {
it('hides the currency symbol while preserving formatting', () => {
render(<Amount value={1299} hideCurrency />);
expect(screen.getByText('12.99')).toBeInTheDocument();
});

it('hides currency for bigint values', () => {
render(<Amount value={1299n} hideCurrency />);
expect(screen.getByText('1,299')).toBeInTheDocument();
});

it('overrides currencyDisplay when set', () => {
render(<Amount value={1299} hideCurrency currencyDisplay='code' />);
expect(screen.getByText('12.99')).toBeInTheDocument();
});

it('respects the currency for decimal-place math even when hidden', () => {
render(<Amount value={1299} currency='JPY' hideCurrency />);
expect(screen.getByText('1,299')).toBeInTheDocument();
});
});
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

hideCurrency test suite covers only non-round amounts

All four hideCurrency tests use value={1299} (USD → 12.99) or a zero-decimal currency (JPY). None exercise a round amount like value={1200} (USD → 12.00) where the fraction digit discrepancy from the major issue above would manifest. The coverage gap means the bug is undetected by the test suite even though the docs promise "fraction digits are preserved."

These tests will need to be updated once the fix proposed in the amount.tsx comment is applied.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/raystack/components/amount/__tests__/amount.test.tsx` around lines
251 - 271, The hideCurrency test suite only covers non-round amounts; add tests
that assert behavior for round amounts (e.g., render <Amount value={1200}
hideCurrency /> and expect "12.00") and for bigint round values (e.g.,
value={1200n} hideCurrency expecting "1,200") to ensure fraction digits are
preserved, and duplicate the override and zero-decimal (JPY) cases with round
values (e.g., currencyDisplay='code' and currency='JPY' with value=1200) so the
suite catches the fraction-digit regression in the Amount component.

Comment on lines +223 to +234
const formatOptions: Intl.NumberFormatOptions = {
minimumFractionDigits: hideDecimals ? 0 : minimumFractionDigits,
maximumFractionDigits: hideDecimals ? 0 : maximumFractionDigits,
useGrouping: groupDigits
useGrouping: groupDigits,
...(hideCurrency
? { style: 'decimal' }
: {
style: 'currency',
currency: validCurrency.toUpperCase(),
currencyDisplay
})
};
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

hideCurrency=true does not preserve currency-driven fraction digits

The spread { style: 'decimal' } for hideCurrency doesn't override minimumFractionDigits, so it inherits undefined from the base object. For style: 'decimal', the default minimumFractionDigits is 0, whereas the default maximum digit value is 3 for plain values, 2 for currency. This means for round amounts, trailing zeros are stripped: value={1200} (USD minor units) formats to "12" instead of "12.00", directly contradicting the JSDoc comment on Line 77 ("fraction digits are preserved") and the PR summary ("fraction-digits remain").

The test suite covers only value={1299} (which produces non-zero decimal digits 12.99), so this discrepancy is invisible in tests. The same issue affects bigint values: 1299n with hideCurrency=false gives "$1,299.00" but hideCurrency=true gives "1,299".

🛠️ Proposed fix — propagate `decimals` as default in the `hideCurrency` branch
-    const formatOptions: Intl.NumberFormatOptions = {
-      minimumFractionDigits: hideDecimals ? 0 : minimumFractionDigits,
-      maximumFractionDigits: hideDecimals ? 0 : maximumFractionDigits,
-      useGrouping: groupDigits,
-      ...(hideCurrency
-        ? { style: 'decimal' }
-        : {
-            style: 'currency',
-            currency: validCurrency.toUpperCase(),
-            currencyDisplay
-          })
-    };
+    const formatOptions: Intl.NumberFormatOptions = {
+      useGrouping: groupDigits,
+      ...(hideCurrency
+        ? {
+            style: 'decimal',
+            minimumFractionDigits: hideDecimals ? 0 : (minimumFractionDigits ?? decimals),
+            maximumFractionDigits: hideDecimals ? 0 : (maximumFractionDigits ?? decimals),
+          }
+        : {
+            style: 'currency',
+            currency: validCurrency.toUpperCase(),
+            currencyDisplay,
+            minimumFractionDigits: hideDecimals ? 0 : minimumFractionDigits,
+            maximumFractionDigits: hideDecimals ? 0 : maximumFractionDigits,
+          })
+    };

After this fix, also update the bigint hideCurrency test expectation:

- expect(screen.getByText('1,299')).toBeInTheDocument();
+ expect(screen.getByText('1,299.00')).toBeInTheDocument();

And add a coverage case for round amounts:

it('preserves currency fraction digits for round amounts', () => {
  render(<Amount value={1200} hideCurrency />);
  expect(screen.getByText('12.00')).toBeInTheDocument();
});
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const formatOptions: Intl.NumberFormatOptions = {
minimumFractionDigits: hideDecimals ? 0 : minimumFractionDigits,
maximumFractionDigits: hideDecimals ? 0 : maximumFractionDigits,
useGrouping: groupDigits
useGrouping: groupDigits,
...(hideCurrency
? { style: 'decimal' }
: {
style: 'currency',
currency: validCurrency.toUpperCase(),
currencyDisplay
})
};
const formatOptions: Intl.NumberFormatOptions = {
useGrouping: groupDigits,
...(hideCurrency
? {
style: 'decimal',
minimumFractionDigits: hideDecimals ? 0 : (minimumFractionDigits ?? decimals),
maximumFractionDigits: hideDecimals ? 0 : (maximumFractionDigits ?? decimals),
}
: {
style: 'currency',
currency: validCurrency.toUpperCase(),
currencyDisplay,
minimumFractionDigits: hideDecimals ? 0 : minimumFractionDigits,
maximumFractionDigits: hideDecimals ? 0 : maximumFractionDigits,
})
};
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/raystack/components/amount/amount.tsx` around lines 223 - 234, The
current formatOptions object (in Amount component) leaves
minimumFractionDigits/maximumFractionDigits undefined when hideCurrency is true,
causing decimal digits to be stripped for round values; update the hideCurrency
branch inside formatOptions so it sets style: 'decimal' and also explicitly
applies the currency-driven fraction digits (use minimumFractionDigits and
maximumFractionDigits or the computed decimals value used elsewhere) so decimals
are preserved when hideCurrency=true; adjust tests for bigint expectations and
add a new test case asserting round amounts (e.g., value=1200) still render the
expected trailing zeros.

Comment on lines 239 to +240
// @ts-expect-error TS lib types omit `string` from format() params, but Intl.NumberFormat accepts numeric strings at runtime — needed for large values that would lose precision as `number`.
} as Intl.NumberFormatOptions).format(finalBaseValue);
).format(finalBaseValue);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

cat -n packages/raystack/components/amount/amount.tsx | sed -n '230,250p'

Repository: raystack/apsara

Length of output: 860


🏁 Script executed:

find . -name "package.json" -type f | head -5

Repository: raystack/apsara

Length of output: 206


🏁 Script executed:

cat package.json | jq '.browserslist, .engines, .targets' 2>/dev/null || cat package.json | grep -A 5 -E '(browserslist|engines|targets)'

Repository: raystack/apsara

Length of output: 90


🏁 Script executed:

cat packages/raystack/package.json | jq '.browserslist, .engines, .targets, .exports, .main' 2>/dev/null || cat packages/raystack/package.json

Repository: raystack/apsara

Length of output: 691


🌐 Web query:

Intl.NumberFormat V3 browser support timeline string precision ES2023

💡 Result:

Intl.NumberFormat V3 refers to the ECMA-402 proposal adding advanced features like roundingPriority ("auto", "morePrecision", "lessPrecision"), roundingMode, and enhanced string precision control via minimumSignificantDigits/maximumSignificantDigits (1-21 range) in conjunction with fractionDigits, resolving conflicts via roundingPriority. These enable precise string-based number formatting without floating-point loss, key for ES2023 Intl updates.[1][2][3][4] Browser support timeline (full V3 features like roundingMode, roundingPriority): - Chrome/Edge: 106+ (Oct 2022)[1][5][6] - Firefox: 93 (initial), full 116+ (2023)[1][6] - Safari: 15.4+ (Mar 2022)[1][6] By 2026-05-07, universally supported in modern browsers (global usage >98%). Basic significantDigits supported earlier (Chrome 24+, Firefox 29+, Safari 10+).[7] Example for string precision: new Intl.NumberFormat('en-US', { maximumSignificantDigits: 3, roundingPriority: 'lessPrecision' }).format('1234.56789'); // "1,230" (respects both sig/maxFraction via priority)[3][4] Official spec in ECMAScript 2023 Intl API (10.0).[8]

Citations:


🏁 Script executed:

grep -r "Amount" packages/raystack/package.json | head -10

Repository: raystack/apsara

Length of output: 41


🏁 Script executed:

grep -B5 "finalBaseValue" packages/raystack/components/amount/amount.tsx | head -20

Repository: raystack/apsara

Length of output: 826


🏁 Script executed:

cat packages/raystack/README.md 2>/dev/null | head -50 || echo "No README found"

Repository: raystack/apsara

Length of output: 1785


🏁 Script executed:

find . -maxdepth 2 -name ".browserslistrc" -o -name "tsconfig.json" -o -name "tsconfig.build.json" -o -name "vite.config.ts" -o -name "tsup.config.ts" 2>/dev/null | head -10

Repository: raystack/apsara

Length of output: 41


🏁 Script executed:

cat tsconfig.json | jq '.compilerOptions.lib, .compilerOptions.target, .compilerOptions.useDefineForClassFields' 2>/dev/null || grep -A 5 '"target"' tsconfig.json

Repository: raystack/apsara

Length of output: 105


🏁 Script executed:

find packages/raystack -name "tsconfig.json" -o -name "vite.config.ts" -o -name "tsup.config.ts" 2>/dev/null

Repository: raystack/apsara

Length of output: 91


🏁 Script executed:

find . -maxdepth 3 -name ".browserslistrc" 2>/dev/null

Repository: raystack/apsara

Length of output: 41


🏁 Script executed:

cat packages/raystack/tsconfig.json 2>/dev/null || echo "Not found"

Repository: raystack/apsara

Length of output: 568


🏁 Script executed:

cat packages/tools-config/tsconfig/react.json 2>/dev/null || find . -path "*tools-config*" -name "*.json" -type f | head -10

Repository: raystack/apsara

Length of output: 310


🏁 Script executed:

find . -maxdepth 2 -name "*.config.ts" -o -name "*.config.js" -type f | grep -E "(vite|tsup|esbuild|webpack)" | head -5

Repository: raystack/apsara

Length of output: 41


🏁 Script executed:

cat packages/raystack/package.json | jq '.browserslist' 2>/dev/null

Repository: raystack/apsara

Length of output: 64


🏁 Script executed:

cat packages/tools-config/tsconfig/base.json 2>/dev/null | head -30

Repository: raystack/apsara

Length of output: 580


🌐 Web query:

Intl.NumberFormat string parameter support es5 es6 when added specification history

💡 Result:

Intl.NumberFormat supports string parameters for the locales argument (a BCP 47 language tag string or array of strings) and accepts an options object as the second parameter. This API is not part of ES5 (ECMA-262 5th edition, 2009), which has no Intl object or NumberFormat.[1][2] It was first specified in ECMA-402 1st edition (December 2012), the ECMAScript Internationalization API Specification, with the constructor defined as Intl.NumberFormat([locales [, options ]]), where locales can be a string.[3][4][5][6] ECMA-402 1st edition aligned with ES6 (ECMA-262 6th edition, June 2015) development timeline, but the core API (including string locales support) was present from the initial 2012 spec and unchanged in basic constructor signature across editions.[7][8][9] Browser support began pre-ES6: Chrome 24 (2013), Firefox 29 (2014), Safari 10 (2016).[10][11] No evidence of string parameter support in ES5; Intl was added via ECMA-402.[12][13]

Citations:


Verify minimum browser targets for Intl.NumberFormat string-precision support

The code passes strings to Intl.NumberFormat.format() for large-value precision preservation, relying on V3 spec behavior that treats strings with exact precision rather than converting to Number first. However, the library has no explicit browser version constraints (TypeScript target is "es6" with no browserslist), making it unclear whether all deployment environments meet the V3 adoption timeline (Chrome 106+, Firefox 116+, Safari 15.4+ — ~2022–2023).

Confirm that the minimum supported browsers include full Intl.NumberFormat V3 string-precision support, or add a feature-detection guard:

const supportsStringFormat = new Intl.NumberFormat('und').format('11111111111111111112').indexOf('2') !== -1;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/raystack/components/amount/amount.tsx` around lines 239 - 240, The
code calls Intl.NumberFormat.format(finalBaseValue) with finalBaseValue as a
string to preserve precision, but environments may not support V3
string-precision; either ensure your supported browsers include V3 (update
browserslist/README) or add a runtime feature-detection and fallback: implement
the suggested supportsStringFormat check (new
Intl.NumberFormat('und').format('11111111111111111112').indexOf('2') !== -1)
and, if false, convert finalBaseValue to a safe fallback (e.g.,
Number(finalBaseValue) or a BigInt-to-string grouping formatter) before calling
Intl.NumberFormat.format; update the code around the
Intl.NumberFormat.format(...) call in amount.tsx that uses finalBaseValue
accordingly.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant