From e6949afd377c71266e3d5163e8a3c5a3b448ac45 Mon Sep 17 00:00:00 2001 From: Steven Spriggs Date: Thu, 14 May 2026 17:26:02 -0400 Subject: [PATCH 1/5] feat(back-to-top): port pf-v5-back-to-top to pf-v6-back-to-top --- elements/pf-v5-back-to-top/README.md | 32 --- .../demo/always-visible.html | 28 --- .../demo/button-no-text.html | 43 ---- elements/pf-v5-back-to-top/demo/button.html | 52 ----- elements/pf-v5-back-to-top/demo/index.html | 41 ---- elements/pf-v5-back-to-top/demo/label.html | 41 ---- elements/pf-v5-back-to-top/demo/no-text.html | 41 ---- .../demo/scroll-distance.html | 41 ---- .../docs/pf-v5-back-to-top.md | 110 ---------- .../pf-v5-back-to-top/pf-v5-back-to-top.css | 77 ------- .../pf-v5-back-to-top/pf-v5-back-to-top.ts | 195 ------------------ .../test/pf-back-to-top.e2e.ts | 25 --- elements/pf-v6-back-to-top/README.md | 40 ++++ .../demo/always-visible.html | 12 ++ .../pf-v6-back-to-top/demo/button-mode.html | 28 +++ elements/pf-v6-back-to-top/demo/index.html | 28 +++ .../demo/scrollable-container.html | 17 ++ .../pf-v6-back-to-top/pf-v6-back-to-top.css | 88 ++++++++ .../pf-v6-back-to-top/pf-v6-back-to-top.ts | 187 +++++++++++++++++ .../test/pf-back-to-top.spec.ts | 134 +++++++----- 20 files changed, 477 insertions(+), 783 deletions(-) delete mode 100644 elements/pf-v5-back-to-top/README.md delete mode 100644 elements/pf-v5-back-to-top/demo/always-visible.html delete mode 100644 elements/pf-v5-back-to-top/demo/button-no-text.html delete mode 100644 elements/pf-v5-back-to-top/demo/button.html delete mode 100644 elements/pf-v5-back-to-top/demo/index.html delete mode 100644 elements/pf-v5-back-to-top/demo/label.html delete mode 100644 elements/pf-v5-back-to-top/demo/no-text.html delete mode 100644 elements/pf-v5-back-to-top/demo/scroll-distance.html delete mode 100644 elements/pf-v5-back-to-top/docs/pf-v5-back-to-top.md delete mode 100644 elements/pf-v5-back-to-top/pf-v5-back-to-top.css delete mode 100644 elements/pf-v5-back-to-top/pf-v5-back-to-top.ts delete mode 100644 elements/pf-v5-back-to-top/test/pf-back-to-top.e2e.ts create mode 100644 elements/pf-v6-back-to-top/README.md create mode 100644 elements/pf-v6-back-to-top/demo/always-visible.html create mode 100644 elements/pf-v6-back-to-top/demo/button-mode.html create mode 100644 elements/pf-v6-back-to-top/demo/index.html create mode 100644 elements/pf-v6-back-to-top/demo/scrollable-container.html create mode 100644 elements/pf-v6-back-to-top/pf-v6-back-to-top.css create mode 100644 elements/pf-v6-back-to-top/pf-v6-back-to-top.ts rename elements/{pf-v5-back-to-top => pf-v6-back-to-top}/test/pf-back-to-top.spec.ts (65%) diff --git a/elements/pf-v5-back-to-top/README.md b/elements/pf-v5-back-to-top/README.md deleted file mode 100644 index 7b95a9b756..0000000000 --- a/elements/pf-v5-back-to-top/README.md +++ /dev/null @@ -1,32 +0,0 @@ -# Back to top - -The **back to top** component is a shortcut that allows users to quickly navigate to the top of a lengthy content page. - - -## 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-back-to-top/pf-v5-back-to-top.js'; -``` - -## Usage - -```html -Back to Top - -``` - -[docs]: https://patternflyelements.org/components/back-to-top diff --git a/elements/pf-v5-back-to-top/demo/always-visible.html b/elements/pf-v5-back-to-top/demo/always-visible.html deleted file mode 100644 index 2064eb4126..0000000000 --- a/elements/pf-v5-back-to-top/demo/always-visible.html +++ /dev/null @@ -1,28 +0,0 @@ -
-

Always visible

- Focusable element (top) -
- -Back to top - - - - diff --git a/elements/pf-v5-back-to-top/demo/button-no-text.html b/elements/pf-v5-back-to-top/demo/button-no-text.html deleted file mode 100644 index af285d7147..0000000000 --- a/elements/pf-v5-back-to-top/demo/button-no-text.html +++ /dev/null @@ -1,43 +0,0 @@ -
-
-

