diff --git a/.changeset/calm-friends-carry.md b/.changeset/calm-friends-carry.md new file mode 100644 index 0000000000..bed22ff7d6 --- /dev/null +++ b/.changeset/calm-friends-carry.md @@ -0,0 +1,22 @@ +--- +"@patternfly/elements": major +--- + +✨ Added `` replacing ``. Progress bar now follows +PatternFly v6 design specs. + +```html + +``` + +** Breaking Changes from v5 ** + +- Renamed tag from `` to `` +- ✨ Added `accessible-label` attribute for screen reader text via ElementInternals +- ✨ Added `accessible-labelledby` and `accessible-describedby` for cross-root ARIA references +- ✨ Added `value-text` attribute for custom aria-valuetext (finite step displays) +- ✨ Added `truncated `attribute for ellipsis overflow on description +- ✨ Added `hide-status-icon` attribute for tight layouts +- ✨ Added `helper-text` slot for supplementary text below the bar +- ✨ Added v6 design tokens and `light-dark()` support +- CSS custom properties renamed from `--pf-v5-c-progress--*` to `--pf-v6-c-progress--*` diff --git a/elements/pf-v5-progress/README.md b/elements/pf-v5-progress/README.md deleted file mode 100644 index 688ff7ecbb..0000000000 --- a/elements/pf-v5-progress/README.md +++ /dev/null @@ -1,33 +0,0 @@ -# Progress - -A progress bar gives the user a visual representation of their completion status of an ongoing process or task. - -Read more about Progress in the [PatternFly Elements Progress documentation][docs]. - -## Installation - -Load `` via CDN: - -```html - -``` - -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.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 new file mode 100644 index 0000000000..8e927926f0 --- /dev/null +++ b/elements/pf-v6-progress/README.md @@ -0,0 +1,60 @@ +# Progress + +A progress bar gives the user a visual representation of their completion status of an ongoing process or task. + +## Usage + +```html + + + +``` + +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` | Not implemented. | + +### 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 | Singleline layout is derived automatically when no `description` is set, matching React behavior. | +| `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"`. | +| `hideStatusIcon` | `hide-status-icon` attribute | Boolean. Hides the variant status icon while keeping variant coloring on the bar. | +| `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`. | +| `aria-describedby` | `accessible-describedby` attribute | Accepts space-separated element ID(s). Resolves cross-root `aria-describedby` via `ariaDescribedByElements` on ElementInternals. | + +### 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/failure-without-measure.html b/elements/pf-v6-progress/demo/failure-without-measure.html new file mode 100644 index 0000000000..0cc9071025 --- /dev/null +++ b/elements/pf-v6-progress/demo/failure-without-measure.html @@ -0,0 +1,8 @@ +--- +description: Failure 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..1437d80d87 --- /dev/null +++ b/elements/pf-v6-progress/demo/failure.html @@ -0,0 +1,8 @@ +--- +description: Failure 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..908c0e5bd0 --- /dev/null +++ b/elements/pf-v6-progress/demo/outside-failure.html @@ -0,0 +1,8 @@ +--- +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 new file mode 100644 index 0000000000..20dffc23e6 --- /dev/null +++ b/elements/pf-v6-progress/demo/outside-static-width.html @@ -0,0 +1,18 @@ +--- +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..04d481ce88 --- /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/title-outside.html b/elements/pf-v6-progress/demo/title-outside.html new file mode 100644 index 0000000000..d0dd3a944a --- /dev/null +++ b/elements/pf-v6-progress/demo/title-outside.html @@ -0,0 +1,11 @@ +--- +description: Progress bar labeled by an external heading element. +--- +

