Skip to content

Commit a9cd207

Browse files
committed
feat(core): progress gauge
Signed-off-by: Cory Rylan <crylan@nvidia.com>
1 parent 054992e commit a9cd207

17 files changed

Lines changed: 875 additions & 1 deletion
Lines changed: 3 additions & 0 deletions
Loading
Lines changed: 3 additions & 0 deletions
Loading

projects/core/package.json

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -605,6 +605,18 @@
605605
"types": "./dist/progress-bar/define.d.ts",
606606
"default": "./dist/progress-bar/define.js"
607607
},
608+
"./progress-gauge": {
609+
"types": "./dist/progress-gauge/index.d.ts",
610+
"default": "./dist/progress-gauge/index.js"
611+
},
612+
"./progress-gauge/index.js": {
613+
"types": "./dist/progress-gauge/index.d.ts",
614+
"default": "./dist/progress-gauge/index.js"
615+
},
616+
"./progress-gauge/define.js": {
617+
"types": "./dist/progress-gauge/define.d.ts",
618+
"default": "./dist/progress-gauge/define.js"
619+
},
608620
"./progress-ring": {
609621
"types": "./dist/progress-ring/index.d.ts",
610622
"default": "./dist/progress-ring/index.js"

projects/core/src/bundle.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ import '@nvidia-elements/core/panel/define.js';
4747
import '@nvidia-elements/core/password/define.js';
4848
import '@nvidia-elements/core/preferences-input/define.js';
4949
import '@nvidia-elements/core/progress-bar/define.js';
50+
import '@nvidia-elements/core/progress-gauge/define.js';
5051
import '@nvidia-elements/core/progress-ring/define.js';
5152
import '@nvidia-elements/core/progressive-filter-chip/define.js';
5253
import '@nvidia-elements/core/pulse/define.js';
@@ -115,6 +116,7 @@ export * from '@nvidia-elements/core/panel';
115116
export * from '@nvidia-elements/core/password';
116117
export * from '@nvidia-elements/core/preferences-input';
117118
export * from '@nvidia-elements/core/progress-bar';
119+
export * from '@nvidia-elements/core/progress-gauge';
118120
export * from '@nvidia-elements/core/progress-ring';
119121
export * from '@nvidia-elements/core/progressive-filter-chip';
120122
export * from '@nvidia-elements/core/pulse';

projects/core/src/index.test.lighthouse.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ describe('lighthouse report', () => {
1515
expect(report.scores.performance).toBe(100);
1616
expect(report.scores.accessibility).toBe(100);
1717
expect(report.scores.bestPractices).toBe(100);
18-
expect(report.payload.javascript.requests['index.js'].kb).toBeLessThan(130.5);
18+
expect(report.payload.javascript.requests['index.js'].kb).toBeLessThan(132);
1919

2020
// if sudden drop in size, check vite bundle config and bundle demo to ensure side effects are properly preserved
2121
expect(report.payload.javascript.requests['index.js'].kb).toBeGreaterThan(120);
@@ -62,6 +62,7 @@ describe('lighthouse report', () => {
6262
import '@nvidia-elements/core/password/define.js';
6363
import '@nvidia-elements/core/preferences-input/define.js';
6464
import '@nvidia-elements/core/progress-bar/define.js';
65+
import '@nvidia-elements/core/progress-gauge/define.js';
6566
import '@nvidia-elements/core/progress-ring/define.js';
6667
import '@nvidia-elements/core/progressive-filter-chip/define.js';
6768
import '@nvidia-elements/core/radio/define.js';
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 { ProgressGauge } from '@nvidia-elements/core/progress-gauge';
6+
7+
define(ProgressGauge);
8+
9+
declare global {
10+
interface HTMLElementTagNameMap {
11+
'nve-progress-gauge': ProgressGauge;
12+
}
13+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
export * from './progress-gauge.js';
Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
/* SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. */
2+
/* SPDX-License-Identifier: Apache-2.0 */
3+
4+
:host {
5+
--color: var(--nve-sys-text-emphasis-color);
6+
--background: var(--nve-sys-interaction-background);
7+
--accent-color: var(--nve-sys-interaction-color);
8+
--gauge-width: 12px;
9+
--font-size: var(--nve-ref-font-size-400);
10+
--gap: var(--nve-ref-space-xs);
11+
--width: 128px;
12+
--height: var(--width);
13+
--_animation-duration: var(--nve-ref-animation-duration-250);
14+
15+
display: inline-block;
16+
position: relative;
17+
width: var(--width);
18+
height: var(--height);
19+
container-type: inline-size;
20+
contain: content;
21+
text-box: trim-both cap alphabetic;
22+
}
23+
24+
:host([size='sm']) {
25+
--font-size: var(--nve-ref-font-size-300);
26+
--width: 96px;
27+
}
28+
29+
:host([size='lg']) {
30+
--font-size: var(--nve-ref-font-size-500);
31+
--width: 160px;
32+
}
33+
34+
[internal-host] {
35+
display: grid;
36+
place-items: center;
37+
position: relative;
38+
height: 100%;
39+
}
40+
41+
:host([container='half']) {
42+
--height: calc(var(--width) / 2);
43+
}
44+
45+
:host([container='half']) [internal-host] {
46+
place-items: end center;
47+
}
48+
49+
svg {
50+
position: absolute;
51+
inset: 0;
52+
width: 100%;
53+
height: 100%;
54+
overflow: visible;
55+
}
56+
57+
path {
58+
fill: none;
59+
stroke-linecap: round;
60+
stroke-width: var(--gauge-width);
61+
}
62+
63+
path.background {
64+
stroke: var(--background);
65+
}
66+
67+
path.gauge {
68+
animation: gauge-progress-in var(--_animation-duration) var(--nve-ref-animation-easing-100);
69+
stroke: var(--accent-color);
70+
stroke-dasharray: var(--_progress) 100;
71+
transition: stroke-dasharray var(--_animation-duration) var(--nve-ref-animation-easing-100);
72+
will-change: stroke-dasharray;
73+
}
74+
75+
path.gauge[empty] {
76+
animation: none;
77+
stroke-linecap: butt;
78+
}
79+
80+
slot {
81+
display: flex;
82+
flex-direction: column;
83+
place-items: center;
84+
justify-content: center;
85+
height: 100%;
86+
gap: var(--gap);
87+
color: var(--color);
88+
font-size: var(--font-size);
89+
font-weight: var(--nve-ref-font-weight-medium);
90+
}
91+
92+
:host([container='half']) slot {
93+
transform: translateY(10cqw);
94+
}
95+
96+
::slotted(*) {
97+
color: var(--color);
98+
font-size: var(--font-size);
99+
}
100+
101+
:host([status='success']) {
102+
--accent-color: var(--nve-sys-support-success-emphasis-color);
103+
--background: var(--nve-sys-support-success-muted-color);
104+
}
105+
106+
:host([status='warning']) {
107+
--accent-color: var(--nve-sys-support-warning-emphasis-color);
108+
--background: var(--nve-sys-support-warning-muted-color);
109+
}
110+
111+
:host([status='danger']) {
112+
--accent-color: var(--nve-sys-support-danger-emphasis-color);
113+
--background: var(--nve-sys-support-danger-muted-color);
114+
}
115+
116+
:host([status='accent']) {
117+
--accent-color: var(--nve-sys-accent-secondary-background);
118+
}
119+
120+
@media (prefers-reduced-motion: reduce) {
121+
:host {
122+
--_animation-duration: 0s;
123+
}
124+
}
125+
126+
@keyframes gauge-progress-in {
127+
from {
128+
stroke-dasharray: 0 100;
129+
}
130+
131+
to {
132+
stroke-dasharray: var(--_progress) 100;
133+
}
134+
}
Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
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/progress-gauge/define.js';
6+
7+
export default {
8+
title: 'Elements/Progress Gauge',
9+
component: 'nve-progress-gauge',
10+
};
11+
12+
/**
13+
* @summary 270-degree progress gauges for displaying system resource usage.
14+
*/
15+
export const Default = {
16+
render: () => html`
17+
<div nve-layout="row gap:sm">
18+
<nve-progress-gauge value="50">50%</nve-progress-gauge>
19+
<nve-progress-gauge status="accent" value="66">66%</nve-progress-gauge>
20+
</div>
21+
`};
22+
23+
/**
24+
* @summary Container variants compare the default 270-degree gauge with the compact half gauge for telemetry layouts with tighter vertical space.
25+
* @tags test-case
26+
*/
27+
export const Container = {
28+
render: () => html`
29+
<div nve-layout="row gap:sm align:vertical-center">
30+
<nve-progress-gauge status="accent" value="66">66%</nve-progress-gauge>
31+
<nve-progress-gauge container="half" status="accent" value="66">66%</nve-progress-gauge>
32+
</div>
33+
`};
34+
35+
/**
36+
* @summary Gauges with values from 0% to 100% for displaying system resource usage.
37+
* @tags test-case
38+
*/
39+
export const Values = {
40+
render: () => html`
41+
<div nve-layout="row gap:sm">
42+
<nve-progress-gauge value="0">0%</nve-progress-gauge>
43+
<nve-progress-gauge value="33">33%</nve-progress-gauge>
44+
<nve-progress-gauge value="66">66%</nve-progress-gauge>
45+
<nve-progress-gauge value="100">100%</nve-progress-gauge>
46+
</div>
47+
`};
48+
49+
/**
50+
* @summary Progress gauges with custom max values for mission checkpoints, validation clips, and map tile processing.
51+
* @tags test-case
52+
*/
53+
export const Max = {
54+
render: () => html`
55+
<div nve-layout="row gap:sm">
56+
<nve-progress-gauge status="accent" max="20" value="5">5/20</nve-progress-gauge>
57+
<nve-progress-gauge max="20" value="10">10/20</nve-progress-gauge>
58+
<nve-progress-gauge max="20" value="15">15/20</nve-progress-gauge>
59+
</div>
60+
`};
61+
62+
/**
63+
* @summary Progress gauges with accent, success, warning, and danger colors for autonomous system health and readiness signals.
64+
* @tags test-case
65+
*/
66+
export const Status = {
67+
render: () => html`
68+
<div nve-layout="row gap:sm">
69+
<nve-progress-gauge value="50">50%</nve-progress-gauge>
70+
<nve-progress-gauge status="accent" value="75">75%</nve-progress-gauge>
71+
<nve-progress-gauge status="success" value="75">75%</nve-progress-gauge>
72+
<nve-progress-gauge status="warning" value="75">2.1m</nve-progress-gauge>
73+
<nve-progress-gauge status="danger" value="75">0Hz</nve-progress-gauge>
74+
</div>
75+
`};
76+
77+
/**
78+
* @summary Small progress gauge paired with route-solve text for compact autonomous vehicle task rows.
79+
* @tags test-case
80+
*/
81+
export const WithText = {
82+
render: () => html`
83+
<div nve-layout="row gap:xs align:vertical-center" nve-text="medium">
84+
<nve-progress-gauge status="accent" size="sm" value="50" aria-labelledby="route-solve-label">2.4s</nve-progress-gauge>
85+
<span id="route-solve-label">Route solve</span>
86+
</div>
87+
`};
88+
89+
/**
90+
* @summary Progress gauges in small, medium, and large sizes for dense robotics and autonomous vehicle dashboards.
91+
* @tags test-case
92+
*/
93+
export const Sizing = {
94+
render: () => html`
95+
<div nve-layout="row gap:sm">
96+
<nve-progress-gauge size="sm" value="50">30Hz</nve-progress-gauge>
97+
<nve-progress-gauge size="md" value="50">12Hz</nve-progress-gauge>
98+
<nve-progress-gauge size="lg" value="50">84%</nve-progress-gauge>
99+
</div>
100+
`};
101+
102+
/**
103+
* @summary Use for displaying real-time system load and performance metrics.
104+
* @tags pattern
105+
*/
106+
export const Dynamic = {
107+
render: () => html`
108+
<div nve-layout="row gap:sm">
109+
<progress-gauge-dynamic-example style="display: contents">
110+
<nve-progress-gauge status="success" value="0">
111+
<span>0%</span>
112+
<span nve-text="body sm muted">GPU</span>
113+
</nve-progress-gauge>
114+
</progress-gauge-dynamic-example>
115+
</div>
116+
<script type="module">
117+
if (!customElements.get('progress-gauge-dynamic-example')) {
118+
customElements.define('progress-gauge-dynamic-example', class extends HTMLElement {
119+
connectedCallback() {
120+
const gauge = this.querySelector('nve-progress-gauge');
121+
const valueElement = gauge.querySelector('span');
122+
this.timer = setInterval(() => {
123+
const value = Math.floor(Math.random() * 101);
124+
gauge.value = value;
125+
gauge.status = value >= 80 ? 'danger' : value >= 60 ? 'warning' : 'success';
126+
valueElement.textContent = value + '%';
127+
}, 1500);
128+
}
129+
130+
disconnectedCallback() {
131+
clearInterval(this.timer);
132+
}
133+
});
134+
}
135+
</script>
136+
`};
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
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, beforeEach, afterEach } from 'vitest';
6+
import { createFixture, removeFixture, elementIsStable } from '@internals/testing';
7+
import { runAxe } from '@internals/testing/axe';
8+
import { ProgressGauge } from '@nvidia-elements/core/progress-gauge';
9+
import '@nvidia-elements/core/progress-gauge/define.js';
10+
11+
describe(ProgressGauge.metadata.tag, () => {
12+
let fixture: HTMLElement;
13+
14+
beforeEach(async () => {
15+
fixture = await createFixture(html`
16+
<nve-progress-gauge aria-label="progress" value="0"></nve-progress-gauge>
17+
<nve-progress-gauge aria-label="progress" value="50"></nve-progress-gauge>
18+
<nve-progress-gauge aria-label="progress" status="warning" value="75"></nve-progress-gauge>
19+
<nve-progress-gauge aria-label="progress" container="half" status="success" value="100"></nve-progress-gauge>
20+
`);
21+
const elements = Array.from(fixture.querySelectorAll(ProgressGauge.metadata.tag)) as ProgressGauge[];
22+
await Promise.all(elements.map(gauge => elementIsStable(gauge)));
23+
});
24+
25+
afterEach(() => {
26+
removeFixture(fixture);
27+
});
28+
29+
it('should pass axe check', async () => {
30+
const results = await runAxe([ProgressGauge.metadata.tag]);
31+
expect(results.violations.length).toBe(0);
32+
});
33+
});

0 commit comments

Comments
 (0)