Button No Text

-

Focusable element (top)

- Scroll down to end of cyan box, 400px (default). -
-
-Focusable element (bottom) - - - - - - diff --git a/elements/pf-v5-back-to-top/demo/button.html b/elements/pf-v5-back-to-top/demo/button.html deleted file mode 100644 index 452104c0a9..0000000000 --- a/elements/pf-v5-back-to-top/demo/button.html +++ /dev/null @@ -1,52 +0,0 @@ - - - Accessibility Warning Using the Button/JS variant, implementation must apply click event and focus to the element that is scrolled to. - -
-
-

Button

-

Focusable element (top)

- Scroll down to end of cyan box, 400px (default). -
-
-Focusable element (bottom) - -Back to top - - - - diff --git a/elements/pf-v5-back-to-top/demo/index.html b/elements/pf-v5-back-to-top/demo/index.html deleted file mode 100644 index 8fe24f6e22..0000000000 --- a/elements/pf-v5-back-to-top/demo/index.html +++ /dev/null @@ -1,41 +0,0 @@ -
-
-

Default

-

Focusable element (top)

- Scroll down to end of cyan box, 400px (default). -
-
-Focusable element (bottom) - -Back to top - - - - diff --git a/elements/pf-v5-back-to-top/demo/label.html b/elements/pf-v5-back-to-top/demo/label.html deleted file mode 100644 index afbd54ca59..0000000000 --- a/elements/pf-v5-back-to-top/demo/label.html +++ /dev/null @@ -1,41 +0,0 @@ -
-
-

Default

-

Focusable element (top)

- Scroll down to end of cyan box, 400px (default). -
-
-Focusable element (bottom) - - - - - - diff --git a/elements/pf-v5-back-to-top/demo/no-text.html b/elements/pf-v5-back-to-top/demo/no-text.html deleted file mode 100644 index ae337f891f..0000000000 --- a/elements/pf-v5-back-to-top/demo/no-text.html +++ /dev/null @@ -1,41 +0,0 @@ -
-
-

No Text

-

Focusable element (top)

- Scroll down to end of cyan box, 400px (default). -
-
-Focusable element (bottom) - - - - - - diff --git a/elements/pf-v5-back-to-top/demo/scroll-distance.html b/elements/pf-v5-back-to-top/demo/scroll-distance.html deleted file mode 100644 index c3c0a8ef7a..0000000000 --- a/elements/pf-v5-back-to-top/demo/scroll-distance.html +++ /dev/null @@ -1,41 +0,0 @@ -
-
-

Default

-

Focusable element (top)

