From ecc5a70c5a5fbb2946c7c6ff1e124660ea6104ba Mon Sep 17 00:00:00 2001 From: Steven Spriggs Date: Fri, 15 May 2026 12:40:36 -0400 Subject: [PATCH 1/8] feat(progress): port pf-v5-progress to pf-v6-progress Update progress bar element for PatternFly v6 with inline SVG status icons, InternalsController, `helper-text` `@slot`, `value-text` and `static-width` properties, `singleline` measure location, and v6 design tokens with CSS grid layout. Closes #3031 Assisted-By: Claude Opus 4.6 --- elements/pf-v6-progress/README.md | 13 + .../demo/failure-without-measure.html | 8 + elements/pf-v6-progress/demo/failure.html | 8 + elements/pf-v6-progress/demo/finite-step.html | 8 + elements/pf-v6-progress/demo/helper-text.html | 10 + elements/pf-v6-progress/demo/index.html | 8 + .../pf-v6-progress/demo/inside-success.html | 8 + .../pf-v6-progress/demo/inside-warning.html | 8 + elements/pf-v6-progress/demo/inside.html | 8 + elements/pf-v6-progress/demo/large.html | 8 + .../pf-v6-progress/demo/outside-failure.html | 8 + .../demo/outside-static-width.html | 20 + elements/pf-v6-progress/demo/outside.html | 8 + .../demo/progress-step-instruction.html | 8 + elements/pf-v6-progress/demo/singleline.html | 8 + elements/pf-v6-progress/demo/small.html | 8 + elements/pf-v6-progress/demo/success.html | 8 + .../demo/truncate-description.html | 12 + elements/pf-v6-progress/demo/warning.html | 8 + .../pf-v6-progress/demo/without-measure.html | 8 + elements/pf-v6-progress/pf-v6-progress.css | 271 ++++++++++++++ elements/pf-v6-progress/pf-v6-progress.ts | 162 ++++++++ .../pf-v6-progress/test/pf-progress.e2e.ts | 25 ++ .../pf-v6-progress/test/pf-progress.spec.ts | 351 ++++++++++++++++++ 24 files changed, 992 insertions(+) create mode 100644 elements/pf-v6-progress/README.md create mode 100644 elements/pf-v6-progress/demo/failure-without-measure.html create mode 100644 elements/pf-v6-progress/demo/failure.html create mode 100644 elements/pf-v6-progress/demo/finite-step.html create mode 100644 elements/pf-v6-progress/demo/helper-text.html create mode 100644 elements/pf-v6-progress/demo/index.html create mode 100644 elements/pf-v6-progress/demo/inside-success.html create mode 100644 elements/pf-v6-progress/demo/inside-warning.html create mode 100644 elements/pf-v6-progress/demo/inside.html create mode 100644 elements/pf-v6-progress/demo/large.html create mode 100644 elements/pf-v6-progress/demo/outside-failure.html create mode 100644 elements/pf-v6-progress/demo/outside-static-width.html create mode 100644 elements/pf-v6-progress/demo/outside.html create mode 100644 elements/pf-v6-progress/demo/progress-step-instruction.html create mode 100644 elements/pf-v6-progress/demo/singleline.html create mode 100644 elements/pf-v6-progress/demo/small.html create mode 100644 elements/pf-v6-progress/demo/success.html create mode 100644 elements/pf-v6-progress/demo/truncate-description.html create mode 100644 elements/pf-v6-progress/demo/warning.html create mode 100644 elements/pf-v6-progress/demo/without-measure.html create mode 100644 elements/pf-v6-progress/pf-v6-progress.css create mode 100644 elements/pf-v6-progress/pf-v6-progress.ts create mode 100644 elements/pf-v6-progress/test/pf-progress.e2e.ts create mode 100644 elements/pf-v6-progress/test/pf-progress.spec.ts diff --git a/elements/pf-v6-progress/README.md b/elements/pf-v6-progress/README.md new file mode 100644 index 0000000000..111363800b --- /dev/null +++ b/elements/pf-v6-progress/README.md @@ -0,0 +1,13 @@ +# Progress + +A progress bar gives the user a visual representation of their completion status of an ongoing process or task. + +## Usage + +```html + + + +``` diff --git a/elements/pf-v6-progress/demo/failure-without-measure.html b/elements/pf-v6-progress/demo/failure-without-measure.html new file mode 100644 index 0000000000..cea186f463 --- /dev/null +++ b/elements/pf-v6-progress/demo/failure-without-measure.html @@ -0,0 +1,8 @@ +--- +description: Failure (danger) variant without the percentage measure displayed. +--- + + + diff --git a/elements/pf-v6-progress/demo/failure.html b/elements/pf-v6-progress/demo/failure.html new file mode 100644 index 0000000000..d735f2c663 --- /dev/null +++ b/elements/pf-v6-progress/demo/failure.html @@ -0,0 +1,8 @@ +--- +description: Failure (danger) variant progress bar. +--- + + + diff --git a/elements/pf-v6-progress/demo/finite-step.html b/elements/pf-v6-progress/demo/finite-step.html new file mode 100644 index 0000000000..3353a04ab2 --- /dev/null +++ b/elements/pf-v6-progress/demo/finite-step.html @@ -0,0 +1,8 @@ +--- +description: Finite step progress with custom max value and descriptive value text. +--- + + + diff --git a/elements/pf-v6-progress/demo/helper-text.html b/elements/pf-v6-progress/demo/helper-text.html new file mode 100644 index 0000000000..005e6a2bba --- /dev/null +++ b/elements/pf-v6-progress/demo/helper-text.html @@ -0,0 +1,10 @@ +--- +description: Progress bar with helper text displayed below the bar. +--- + + With helper text to provide additional context. + + + diff --git a/elements/pf-v6-progress/demo/index.html b/elements/pf-v6-progress/demo/index.html new file mode 100644 index 0000000000..a8c90a783c --- /dev/null +++ b/elements/pf-v6-progress/demo/index.html @@ -0,0 +1,8 @@ +--- +description: Simple progress bar at 33%. +--- + + + diff --git a/elements/pf-v6-progress/demo/inside-success.html b/elements/pf-v6-progress/demo/inside-success.html new file mode 100644 index 0000000000..9068f6b736 --- /dev/null +++ b/elements/pf-v6-progress/demo/inside-success.html @@ -0,0 +1,8 @@ +--- +description: Success variant with measure displayed inside the bar. +--- + + + diff --git a/elements/pf-v6-progress/demo/inside-warning.html b/elements/pf-v6-progress/demo/inside-warning.html new file mode 100644 index 0000000000..ee7a294431 --- /dev/null +++ b/elements/pf-v6-progress/demo/inside-warning.html @@ -0,0 +1,8 @@ +--- +description: Warning variant with measure displayed inside the bar. +--- + + + diff --git a/elements/pf-v6-progress/demo/inside.html b/elements/pf-v6-progress/demo/inside.html new file mode 100644 index 0000000000..86d5a7a2d0 --- /dev/null +++ b/elements/pf-v6-progress/demo/inside.html @@ -0,0 +1,8 @@ +--- +description: Progress bar with measure displayed inside the indicator. Requires large size. +--- + + + diff --git a/elements/pf-v6-progress/demo/large.html b/elements/pf-v6-progress/demo/large.html new file mode 100644 index 0000000000..4ce3064519 --- /dev/null +++ b/elements/pf-v6-progress/demo/large.html @@ -0,0 +1,8 @@ +--- +description: Large size progress bar. +--- + + + diff --git a/elements/pf-v6-progress/demo/outside-failure.html b/elements/pf-v6-progress/demo/outside-failure.html new file mode 100644 index 0000000000..56427b493a --- /dev/null +++ b/elements/pf-v6-progress/demo/outside-failure.html @@ -0,0 +1,8 @@ +--- +description: Failure (danger) variant with measure displayed outside the bar. +--- + + + diff --git a/elements/pf-v6-progress/demo/outside-static-width.html b/elements/pf-v6-progress/demo/outside-static-width.html new file mode 100644 index 0000000000..9b762b51a0 --- /dev/null +++ b/elements/pf-v6-progress/demo/outside-static-width.html @@ -0,0 +1,20 @@ +--- +description: Progress bar with outside measure using a static minimum width for alignment across multiple bars. +--- + + +
+ + + +
+ + diff --git a/elements/pf-v6-progress/demo/outside.html b/elements/pf-v6-progress/demo/outside.html new file mode 100644 index 0000000000..8a778a3094 --- /dev/null +++ b/elements/pf-v6-progress/demo/outside.html @@ -0,0 +1,8 @@ +--- +description: Progress bar with measure displayed outside and to the right of the bar. +--- + + + diff --git a/elements/pf-v6-progress/demo/progress-step-instruction.html b/elements/pf-v6-progress/demo/progress-step-instruction.html new file mode 100644 index 0000000000..21a9c2e5e4 --- /dev/null +++ b/elements/pf-v6-progress/demo/progress-step-instruction.html @@ -0,0 +1,8 @@ +--- +description: Step instruction progress with value text describing the current step. +--- + + + diff --git a/elements/pf-v6-progress/demo/singleline.html b/elements/pf-v6-progress/demo/singleline.html new file mode 100644 index 0000000000..c371767fa0 --- /dev/null +++ b/elements/pf-v6-progress/demo/singleline.html @@ -0,0 +1,8 @@ +--- +description: Single line progress bar without a description row. +--- + + + diff --git a/elements/pf-v6-progress/demo/small.html b/elements/pf-v6-progress/demo/small.html new file mode 100644 index 0000000000..73e1cf74d4 --- /dev/null +++ b/elements/pf-v6-progress/demo/small.html @@ -0,0 +1,8 @@ +--- +description: Small size progress bar. +--- + + + diff --git a/elements/pf-v6-progress/demo/success.html b/elements/pf-v6-progress/demo/success.html new file mode 100644 index 0000000000..073ab30eb8 --- /dev/null +++ b/elements/pf-v6-progress/demo/success.html @@ -0,0 +1,8 @@ +--- +description: Success variant progress bar. +--- + + + diff --git a/elements/pf-v6-progress/demo/truncate-description.html b/elements/pf-v6-progress/demo/truncate-description.html new file mode 100644 index 0000000000..39d37a3925 --- /dev/null +++ b/elements/pf-v6-progress/demo/truncate-description.html @@ -0,0 +1,12 @@ +--- +description: Progress bar with a long description that is truncated with an ellipsis. +--- +
+ +
+ + diff --git a/elements/pf-v6-progress/demo/warning.html b/elements/pf-v6-progress/demo/warning.html new file mode 100644 index 0000000000..9146517f48 --- /dev/null +++ b/elements/pf-v6-progress/demo/warning.html @@ -0,0 +1,8 @@ +--- +description: Warning variant progress bar. +--- + + + diff --git a/elements/pf-v6-progress/demo/without-measure.html b/elements/pf-v6-progress/demo/without-measure.html new file mode 100644 index 0000000000..fe008d0cef --- /dev/null +++ b/elements/pf-v6-progress/demo/without-measure.html @@ -0,0 +1,8 @@ +--- +description: Progress bar without the percentage measure displayed. +--- + + + diff --git a/elements/pf-v6-progress/pf-v6-progress.css b/elements/pf-v6-progress/pf-v6-progress.css new file mode 100644 index 0000000000..aba32f49d5 --- /dev/null +++ b/elements/pf-v6-progress/pf-v6-progress.css @@ -0,0 +1,271 @@ +:host { + display: grid; + grid-template-rows: 1fr auto; + grid-template-columns: auto auto; + gap: var(--pf-v6-c-progress--GridGap, var(--pf-t--global--spacer--md, 1rem)); + align-items: end; +} + +[hidden] { + display: none !important; +} + +/* Size modifiers */ + +:host([size="sm"]) { + --_bar-height: var(--pf-v6-c-progress--m-sm__bar--Height, + var(--pf-t--global--spacer--sm, 0.5rem)); +} + +:host([size="sm"]) #measure { + font-size: var(--pf-v6-c-progress--m-sm__measure--FontSize, + var(--pf-t--global--font--size--body--sm, 0.75rem)); +} + +:host([size="lg"]) { + --_bar-height: var(--pf-v6-c-progress--m-lg__bar--Height, + var(--pf-t--global--spacer--lg, 1.5rem)); +} + +/* Measure location modifiers */ + +:host([measure-location="outside"]), +:host([measure-location="singleline"]) { + grid-template-columns: 1fr fit-content(50%); +} + +:host([measure-location="outside"]) #description { + grid-column: 1 / 3; +} + +:host([measure-location="outside"]) #status { + grid-row: 2 / 3; + grid-column: 2 / 3; + align-self: center; +} + +:host([measure-location="outside"]) #measure { + display: inline-block; + font-size: var(--pf-v6-c-progress--m-outside__measure--FontSize, + var(--pf-t--global--font--size--sm, 0.875rem)); +} + +:host([measure-location="outside"]) #bar { + grid-column: 1 / 2; +} + +:host([measure-location="singleline"]) { + grid-template-rows: 1fr; +} + +:host([measure-location="singleline"]) #description { + display: none; +} + +:host([measure-location="singleline"]) #bar { + grid-row: 1 / 2; + grid-column: 1 / 2; +} + +:host([measure-location="singleline"]) #status { + grid-row: 1 / 2; + grid-column: 2 / 3; +} + +:host([measure-location="inside"]) #indicator { + display: flex; + align-items: center; + justify-content: center; + min-width: var(--pf-v6-c-progress--m-inside__indicator--MinWidth, + var(--pf-t--global--spacer--xl, 2rem)); +} + +:host([measure-location="inside"]) #measure { + font-size: var(--pf-v6-c-progress--m-inside__measure--FontSize, + var(--pf-t--global--font--size--sm, 0.875rem)); + color: var(--_inside-measure-color, + var(--pf-v6-c-progress--m-inside__measure--Color, + var(--pf-t--global--text--color--on-brand--default, #fff))); + text-align: center; +} + +/* Variant modifiers */ + +:host([variant="success"]) { + --_indicator-bg: var(--pf-v6-c-progress--m-success__indicator--BackgroundColor, + var(--pf-t--global--color--status--success--default, #3e8635)); + --_status-icon-color: var(--pf-v6-c-progress--m-success__status-icon--Color, + var(--pf-t--global--icon--color--status--success--default, #3e8635)); + --_inside-measure-color: var(--pf-v6-c-progress--m-success--m-inside__measure--Color, + var(--pf-t--global--text--color--status--on-success--default, #fff)); +} + +:host([variant="warning"]) { + --_indicator-bg: var(--pf-v6-c-progress--m-warning__indicator--BackgroundColor, + var(--pf-t--global--color--status--warning--default, #f0ab00)); + --_status-icon-color: var(--pf-v6-c-progress--m-warning__status-icon--Color, + var(--pf-t--global--icon--color--status--warning--default, #f0ab00)); + --_inside-measure-color: var(--pf-v6-c-progress--m-warning--m-inside__measure--Color, + var(--pf-t--global--text--color--status--on-warning--default, #151515)); +} + +:host([variant="danger"]) { + --_indicator-bg: var(--pf-v6-c-progress--m-danger__indicator--BackgroundColor, + var(--pf-t--global--color--status--danger--default, #c9190b)); + --_status-icon-color: var(--pf-v6-c-progress--m-danger__status-icon--Color, + var(--pf-t--global--icon--color--status--danger--default, #c9190b)); + --_inside-measure-color: var(--pf-v6-c-progress--m-danger--m-inside__measure--Color, + var(--pf-t--global--text--color--status--on-danger--default, #fff)); +} + +/* Danger animations */ + +@media (prefers-reduced-motion: no-preference) { + :host([variant="danger"]) #bar { + animation: danger-jiggle + var(--pf-t--global--motion--duration--fade--default, 0.3s) + var(--pf-t--global--motion--timing-function--default, cubic-bezier(0.25, 0.1, 0.25, 1)) + both; + } +} + +:host([variant="danger"]) #status-icon { + animation: fade-in + var(--pf-t--global--motion--duration--fade--default, 0.3s) + var(--pf-t--global--motion--timing-function--default, cubic-bezier(0.25, 0.1, 0.25, 1)); +} + +/* Description truncation */ + +:host([description-truncated]) #description { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +/* Static width measure */ + +:host([static-width]) #measure { + min-width: var(--pf-v6-c-progress__measure--m-static-width--MinWidth, 4.5ch); + text-align: start; +} + +/* Elements */ + +#description { + grid-column: 1 / 2; + word-break: break-word; +} + +#status { + display: flex; + grid-row: 1 / 2; + grid-column: 2 / 3; + gap: var(--pf-v6-c-progress__status--Gap, + var(--pf-t--global--spacer--sm, 0.5rem)); + align-items: flex-start; + justify-content: flex-end; + text-align: end; + word-break: break-word; +} + +#status-icon { + color: var(--_status-icon-color, + var(--pf-v6-c-progress__status-icon--Color, + var(--pf-t--global--icon--color--regular, #151515))); +} + +#bar { + position: relative; + grid-row: 2 / 3; + grid-column: 1 / 3; + align-self: center; + height: var(--_bar-height, + var(--pf-v6-c-progress__bar--Height, + var(--pf-t--global--spacer--md, 1rem))); + overflow: hidden; + background-color: var(--pf-v6-c-progress__bar--BackgroundColor, + var(--pf-t--global--color--nonstatus--gray--default, #f0f0f0)); + border-radius: var(--pf-v6-c-progress__bar--BorderRadius, + var(--pf-t--global--border--radius--medium, 6px)); + + &::before { + position: absolute; + inset: 0; + pointer-events: none; + content: ''; + border: var(--pf-v6-c-progress__bar--BorderWidth, + var(--pf-t--global--border--width--high-contrast--regular, 0)) + solid + var(--pf-v6-c-progress__bar--BorderColor, + var(--pf-t--global--border--color--high-contrast, transparent)); + border-radius: inherit; + } +} + +#indicator { + position: absolute; + inset-block-start: 0; + inset-inline-start: 0; + height: var(--_bar-height, + var(--pf-v6-c-progress__indicator--Height, + var(--pf-v6-c-progress__bar--Height, + var(--pf-t--global--spacer--md, 1rem)))); + background-color: var(--_indicator-bg, + var(--pf-v6-c-progress__indicator--BackgroundColor, + var(--pf-t--global--color--brand--default, #0066cc))); + + &::before { + position: absolute; + inset: 0; + content: ''; + border: var(--pf-v6-c-progress__indicator--BorderWidth, + var(--pf-t--global--border--width--high-contrast--extra-strong, 0)) + solid + var(--pf-v6-c-progress__indicator--BorderColor, transparent); + border-radius: var(--pf-v6-c-progress__bar--BorderRadius, + var(--pf-t--global--border--radius--medium, 6px)); + } +} + +#helper-text { + grid-row: 3 / 4; + grid-column: 1 / 3; + margin-block-start: var(--pf-v6-c-progress__helper-text--MarginBlockStart, + calc(var(--pf-t--global--spacer--sm, 0.5rem) - var(--pf-v6-c-progress--GridGap, + var(--pf-t--global--spacer--md, 1rem)))); +} + +#measure { + font-variant-numeric: tabular-nums; +} + +svg { + width: 1em; + height: 1em; + fill: currentColor; +} + +@keyframes danger-jiggle { + 33% { + translate: -2px; + } + + 66% { + translate: 3px; + } + + 100% { + translate: 0; + } +} + +@keyframes fade-in { + from { + opacity: 0; + } + + to { + opacity: 1; + } +} diff --git a/elements/pf-v6-progress/pf-v6-progress.ts b/elements/pf-v6-progress/pf-v6-progress.ts new file mode 100644 index 0000000000..cc54d4b518 --- /dev/null +++ b/elements/pf-v6-progress/pf-v6-progress.ts @@ -0,0 +1,162 @@ +import type { PropertyValues, TemplateResult } from 'lit'; +import { LitElement, html, nothing } from 'lit'; +import { customElement } from 'lit/decorators/custom-element.js'; +import { property } from 'lit/decorators/property.js'; +import { ifDefined } from 'lit/directives/if-defined.js'; +import { styleMap } from 'lit/directives/style-map.js'; + +import { InternalsController } from '@patternfly/pfe-core/controllers/internals-controller.js'; + +import styles from './pf-v6-progress.css'; + +export type ProgressSize = 'sm' | 'lg'; +export type ProgressMeasureLocation = 'outside' | 'inside' | 'none' | 'singleline'; +export type ProgressVariant = 'success' | 'danger' | 'warning'; + +const checkCircleIcon = html``; +const triangleExclamationIcon = html``; +const circleExclamationIcon = html``; + +const VARIANT_ICONS = new Map([ + ['success', checkCircleIcon], + ['warning', triangleExclamationIcon], + ['danger', circleExclamationIcon], +]); + +/** + * A progress bar gives the user a visual representation of their completion + * status of an ongoing process or task. + * @summary Display completion status of ongoing process or task. + * @slot helper-text - Helper text displayed below the progress bar. + * @cssprop {} --pf-v6-c-progress__bar--BackgroundColor - Background color of the progress bar track + * @cssprop {} --pf-v6-c-progress__indicator--BackgroundColor - Background color of the progress indicator + * @cssprop {} --pf-v6-c-progress__bar--Height - Height of the progress bar + * @cssprop {} --pf-v6-c-progress__status-icon--Color - Color of the status icon + * @cssprop {} --pf-v6-c-progress--GridGap - Gap between progress bar grid rows + */ +@customElement('pf-v6-progress') +export class PfV6Progress extends LitElement { + static readonly styles: CSSStyleSheet[] = [styles]; + + /** Represents the value of the progress bar */ + @property({ reflect: true, type: Number }) value = 0; + + /** Description (title) above the progress bar */ + @property() description?: string; + + /** Indicate whether to truncate the string description (title) */ + @property({ + type: Boolean, + reflect: true, + attribute: 'description-truncated', + }) descriptionTruncated = false; + + /** Maximum value for the progress bar */ + @property({ type: Number }) max = 100; + + /** Minimum value for the progress bar */ + @property({ type: Number }) min = 0; + + /** Size of the progress bar (height) */ + @property({ reflect: true }) size?: ProgressSize; + + /** Where the percentage will be displayed with the progress element */ + @property({ + reflect: true, + attribute: 'measure-location', + }) measureLocation?: ProgressMeasureLocation; + + /** Variant of the progress bar */ + @property({ reflect: true }) variant?: ProgressVariant; + + /** Custom text for aria-valuetext, used for finite step and step instruction displays */ + @property({ attribute: 'value-text' }) valueText?: string; + + /** When true, applies a fixed minimum width to the measure display for visual alignment */ + @property({ + type: Boolean, + reflect: true, + attribute: 'static-width', + }) staticWidth = false; + + #internals = InternalsController.of(this); + + #hasHelperText = false; + + get #calculatedPercentage(): number { + const { value, min, max } = this; + const percentage = Math.round((value - min) / (max - min) * 100); + if (Number.isNaN(percentage) || percentage < 0) { + return 0; + } + return Math.min(percentage, 100); + } + + get #displayText(): string { + return this.valueText ?? `${this.#calculatedPercentage}%`; + } + + get #icon(): TemplateResult | typeof nothing { + return VARIANT_ICONS.get(this.variant!) ?? nothing; + } + + override willUpdate(changed: PropertyValues): void { + if (changed.has('value') || changed.has('min') || changed.has('max')) { + this.#internals.ariaValueNow = this.#calculatedPercentage.toString(); + } + } + + override render(): TemplateResult<1> { + const pct = this.#calculatedPercentage; + const displayText = this.#displayText; + const icon = this.#icon; + const noMeasure = this.measureLocation === 'none'; + const inside = this.measureLocation === 'inside'; + const hasDescription = this.description != null; + const hasIcon = this.variant != null; + + return html` +
${this.description ?? ''}
+ + + +
+
+ ${inside && !noMeasure ? html`${displayText}` : nothing} +
+
+ +
+ +
+ `; + } + + #onHelperTextSlotchange(event: Event) { + const slot = event.currentTarget as HTMLSlotElement; + this.#hasHelperText = slot.assignedNodes().length > 0; + this.requestUpdate(); + } +} + +declare global { + interface HTMLElementTagNameMap { + 'pf-v6-progress': PfV6Progress; + } +} diff --git a/elements/pf-v6-progress/test/pf-progress.e2e.ts b/elements/pf-v6-progress/test/pf-progress.e2e.ts new file mode 100644 index 0000000000..261d6b0a59 --- /dev/null +++ b/elements/pf-v6-progress/test/pf-progress.e2e.ts @@ -0,0 +1,25 @@ +import { test } from '@playwright/test'; +import { PfeDemoPage } from '@patternfly/pfe-tools/test/playwright/PfeDemoPage.js'; +import { SSRPage } from '@patternfly/pfe-tools/test/playwright/SSRPage.js'; + +const tagName = 'pf-v6-progress'; + +test.describe(tagName, () => { + test('snapshot', async ({ page }) => { + const componentPage = new PfeDemoPage(page, tagName); + await componentPage.navigate(); + await componentPage.snapshot(); + }); + + test('ssr', async ({ browser }) => { + const fixture = new SSRPage({ + tagName, + browser, + demoDir: new URL('../demo/', import.meta.url), + importSpecifiers: [ + `@patternfly/elements/${tagName}/${tagName}.js`, + ], + }); + await fixture.snapshots(); + }); +}); diff --git a/elements/pf-v6-progress/test/pf-progress.spec.ts b/elements/pf-v6-progress/test/pf-progress.spec.ts new file mode 100644 index 0000000000..9ec2d47474 --- /dev/null +++ b/elements/pf-v6-progress/test/pf-progress.spec.ts @@ -0,0 +1,351 @@ +import { expect, html } from '@open-wc/testing'; +import { createFixture } from '@patternfly/pfe-tools/test/create-fixture.js'; +import { a11ySnapshot } from '@patternfly/pfe-tools/test/a11y-snapshot.js'; + +import { PfV6Progress } from '../pf-v6-progress.js'; + +describe('', function() { + it('imperatively instantiates', function() { + expect(document.createElement('pf-v6-progress')).to.be.an.instanceof(PfV6Progress); + }); + + describe('simply instantiating', function() { + let element: PfV6Progress; + + beforeEach(async function() { + element = await createFixture(html``); + }); + + it('should upgrade', function() { + const klass = customElements.get('pf-v6-progress'); + expect(element) + .to.be.an.instanceOf(klass) + .and + .to.be.an.instanceOf(PfV6Progress); + }); + }); + + describe('with default values', function() { + let element: PfV6Progress; + + beforeEach(async function() { + element = await createFixture(html` + + `); + }); + + it('should be accessible', async function() { + await expect(element).to.be.accessible(); + }); + + it('should have value of 33', function() { + expect(element.value).to.equal(33); + }); + + it('should have description of "Title"', function() { + expect(element.description).to.equal('Title'); + }); + + it('should have progressbar role in ax tree', async function() { + const snapshot = await a11ySnapshot(); + expect(snapshot).to.axContainRole('progressbar'); + }); + + it('should have aria-valuenow of 33 in ax tree', async function() { + const snapshot = await a11ySnapshot(); + expect(snapshot).to.axContainQuery({ role: 'progressbar', value: 33 }); + }); + }); + + describe('with value and max', function() { + let element: PfV6Progress; + + beforeEach(async function() { + element = await createFixture(html` + + `); + }); + + it('should be accessible', async function() { + await expect(element).to.be.accessible(); + }); + }); + + describe('with min and max', function() { + let element: PfV6Progress; + + beforeEach(async function() { + element = await createFixture(html` + + `); + }); + + it('should be accessible', async function() { + await expect(element).to.be.accessible(); + }); + }); + + describe('with value-text', function() { + let element: PfV6Progress; + + beforeEach(async function() { + element = await createFixture(html` + + `); + }); + + it('should have valueText property', function() { + expect(element.valueText).to.equal('2 of 5 units'); + }); + + it('should be accessible', async function() { + await expect(element).to.be.accessible(); + }); + }); + + describe('with size="sm"', function() { + let element: PfV6Progress; + + beforeEach(async function() { + element = await createFixture(html` + + `); + }); + + it('should reflect size attribute', function() { + expect(element.getAttribute('size')).to.equal('sm'); + }); + + it('should be accessible', async function() { + await expect(element).to.be.accessible(); + }); + }); + + describe('with size="lg"', function() { + let element: PfV6Progress; + + beforeEach(async function() { + element = await createFixture(html` + + `); + }); + + it('should reflect size attribute', function() { + expect(element.getAttribute('size')).to.equal('lg'); + }); + }); + + describe('with measure-location="outside"', function() { + let element: PfV6Progress; + + beforeEach(async function() { + element = await createFixture(html` + + `); + }); + + it('should reflect attribute', function() { + expect(element.getAttribute('measure-location')).to.equal('outside'); + }); + + it('should be accessible', async function() { + await expect(element).to.be.accessible(); + }); + }); + + describe('with measure-location="inside"', function() { + let element: PfV6Progress; + + beforeEach(async function() { + element = await createFixture(html` + + `); + }); + + it('should reflect attribute', function() { + expect(element.getAttribute('measure-location')).to.equal('inside'); + }); + + it('should be accessible', async function() { + await expect(element).to.be.accessible(); + }); + }); + + describe('with measure-location="none"', function() { + let element: PfV6Progress; + + beforeEach(async function() { + element = await createFixture(html` + + `); + }); + + it('should be accessible', async function() { + await expect(element).to.be.accessible(); + }); + }); + + describe('with measure-location="singleline"', function() { + let element: PfV6Progress; + + beforeEach(async function() { + element = await createFixture(html` + + `); + }); + + it('should reflect attribute', function() { + expect(element.getAttribute('measure-location')).to.equal('singleline'); + }); + + it('should be accessible', async function() { + await expect(element).to.be.accessible(); + }); + }); + + describe('with variant="success"', function() { + let element: PfV6Progress; + + beforeEach(async function() { + element = await createFixture(html` + + `); + }); + + it('should reflect variant attribute', function() { + expect(element.getAttribute('variant')).to.equal('success'); + }); + + it('should be accessible', async function() { + await expect(element).to.be.accessible(); + }); + }); + + describe('with variant="warning"', function() { + let element: PfV6Progress; + + beforeEach(async function() { + element = await createFixture(html` + + `); + }); + + it('should be accessible', async function() { + await expect(element).to.be.accessible(); + }); + }); + + describe('with variant="danger"', function() { + let element: PfV6Progress; + + beforeEach(async function() { + element = await createFixture(html` + + `); + }); + + it('should be accessible', async function() { + await expect(element).to.be.accessible(); + }); + }); + + describe('with description-truncated', function() { + let element: PfV6Progress; + + beforeEach(async function() { + element = await createFixture(html` + + `); + }); + + it('should reflect attribute', function() { + expect(element.descriptionTruncated).to.be.true; + }); + + it('should be accessible', async function() { + await expect(element).to.be.accessible(); + }); + }); + + describe('with static-width', function() { + let element: PfV6Progress; + + beforeEach(async function() { + element = await createFixture(html` + + `); + }); + + it('should reflect attribute', function() { + expect(element.staticWidth).to.be.true; + }); + }); + + describe('with helper-text slot', function() { + let element: PfV6Progress; + + beforeEach(async function() { + element = await createFixture(html` + + Helper text content + + `); + }); + + it('should be accessible', async function() { + await expect(element).to.be.accessible(); + }); + }); + + describe('edge cases', function() { + describe('value below min', function() { + let element: PfV6Progress; + + beforeEach(async function() { + element = await createFixture(html` + + `); + }); + + it('should be accessible', async function() { + await expect(element).to.be.accessible(); + }); + }); + + describe('value above max', function() { + let element: PfV6Progress; + + beforeEach(async function() { + element = await createFixture(html` + + `); + }); + + it('should be accessible', async function() { + await expect(element).to.be.accessible(); + }); + }); + }); + + describe('updating value dynamically', function() { + let element: PfV6Progress; + + beforeEach(async function() { + element = await createFixture(html` + + `); + element.value = 75; + await element.updateComplete; + }); + + it('should update progressbar value in ax tree', async function() { + const snapshot = await a11ySnapshot(); + expect(snapshot).to.axContainQuery({ role: 'progressbar', value: 75 }); + }); + }); +}); From ac4e527ef23de22afa093defa81e1396bd454b92 Mon Sep 17 00:00:00 2001 From: Steven Spriggs Date: Fri, 15 May 2026 13:13:18 -0400 Subject: [PATCH 2/8] fix(progress): address review findings from a11y, API, and demo audits Move progressbar role and all ARIA to host via InternalsController, fix aria-valuemax to always be 100, remove unnecessary value reflection, guard fade-in animation with prefers-reduced-motion, rename failure demos to danger, and inline demo wrapper styles. Assisted-By: Claude Opus 4.6 --- ...asure.html => danger-without-measure.html} | 2 +- .../demo/{failure.html => danger.html} | 2 +- ...tside-failure.html => outside-danger.html} | 2 +- .../demo/outside-static-width.html | 10 +-------- .../demo/truncate-description.html | 9 ++++---- elements/pf-v6-progress/pf-v6-progress.css | 10 ++++----- elements/pf-v6-progress/pf-v6-progress.ts | 22 +++++++++---------- 7 files changed, 24 insertions(+), 33 deletions(-) rename elements/pf-v6-progress/demo/{failure-without-measure.html => danger-without-measure.html} (72%) rename elements/pf-v6-progress/demo/{failure.html => danger.html} (78%) rename elements/pf-v6-progress/demo/{outside-failure.html => outside-danger.html} (73%) diff --git a/elements/pf-v6-progress/demo/failure-without-measure.html b/elements/pf-v6-progress/demo/danger-without-measure.html similarity index 72% rename from elements/pf-v6-progress/demo/failure-without-measure.html rename to elements/pf-v6-progress/demo/danger-without-measure.html index cea186f463..92420ae30f 100644 --- a/elements/pf-v6-progress/demo/failure-without-measure.html +++ b/elements/pf-v6-progress/demo/danger-without-measure.html @@ -1,5 +1,5 @@ --- -description: Failure (danger) variant without the percentage measure displayed. +description: Danger variant without the percentage measure displayed. --- diff --git a/elements/pf-v6-progress/demo/failure.html b/elements/pf-v6-progress/demo/danger.html similarity index 78% rename from elements/pf-v6-progress/demo/failure.html rename to elements/pf-v6-progress/demo/danger.html index d735f2c663..8113e988d2 100644 --- a/elements/pf-v6-progress/demo/failure.html +++ b/elements/pf-v6-progress/demo/danger.html @@ -1,5 +1,5 @@ --- -description: Failure (danger) variant progress bar. +description: Danger variant progress bar. --- diff --git a/elements/pf-v6-progress/demo/outside-failure.html b/elements/pf-v6-progress/demo/outside-danger.html similarity index 73% rename from elements/pf-v6-progress/demo/outside-failure.html rename to elements/pf-v6-progress/demo/outside-danger.html index 56427b493a..6788dc46a7 100644 --- a/elements/pf-v6-progress/demo/outside-failure.html +++ b/elements/pf-v6-progress/demo/outside-danger.html @@ -1,5 +1,5 @@ --- -description: Failure (danger) variant with measure displayed outside the bar. +description: Danger variant with measure displayed outside the bar. --- diff --git a/elements/pf-v6-progress/demo/outside-static-width.html b/elements/pf-v6-progress/demo/outside-static-width.html index 9b762b51a0..a92f6eb864 100644 --- a/elements/pf-v6-progress/demo/outside-static-width.html +++ b/elements/pf-v6-progress/demo/outside-static-width.html @@ -1,15 +1,7 @@ --- description: Progress bar with outside measure using a static minimum width for alignment across multiple bars. --- - - -
+
diff --git a/elements/pf-v6-progress/demo/truncate-description.html b/elements/pf-v6-progress/demo/truncate-description.html index 39d37a3925..a7dc5872c7 100644 --- a/elements/pf-v6-progress/demo/truncate-description.html +++ b/elements/pf-v6-progress/demo/truncate-description.html @@ -1,11 +1,10 @@ --- description: Progress bar with a long description that is truncated with an ellipsis. --- -
- -
+ -``` - -Or, if you are using [NPM](https://npm.im), install it - -```bash -npm install @patternfly/elements -``` - -Then once installed, import it to your application: - -```js -import '@patternfly/elements/pf-v5-progress/pf-v5-progress.js'; -``` - -## Usage - -```html - -``` - -[docs]: https://patternflyelements.org/components/progress diff --git a/elements/pf-v5-progress/demo/index.html b/elements/pf-v5-progress/demo/index.html deleted file mode 100644 index 6707c5b632..0000000000 --- a/elements/pf-v5-progress/demo/index.html +++ /dev/null @@ -1,5 +0,0 @@ - - - diff --git a/elements/pf-v5-progress/demo/kitchen-sink.css b/elements/pf-v5-progress/demo/kitchen-sink.css deleted file mode 100644 index ccd286b44c..0000000000 --- a/elements/pf-v5-progress/demo/kitchen-sink.css +++ /dev/null @@ -1,4 +0,0 @@ -pf-v5-progress { - padding-bottom: 0.25rem; - display: block; -} \ No newline at end of file diff --git a/elements/pf-v5-progress/demo/kitchen-sink.html b/elements/pf-v5-progress/demo/kitchen-sink.html deleted file mode 100644 index ca3b20d5ab..0000000000 --- a/elements/pf-v5-progress/demo/kitchen-sink.html +++ /dev/null @@ -1,141 +0,0 @@ - - - -

pf-v5-progress

- -

Default States

- - - -

Value

- - -

Description

- - -

Aria-label

- - -

Max

- - -

Min

- - -

Size (sm, lg)

- - - -

Measure Location (Inside, Outside, None)

- - - - -

Variant (Sucess, danger, warning)

- - - - -

Variant (Success, Danger, Warning) and Size (sm, lg)

- - - - - - - -

Variant (Success, Danger, Warning) and Measure Location (Inside, Outside, None)

- - - - - - - - - - -

Variant (Success, Danger, Warning), Size (sm, lg) and Measure Location (Inside, Outside, None)

- - - - - - - - - - - - - - - - - - - -

Label w/ no description

- -

Value

- - - - -

Size (sm, lg)

- - - - - - - -

Measure Location (Inside, Outside)

- - - - - - - -

Variant (Sucess, danger, warning)

- - - - - - - - - - -

Variant (Success, Danger, Warning) and Size (sm, lg)

- - - - - - - - - - - - - - - - - - - -

Truncated description

- - - diff --git a/elements/pf-v5-progress/demo/truncated-description.html b/elements/pf-v5-progress/demo/truncated-description.html deleted file mode 100644 index ab77ce90a0..0000000000 --- a/elements/pf-v5-progress/demo/truncated-description.html +++ /dev/null @@ -1,20 +0,0 @@ -
- -
- - - - diff --git a/elements/pf-v5-progress/docs/pf-v5-progress.md b/elements/pf-v5-progress/docs/pf-v5-progress.md deleted file mode 100644 index e66c25db5e..0000000000 --- a/elements/pf-v5-progress/docs/pf-v5-progress.md +++ /dev/null @@ -1,49 +0,0 @@ -{% renderInstallation %} {% endrenderInstallation %} - -{% renderOverview %} - -{% endrenderOverview %} - -{% band header="Usage" %} - - ### Success variant - {% htmlexample %} - - {% endhtmlexample %} - - ### Inside measurement - {% htmlexample %} - - {% endhtmlexample %} - - ### Large size - {% htmlexample %} - - {% endhtmlexample %} - - ### Inside measurement - {% htmlexample %} - - {% endhtmlexample %} - - ### Truncated Description - {% htmlexample %} - - {% endhtmlexample %} -{% endband %} - -{% renderSlots %}{% endrenderSlots %} - -{% renderAttributes %}{% endrenderAttributes %} - -{% renderMethods %}{% endrenderMethods %} - -{% renderEvents %}{% endrenderEvents %} - -{% renderCssCustomProperties %}{% endrenderCssCustomProperties %} - -{% renderCssParts %}{% endrenderCssParts %} diff --git a/elements/pf-v5-progress/pf-v5-progress.css b/elements/pf-v5-progress/pf-v5-progress.css deleted file mode 100644 index 2088aefb98..0000000000 --- a/elements/pf-v5-progress/pf-v5-progress.css +++ /dev/null @@ -1,229 +0,0 @@ -* { - box-sizing: border-box; -} - -#container { - --_pf-c-progress__bar--before--BackgroundColorWithOpacity: #0066cc33; /* WARNING: not a recognized token value */ - --_pf-c-progress--m-success__bar--BackgroundColorWithOpacity: #3e863533; /* WARNING: not a recognized token value */ - --_pf-c-progress--m-warning__bar--BackgroundColorWithOpacity: #f0ab0033; /* WARNING: not a recognized token value */ - --_pf-c-progress--m-danger__bar--BackgroundColorWithOpacity: #c9190b33; /* WARNING: not a recognized token value */ - - /** Gap between sections of progress bar */ - --pf-v5-c-progress--GridGap: var(--pf-global--spacer--md, 1rem); - /** Color of progress bar */ - --pf-v5-c-progress__bar--before--BackgroundColor: var(--pf-global--primary-color--100, #0066cc); - /** Height of progress bar */ - --pf-v5-c-progress__bar--Height: var(--pf-global--spacer--md, 1rem); - /** Background color of progress bar */ - --pf-v5-c-progress__bar--BackgroundColor: var(--pf-global--BackgroundColor--light-100, #ffffff); - /** Color of status icon */ - --pf-v5-c-progress__status-icon--Color: var(--pf-global--Color--100, #151515); - /** Margin left of status icon */ - --pf-v5-c-progress__status-icon--MarginLeft: var(--pf-global--spacer--sm, 0.5rem); - /** Height of progress bar indicator */ - --pf-v5-c-progress__indicator--Height: var(--pf-v5-c-progress__bar--Height); - /** Background color of progress bar indicator */ - --pf-v5-c-progress__indicator--BackgroundColor: var(--pf-v5-c-progress__bar--before--BackgroundColor); - /** Background color of progress bar when variant is success */ - --pf-v5-c-progress--m-success__bar--BackgroundColor: var(--pf-global--success-color--100, #3e8635); - /** Background color of progress bar when variant is warning */ - --pf-v5-c-progress--m-warning__bar--BackgroundColor: var(--pf-global--warning-color--100, #f0ab00); - /** Background color of progress bar when variant is danger */ - --pf-v5-c-progress--m-danger__bar--BackgroundColor: var(--pf-global--danger-color--100, #c9190b); - /** Color of status icon when variant is success */ - --pf-v5-c-progress--m-success__status-icon--Color: var(--pf-global--success-color--100, #3e8635); - /** Color of status icon when variant is warning */ - --pf-v5-c-progress--m-warning__status-icon--Color: var(--pf-global--warning-color--100, #f0ab00); - /** Color of status icon when variant is danger */ - --pf-v5-c-progress--m-danger__status-icon--Color: var(--pf-global--danger-color--100, #c9190b); - /** Color of progress bar measure when variant is success and measure location is inside */ - --pf-v5-c-progress--m-success--m-inside__measure--Color: var(--pf-global--Color--light-100, #ffffff); - /** Font size of progress bar measure when measure location is outside */ - --pf-v5-c-progress--m-outside__measure--FontSize: var(--pf-global--FontSize--sm, 0.875rem); - /** Height of progress bar when size is small */ - --pf-v5-c-progress--m-sm__bar--Height: var(--pf-global--spacer--sm, 0.5rem); - /** Font size of progress bar description when size is small */ - --pf-v5-c-progress--m-sm__description--FontSize: var(--pf-global--FontSize--sm, 0.875rem); - /** Height of progress bar when size is large */ - --pf-v5-c-progress--m-lg__bar--Height: var(--pf-global--spacer--lg, 1.5rem); - display: grid; - align-items: end; - grid-gap: var(--pf-v5-c-progress--GridGap); - grid-template-columns: 1fr auto; - grid-template-rows: 1fr auto; - width: 100%; -} - -.sm { - --pf-v5-c-progress__bar--Height: var(--pf-v5-c-progress--m-sm__bar--Height); - --pf-v5-c-progress__indicator--Height: var(--pf-v5-c-progress--m-sm__bar--Height); -} - -.sm #description { - font-size: var(--pf-v5-c-progress--m-sm__description--FontSize); -} - -.lg { - --pf-v5-c-progress__bar--Height: var(--pf-v5-c-progress--m-lg__bar--Height); - --pf-v5-c-progress__indicator--Height: var(--pf-v5-c-progress--m-lg__bar--Height); -} - -.outside #description { - grid-column: 1/3; -} - -.outside #status { - grid-column: 2/3; - grid-row: 2/3; - align-self: center; -} - -.outside progress, -.outside span { - display: inline-block; - font-size: var(--pf-v5-c-progress--m-outside__measure--FontSize); - grid-column: 1/2; -} - -.singleline { - grid-template-rows: 1fr; -} - -.singleline #description { - display: none; - visibility: hidden; -} - -.singleline progress, -.singleline span { - grid-row: 1/2; - grid-column: 1/2; -} - -.singleline #status { - grid-row: 1/2; - grid-column: 2/3; -} - -.outside, .singleline { - grid-template-columns: 1fr fit-content(50%); -} - -#container.success { - --pf-v5-c-progress__bar--before--BackgroundColor: var(--pf-v5-c-progress--m-success__bar--BackgroundColor); - --_pf-c-progress__bar--before--BackgroundColorWithOpacity: var(--_pf-c-progress--m-success__bar--BackgroundColorWithOpacity); - --pf-v5-c-progress__status-icon--Color: var(--pf-v5-c-progress--m-success__status-icon--Color); -} - -#container.warning { - --pf-v5-c-progress__bar--before--BackgroundColor: var(--pf-v5-c-progress--m-warning__bar--BackgroundColor); - --_pf-c-progress__bar--before--BackgroundColorWithOpacity: var(--_pf-c-progress--m-warning__bar--BackgroundColorWithOpacity); - --pf-v5-c-progress__status-icon--Color: var(--pf-v5-c-progress--m-warning__status-icon--Color); -} - -#container.danger { - --pf-v5-c-progress__bar--before--BackgroundColor: var(--pf-v5-c-progress--m-danger__bar--BackgroundColor); - --_pf-c-progress__bar--before--BackgroundColorWithOpacity: var(--_pf-c-progress--m-danger__bar--BackgroundColorWithOpacity); - --pf-v5-c-progress__status-icon--Color: var(--pf-v5-c-progress--m-danger__status-icon--Color); -} - -#description { - word-break: break-word; - grid-column: 1/2; -} - -.descriptionTruncated #description { - overflow-x: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} - -#status { - grid-column: 2/3; - grid-row: 1/2; - text-align: right; - word-break: break-word; - display: flex; - align-items: center; - justify-content: end; -} - -pf-v5-icon { - margin-left: var(--pf-v5-c-progress__status-icon--MarginLeft); - color: var(--pf-v5-c-progress__status-icon--Color); -} - -progress { - position: relative; - grid-column: 1/3; - grid-row: 2/3; - align-self: center; - height: var(--pf-v5-c-progress__bar--Height); - background-color: var(--pf-v5-c-progress__bar--BackgroundColor); -} - -.indicator { - position: absolute; - top: 0; - left: 0; - height: var(--pf-v5-c-progress__indicator--Height); - background-color: var(--pf-v5-c-progress__indicator--BackgroundColor); -} - -.indicator { - width: 100%; - height: var(--pf-v5-c-progress__bar--Height); - - display: block; -} - -span { - grid-column: 1/3; - grid-row: 2/3; - text-align: center; - color: var(--pf-v5-c-progress--m-success--m-inside__measure--Color); -} - -span::after { - content: attr(data-value); - position: relative; - height: 100%; -} - -progress[value] { - -webkit-appearance: none; - -moz-appearance: none; - appearance: none; - - background: var(--_pf-c-progress__bar--before--BackgroundColorWithOpacity); - - width: 100%; - height: var(--pf-v5-c-progress__bar--Height); -} - -progress:not([value]) { - -webkit-appearance: none; - -moz-appearance: none; - appearance: none; -} - -progress[value]::-webkit-progress-bar { - background: var(--_pf-c-progress__bar--before--BackgroundColorWithOpacity); -} - -progress[value]::-moz-progress-bar { - background: var(--pf-v5-c-progress__bar--before--BackgroundColor); -} - -progress[value]::-webkit-progress-value { - background-size: 100% 100%; - background-image: linear-gradient( - 90deg, - var(--pf-v5-c-progress__bar--before--BackgroundColor) 100%, - var(--pf-v5-c-progress__bar--before--BackgroundColor) 100% - ); -} - -pf-v5-tooltip { - height: 0.01px; -} diff --git a/elements/pf-v5-progress/pf-v5-progress.ts b/elements/pf-v5-progress/pf-v5-progress.ts deleted file mode 100644 index 2791b1601c..0000000000 --- a/elements/pf-v5-progress/pf-v5-progress.ts +++ /dev/null @@ -1,136 +0,0 @@ -import type { PropertyValues, TemplateResult } from 'lit'; -import { LitElement, html } from 'lit'; -import { classMap } from 'lit/directives/class-map.js'; -import { customElement } from 'lit/decorators/custom-element.js'; -import { property } from 'lit/decorators/property.js'; -import { ifDefined } from 'lit/directives/if-defined.js'; -import { styleMap } from 'lit/directives/style-map.js'; - -import styles from './pf-v5-progress.css'; - -const ICONS = new Map(Object.entries({ - success: { icon: 'circle-check' }, - danger: { icon: 'circle-xmark' }, - warning: { icon: 'triangle-exclamation' }, -})); - -/** - * A progress bar gives the user a visual representation of their completion status of an ongoing process or task. - * @summary Display completion status of ongoing process or task. - * @alias Progress - */ -@customElement('pf-v5-progress') -export class PfV5Progress extends LitElement { - static readonly styles: CSSStyleSheet[] = [styles]; - - #internals = this.attachInternals(); - - /** Represents the value of the progress bar */ - @property({ reflect: true, type: Number }) value = 0; - - /** Description (title) above the progress bar */ - @property() description?: string; - - /** Indicate whether to truncate the string description (title) */ - @property({ - type: Boolean, - reflect: true, - attribute: 'description-truncated', - }) descriptionTruncated = false; - - /** Maximum value for the progress bar */ - @property({ type: Number, reflect: true }) max = 100; - - /** Minimum value for the progress bar */ - @property({ type: Number, reflect: true }) min = 0; - - /** Size of the progress bar (height) */ - @property() size?: 'sm' | 'lg'; - - /** Where the percentage will be displayed with the progress element */ - @property({ attribute: 'measure-location' }) measureLocation?: 'outside' | 'inside' | 'none'; - - /** Variant of the progress bar */ - @property() variant?: 'success' | 'danger' | 'warning'; - - get #calculatedPercentage(): number { - const { value, min, max } = this; - const percentage = Math.round((value - min) / (max - min) * 100); - if (Number.isNaN(percentage) || percentage < 0) { - return 0; - } - return Math.min(percentage, 100); - } - - get #icon() { - return ICONS.get(this.variant ?? '')?.icon; - } - - override willUpdate(changed: PropertyValues): void { - if (changed.has('value') || changed.has('min') || changed.has('max')) { - this.#internals.ariaValueNow = this.#calculatedPercentage.toString(); - } - if (this.#icon) { - import('@patternfly/elements/pf-v5-icon/pf-v5-icon.js'); - } - if (this.descriptionTruncated) { - import('@patternfly/elements/pf-v5-tooltip/pf-v5-tooltip.js'); - } - } - - render(): TemplateResult<1> { - const { size, measureLocation, variant, description, descriptionTruncated } = this; - const icon = this.#icon; - const singleLine = description?.length === 0; - const pct = this.#calculatedPercentage; - const width = `${pct}%`; - - return html` -
- - - - ${!descriptionTruncated ? '' : html` - - `} - - ${measureLocation === 'none' ? '' : html` - - `} - - - - ${measureLocation !== 'inside' ? '' : html` - - `} -
`; - } -} - -declare global { - interface HTMLElementTagNameMap { - 'pf-v5-progress': PfV5Progress; - } -} diff --git a/elements/pf-v5-progress/test/pf-progress.e2e.ts b/elements/pf-v5-progress/test/pf-progress.e2e.ts deleted file mode 100644 index 5aa242fb07..0000000000 --- a/elements/pf-v5-progress/test/pf-progress.e2e.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { test } from '@playwright/test'; -import { PfeDemoPage } from '@patternfly/pfe-tools/test/playwright/PfeDemoPage.js'; -import { SSRPage } from '@patternfly/pfe-tools/test/playwright/SSRPage.js'; - -const tagName = 'pf-v5-progress'; - -test.describe(tagName, () => { - test('snapshot', async ({ page }) => { - const componentPage = new PfeDemoPage(page, tagName); - await componentPage.navigate(); - await componentPage.snapshot(); - }); - - test('ssr', async ({ browser }) => { - const fixture = new SSRPage({ - tagName, - browser, - demoDir: new URL('../demo/', import.meta.url), - importSpecifiers: [ - `@patternfly/elements/${tagName}/${tagName}.js`, - ], - }); - await fixture.snapshots(); - }); -}); diff --git a/elements/pf-v5-progress/test/pf-progress.spec.ts b/elements/pf-v5-progress/test/pf-progress.spec.ts deleted file mode 100644 index bc0b45a15f..0000000000 --- a/elements/pf-v5-progress/test/pf-progress.spec.ts +++ /dev/null @@ -1,52 +0,0 @@ -import { expect, html, fixture } from '@open-wc/testing'; -import { PfV5Progress } from '@patternfly/elements/pf-v5-progress/pf-v5-progress.js'; - -describe('', function() { - let element: PfV5Progress; - - beforeEach(async function() { - element = await fixture(html` - - - `); - }); - - it('should upgrade', async function() { - const klass = customElements.get('pf-v5-progress'); - expect(element).to.be.an.instanceOf(klass).and.to.be.an.instanceOf(PfV5Progress); - }); - - it('should be accessible', async function() { - await expect(element).shadowDom.to.be.accessible(); - }); - - it('should set the correct value on the progress bar', async function() { - const element = await fixture(html` - - - `); - expect(element.value).to.equal(33); - }); - - it('should set the correct title on the progress bar', async function() { - const element = await fixture(html` - - - `); - expect(element.title).to.equal('Progress title'); - }); - - it('should have the correct value with the max value set', async function() { - const max = Math.floor(Math.random() * 100); - const value = Math.floor(Math.random() * (max)); - - const element = await fixture(html` - - - `); - expect(element.value).to.equal(value); - expect(element.max).to.equal(max); - }); -}); diff --git a/elements/pf-v6-progress/README.md b/elements/pf-v6-progress/README.md index 111363800b..1759ceae12 100644 --- a/elements/pf-v6-progress/README.md +++ b/elements/pf-v6-progress/README.md @@ -11,3 +11,50 @@ A progress bar gives the user a visual representation of their completion status import '@patternfly/elements/pf-v6-progress/pf-v6-progress.js'; ``` + +With variant and measure location: + +```html + +``` + +With helper text: + +```html + + Uploading 3 of 9 files + +``` + +## Divergences from React `Progress` + +### Not implemented + +| React prop | Notes | +|---|---| +| `label` | Use `value-text` for custom measure text. Rich content (ReactNode) not supported. | +| `tooltipPosition` | Browser-native `title` tooltip used when `truncated` is set. | +| `hideStatusIcon` | No equivalent. Use CSS to hide the icon if needed. | +| `aria-describedby` | Not supported. Use the `helper-text` slot for supplementary text. | + +### Changed API + +| React prop | Web component | Difference | +|---|---|---| +| `title` | `description` attribute | Visible title text above the bar. Renamed to avoid shadowing the native HTML `title` attribute. | +| `isTitleTruncated` | `truncated` attribute | Boolean. Truncates `description` with CSS ellipsis and adds a native `title` tooltip. | +| `measureLocation` `"top"` | Default (no attribute) | React's `"top"` is the default; omitting `measure-location` produces the same layout. | +| `measureLocation` | `measure-location` attribute | Adds `"singleline"` value not in React. React achieves singleline via `measureLocation="outside"` without a `title`. | +| `size` `"md"` | Default (no attribute) | React's `"md"` is the default size; omitting `size` produces the same result. | +| `helperText` | `helper-text` slot | Slot instead of prop, accepts rich content. | +| `aria-label` | `accessible-label` attribute | Screen reader name only, set via ElementInternals. Falls back to `description`, then `"Progress status"`. | +| `aria-labelledby` | `accessible-labelledby` attribute | Accepts space-separated element ID(s). Resolves cross-root `aria-labelledby` via `ariaLabelledByElements` on ElementInternals. Takes precedence over `accessible-label` and `description`. | + +### Added + +| Web component API | Notes | +|---|---| +| `helper-text` slot | Accepts rich content below the progress bar. React uses a `helperText` prop (ReactNode). | diff --git a/elements/pf-v6-progress/demo/danger-without-measure.html b/elements/pf-v6-progress/demo/failure-without-measure.html similarity index 75% rename from elements/pf-v6-progress/demo/danger-without-measure.html rename to elements/pf-v6-progress/demo/failure-without-measure.html index 92420ae30f..0cc9071025 100644 --- a/elements/pf-v6-progress/demo/danger-without-measure.html +++ b/elements/pf-v6-progress/demo/failure-without-measure.html @@ -1,5 +1,5 @@ --- -description: Danger variant without the percentage measure displayed. +description: Failure variant without the percentage measure displayed. --- diff --git a/elements/pf-v6-progress/demo/danger.html b/elements/pf-v6-progress/demo/failure.html similarity index 81% rename from elements/pf-v6-progress/demo/danger.html rename to elements/pf-v6-progress/demo/failure.html index 8113e988d2..1437d80d87 100644 --- a/elements/pf-v6-progress/demo/danger.html +++ b/elements/pf-v6-progress/demo/failure.html @@ -1,5 +1,5 @@ --- -description: Danger variant progress bar. +description: Failure variant progress bar. --- diff --git a/elements/pf-v6-progress/demo/outside-danger.html b/elements/pf-v6-progress/demo/outside-failure.html similarity index 75% rename from elements/pf-v6-progress/demo/outside-danger.html rename to elements/pf-v6-progress/demo/outside-failure.html index 6788dc46a7..908c0e5bd0 100644 --- a/elements/pf-v6-progress/demo/outside-danger.html +++ b/elements/pf-v6-progress/demo/outside-failure.html @@ -1,5 +1,5 @@ --- -description: Danger variant with measure displayed outside the bar. +description: Failure variant with measure displayed outside the bar. --- diff --git a/elements/pf-v6-progress/demo/outside-static-width.html b/elements/pf-v6-progress/demo/outside-static-width.html index a92f6eb864..20dffc23e6 100644 --- a/elements/pf-v6-progress/demo/outside-static-width.html +++ b/elements/pf-v6-progress/demo/outside-static-width.html @@ -1,10 +1,16 @@ --- description: Progress bar with outside measure using a static minimum width for alignment across multiple bars. --- + +
- - - + + +
diff --git a/elements/pf-v6-progress/demo/truncate-description.html b/elements/pf-v6-progress/demo/truncate-description.html index a7dc5872c7..1e61f78475 100644 --- a/elements/pf-v6-progress/demo/truncate-description.html +++ b/elements/pf-v6-progress/demo/truncate-description.html @@ -4,7 +4,7 @@ + truncated>