Skip to content

Commit d49ec24

Browse files
committed
feat(core): add nve-format-number component
• string and number properties mapping to Intl.NumberFormat options • decimal, currency, percent, and unit format styles • standard, scientific, engineering, and compact notation modes • LogService warnings for invalid values and invalid Intl options • inherits --nve-sys-text-color for theme compatibility • examples, full test suite and docs, visual tests skipped Signed-off-by: Jake Guza <jguza@nvidia.com>
1 parent 72fe1ff commit d49ec24

15 files changed

Lines changed: 791 additions & 1 deletion

File tree

projects/core/eslint.config.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,11 @@ export default [
3030
}
3131
},
3232
{
33-
files: ['src/format-datetime/format-datetime.ts', 'src/format-relative-time/format-relative-time.ts'],
33+
files: [
34+
'src/format-datetime/format-datetime.ts',
35+
'src/format-number/format-number.ts',
36+
'src/format-relative-time/format-relative-time.ts'
37+
],
3438
rules: {
3539
'local/require-test-completeness': ['error', { skipSuffixes: ['.test.visual.ts'] }]
3640
}

projects/core/package.json

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -381,6 +381,18 @@
381381
"types": "./dist/format-datetime/define.d.ts",
382382
"default": "./dist/format-datetime/define.js"
383383
},
384+
"./format-number": {
385+
"types": "./dist/format-number/index.d.ts",
386+
"default": "./dist/format-number/index.js"
387+
},
388+
"./format-number/index.js": {
389+
"types": "./dist/format-number/index.d.ts",
390+
"default": "./dist/format-number/index.js"
391+
},
392+
"./format-number/define.js": {
393+
"types": "./dist/format-number/define.d.ts",
394+
"default": "./dist/format-number/define.js"
395+
},
384396
"./format-relative-time": {
385397
"types": "./dist/format-relative-time/index.d.ts",
386398
"default": "./dist/format-relative-time/index.js"

projects/core/src/bundle.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import '@nvidia-elements/core/dropdown-group/define.js';
2828
import '@nvidia-elements/core/dropzone/define.js';
2929
import '@nvidia-elements/core/file/define.js';
3030
import '@nvidia-elements/core/format-datetime/define.js';
31+
import '@nvidia-elements/core/format-number/define.js';
3132
import '@nvidia-elements/core/format-relative-time/define.js';
3233
import '@nvidia-elements/core/forms/define.js';
3334
import '@nvidia-elements/core/grid/define.js';
@@ -81,6 +82,7 @@ export * from '@nvidia-elements/core/dropdown';
8182
export * from '@nvidia-elements/core/dropzone';
8283
export * from '@nvidia-elements/core/file';
8384
export * from '@nvidia-elements/core/format-datetime';
85+
export * from '@nvidia-elements/core/format-number';
8486
export * from '@nvidia-elements/core/format-relative-time';
8587
export * from '@nvidia-elements/core/forms';
8688
export * from '@nvidia-elements/core/grid';
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
import { define } from '@nvidia-elements/core/internal';
5+
import { FormatNumber } from '@nvidia-elements/core/format-number';
6+
7+
define(FormatNumber);
8+
9+
declare global {
10+
interface HTMLElementTagNameMap {
11+
'nve-format-number': FormatNumber;
12+
}
13+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
/* SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. */
2+
/* SPDX-License-Identifier: Apache-2.0 */
3+
4+
:host {
5+
display: inline;
6+
}
7+
8+
[internal-host] {
9+
color: var(--nve-sys-text-color, inherit);
10+
}
11+
12+
slot {
13+
display: none;
14+
}
Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
import { html } from 'lit';
5+
import '@nvidia-elements/core/format-number/define.js';
6+
7+
export default {
8+
title: 'Elements/FormatNumber',
9+
component: 'nve-format-number'
10+
};
11+
12+
/**
13+
* @summary Basic decimal formatting with localized grouping separators. Use for inline counts and metrics.
14+
*/
15+
export const Default = {
16+
render: () => html`
17+
<nve-format-number>1234567.89</nve-format-number>
18+
`
19+
};
20+
21+
/**
22+
* @summary Currency formatting for monetary values with locale-aware symbols and separators. Use for prices, budgets, and financial totals.
23+
*/
24+
export const Currency = {
25+
render: () => html`
26+
<div nve-layout="column gap:sm">
27+
<nve-format-number format-style="currency" currency="USD">1234.56</nve-format-number>
28+
<nve-format-number format-style="currency" currency="EUR" currency-display="code">1234.56</nve-format-number>
29+
<nve-format-number format-style="currency" currency="JPY" currency-display="name">1234</nve-format-number>
30+
</div>
31+
`
32+
};
33+
34+
/**
35+
* @summary Percent formatting for ratios and completion values. Source values should already represent a fraction (such as 0.85 for 85 percent).
36+
*/
37+
export const Percent = {
38+
render: () => html`
39+
<div nve-layout="column gap:sm">
40+
<nve-format-number format-style="percent">0.85</nve-format-number>
41+
<nve-format-number format-style="percent">0.126</nve-format-number>
42+
</div>
43+
`
44+
};
45+
46+
/**
47+
* @summary Unit formatting for measurements and quantities. Use for distances, storage sizes, or other numeric labels that need a localized unit suffix.
48+
*/
49+
export const Unit = {
50+
render: () => html`
51+
<div nve-layout="column gap:sm">
52+
<nve-format-number format-style="unit" unit="kilometer">1234.56</nve-format-number>
53+
<nve-format-number format-style="unit" unit="byte" unit-display="long">2048</nve-format-number>
54+
<nve-format-number format-style="unit" unit="celsius" unit-display="narrow">22</nve-format-number>
55+
</div>
56+
`
57+
};
58+
59+
/**
60+
* @summary Notation presets for scientific, engineering, and compact display. Use compact notation in dashboards or cards where space matters.
61+
*/
62+
export const Notation = {
63+
render: () => html`
64+
<div nve-layout="column gap:sm">
65+
<nve-format-number notation="compact" compact-display="short">1234567</nve-format-number>
66+
<nve-format-number notation="compact" compact-display="long">1234567</nve-format-number>
67+
<nve-format-number notation="scientific">1234567</nve-format-number>
68+
<nve-format-number notation="engineering">1234567</nve-format-number>
69+
</div>
70+
`
71+
};
72+
73+
/**
74+
* @summary Sign display options for controlling positive and negative indicators. Use 'always' for delta values or 'exceptZero' for change indicators.
75+
*/
76+
export const SignDisplay = {
77+
render: () => html`
78+
<div nve-layout="column gap:sm">
79+
<nve-format-number sign-display="always">42</nve-format-number>
80+
<nve-format-number sign-display="always">-42</nve-format-number>
81+
<nve-format-number sign-display="exceptZero">0</nve-format-number>
82+
<nve-format-number sign-display="never">-42</nve-format-number>
83+
</div>
84+
`
85+
};
86+
87+
/**
88+
* @summary Fraction digit control for tuning decimal precision. Use to enforce fixed decimal places in financial or scientific contexts.
89+
*/
90+
export const FractionDigits = {
91+
render: () => html`
92+
<div nve-layout="column gap:sm">
93+
<nve-format-number minimum-fraction-digits="4">1.5</nve-format-number>
94+
<nve-format-number maximum-fraction-digits="0">1.567</nve-format-number>
95+
<nve-format-number minimum-fraction-digits="2" maximum-fraction-digits="2">3</nve-format-number>
96+
</div>
97+
`
98+
};
99+
100+
/**
101+
* @summary Explicit locale settings for internationalized number output. Use when the target audience locale differs from the document language or browser default.
102+
*/
103+
export const Locale = {
104+
render: () => html`
105+
<div nve-layout="column gap:sm">
106+
<nve-format-number locale="de-DE" format-style="currency" currency="EUR">1234.56</nve-format-number>
107+
<nve-format-number locale="ja-JP" format-style="currency" currency="JPY">1234</nve-format-number>
108+
<nve-format-number locale="fr-FR">1234567.89</nve-format-number>
109+
</div>
110+
`
111+
};
112+
113+
/**
114+
* @summary Number attribute input for values supplied by JavaScript or bound data. By default, the component formats text content, which also serves as the SSR fallback, and `number` wins when both are present.
115+
*/
116+
export const NumberAttribute = {
117+
render: () => html`
118+
<nve-format-number number="1234.56" format-style="currency" currency="USD"></nve-format-number>
119+
`
120+
};
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
import { html } from 'lit';
5+
import { describe, expect, it } from 'vitest';
6+
import { createFixture, elementIsStable, removeFixture } from '@internals/testing';
7+
import { runAxe } from '@internals/testing/axe';
8+
import { FormatNumber } from '@nvidia-elements/core/format-number';
9+
import '@nvidia-elements/core/format-number/define.js';
10+
11+
describe(FormatNumber.metadata.tag, () => {
12+
it('should pass axe check', async () => {
13+
const fixture = await createFixture(html`
14+
<nve-format-number locale="en-US">1234567</nve-format-number>
15+
<nve-format-number locale="en-US" format-style="currency" currency="USD">1234.56</nve-format-number>
16+
<nve-format-number locale="de-DE">1234.56</nve-format-number>
17+
`);
18+
19+
try {
20+
await elementIsStable(fixture.querySelector(FormatNumber.metadata.tag));
21+
const results = await runAxe([FormatNumber.metadata.tag]);
22+
expect(results.violations.length).toBe(0);
23+
} finally {
24+
removeFixture(fixture);
25+
}
26+
});
27+
});
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
import { expect, test, describe } from 'vitest';
5+
import { lighthouseRunner } from '@internals/vite';
6+
7+
describe('format-number lighthouse report', () => {
8+
test('format-number should meet lighthouse benchmarks', async () => {
9+
const report = await lighthouseRunner.getReport('nve-format-number', /* html */`
10+
<nve-format-number format-style="currency" currency="USD">1234.56</nve-format-number>
11+
<script type="module">
12+
import '@nvidia-elements/core/format-number/define.js';
13+
</script>
14+
`);
15+
16+
expect(report.scores.performance).toBe(100);
17+
expect(report.scores.accessibility).toBe(100);
18+
expect(report.scores.bestPractices).toBe(100);
19+
expect(report.payload.javascript.kb).toBeLessThan(12);
20+
});
21+
});
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
import { html } from 'lit';
5+
import { describe, expect, it } from 'vitest';
6+
import { ssrRunner } from '@internals/vite';
7+
import { FormatNumber } from '@nvidia-elements/core/format-number';
8+
import '@nvidia-elements/core/format-number/define.js';
9+
10+
describe(FormatNumber.metadata.tag, () => {
11+
it('should pass baseline ssr check', async () => {
12+
const result = await ssrRunner.render(
13+
html`<nve-format-number format-style="currency" currency="USD">1234.56</nve-format-number>`
14+
);
15+
expect(result.includes('shadowroot="open"')).toBe(true);
16+
expect(result.includes('nve-format-number')).toBe(true);
17+
});
18+
});

0 commit comments

Comments
 (0)