- Scroll down to end of cyan box, 200px (default). -
-
-Focusable element (bottom) - -Back to top - - - - diff --git a/elements/pf-v5-back-to-top/docs/pf-v5-back-to-top.md b/elements/pf-v5-back-to-top/docs/pf-v5-back-to-top.md deleted file mode 100644 index 23266eea16..0000000000 --- a/elements/pf-v5-back-to-top/docs/pf-v5-back-to-top.md +++ /dev/null @@ -1,110 +0,0 @@ - - -{% renderOverview %} - Back to top button is designed to only be used once per page. - Back to top -{% endrenderOverview %} - -{% band header="Usage" %} - - ### Default - {% htmlexample %}Back to top{% endhtmlexample %} - - ### Label attribute - {% htmlexample %}{% endhtmlexample %} - - ### No text or label attribute - `[aria-label]` attribute defaults to text 'Back to top' - {% htmlexample %} - - - {% endhtmlexample %} - -
- - ### Scrollable Selector - - See [scrollable-selector](#attributes) in Attributes section below for more information. - - {% htmlexample %} -
-
-
- Scroll down to end of cyan box, 400px (default). -
-
- Back to top -
- {% endhtmlexample %} - - ### Scroll Distance - - See [scroll-distance](#attributes) in Attributes section below for more information. - - {% htmlexample %} -
-
-
- Scroll down to end of cyan box, 100px. -
-
- Back to top -
- {% endhtmlexample %} - -
- -{% endband %} - -{% renderSlots %}{% endrenderSlots %} - -{% renderAttributes %}{% endrenderAttributes %} - -{% renderMethods %}{% endrenderMethods %} - -{% renderEvents %}{% endrenderEvents %} - -{% renderCssCustomProperties %}{% endrenderCssCustomProperties %} - -{% renderCssParts %}{% endrenderCssParts %} diff --git a/elements/pf-v5-back-to-top/pf-v5-back-to-top.css b/elements/pf-v5-back-to-top/pf-v5-back-to-top.css deleted file mode 100644 index 9b4960d2f0..0000000000 --- a/elements/pf-v5-back-to-top/pf-v5-back-to-top.css +++ /dev/null @@ -1,77 +0,0 @@ -:host { - display: inline-block; - position: absolute; - /** Right position for back to top */ - right: var(--pf-v5-c-back-to-top--Right, var(--pf-global--spacer--2xl, 3rem)); - /** Bottom position for back to top */ - bottom: var(--pf-v5-c-back-to-top--Bottom, var(--pf-global--spacer--lg, 1.5rem)); -} - -[part="trigger"] { - /** Box shadow for back to top button */ - box-shadow: var(--pf-v5-c-back-to-top--c-button--BoxShadow, var(--pf-global--BoxShadow--lg-bottom, 0 0.75rem 0.75rem -0.5rem rgba(3, 3, 3, 0.18))); - - /** Font size for back to top button */ - --pf-v5-c-button--FontSize: var(--pf-v5-c-back-to-top--c-button--FontSize, var(--pf-global--FontSize--xs, 0.75rem)); - /** Border radius for back to top button */ - --pf-v5-c-button--BorderRadius: var(--pf-v5-c-back-to-top--c-button--BorderRadius, var(--pf-global--BorderRadius--lg, 30em)); - /** Top padding for back to top button */ - --pf-v5-c-button--PaddingTop: var(--pf-v5-c-back-to-top--c-button--PaddingTop, var(--pf-global--spacer--xs, 0.25rem)); - /** Right padding for back to top button */ - --pf-v5-c-button--PaddingRight: var(--pf-v5-c-back-to-top--c-button--PaddingRight, var(--pf-global--spacer--sm, 0.5rem)); - /** Bottom padding for back to top button */ - --pf-v5-c-button--PaddingBottom: var(--pf-v5-c-back-to-top--c-button--PaddingBottom, var(--pf-global--spacer--xs, 0.25rem)); - /** Left padding for back to top button */ - --pf-v5-c-button--PaddingLeft: var(--pf-v5-c-back-to-top--c-button--PaddingLeft, var(--pf-global--spacer--sm, 0.5rem)); -} - -a { - display: inline-flex; - align-items: center; - justify-content: center; - /** Primary color for back to top button */ - color: var(--pf-v5-c-button--m-primary--Color, var(--pf-global--Color--light-100, #fff)); - /** Primary background color for back to top button */ - background-color: var(--pf-v5-c-button--m-primary--BackgroundColor, var(--pf-global--primary-color--100, #06c)); - text-decoration: none; - font-size: var(--pf-v5-c-button--FontSize); - padding: var(--pf-v5-c-button--PaddingTop) var(--pf-v5-c-button--PaddingRight) var(--pf-v5-c-button--PaddingBottom) var(--pf-v5-c-button--PaddingLeft); - border-radius: var(--pf-v5-c-button--BorderRadius); - /** End icon margin left for back to top button */ - gap: var(--pf-v5-c-button__icon--m-end--MarginLeft, var(--pf-global--spacer--xs, 0.25rem)); -} - -a:hover, -a:focus { - --pf-v5-c-button--m-primary--Color: var(--pf-v5-c-button--m-primary--hover--Color, var(--pf-global--Color--light-100, #fff)); - --pf-v5-c-button--m-primary--BackgroundColor: var(--pf-v5-c-button--m-primary--hover--BackgroundColor, var(--pf-global--primary-color--200, #004080)); -} - -[part="trigger"]:is(pf-v5-button):hover, -[part="trigger"]:is(pf-v5-button):focus-within { - --pf-v5-c-button--m-primary--BackgroundColor: var(--pf-v5-c-button--m-primary--hover--BackgroundColor, var(--pf-global--primary-color--200, #004080)); - --pf-v5-c-button--m-primary--Color: var(--pf-v5-c-button--m-primary--hover--Color, var(--pf-global--Color--light-100, #fff)); -} - -[part="trigger"][hidden] { - display: none; -} - -pf-v5-icon { - /* override icon size as default sm variant is incorrect */ - --pf-v5-icon--size: var(--pf-v5-c-button--FontSize); - vertical-align: -0.125rem; -} - -span { - display: inline-flex; - align-items: center; - justify-content: center; - gap: var(--pf-v5-c-button__icon--m-end--MarginLeft, var(--pf-global--spacer--xs, 0.25rem)); -} - -@media (min-width: 768px) { - :host { - --pf-v5-c-back-to-top--Bottom: var(--pf-v5-c-back-to-top--md--Bottom, var(--pf-global--spacer--2xl, 3rem)); - } -} diff --git a/elements/pf-v5-back-to-top/pf-v5-back-to-top.ts b/elements/pf-v5-back-to-top/pf-v5-back-to-top.ts deleted file mode 100644 index d4d272868a..0000000000 --- a/elements/pf-v5-back-to-top/pf-v5-back-to-top.ts +++ /dev/null @@ -1,195 +0,0 @@ -import { LitElement, html, isServer, type PropertyValues, type TemplateResult } 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 { Logger } from '@patternfly/pfe-core/controllers/logger.js'; - -import '@patternfly/elements/pf-v5-button/pf-v5-button.js'; -import '@patternfly/elements/pf-v5-icon/pf-v5-icon.js'; - -import styles from './pf-v5-back-to-top.css'; - -/** - * The **back to top** component is a shortcut that allows users to quickly navigate to the top of a lengthy content page. - * @summary A shortcut that allows users to quickly navigate to the top of a lengthy content page. - * @alias Back to Top - */ -@customElement('pf-v5-back-to-top') -export class PfV5BackToTop extends LitElement { - static readonly styles: CSSStyleSheet[] = [styles]; - - /** Shorthand for the `icon` slot, the value is icon name */ - @property({ reflect: true }) icon?: string; - - /** Icon set for the `icon` property */ - @property({ attribute: 'icon-set' }) iconSet?: string; - - /** Flag to always show back to top button, defaults to false. */ - @property({ reflect: true, type: Boolean, attribute: 'always-visible' }) alwaysVisible = false; - - /** Element selector to spy on for scrolling. Not passing a selector defaults to spying on window scroll events */ - @property({ reflect: true, attribute: 'scrollable-selector' }) scrollableSelector?: string; - - /** Distance from the top of the scrollable element to trigger the visibility of the back to top button */ - @property({ type: Number, attribute: 'scroll-distance' }) scrollDistance = 400; - - /** Accessible name for the back-to-top link, use when component does not have slotted text */ - @property() label?: string; - - /** Page fragment link to target element, must include hash ex: #top */ - @property({ reflect: true }) href?: string; - - #scrollSpy = false; - - #visible = false; - - #scrollElement?: Element | Window; - - #hasSlottedText = false; - - #logger = new Logger(this); - - get #rootNode(): Document | ShadowRoot | null { - let root = null; - if (isServer) { - return null; - } else if ((root = this.getRootNode()) instanceof Document || root instanceof ShadowRoot) { - return root; - } else { - return document; - } - } - - get #ariaLabel(): string | undefined { - if (this.#hasSlottedText) { - return undefined; - } - return this.label ?? 'Back to top'; - } - - override connectedCallback(): void { - super.connectedCallback(); - this.#addScrollListener(); - } - - override disconnectedCallback(): void { - super.disconnectedCallback(); - this.#removeScrollListener(); - } - - override willUpdate(changed: PropertyValues): void { - if (changed.has('scrollableSelector')) { - this.#addScrollListener(); - } - if (changed.has('alwaysVisible')) { - this.#toggleVisibility(); - } - } - - render(): TemplateResult<1> { - // ensure href has a hash - if (this.href && this.href.charAt(0) !== '#') { - this.href = `#${this.href}`; - this.#logger.warn(`missing hash in href fragment link`); - } - - if (this.href) { - return html` - - - - - - - - - `; - } else { - return html` - - - - - - - - - - - `; - } - } - - #onSlotchange(event: Event) { - const slot = event.currentTarget as HTMLSlotElement; - const nodes = slot.assignedNodes(); - this.#hasSlottedText = nodes.length > 0 ? true : false; - this.requestUpdate(); - } - - #removeScrollListener() { - this.#scrollElement?.removeEventListener('scroll', this.#toggleVisibility); - } - - #addScrollListener() { - this.#removeScrollListener(); - - if (this.scrollableSelector?.trim() === '') { - this.#logger.error(`scrollable-selector attribute cannot be empty`); - return; - } - - this.#scrollSpy = !!this.scrollableSelector; - if (isServer) { - return; - } else if (this.#scrollSpy && this.scrollableSelector) { - const scrollableElement = this.#rootNode?.querySelector?.(this.scrollableSelector); - if (!scrollableElement) { - this.#logger.error(`unable to find element with selector ${this.scrollableSelector}`); - return; - } - this.#scrollElement = scrollableElement; - } else { - this.#scrollElement = window; - } - - this.#scrollElement.addEventListener('scroll', this.#toggleVisibility, { passive: true }); - this.#toggleVisibility(); - } - - #toggleVisibility = () => { - if (this.alwaysVisible) { - this.#visible = true; - this.requestUpdate(); - return; - } - const previousVisibility = this.#visible; - if (this.#scrollElement) { - const scrolled = - (this.#scrollElement instanceof Window) ? - this.#scrollElement.scrollY - : this.#scrollElement.scrollTop; - this.#visible = (scrolled > this.scrollDistance); - if (previousVisibility !== this.#visible) { - this.requestUpdate(); - } - } - }; -} - -declare global { - interface HTMLElementTagNameMap { - 'pf-v5-back-to-top': PfV5BackToTop; - } -} diff --git a/elements/pf-v5-back-to-top/test/pf-back-to-top.e2e.ts b/elements/pf-v5-back-to-top/test/pf-back-to-top.e2e.ts deleted file mode 100644 index d9c618da25..0000000000 --- a/elements/pf-v5-back-to-top/test/pf-back-to-top.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-back-to-top'; - -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-back-to-top/README.md b/elements/pf-v6-back-to-top/README.md new file mode 100644 index 0000000000..17a2c79c06 --- /dev/null +++ b/elements/pf-v6-back-to-top/README.md @@ -0,0 +1,40 @@ +# Back to top + +`` is a shortcut that allows users to quickly navigate to the top of a lengthy content page. + +## Usage + +```html + +Back to top + + +Back to top + + +Back to top +``` + +## Divergences from React `BackToTop` + +### Not implemented + +| React prop | Notes | +|-------------|--------------------------------------------------------| +| `className` | Not needed; shadow DOM provides encapsulation. | + +### Changed API + +| React prop | Web component | Difference | +|---------------------|-------------------------|---------------------------------------------------------------------------| +| `title` | Default slot | React uses a `title` prop for button text; WC uses slotted content. | +| `isAlwaysVisible` | `always-visible` | Boolean attribute instead of React prop. | +| `scrollableSelector`| `scrollable-selector` | Dash-case attribute for the camelCase property. | + +### Added + +| Web component API | Notes | +|------------------------|---------------------------------------------------------------------------------------| +| `href` | When set, renders as an `` link instead of a ` + `; + } + + #onClick() { + const prefersReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches; + this.#scrollElement?.scrollTo({ + top: 0, + behavior: (prefersReducedMotion ? 'instant' : 'smooth'), + }); + } + + #onSlotchange(event: Event) { + const slot = event.currentTarget as HTMLSlotElement; + this.#hasSlottedText = slot.assignedNodes().some( + n => n.nodeType === Node.ELEMENT_NODE || !!n.textContent?.trim(), + ); + this.requestUpdate(); + } + + #removeScrollListener() { + this.#scrollElement?.removeEventListener('scroll', this.#toggleVisibility); + } + + #addScrollListener() { + this.#removeScrollListener(); + + if (this.scrollableSelector?.trim() === '') { + this.#logger.error(`scrollable-selector attribute cannot be empty`); + return; + } + + if (isServer) { + return; + } + + if (this.scrollableSelector) { + const scrollableElement = this.#rootNode?.querySelector?.(this.scrollableSelector); + if (!scrollableElement) { + this.#logger.error(`unable to find element with selector ${this.scrollableSelector}`); + return; + } + this.#scrollElement = scrollableElement; + } else { + this.#scrollElement = window; + } + + this.#scrollElement.addEventListener('scroll', this.#toggleVisibility, { passive: true }); + this.#toggleVisibility(); + } + + #toggleVisibility = () => { + if (this.alwaysVisible) { + this.#visible = true; + this.requestUpdate(); + return; + } + const previousVisibility = this.#visible; + if (this.#scrollElement) { + const scrolled = + (this.#scrollElement instanceof Window) ? + this.#scrollElement.scrollY + : this.#scrollElement.scrollTop; + this.#visible = scrolled > 400; + if (previousVisibility !== this.#visible) { + this.requestUpdate(); + } + } + }; +} + +declare global { + interface HTMLElementTagNameMap { + 'pf-v6-back-to-top': PfV6BackToTop; + } +} diff --git a/elements/pf-v5-back-to-top/test/pf-back-to-top.spec.ts b/elements/pf-v6-back-to-top/test/pf-back-to-top.spec.ts similarity index 65% rename from elements/pf-v5-back-to-top/test/pf-back-to-top.spec.ts rename to elements/pf-v6-back-to-top/test/pf-back-to-top.spec.ts index 2524318a49..c7a05ead75 100644 --- a/elements/pf-v5-back-to-top/test/pf-back-to-top.spec.ts +++ b/elements/pf-v6-back-to-top/test/pf-back-to-top.spec.ts @@ -4,44 +4,44 @@ import { setViewport, sendKeys } from '@web/test-runner-commands'; import { allUpdates } from '@patternfly/pfe-tools/test/utils.js'; -import { PfV5BackToTop } from '../pf-v5-back-to-top.js'; +import { PfV6BackToTop } from '../pf-v6-back-to-top.js'; import { a11ySnapshot } from '@patternfly/pfe-tools/test/a11y-snapshot.js'; -describe('', function() { +describe('', function() { it('imperatively instantiates', function() { - expect(document.createElement('pf-v5-back-to-top')).to.be.an.instanceof(PfV5BackToTop); + expect(document.createElement('pf-v6-back-to-top')).to.be.an.instanceof(PfV6BackToTop); }); describe('simply instantiating', function() { - let element: PfV5BackToTop; + let element: PfV6BackToTop; beforeEach(async function() { - element = await createFixture(html``); + element = await createFixture(html``); }); it('should upgrade', function() { - const klass = customElements.get('pf-v5-back-to-top'); + const klass = customElements.get('pf-v6-back-to-top'); expect(element) .to.be.an.instanceOf(klass) .and - .to.be.an.instanceOf(PfV5BackToTop); + .to.be.an.instanceOf(PfV6BackToTop); }); }); - describe('when rendered in a viewport with a height smaller then content length', function() { - let element: PfV5BackToTop; + describe('with href (link mode)', function() { + let element: PfV6BackToTop; beforeEach(async function() { await setViewport({ width: 320, height: 640 }); window.scrollTo({ top: 0, behavior: 'instant' }); await nextFrame(); - const container = await createFixture(html` + const container = await createFixture(html`
- Back to top + Back to top
`); - element = container.querySelector('pf-v5-back-to-top')!; + element = container.querySelector('pf-v6-back-to-top')!; await allUpdates(element); }); @@ -50,7 +50,7 @@ describe('', function() { expect(snapshot).to.not.axContainRole('link'); }); - it('should not be accessible', async function() { + it('should not be accessible when hidden', async function() { const snapshot = await a11ySnapshot(); expect(snapshot).to.not.axContainName('Back to top'); }); @@ -62,7 +62,7 @@ describe('', function() { await allUpdates(element); }); - it('should be visible', async function() { + it('should be visible as a link', async function() { expect(await a11ySnapshot()) .to.axContainQuery({ role: 'link', name: 'Back to top' }); }); @@ -84,7 +84,7 @@ describe('', function() { }); }); - describe('when the always visible property is true', function() { + describe('when always-visible is true', function() { beforeEach(async function() { window.scrollTo({ top: 0, behavior: 'instant' }); await nextFrame(); @@ -112,50 +112,66 @@ describe('', function() { }); }); }); + }); + + describe('without href (button mode)', function() { + let element: PfV6BackToTop; - describe('when the scroll distance is set to 1000', function() { + beforeEach(async function() { + await setViewport({ width: 320, height: 640 }); + window.scrollTo({ top: 0, behavior: 'instant' }); + await nextFrame(); + const container = await createFixture(html` +
+
+ Back to top +
+ `); + element = container.querySelector('pf-v6-back-to-top')!; + await allUpdates(element); + }); + + it('should be hidden on init', async function() { + const snapshot = await a11ySnapshot(); + expect(snapshot).to.not.axContainRole('button'); + }); + + describe('when scrolled 401px', function() { beforeEach(async function() { - element.scrollDistance = 1000; + window.scrollTo({ top: 401, behavior: 'instant' }); + await nextFrame(); await allUpdates(element); }); - it('should be hidden', async function() { - expect(await a11ySnapshot()).to.not.axContainRole('link'); + it('should be visible as a button', async function() { + expect(await a11ySnapshot()) + .to.axContainQuery({ role: 'button', name: 'Back to top' }); }); - describe('when scrolled 1001px', function() { - beforeEach(async function() { - window.scrollTo({ top: 1001, behavior: 'instant' }); - await nextFrame(); - await allUpdates(element); - }); - - it('should be visible', async function() { - expect(await a11ySnapshot()) - .to.axContainQuery({ role: 'link', name: 'Back to top' }); - }); + it('should be accessible', async function() { + await expect(element).to.be.accessible(); }); }); }); - describe('when rendered in an element with an overflowed height', function() { - let element: PfV5BackToTop; + describe('in an overflowed container with scrollable-selector', function() { + let element: PfV6BackToTop; beforeEach(async function() { window.scrollTo({ top: 0, behavior: 'instant' }); await nextFrame(); - const container = await createFixture(html` + const container = await createFixture(html`
- Back to top + Back to top
`); - element = container.querySelector('pf-v5-back-to-top')!; + element = container.querySelector('pf-v6-back-to-top')!; await allUpdates(element); }); it('should be hidden on init', async function() { - const snapshot = await a11ySnapshot({ selector: 'pf-v5-back-to-top' }); + const snapshot = await a11ySnapshot({ selector: 'pf-v6-back-to-top' }); expect(snapshot?.children).to.not.be.ok; }); @@ -176,20 +192,20 @@ describe('', function() { }); describe('when no text is provided', function() { - let element: PfV5BackToTop; - describe('as a link', function() { + let element: PfV6BackToTop; + beforeEach(async function() { await setViewport({ width: 320, height: 640 }); window.scrollTo({ top: 0, behavior: 'instant' }); await nextFrame(); - const container = await createFixture(html` + const container = await createFixture(html`
- +
`); - element = container.querySelector('pf-v5-back-to-top')!; + element = container.querySelector('pf-v6-back-to-top')!; await allUpdates(element); }); @@ -200,7 +216,7 @@ describe('', function() { await allUpdates(element); }); - it('should have a label of "Back to top"', async function() { + it('should have a default accessible label of "Back to top"', async function() { expect(await a11ySnapshot()) .to.axContainQuery({ role: 'link', name: 'Back to top' }); }); @@ -208,17 +224,19 @@ describe('', function() { }); describe('as a button', function() { + let element: PfV6BackToTop; + beforeEach(async function() { await setViewport({ width: 320, height: 640 }); window.scrollTo({ top: 0, behavior: 'instant' }); await nextFrame(); - const container = await createFixture(html` + const container = await createFixture(html`
- +
`); - element = container.querySelector('pf-v5-back-to-top')!; + element = container.querySelector('pf-v6-back-to-top')!; await allUpdates(element); }); @@ -229,7 +247,7 @@ describe('', function() { await allUpdates(element); }); - it('should have a label of "Back to top"', async function() { + it('should have a default accessible label of "Back to top"', async function() { expect(await a11ySnapshot()) .to.axContainQuery({ role: 'button', name: 'Back to top' }); }); @@ -237,21 +255,21 @@ describe('', function() { }); }); - describe('when a label is provided', function() { - let element: PfV5BackToTop; - + describe('when accessible-label is provided', function() { describe('as a link', function() { + let element: PfV6BackToTop; + beforeEach(async function() { await setViewport({ width: 320, height: 640 }); window.scrollTo({ top: 0, behavior: 'instant' }); await nextFrame(); - const container = await createFixture(html` + const container = await createFixture(html`
- +
`); - element = container.querySelector('pf-v5-back-to-top')!; + element = container.querySelector('pf-v6-back-to-top')!; await allUpdates(element); }); @@ -262,7 +280,7 @@ describe('', function() { await allUpdates(element); }); - it('should have a label of "Return to top"', async function() { + it('should have the custom label', async function() { expect(await a11ySnapshot()) .to.axContainQuery({ role: 'link', name: 'Return to top' }); }); @@ -270,17 +288,19 @@ describe('', function() { }); describe('as a button', function() { + let element: PfV6BackToTop; + beforeEach(async function() { await setViewport({ width: 320, height: 640 }); window.scrollTo({ top: 0, behavior: 'instant' }); await nextFrame(); - const container = await createFixture(html` + const container = await createFixture(html`
- +
`); - element = container.querySelector('pf-v5-back-to-top')!; + element = container.querySelector('pf-v6-back-to-top')!; await allUpdates(element); }); @@ -291,7 +311,7 @@ describe('', function() { await allUpdates(element); }); - it('should have a label of "Return to top"', async function() { + it('should have the custom label', async function() { expect(await a11ySnapshot()) .to.axContainQuery({ role: 'button', name: 'Return to top' }); }); From d8a91f42c4fde512fa31976e3fe65d46e652acab Mon Sep 17 00:00:00 2001 From: Steven Spriggs Date: Tue, 19 May 2026 12:21:18 -0400 Subject: [PATCH 2/5] fix(back-to-top): css tweak --- elements/pf-v6-back-to-top/pf-v6-back-to-top.css | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/elements/pf-v6-back-to-top/pf-v6-back-to-top.css b/elements/pf-v6-back-to-top/pf-v6-back-to-top.css index a23a17f2ce..55c0e878c0 100644 --- a/elements/pf-v6-back-to-top/pf-v6-back-to-top.css +++ b/elements/pf-v6-back-to-top/pf-v6-back-to-top.css @@ -47,6 +47,10 @@ /** Design system brand color for the trigger background. */ background-color: var(--pf-t--global--color--brand--default, #92c5f9); + &[hidden] { + display: none; + } + &:hover { /** Design system hover text color on brand surfaces. */ color: var(--pf-t--global--text--color--on-brand--hover, #1f1f1f); @@ -72,10 +76,6 @@ } } -#trigger[hidden] { - display: none; -} - svg { vertical-align: -0.125rem; } From adcfe0c298503e2bf46da44ef23f18ac5834bb4c Mon Sep 17 00:00:00 2001 From: Steven Spriggs Date: Tue, 19 May 2026 12:34:37 -0400 Subject: [PATCH 3/5] chore(back-to-top): add changeset --- .changeset/free-forks-lay.md | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 .changeset/free-forks-lay.md diff --git a/.changeset/free-forks-lay.md b/.changeset/free-forks-lay.md new file mode 100644 index 0000000000..28d581af51 --- /dev/null +++ b/.changeset/free-forks-lay.md @@ -0,0 +1,22 @@ +--- +"@patternfly/elements": major +--- + +✨ Added `` replacing ``. Back to top now follows +PatternFly v6 design specs. + +```html +Back to top +``` + +** Breaking Changes from v5 ** + +- Renamed tag from `` to `` +- ✨ Added accessible-label attribute for screen reader text +- ✨ Added delegatesFocus for keyboard accessibility +- ✨ Added prefers-reduced-motion support (instant vs smooth scroll) +- ✨ Added v6 design tokens +- CSS custom properties renamed from --pf-v5-c-back-to-top--* to --pf-v6-c-back-to-top--* +- Removed icon and icon-set attributes (caret-up icon is now built-in SVG) +- Removed scroll-distance attribute (hardcoded to 400px threshold) +- Renamed label attribute to accessible-label From 176ff9424cd7907ba8fd61c8c1fcb1220d153672 Mon Sep 17 00:00:00 2001 From: Steven Spriggs Date: Tue, 19 May 2026 15:15:50 -0400 Subject: [PATCH 4/5] docs(back-to-top): add missing docs markdown and screenshot --- .../docs/pf-v6-back-to-top.md | 40 ++++++++++++++++++ .../pf-v6-back-to-top/docs/screenshot.png | Bin 0 -> 11662 bytes 2 files changed, 40 insertions(+) create mode 100644 elements/pf-v6-back-to-top/docs/pf-v6-back-to-top.md create mode 100644 elements/pf-v6-back-to-top/docs/screenshot.png diff --git a/elements/pf-v6-back-to-top/docs/pf-v6-back-to-top.md b/elements/pf-v6-back-to-top/docs/pf-v6-back-to-top.md new file mode 100644 index 0000000000..f3732a2dba --- /dev/null +++ b/elements/pf-v6-back-to-top/docs/pf-v6-back-to-top.md @@ -0,0 +1,40 @@ +{% renderOverview %} + A back-to-top shortcut navigates to the top of a lengthy content page. + + Back to top +{% endrenderOverview %} + +{% band header="Usage" %} + ### Default + The component appears automatically when the user scrolls down. + + {% htmlexample %} + Back to top + {% endhtmlexample %} + + ### Always visible + Use `always-visible` to display the button regardless of scroll position. + + {% htmlexample %} + Back to top + {% endhtmlexample %} + + ### Link mode + Use `href` to render the component as a link instead of a button. + + {% htmlexample %} + Back to top + {% endhtmlexample %} +{% endband %} + +{% renderSlots %}{% endrenderSlots %} + +{% renderAttributes %}{% endrenderAttributes %} + +{% renderMethods %}{% endrenderMethods %} + +{% renderEvents %}{% endrenderEvents %} + +{% renderCssCustomProperties %}{% endrenderCssCustomProperties %} + +{% renderCssParts %}{% endrenderCssParts %} diff --git a/elements/pf-v6-back-to-top/docs/screenshot.png b/elements/pf-v6-back-to-top/docs/screenshot.png new file mode 100644 index 0000000000000000000000000000000000000000..ae88e5dcaf5d0a89404a3fb9ac5dfc51a460e678 GIT binary patch literal 11662 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 Date: Tue, 19 May 2026 15:16:09 -0400 Subject: [PATCH 5/5] docs(back-to-top): add missing e2e test file --- .../test/pf-v6-back-to-top.e2e.ts | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 elements/pf-v6-back-to-top/test/pf-v6-back-to-top.e2e.ts diff --git a/elements/pf-v6-back-to-top/test/pf-v6-back-to-top.e2e.ts b/elements/pf-v6-back-to-top/test/pf-v6-back-to-top.e2e.ts new file mode 100644 index 0000000000..d7c293cf99 --- /dev/null +++ b/elements/pf-v6-back-to-top/test/pf-v6-back-to-top.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-back-to-top'; + +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(); + }); +});