Title outside of 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..1e61f78475 --- /dev/null +++ b/elements/pf-v6-progress/demo/truncate-description.html @@ -0,0 +1,11 @@ +--- +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..04cb812b7b --- /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..9e2a302119 --- /dev/null +++ b/elements/pf-v6-progress/pf-v6-progress.css @@ -0,0 +1,310 @@ +*, +*::before, +*::after { + box-sizing: border-box; +} + +:host { + display: block; +} + +[hidden] { + display: none !important; +} + +#container { + display: grid; + grid-template-rows: 1fr auto; + grid-template-columns: auto auto; + /** Gap between the description, bar, and helper-text rows. Defaults to `--pf-t--global--spacer--md`. */ + gap: var(--pf-v6-c-progress--GridGap, var(--pf-t--global--spacer--md, 1rem)); + align-items: end; + + /* Size modifiers */ + + &.sm { + /** Height of the bar at small size. Defaults to `--pf-t--global--spacer--sm`. */ + --_bar-height: var(--pf-v6-c-progress--m-sm__bar--Height, + var(--pf-t--global--spacer--sm, 0.5rem)); + + & #measure { + /** Font size of the measure text at small size. Defaults to `--pf-t--global--font--size--body--sm`. */ + font-size: var(--pf-v6-c-progress--m-sm__measure--FontSize, + var(--pf-t--global--font--size--body--sm, 0.75rem)); + } + } + + &.lg { + /** Height of the bar at large size. Defaults to `--pf-t--global--spacer--lg`. */ + --_bar-height: var(--pf-v6-c-progress--m-lg__bar--Height, + var(--pf-t--global--spacer--lg, 1.5rem)); + } + + /* Measure location modifiers */ + + &.outside, + &.singleline { + grid-template-columns: 1fr fit-content(50%); + } + + &.outside { + & #description { + grid-column: 1 / 3; + } + + & #status { + grid-row: 2 / 3; + grid-column: 2 / 3; + align-self: center; + } + + & #measure { + display: inline-block; + /** Font size of the measure text in outside layout. Defaults to `--pf-t--global--font--size--sm`. */ + font-size: var(--pf-v6-c-progress--m-outside__measure--FontSize, + var(--pf-t--global--font--size--sm, 0.875rem)); + } + + & #bar { + grid-column: 1 / 2; + } + } + + &.singleline { + grid-template-rows: 1fr; + + & #description { + display: none; + } + + & #bar { + grid-row: 1 / 2; + grid-column: 1 / 2; + } + + & #status { + grid-row: 1 / 2; + grid-column: 2 / 3; + } + } + + &.inside { + & #indicator { + display: flex; + align-items: center; + justify-content: center; + /** Minimum width of the indicator when measure is inside. Defaults to `--pf-t--global--spacer--xl`. */ + min-width: var(--pf-v6-c-progress--m-inside__indicator--MinWidth, + var(--pf-t--global--spacer--xl, 2rem)); + } + + & #measure { + /** Font size of the measure text inside the bar. Defaults to `--pf-t--global--font--size--sm`. */ + font-size: var(--pf-v6-c-progress--m-inside__measure--FontSize, + var(--pf-t--global--font--size--sm, 0.875rem)); + /** Color of the measure text inside the bar. Defaults to `--pf-t--global--text--color--on-brand--default`. */ + 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 */ + + &.success { + /** Indicator color for the success variant. Defaults to `--pf-t--global--color--status--success--default`. */ + --_indicator-bg: var(--pf-v6-c-progress--m-success__indicator--BackgroundColor, + var(--pf-t--global--color--status--success--default, #3e8635)); + /** Status icon color for the success variant. Defaults to `--pf-t--global--icon--color--status--success--default`. */ + --_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 text color for the success variant. Defaults to `--pf-t--global--text--color--status--on-success--default`. */ + --_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)); + } + + &.warning { + /** Indicator color for the warning variant. Defaults to `--pf-t--global--color--status--warning--default`. */ + --_indicator-bg: var(--pf-v6-c-progress--m-warning__indicator--BackgroundColor, + var(--pf-t--global--color--status--warning--default, #f0ab00)); + /** Status icon color for the warning variant. Defaults to `--pf-t--global--icon--color--status--warning--default`. */ + --_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 text color for the warning variant. Defaults to `--pf-t--global--text--color--status--on-warning--default`. */ + --_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)); + } + + &.danger { + /** Indicator color for the danger variant. Defaults to `--pf-t--global--color--status--danger--default`. */ + --_indicator-bg: var(--pf-v6-c-progress--m-danger__indicator--BackgroundColor, + var(--pf-t--global--color--status--danger--default, #c9190b)); + /** Status icon color for the danger variant. Defaults to `--pf-t--global--icon--color--status--danger--default`. */ + --_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 text color for the danger variant. Defaults to `--pf-t--global--text--color--status--on-danger--default`. */ + --_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)); + } + + /* Description truncation */ + + &.truncated #description { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } + +} + +/* Danger animations */ + +@media (prefers-reduced-motion: no-preference) { + .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; + } + + .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)); + } +} + +/* Elements */ + +#description { + grid-column: 1 / 2; + word-break: break-word; +} + +#status { + display: flex; + grid-row: 1 / 2; + grid-column: 2 / 3; + /** Gap between the measure text and status icon. Defaults to `--pf-t--global--spacer--sm`. */ + gap: var(--pf-v6-c-progress__status--Gap, + var(--pf-t--global--spacer--sm, 0.5rem)); + align-items: start; + justify-content: end; + text-align: end; + word-break: break-word; +} + +/** Color of the status icon. Overridden by variant. Defaults to `--pf-t--global--icon--color--regular`. */ +#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 of the progress bar track. Defaults to `--pf-t--global--spacer--md`. */ + height: var(--_bar-height, + var(--pf-v6-c-progress__bar--Height, + var(--pf-t--global--spacer--md, 1rem))); + overflow: hidden; + /** Background color of the bar track. Defaults to `--pf-t--global--color--nonstatus--gray--default`. */ + background-color: var(--pf-v6-c-progress__bar--BackgroundColor, + var(--pf-t--global--color--nonstatus--gray--default, #f0f0f0)); + /** Border radius of the bar track. Defaults to `--pf-t--global--border--radius--medium`. */ + 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 width of the bar track for high-contrast mode. Defaults to `--pf-t--global--border--width--high-contrast--regular`. */ + border: var(--pf-v6-c-progress__bar--BorderWidth, + var(--pf-t--global--border--width--high-contrast--regular, 0)) + solid + /** Border color of the bar track for high-contrast mode. Defaults to `--pf-t--global--border--color--high-contrast`. */ + 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 of the filled indicator. Defaults to `--pf-v6-c-progress__bar--Height`. */ + 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 of the filled indicator. Overridden by variant. Defaults to `--pf-t--global--color--brand--default`. */ + 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 width of the indicator for high-contrast mode. Defaults to `--pf-t--global--border--width--high-contrast--extra-strong`. */ + border: var(--pf-v6-c-progress__indicator--BorderWidth, + var(--pf-t--global--border--width--high-contrast--extra-strong, 0)) + solid + /** Border color of the indicator for high-contrast mode. */ + 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; + /** Negative offset to bring helper text closer to the bar. */ + 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)))); +} + +/** Minimum width of the measure display for visual alignment across stacked progress bars. Set via CSS custom property. */ +#measure { + min-width: var(--pf-v6-c-progress__measure--m-static-width--MinWidth, 0); + 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..6b225ab864 --- /dev/null +++ b/elements/pf-v6-progress/pf-v6-progress.ts @@ -0,0 +1,219 @@ +import type { TemplateResult } from 'lit'; +import { LitElement, html, nothing, isServer } from 'lit'; +import { customElement } from 'lit/decorators/custom-element.js'; +import { property } from 'lit/decorators/property.js'; +import { classMap } from 'lit/directives/class-map.js'; +import { styleMap } from 'lit/directives/style-map.js'; + +import { observes } from '@patternfly/pfe-core/decorators/observes.js'; +import { InternalsController } from '@patternfly/pfe-core/controllers/internals-controller.js'; +import { SlotController } from '@patternfly/pfe-core/controllers/slot-controller.js'; + +import styles from './pf-v6-progress.css'; + +export type ProgressSize = 'sm' | 'lg'; +export type ProgressMeasureLocation = 'outside' | 'inside' | 'none'; +export type ProgressVariant = 'success' | 'danger' | 'warning'; + +// TODO: replace inline SVGs with when available +const checkCircleIcon = html``; +const triangleExclamationIcon = html``; +const circleExclamationIcon = html``; + +const VARIANT_ICONS = new Map([ + ['success', checkCircleIcon], + ['warning', triangleExclamationIcon], + ['danger', circleExclamationIcon], +]); + +/** + * A progress bar provides a visual representation of completion status for an + * ongoing process or task. Authors SHOULD provide a `description` attribute for + * visible title text above the bar. The accessible name is resolved in order: + * `accessible-labelledby`, then `accessible-label`, then `description`, then + * a fallback of `"Progress status"`. Authors SHOULD set the `variant` attribute + * to `success`, `warning`, or `danger` when the progress reaches a terminal + * state. Authors SHOULD AVOID using `measure-location="inside"` without + * `size="lg"`, as the measure text will not fit inside the bar at the default + * size. + * + * This element uses `role="progressbar"` via ElementInternals. `aria-valuenow`, + * `aria-valuemin`, and `aria-valuemax` are managed internally based on the + * `value`, `min`, and `max` properties. This element is non-interactive and + * does not receive keyboard focus. + * + * @summary Displays completion status of an ongoing process or task. + * @slot helper-text - Supplementary text below the progress bar, such as status messages or additional context. SHOULD use `pf-v6-helper-text` or plain text. Content is not associated to the progressbar via `aria-describedby`; authors SHOULD ensure helper text is perceivable to assistive technology users. + */ +@customElement('pf-v6-progress') +export class PfV6Progress extends LitElement { + static readonly styles: CSSStyleSheet[] = [styles]; + + /** Represents the value of the progress bar */ + @property({ type: Number }) value = 0; + + /** Visible title text above the progress bar */ + @property() description?: string; + + // TODO: consider promoting to an enum attribute (e.g. truncated="…" or + // truncated="……") to support locale-specific truncation. CSS text-overflow: + // ellipsis always renders U+2026 (three dots); Chinese convention is six dots + // (two U+2026 characters). The attribute value could set a private CSS custom + // property like --_truncation-string, used as text-overflow: var(--_truncation-string, ellipsis). + // text-overflow accepts arbitrary strings, so any value works (e.g. "……", "Read more"). + // TODO: blocked on pf-v6-tooltip — React shows a positioned tooltip with the + // full description text on hover when truncated. Add tooltip-position attribute to match. + /** Truncate the description with ellipsis when it overflows */ + @property({ type: Boolean }) truncated = false; + + /** Screen reader label for the progress bar, set via ElementInternals. Overrides `description` for the accessible name when both are set. */ + @property({ attribute: 'accessible-label' }) accessibleLabel?: string; + + /** 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() size?: ProgressSize; + + /** Where the percentage will be displayed with the progress element */ + @property({ attribute: 'measure-location' }) measureLocation?: ProgressMeasureLocation; + + /** Variant of the progress bar */ + @property() variant?: ProgressVariant; + + /** Hide the status icon when a variant is set. Useful in tight layouts like table cells. */ + @property({ type: Boolean, attribute: 'hide-status-icon' }) hideStatusIcon = false; + + /** Custom text for aria-valuetext, used for finite step and step instruction displays */ + @property({ attribute: 'value-text' }) valueText?: string; + + /** Space-separated ID(s) of elements that label this progress bar. Resolves cross-root aria-labelledby via ElementInternals. */ + @property({ attribute: 'accessible-labelledby' }) accessibleLabelledby?: string; + + /** Space-separated ID(s) of elements that describe this progress bar. Resolves cross-root aria-describedby via ElementInternals. */ + @property({ attribute: 'accessible-describedby' }) accessibleDescribedby?: string; + + + #internals = InternalsController.of(this, { role: 'progressbar' }); + + #slots = new SlotController(this, 'helper-text'); + + 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 { + if (this.hideStatusIcon) { + return nothing; + } + return VARIANT_ICONS.get(this.variant!) ?? nothing; + } + + @observes('value') + @observes('min') + @observes('max') + private _updateAriaValue() { + this.#internals.ariaValueNow = this.#calculatedPercentage.toString(); + this.#internals.ariaValueMin = '0'; + this.#internals.ariaValueMax = '100'; + } + + @observes('valueText') + private _updateAriaValueText() { + this.#internals.ariaValueText = this.valueText ?? null; + } + + @observes('accessibleLabelledby', { waitFor: 'connected' }) + private _updateAriaLabelledBy() { + if (!isServer && this.accessibleLabelledby) { + const elements = this.accessibleLabelledby.trim().split(/\s+/) + .map(id => document.getElementById(id)) + .filter((el): el is HTMLElement => el != null); + this.#internals.ariaLabelledByElements = elements.length ? elements : null; + } else { + this.#internals.ariaLabelledByElements = null; + } + } + + @observes('accessibleDescribedby', { waitFor: 'connected' }) + private _updateAriaDescribedBy() { + if (!isServer && this.accessibleDescribedby) { + const elements = this.accessibleDescribedby.trim().split(/\s+/) + .map(id => document.getElementById(id)) + .filter((el): el is HTMLElement => el != null); + this.#internals.ariaDescribedByElements = elements.length ? elements : null; + } else { + this.#internals.ariaDescribedByElements = null; + } + } + + @observes('accessibleLabel', { waitFor: 'connected' }) + @observes('description', { waitFor: 'connected' }) + private _updateAriaLabel() { + this.#internals.ariaLabel = this.accessibleLabel ?? this.description ?? 'Progress status'; + } + + 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 && !this.hideStatusIcon; + const singleline = !hasDescription; + + const classes = { + [this.size ?? '']: !!this.size, + [this.measureLocation ?? '']: !!this.measureLocation && this.measureLocation !== 'none', + [this.variant ?? '']: !!this.variant, + singleline, + truncated: this.truncated, + }; + + return html` +
+
${this.description ?? ''}
+ + + +
+
+ ${inside && !noMeasure ? html`${displayText}` : nothing} +
+
+ +
+ + +
+
+ `; + } +} + +declare global { + interface HTMLElementTagNameMap { + 'pf-v6-progress': PfV6Progress; + } +} diff --git a/elements/pf-v5-progress/test/pf-progress.e2e.ts b/elements/pf-v6-progress/test/pf-progress.e2e.ts similarity index 95% rename from elements/pf-v5-progress/test/pf-progress.e2e.ts rename to elements/pf-v6-progress/test/pf-progress.e2e.ts index 5aa242fb07..261d6b0a59 100644 --- a/elements/pf-v5-progress/test/pf-progress.e2e.ts +++ b/elements/pf-v6-progress/test/pf-progress.e2e.ts @@ -2,7 +2,7 @@ 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'; +const tagName = 'pf-v6-progress'; test.describe(tagName, () => { test('snapshot', async ({ page }) => { 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..203686beba --- /dev/null +++ b/elements/pf-v6-progress/test/pf-progress.spec.ts @@ -0,0 +1,345 @@ +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 }); + }); + + it('should have accessible name from description in ax tree', async function() { + const snapshot = await a11ySnapshot(); + expect(snapshot).to.axContainQuery({ role: 'progressbar', name: 'Title' }); + }); + }); + + 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 have aria-valuetext in ax tree', async function() { + const snapshot = await a11ySnapshot(); + expect(snapshot).to.axContainQuery({ role: 'progressbar', valuetext: '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('without description (singleline)', function() { + let element: PfV6Progress; + + beforeEach(async function() { + element = await createFixture(html` + + `); + }); + + it('should have fallback accessible name when no description', async function() { + const snapshot = await a11ySnapshot(); + expect(snapshot).to.axContainQuery({ role: 'progressbar', name: 'Progress status' }); + }); + + 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 truncated', function() { + let element: PfV6Progress; + + beforeEach(async function() { + element = await createFixture(html` + + `); + }); + + it('should reflect attribute', function() { + expect(element.truncated).to.be.true; + }); + + it('should be accessible', async function() { + await expect(element).to.be.accessible(); + }); + }); + + 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 }); + }); + }); +});