-
Notifications
You must be signed in to change notification settings - Fork 5
feat(core): progress gauge #154
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
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| // SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. | ||
| // SPDX-License-Identifier: Apache-2.0 | ||
|
|
||
| import { define } from '@nvidia-elements/core/internal'; | ||
| import { ProgressGauge } from '@nvidia-elements/core/progress-gauge'; | ||
|
|
||
| define(ProgressGauge); | ||
|
|
||
| declare global { | ||
| interface HTMLElementTagNameMap { | ||
| 'nve-progress-gauge': ProgressGauge; | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,4 @@ | ||
| // SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. | ||
| // SPDX-License-Identifier: Apache-2.0 | ||
|
|
||
| export * from './progress-gauge.js'; |
| Original file line number | Diff line number | Diff line change | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,134 @@ | ||||||||||
| /* SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. */ | ||||||||||
| /* SPDX-License-Identifier: Apache-2.0 */ | ||||||||||
|
|
||||||||||
| :host { | ||||||||||
| --color: var(--nve-sys-text-emphasis-color); | ||||||||||
| --background: var(--nve-sys-interaction-background); | ||||||||||
| --accent-color: var(--nve-sys-interaction-color); | ||||||||||
| --gauge-width: 12px; | ||||||||||
| --font-size: var(--nve-ref-font-size-400); | ||||||||||
| --gap: var(--nve-ref-space-xs); | ||||||||||
| --width: 128px; | ||||||||||
| --height: var(--width); | ||||||||||
| --_animation-duration: var(--nve-ref-animation-duration-250); | ||||||||||
|
|
||||||||||
| display: inline-block; | ||||||||||
| position: relative; | ||||||||||
| width: var(--width); | ||||||||||
| height: var(--height); | ||||||||||
| container-type: inline-size; | ||||||||||
| contain: content; | ||||||||||
| text-box: trim-both cap alphabetic; | ||||||||||
| } | ||||||||||
|
|
||||||||||
| :host([size='sm']) { | ||||||||||
| --font-size: var(--nve-ref-font-size-300); | ||||||||||
| --width: 96px; | ||||||||||
| } | ||||||||||
|
|
||||||||||
| :host([size='lg']) { | ||||||||||
| --font-size: var(--nve-ref-font-size-500); | ||||||||||
| --width: 160px; | ||||||||||
| } | ||||||||||
|
|
||||||||||
| [internal-host] { | ||||||||||
| display: grid; | ||||||||||
| place-items: center; | ||||||||||
| position: relative; | ||||||||||
| height: 100%; | ||||||||||
| } | ||||||||||
|
|
||||||||||
| :host([container='half']) { | ||||||||||
| --height: calc(var(--width) / 2); | ||||||||||
| } | ||||||||||
|
|
||||||||||
| :host([container='half']) [internal-host] { | ||||||||||
| place-items: end center; | ||||||||||
| } | ||||||||||
|
|
||||||||||
| svg { | ||||||||||
| position: absolute; | ||||||||||
| inset: 0; | ||||||||||
| width: 100%; | ||||||||||
| height: 100%; | ||||||||||
| overflow: visible; | ||||||||||
| } | ||||||||||
|
|
||||||||||
| path { | ||||||||||
| fill: none; | ||||||||||
| stroke-linecap: round; | ||||||||||
| stroke-width: var(--gauge-width); | ||||||||||
| } | ||||||||||
|
|
||||||||||
| path.background { | ||||||||||
| stroke: var(--background); | ||||||||||
| } | ||||||||||
|
|
||||||||||
| path.gauge { | ||||||||||
| animation: gauge-progress-in var(--_animation-duration) var(--nve-ref-animation-easing-100); | ||||||||||
| stroke: var(--accent-color); | ||||||||||
| stroke-dasharray: var(--_progress) 100; | ||||||||||
| transition: stroke-dasharray var(--_animation-duration) var(--nve-ref-animation-easing-100); | ||||||||||
| will-change: stroke-dasharray; | ||||||||||
| } | ||||||||||
|
|
||||||||||
| path.gauge[empty] { | ||||||||||
| animation: none; | ||||||||||
| stroke-linecap: butt; | ||||||||||
| } | ||||||||||
|
|
||||||||||
| slot { | ||||||||||
| display: flex; | ||||||||||
| flex-direction: column; | ||||||||||
| place-items: center; | ||||||||||
| justify-content: center; | ||||||||||
| height: 100%; | ||||||||||
| gap: var(--gap); | ||||||||||
| color: var(--color); | ||||||||||
| font-size: var(--font-size); | ||||||||||
| font-weight: var(--nve-ref-font-weight-medium); | ||||||||||
| } | ||||||||||
|
|
||||||||||
| :host([container='half']) slot { | ||||||||||
| transform: translateY(10cqw); | ||||||||||
| } | ||||||||||
|
|
||||||||||
| ::slotted(*) { | ||||||||||
| color: var(--color); | ||||||||||
| font-size: var(--font-size); | ||||||||||
| } | ||||||||||
|
Comment on lines
+96
to
+99
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win Do not override all consumer slotted typography.
Proposed fix-::slotted(*) {
- color: var(--color);
- font-size: var(--font-size);
-}📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||
|
|
||||||||||
| :host([status='success']) { | ||||||||||
| --accent-color: var(--nve-sys-support-success-emphasis-color); | ||||||||||
| --background: var(--nve-sys-support-success-muted-color); | ||||||||||
| } | ||||||||||
|
|
||||||||||
| :host([status='warning']) { | ||||||||||
| --accent-color: var(--nve-sys-support-warning-emphasis-color); | ||||||||||
| --background: var(--nve-sys-support-warning-muted-color); | ||||||||||
| } | ||||||||||
|
|
||||||||||
| :host([status='danger']) { | ||||||||||
| --accent-color: var(--nve-sys-support-danger-emphasis-color); | ||||||||||
| --background: var(--nve-sys-support-danger-muted-color); | ||||||||||
| } | ||||||||||
|
|
||||||||||
| :host([status='accent']) { | ||||||||||
| --accent-color: var(--nve-sys-accent-secondary-background); | ||||||||||
| } | ||||||||||
|
|
||||||||||
| @media (prefers-reduced-motion: reduce) { | ||||||||||
| :host { | ||||||||||
| --_animation-duration: 0s; | ||||||||||
| } | ||||||||||
| } | ||||||||||
|
|
||||||||||
| @keyframes gauge-progress-in { | ||||||||||
| from { | ||||||||||
| stroke-dasharray: 0 100; | ||||||||||
| } | ||||||||||
|
|
||||||||||
| to { | ||||||||||
| stroke-dasharray: var(--_progress) 100; | ||||||||||
| } | ||||||||||
| } | ||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,136 @@ | ||
| // SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. | ||
| // SPDX-License-Identifier: Apache-2.0 | ||
|
|
||
| import { html } from 'lit'; | ||
| import '@nvidia-elements/core/progress-gauge/define.js'; | ||
|
|
||
| export default { | ||
| title: 'Elements/Progress Gauge', | ||
| component: 'nve-progress-gauge', | ||
| }; | ||
|
|
||
| /** | ||
| * @summary 270-degree progress gauges for displaying system resource usage. | ||
| */ | ||
| export const Default = { | ||
| render: () => html` | ||
| <div nve-layout="row gap:sm"> | ||
| <nve-progress-gauge value="50">50%</nve-progress-gauge> | ||
| <nve-progress-gauge status="accent" value="66">66%</nve-progress-gauge> | ||
| </div> | ||
| `}; | ||
|
|
||
| /** | ||
| * @summary Container variants compare the default 270-degree gauge with the compact half gauge for telemetry layouts with tighter vertical space. | ||
| * @tags test-case | ||
| */ | ||
| export const Container = { | ||
| render: () => html` | ||
| <div nve-layout="row gap:sm align:vertical-center"> | ||
| <nve-progress-gauge status="accent" value="66">66%</nve-progress-gauge> | ||
| <nve-progress-gauge container="half" status="accent" value="66">66%</nve-progress-gauge> | ||
| </div> | ||
| `}; | ||
|
|
||
| /** | ||
| * @summary Gauges with values from 0% to 100% for displaying system resource usage. | ||
| * @tags test-case | ||
| */ | ||
| export const Values = { | ||
| render: () => html` | ||
| <div nve-layout="row gap:sm"> | ||
| <nve-progress-gauge value="0">0%</nve-progress-gauge> | ||
| <nve-progress-gauge value="33">33%</nve-progress-gauge> | ||
| <nve-progress-gauge value="66">66%</nve-progress-gauge> | ||
| <nve-progress-gauge value="100">100%</nve-progress-gauge> | ||
| </div> | ||
| `}; | ||
|
|
||
| /** | ||
| * @summary Progress gauges with custom max values for mission checkpoints, validation clips, and map tile processing. | ||
| * @tags test-case | ||
| */ | ||
| export const Max = { | ||
| render: () => html` | ||
| <div nve-layout="row gap:sm"> | ||
| <nve-progress-gauge status="accent" max="20" value="5">5/20</nve-progress-gauge> | ||
| <nve-progress-gauge max="20" value="10">10/20</nve-progress-gauge> | ||
| <nve-progress-gauge max="20" value="15">15/20</nve-progress-gauge> | ||
| </div> | ||
| `}; | ||
|
|
||
| /** | ||
| * @summary Progress gauges with accent, success, warning, and danger colors for autonomous system health and readiness signals. | ||
| * @tags test-case | ||
| */ | ||
| export const Status = { | ||
| render: () => html` | ||
| <div nve-layout="row gap:sm"> | ||
| <nve-progress-gauge value="50">50%</nve-progress-gauge> | ||
| <nve-progress-gauge status="accent" value="75">75%</nve-progress-gauge> | ||
| <nve-progress-gauge status="success" value="75">75%</nve-progress-gauge> | ||
| <nve-progress-gauge status="warning" value="75">2.1m</nve-progress-gauge> | ||
| <nve-progress-gauge status="danger" value="75">0Hz</nve-progress-gauge> | ||
| </div> | ||
| `}; | ||
|
|
||
| /** | ||
| * @summary Small progress gauge paired with route-solve text for compact autonomous vehicle task rows. | ||
| * @tags test-case | ||
| */ | ||
| export const WithText = { | ||
| render: () => html` | ||
| <div nve-layout="row gap:xs align:vertical-center" nve-text="medium"> | ||
| <nve-progress-gauge status="accent" size="sm" value="50" aria-labelledby="route-solve-label">2.4s</nve-progress-gauge> | ||
| <span id="route-solve-label">Route solve</span> | ||
| </div> | ||
| `}; | ||
|
|
||
| /** | ||
| * @summary Progress gauges in small, medium, and large sizes for dense robotics and autonomous vehicle dashboards. | ||
| * @tags test-case | ||
| */ | ||
| export const Sizing = { | ||
| render: () => html` | ||
| <div nve-layout="row gap:sm"> | ||
| <nve-progress-gauge size="sm" value="50">30Hz</nve-progress-gauge> | ||
| <nve-progress-gauge size="md" value="50">12Hz</nve-progress-gauge> | ||
| <nve-progress-gauge size="lg" value="50">84%</nve-progress-gauge> | ||
| </div> | ||
| `}; | ||
|
|
||
| /** | ||
| * @summary Use for displaying real-time system load and performance metrics. | ||
| * @tags pattern | ||
| */ | ||
| export const Dynamic = { | ||
| render: () => html` | ||
| <div nve-layout="row gap:sm"> | ||
| <progress-gauge-dynamic-example style="display: contents"> | ||
| <nve-progress-gauge status="success" value="0"> | ||
| <span>0%</span> | ||
| <span nve-text="body sm muted">GPU</span> | ||
| </nve-progress-gauge> | ||
| </progress-gauge-dynamic-example> | ||
| </div> | ||
| <script type="module"> | ||
| if (!customElements.get('progress-gauge-dynamic-example')) { | ||
| customElements.define('progress-gauge-dynamic-example', class extends HTMLElement { | ||
| connectedCallback() { | ||
| const gauge = this.querySelector('nve-progress-gauge'); | ||
| const valueElement = gauge.querySelector('span'); | ||
| this.timer = setInterval(() => { | ||
| const value = Math.floor(Math.random() * 101); | ||
| gauge.value = value; | ||
| gauge.status = value >= 80 ? 'danger' : value >= 60 ? 'warning' : 'success'; | ||
| valueElement.textContent = value + '%'; | ||
| }, 1500); | ||
| } | ||
|
|
||
| disconnectedCallback() { | ||
| clearInterval(this.timer); | ||
| } | ||
| }); | ||
| } | ||
| </script> | ||
|
coderabbitai[bot] marked this conversation as resolved.
|
||
| `}; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,33 @@ | ||
| // SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. | ||
| // SPDX-License-Identifier: Apache-2.0 | ||
|
|
||
| import { html } from 'lit'; | ||
| import { describe, expect, it, beforeEach, afterEach } from 'vitest'; | ||
| import { createFixture, removeFixture, elementIsStable } from '@internals/testing'; | ||
| import { runAxe } from '@internals/testing/axe'; | ||
| import { ProgressGauge } from '@nvidia-elements/core/progress-gauge'; | ||
| import '@nvidia-elements/core/progress-gauge/define.js'; | ||
|
|
||
| describe(ProgressGauge.metadata.tag, () => { | ||
| let fixture: HTMLElement; | ||
|
|
||
| beforeEach(async () => { | ||
| fixture = await createFixture(html` | ||
| <nve-progress-gauge aria-label="progress" value="0"></nve-progress-gauge> | ||
| <nve-progress-gauge aria-label="progress" value="50"></nve-progress-gauge> | ||
| <nve-progress-gauge aria-label="progress" status="warning" value="75"></nve-progress-gauge> | ||
| <nve-progress-gauge aria-label="progress" container="half" status="success" value="100"></nve-progress-gauge> | ||
| `); | ||
| const elements = Array.from(fixture.querySelectorAll(ProgressGauge.metadata.tag)) as ProgressGauge[]; | ||
| await Promise.all(elements.map(gauge => elementIsStable(gauge))); | ||
| }); | ||
|
coderabbitai[bot] marked this conversation as resolved.
|
||
|
|
||
| afterEach(() => { | ||
| removeFixture(fixture); | ||
| }); | ||
|
|
||
| it('should pass axe check', async () => { | ||
| const results = await runAxe([ProgressGauge.metadata.tag]); | ||
| expect(results.violations.length).toBe(0); | ||
| }); | ||
| }); | ||
Uh oh!
There was an error while loading. Please reload this page.