Skip to content

Commit

Permalink
feat: add Back To Top button (#1180)
Browse files Browse the repository at this point in the history
* changed button styling to an icon button
* fix e2e test concerning scroll behavior smooth

Co-authored-by: Stefan Hauke <s.hauke@intershop.de>
Co-authored-by: Silke <s.grueber@intershop.de>
  • Loading branch information
3 people authored Aug 11, 2022
1 parent c7d8d00 commit 27f1f27
Show file tree
Hide file tree
Showing 13 changed files with 150 additions and 3 deletions.
4 changes: 2 additions & 2 deletions e2e/cypress/e2e/framework/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,9 @@ export function fillFormField(parent: string, key: string, value: number | strin
cy.get(parent).within(() => {
if (/^(INPUT|TEXTAREA)$/.test(tagName)) {
const inputField = cy.get(`[data-testing-id="${key}"]`);
inputField.clear();
inputField.focus().clear();
if (value) {
inputField.focus().type(value.toString());
inputField.type(value.toString());
}
} else if (tagName === 'SELECT') {
if (typeof value === 'number') {
Expand Down
6 changes: 6 additions & 0 deletions e2e/cypress/support/commands.js
Original file line number Diff line number Diff line change
Expand Up @@ -74,3 +74,9 @@ Cypress.on('uncaught:exception', (err, runnable) => {
// failing the test
return false;
});

// mark the html tag with a Cypress specific CSS class for Cypress context specific styling
Cypress.on('window:before:load', win => {
const htmlNode = win.document.querySelector('html');
htmlNode.classList.add('cypress-tests');
});
9 changes: 9 additions & 0 deletions src/app/shell/header/back-to-top/back-to-top.component.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<button
type="button"
class="btn btn-primary shadow back-to-top-btn"
[style.display]="isVisible ? 'block' : 'none'"
[title]="'back_to_top.title' | translate"
(click)="jump()"
>
<fa-icon [icon]="['fas', 'angle-up']"></fa-icon>
</button>
31 changes: 31 additions & 0 deletions src/app/shell/header/back-to-top/back-to-top.component.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { FaIconComponent } from '@fortawesome/angular-fontawesome';
import { TranslateModule } from '@ngx-translate/core';
import { MockComponent } from 'ng-mocks';

import { BackToTopComponent } from './back-to-top.component';

describe('Back To Top Component', () => {
let component: BackToTopComponent;
let fixture: ComponentFixture<BackToTopComponent>;
let element: HTMLElement;

beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [TranslateModule.forRoot()],
declarations: [BackToTopComponent, MockComponent(FaIconComponent)],
}).compileComponents();
});

beforeEach(() => {
fixture = TestBed.createComponent(BackToTopComponent);
component = fixture.componentInstance;
element = fixture.nativeElement;
});

it('should be created', () => {
expect(component).toBeTruthy();
expect(element).toBeTruthy();
expect(() => fixture.detectChanges()).not.toThrow();
});
});
59 changes: 59 additions & 0 deletions src/app/shell/header/back-to-top/back-to-top.component.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { ChangeDetectionStrategy, Component, HostListener } from '@angular/core';

@Component({
selector: 'ish-back-to-top',
templateUrl: './back-to-top.component.html',
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class BackToTopComponent {
/**
* @description
* Button will not show if window.scrollY is less than MARGIN_TOP.
* Should be any desired value > 0
*/
private readonly MARGIN_TOP = 50;

/**
* @description
* Button will show if user has scrolled at least SCROLL_MIN upwards.
* If set to 0 the button will show as soon as user scrolls upwards
*/
private readonly SCROLL_MIN = 50;

isVisible = false;
private previousOffset = 0;

jump() {
window.scrollTo(0, 0);
}

private hide() {
this.isVisible = false;
}

private show() {
this.isVisible = true;
}

private updateOffset() {
this.previousOffset = window.scrollY;
}

@HostListener('window:scroll') onWindowScroll() {
const diff = this.previousOffset - window.scrollY;

const scrollsDown = diff < 0;
const isAtTop = window.scrollY < this.MARGIN_TOP;
const hasEnoughIntention = diff > this.SCROLL_MIN;

if (scrollsDown || isAtTop) {
this.updateOffset();
this.hide();
} else if (!this.isVisible && !hasEnoughIntention) {
this.hide();
} else {
this.updateOffset();
this.show();
}
}
}
2 changes: 2 additions & 0 deletions src/app/shell/header/header/header.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,5 @@
></ish-header-default>
</ng-container>
</ng-container>

<ish-back-to-top></ish-back-to-top>
10 changes: 9 additions & 1 deletion src/app/shell/header/header/header.component.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { instance, mock, when } from 'ts-mockito';

import { AppFacade } from 'ish-core/facades/app.facade';
import { findAllCustomElements } from 'ish-core/utils/dev/html-query-utils';
import { BackToTopComponent } from 'ish-shell/header/back-to-top/back-to-top.component';
import { HeaderDefaultComponent } from 'ish-shell/header/header-default/header-default.component';
import { HeaderSimpleComponent } from 'ish-shell/header/header-simple/header-simple.component';

Expand All @@ -23,7 +24,12 @@ describe('Header Component', () => {

await TestBed.configureTestingModule({
imports: [RouterTestingModule],
declarations: [HeaderComponent, MockComponent(HeaderDefaultComponent), MockComponent(HeaderSimpleComponent)],
declarations: [
HeaderComponent,
MockComponent(BackToTopComponent),
MockComponent(HeaderDefaultComponent),
MockComponent(HeaderSimpleComponent),
],
providers: [{ provide: AppFacade, useFactory: () => instance(appFacade) }],
}).compileComponents();
});
Expand All @@ -45,6 +51,7 @@ describe('Header Component', () => {
expect(findAllCustomElements(element)).toMatchInlineSnapshot(`
Array [
"ish-header-default",
"ish-back-to-top",
]
`);
});
Expand All @@ -55,6 +62,7 @@ describe('Header Component', () => {
expect(findAllCustomElements(element)).toMatchInlineSnapshot(`
Array [
"ish-header-simple",
"ish-back-to-top",
]
`);
});
Expand Down
2 changes: 2 additions & 0 deletions src/app/shell/shell.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import { WishlistsExportsModule } from '../extensions/wishlists/exports/wishlist

import { CookiesBannerComponent } from './application/cookies-banner/cookies-banner.component';
import { FooterComponent } from './footer/footer/footer.component';
import { BackToTopComponent } from './header/back-to-top/back-to-top.component';
import { HeaderCheckoutComponent } from './header/header-checkout/header-checkout.component';
import { HeaderDefaultComponent } from './header/header-default/header-default.component';
import { HeaderNavigationComponent } from './header/header-navigation/header-navigation.component';
Expand Down Expand Up @@ -61,6 +62,7 @@ const exportedComponents = [CookiesBannerComponent, FooterComponent, HeaderCompo
],
declarations: [
...exportedComponents,
BackToTopComponent,
CookiesBannerComponent,
HeaderCheckoutComponent,
HeaderDefaultComponent,
Expand Down
1 change: 1 addition & 0 deletions src/assets/i18n/de_DE.json
Original file line number Diff line number Diff line change
Expand Up @@ -564,6 +564,7 @@
"approval.rejectform.button.reject.label": "Ablehnen",
"approval.rejectform.invalid_comment.error": "Ein Kommentar muss angegeben werden.",
"approval.rejectform.reject_order.heading": "Bestellanfrage ablehnen",
"back_to_top.title": "Nach oben scrollen",
"basket.add_quote.error": "Das Preisangebot konnte nicht in den Warenkorb gelegt werden.",
"basket.add_quotelineitem.error": "Das Preisangebot kann nicht in den Warenkorb gelegt werden. Es enthält ungültige Produkte.",
"basket.not_found.error": "Der Warenkorb konnte nicht gefunden werden.",
Expand Down
1 change: 1 addition & 0 deletions src/assets/i18n/en_US.json
Original file line number Diff line number Diff line change
Expand Up @@ -564,6 +564,7 @@
"approval.rejectform.button.reject.label": "Reject",
"approval.rejectform.invalid_comment.error": "Comment is required and cannot be empty",
"approval.rejectform.reject_order.heading": "Reject Requisition",
"back_to_top.title": "Scroll back to the top",
"basket.add_quote.error": "The quote could not be added to the cart.",
"basket.add_quotelineitem.error": "The quote cannot be added to the shopping cart. It contains invalid products.",
"basket.not_found.error": "The shopping cart could not be found.",
Expand Down
1 change: 1 addition & 0 deletions src/assets/i18n/fr_FR.json
Original file line number Diff line number Diff line change
Expand Up @@ -564,6 +564,7 @@
"approval.rejectform.button.reject.label": "Rejeter",
"approval.rejectform.invalid_comment.error": "Le commentaire est obligatoire et ne peut pas être vide",
"approval.rejectform.reject_order.heading": "Rejeter la demande d’achat",
"back_to_top.title": "Revenir en haut",
"basket.add_quote.error": "Le devis n’a pas pu être ajouté au panier.",
"basket.add_quotelineitem.error": "Le devis ne peut pas être ajouté au panier. Il contient des produits non valides.",
"basket.not_found.error": "Le panier n’a pas pu être trouvé.",
Expand Down
26 changes: 26 additions & 0 deletions src/styles/components/header/back-to-top.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// apply scroll-behavior except when running in Cypress context (it breaks a lot of tests)
html:not(.cypress-tests) {
scroll-behavior: smooth;
}

@keyframes back-to-top-btn-animation {
from {
opacity: 0;
}

to {
opacity: 1;
}
}

.back-to-top-btn {
position: fixed;
right: $space-default;
bottom: $space-default;
z-index: $zindex-fixed;
animation: back-to-top-btn-animation 0.15s ease-in-out;

@media (max-width: $screen-xs-max) {
width: auto;
}
}
1 change: 1 addition & 0 deletions src/styles/main.scss
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@
@import 'components/header/main-navigation';
@import 'components/header/header-sticky';
@import 'components/header/user-information-mobile';
@import 'components/header/back-to-top';
@import 'components/footer/footer';
@import 'components/common/enhanced-image';
@import 'components/common/video';
Expand Down

0 comments on commit 27f1f27

Please sign in to comment.