Skip to content

Commit

Permalink
feat(back-to-top): pf-back-to-top element (#2589)
Browse files Browse the repository at this point in the history
* feat(back-to-top): add back-to-top

* fix(back-to-top): improve accessibility

* fix(back-to-top): add href implementation, update demos

* docs(back-to-top): improve demos

* fix(back-to-top): inline-flex

* docs(back-to-top): fix fragment anchors

* fix(back-to-top): add button part to link

* docs(back-to-top): use main target for all examples

* docs(back-to-top): use main target for overview

* test(back-to-top): add basic tests

* fix(back-to-top): rename part to trigger to avoid confusion with button

* docs(back-to-top): update cssprop documentation

* docs(back-to-top): correct slot descriptions

* chore(back-to-top): add changeset

* chore: update typescript version

* chore: pat typescript on the head

* style(back-to-top): small refactors

* perf(back-to-top): prevent memory leak

* docs(back-to-top): add readme

* docs(back-to-top): update readme

* docs(back-to-top): move css link to top of demos

* fix(back-to-top): remove type assertion

* fix(back-to-top): memeber ordering

* fix(back-to-top): rename classes variable

* fix(back-to-top): update var

* test(back-to-top): improve test ordering

* fix(back-to-top): move eventlistener bindings to reactive callback

* fix(back-to-top): wait for updatecomplete to reattach listener

* fix(back-to-top): reorganizing code with suggestions from review

* refactor(back-to-top): refactor element and tests

* fix(back-to-top): add label ifDefined back accidental removal

* docs(back-to-top): fix inline doc demos

* fix(back-to-top): re-add vertical alignment for pf-icon

* docs(back-to-top): update scrollable selector inline demos

* docs(back-to-top): fix inline scroll selector demos

* docs(back-to-top): update jsdoc

* chore: remove errant changeset

* chore: readd changeset misread review comment

* docs: fix elements/readme, was incorrectly edited

* test(back-to-top): fix describe async nesting

* docs(back-to-top): remove content guidelines copy

* refactor(back-to-top): label attribute

* docs(back-to-top): clarify scrollable selector and distance usage

* test(back-to-top): add tests for label attribute

* test(back-to-top): test for keyboard accessibility

* test(back-to-top): use snapshot to check that it doesnt exist in a11y tree

* test(back-to-top): add async to a11y axe tests

---------

Co-authored-by: Benny Powers <web@bennypowers.com>
  • Loading branch information
zeroedin and bennypowers committed Dec 6, 2023
1 parent c71bbe5 commit 22d7536
Show file tree
Hide file tree
Showing 22 changed files with 1,001 additions and 115 deletions.
9 changes: 9 additions & 0 deletions .changeset/khaki-bananas-perform.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
"@patternfly/elements": minor
---

✨ Added `<pf-back-to-top>`

```html
<pf-back-to-top href="#top" scrollable-selector="main">Back to Top</pf-back-to-top>
```
4 changes: 4 additions & 0 deletions .changeset/pfe-tools-ts52.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
---
"@patternfly/pfe-tools": patch
---
Update typescript version
2 changes: 1 addition & 1 deletion elements/README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# PatternFly Elements

See [PatternFly Elements Docs](https://patternflyelements.org) for more
See [PatternFly Elements Docs](https://patternflyelements.org) for more
information.
1 change: 1 addition & 0 deletions elements/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
"./pf-accordion/pf-accordion.js": "./pf-accordion/pf-accordion.js",
"./pf-avatar/BaseAvatar.js": "./pf-avatar/BaseAvatar.js",
"./pf-avatar/pf-avatar.js": "./pf-avatar/pf-avatar.js",
"./pf-back-to-top/pf-back-to-top.js": "./pf-back-to-top/pf-back-to-top.js",
"./pf-background-image/pf-background-image.js": "./pf-background-image/pf-background-image.js",
"./pf-badge/BaseBadge.js": "./pf-badge/BaseBadge.js",
"./pf-badge/pf-badge.js": "./pf-badge/pf-badge.js",
Expand Down
32 changes: 32 additions & 0 deletions elements/pf-back-to-top/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# 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 `<pf-back-to-top>` via CDN:

```html
<script src="https://jspm.dev/@patternfly/elements/pf-back-to-top/pf-back-to-top.js"></script>


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-back-to-top/pf-back-to-top.js';
```

## Usage

```html
<pf-back-to-top href="#top">Back to Top</pf-back-to-top>

```

[docs]: https://patternflyelements.org/components/back-to-top
12 changes: 12 additions & 0 deletions elements/pf-back-to-top/demo/always-visible.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<link rel="stylesheet" href="demo.css">

<div class="outer-container padded" id="top">
<h2>Always visible</h2>
<a href="#focusable-element-top" id="top">Focusable element (top)</a>
</div>

<pf-back-to-top always-visible href="#top">Back to top</pf-back-to-top>

<script type="module">
import '@patternfly/elements/pf-back-to-top/pf-back-to-top.js';
</script>
22 changes: 22 additions & 0 deletions elements/pf-back-to-top/demo/button-no-text.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<link rel="stylesheet" href="demo.css">

<div class="outer-container" id="top">
<div class="scroll-indicator padded">
<h2>Button No Text</h2>
<p><a href="#focusable-element-top">Focusable element (top)</a></p>
<pf-icon icon="arrow-down"></pf-icon> Scroll down to end of cyan box, 400px (default).
</div>
</div>
<a href="#focusable-element-bottom">Focusable element (bottom)</a>

<pf-back-to-top scrollable-selector="main"></pf-back-to-top>

<script type="module">
import '@patternfly/elements/pf-back-to-top/pf-back-to-top.js';
document.querySelector('pf-back-to-top').addEventListener('click', function() {
// scroll to some element
const target = document.querySelector('#top');
target.scrollIntoView();
target.focus();
});
</script>
27 changes: 27 additions & 0 deletions elements/pf-back-to-top/demo/button.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<link rel="stylesheet" href="demo.css">

<pf-banner variant="info" id="top">
<pf-icon icon="info-circle" slot="icon"></pf-icon>
<strong>Accessibility Warning</strong> Using the Button/JS variant, implementation must apply click event and focus to the element that is scrolled to.
</pf-banner>
<div class="outer-container">
<div class="scroll-indicator padded">
<h2>Button</h2>
<p><a href="#focusable-element-top">Focusable element (top)</a></p>
<pf-icon icon="arrow-down"></pf-icon> Scroll down to end of cyan box, 400px (default).
</div>
</div>
<a href="#focusable-element-bottom">Focusable element (bottom)</a>

<pf-back-to-top scrollable-selector="main">Back to top</pf-back-to-top>

<script type="module">
import '@patternfly/elements/pf-back-to-top/pf-back-to-top.js';
import '@patternfly/elements/pf-banner/pf-banner.js';
document.querySelector('pf-back-to-top').addEventListener('click', function() {
// scroll to some element
const target = document.querySelector('#top');
target.scrollIntoView();
target.focus();
});
</script>
25 changes: 25 additions & 0 deletions elements/pf-back-to-top/demo/demo.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
:root {
--_scroll-distance: 400px;
}

main {
scroll-behavior: smooth;
}

.scroll-distance {
--_scroll-distance: 200px;
}

.outer-container {
height: calc(100vh - var(--pf-demo-header-height) + var(--_scroll-distance));
}

.padded {
padding: var(--pf-global--spacer--md, 1rem);
}

.scroll-indicator {
height: var(--_scroll-distance);
background-color: var(--pf-global--palette--cyan-50, #f2f9f9) !important;
}

16 changes: 16 additions & 0 deletions elements/pf-back-to-top/demo/label.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<link rel="stylesheet" href="demo.css">

<div class="outer-container" id="top">
<div class="scroll-indicator padded">
<h2>Default</h2>
<p><a href="#focusable-element-top">Focusable element (top)</a></p>
<pf-icon icon="arrow-down"></pf-icon> Scroll down to end of cyan box, 400px (default).
</div>
</div>
<a href="#focusable-element-bottom">Focusable element (bottom)</a>

<pf-back-to-top scrollable-selector="main" href="#top" label="Top"></pf-back-to-top>

<script type="module">
import '@patternfly/elements/pf-back-to-top/pf-back-to-top.js';
</script>
16 changes: 16 additions & 0 deletions elements/pf-back-to-top/demo/no-text.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<link rel="stylesheet" href="demo.css">

<div class="outer-container" id="top">
<div class="scroll-indicator padded">
<h2>No Text</h2>
<p><a href="#focusable-element-top">Focusable element (top)</a></p>
<pf-icon icon="arrow-down"></pf-icon> Scroll down to end of cyan box, 400px (default).
</div>
</div>
<a href="#focusable-element-bottom">Focusable element (bottom)</a>

<pf-back-to-top scrollable-selector="main" href="top"></pf-back-to-top>

<script type="module">
import '@patternfly/elements/pf-back-to-top/pf-back-to-top.js';
</script>
16 changes: 16 additions & 0 deletions elements/pf-back-to-top/demo/pf-back-to-top.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<link rel="stylesheet" href="demo.css">

<div class="outer-container" id="top">
<div class="scroll-indicator padded">
<h2>Default</h2>
<p><a href="#focusable-element-top">Focusable element (top)</a></p>
<pf-icon icon="arrow-down"></pf-icon> Scroll down to end of cyan box, 400px (default).
</div>
</div>
<a href="#focusable-element-bottom">Focusable element (bottom)</a>

<pf-back-to-top scrollable-selector="main" href="#top">Back to top</pf-back-to-top>

<script type="module">
import '@patternfly/elements/pf-back-to-top/pf-back-to-top.js';
</script>
16 changes: 16 additions & 0 deletions elements/pf-back-to-top/demo/scroll-distance.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<link rel="stylesheet" href="demo.css">

<div class="outer-container scroll-distance" id="top">
<div class="scroll-indicator padded">
<h2>Default</h2>
<p><a href="#focusable-element-top" id="top">Focusable element (top)</a></p>
<pf-icon icon="arrow-down"></pf-icon> Scroll down to end of cyan box, 200px (default).
</div>
</div>
<a href="#focusable-element-bottom">Focusable element (bottom)</a>

<pf-back-to-top href="#top" scrollable-selector="main" scroll-distance="200">Back to top</pf-back-to-top>

<script type="module">
import '@patternfly/elements/pf-back-to-top/pf-back-to-top.js';
</script>
110 changes: 110 additions & 0 deletions elements/pf-back-to-top/docs/pf-back-to-top.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
<style>
:not(.override) > .example-preview pf-back-to-top {
position: sticky !important;
left: 100%;
bottom: 0;
}

:not(.override) > .example-preview pf-back-to-top::part(trigger) {
display: inline-block !important;
}

.override > .example-preview :is(#scrollable-selector-example, #scroll-distance-example) {
position: relative;
height: 200px;
overflow-y: scroll;
}

.override > .example-preview :is(#scrollable-selector-example, #scroll-distance-example) pf-back-to-top {
position: sticky !important;
left: 100%;
bottom: 0;
}

.overflow {
height: 573px;
position: relative;
}

.scroll-indicator {
padding: var(--pf-global--spacer--md, 1rem);
background-color: var(--pf-global--palette--cyan-50, #f2f9f9) !important;
}

#scrollable-selector-example .scroll-indicator {
height: 400px;
}

#scroll-distance-example .scroll-indicator {
height: 100px;
}

</style>

{% renderOverview %}
Back to top button is designed to only be used once per page.
<pf-back-to-top href="#main">Back to top</pf-back-to-top>
{% endrenderOverview %}

{% band header="Usage" %}

### Default
{% htmlexample %}<pf-back-to-top href="#main">Back to top</pf-back-to-top>{% endhtmlexample %}

### Label attribute
{% htmlexample %}<pf-back-to-top href="#main" label="Return to top"></pf-back-to-top>{% endhtmlexample %}

### No text or label attribute
`[aria-label]` attribute defaults to text 'Back to top'
{% htmlexample %}

<pf-back-to-top href="#main"></pf-back-to-top>
{% endhtmlexample %}

<div class="override">

### Scrollable Selector

See [scrollable-selector](#attributes) in Attributes section below for more information.

{% htmlexample %}
<div id="scrollable-selector-example">
<div class="overflow" tabindex="0">
<div class="scroll-indicator">
<pf-icon icon="arrow-down"></pf-icon> Scroll down to end of cyan box, 400px (default).
</div>
</div>
<pf-back-to-top href="#main" scrollable-selector="#scrollable-selector-example">Back to top</pf-back-to-top>
</div>
{% endhtmlexample %}

### Scroll Distance

See [scroll-distance](#attributes) in Attributes section below for more information.

{% htmlexample %}
<div id="scroll-distance-example">
<div class="overflow" tabindex="0">
<div class="scroll-indicator">
<pf-icon icon="arrow-down"></pf-icon> Scroll down to end of cyan box, 100px.
</div>
</div>
<pf-back-to-top href="#main" scroll-distance="100" scrollable-selector="#scroll-distance-example">Back to top</pf-back-to-top>
</div>
{% endhtmlexample %}

</div>

{% endband %}

{% renderSlots %}{% endrenderSlots %}

{% renderAttributes %}{% endrenderAttributes %}

{% renderMethods %}{% endrenderMethods %}

{% renderEvents %}{% endrenderEvents %}

{% renderCssCustomProperties %}{% endrenderCssCustomProperties %}

{% renderCssParts %}{% endrenderCssParts %}
53 changes: 53 additions & 0 deletions elements/pf-back-to-top/pf-back-to-top.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
:host {
display: inline-block;
position: absolute;
right: var(--pf-c-back-to-top--Right, var(--pf-global--spacer--2xl, 3rem));
bottom: var(--pf-c-back-to-top--Bottom, var(--pf-global--spacer--lg, 1.5rem));
}

[part="trigger"] {
box-shadow: var(--pf-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)));

--pf-c-button--FontSize: var(--pf-c-back-to-top--c-button--FontSize, var(--pf-global--FontSize--xs, 0.75rem));
--pf-c-button--BorderRadius: var(--pf-c-back-to-top--c-button--BorderRadius, var(--pf-global--BorderRadius--lg, 30em));
--pf-c-button--PaddingTop: var(--pf-c-back-to-top--c-button--PaddingTop, var(--pf-global--spacer--xs, 0.25rem));
--pf-c-button--PaddingRight: var(--pf-c-back-to-top--c-button--PaddingRight, var(--pf-global--spacer--sm, 0.5rem));
--pf-c-button--PaddingBottom: var(--pf-c-back-to-top--c-button--PaddingBottom, var(--pf-global--spacer--xs, 0.25rem));
--pf-c-button--PaddingLeft: var(--pf-c-back-to-top--c-button--PaddingLeft, var(--pf-global--spacer--sm, 0.5rem));
}

a {
display: inline-flex;
align-items: center;
justify-content: center;
color: var(--pf-c-button--m-primary--Color, var(--pf-global--Color--light-100, #fff));
background-color: var(--pf-c-button--m-primary--BackgroundColor, var(--pf-global--primary-color--100, #06c));
text-decoration: none;
font-size: var(--pf-c-button--FontSize);
padding: var(--pf-c-button--PaddingTop) var(--pf-c-button--PaddingRight) var(--pf-c-button--PaddingBottom) var(--pf-c-button--PaddingLeft);
border-radius: var(--pf-c-button--BorderRadius);
gap: var(--pf-c-button__icon--m-end--MarginLeft, var(--pf-global--spacer--xs, 0.25rem));
}

[part="trigger"][hidden] {
display: none;
}

pf-icon {
/* override icon size as default sm variant is incorrect */
--pf-icon--size: var(--pf-c-button--FontSize);
vertical-align: -0.125rem;
}

span {
display: inline-flex;
align-items: center;
justify-content: center;
gap: var(--pf-c-button__icon--m-end--MarginLeft, var(--pf-global--spacer--xs, 0.25rem));
}

@media (min-width: 768px) {
:host {
--pf-c-back-to-top--Bottom: var(--pf-c-back-to-top--md--Bottom, var(--pf-global--spacer--2xl, 3rem));
}
}

0 comments on commit 22d7536

Please sign in to comment.