Skip to content

Commit

Permalink
Feature/MARP-1317 Add back to top button (#228)
Browse files Browse the repository at this point in the history
Co-authored-by: Hoang Vu Huy <[email protected]>
  • Loading branch information
vhhoang-axonivy and Hoang Vu Huy authored Nov 11, 2024
1 parent 616424f commit 48ff426
Show file tree
Hide file tree
Showing 6 changed files with 138 additions and 1 deletion.
1 change: 1 addition & 0 deletions marketplace-ui/src/app/app.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
<div class="container">
<router-outlet />
</div>
<app-back-to-top></app-back-to-top>
</main>
@if (!routingQueryParamService.isDesignerEnv()) {
<footer>
Expand Down
3 changes: 2 additions & 1 deletion marketplace-ui/src/app/app.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,12 @@ import {
Event
} from '@angular/router';
import { LoadingSpinnerComponent } from "./shared/components/loading-spinner/loading-spinner.component";
import { BackToTopComponent } from "./shared/components/back-to-top/back-to-top.component";

@Component({
selector: 'app-root',
standalone: true,
imports: [RouterOutlet, HeaderComponent, FooterComponent, CommonModule, LoadingSpinnerComponent],
imports: [RouterOutlet, HeaderComponent, FooterComponent, CommonModule, LoadingSpinnerComponent, BackToTopComponent],
templateUrl: './app.component.html',
styleUrl: './app.component.scss'
})
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<button class="scroll-to-top" aria-label="Back to top" type="button" [ngClass]="{'show': showScrollButton}">
<i id="scroll-to-top-button" class="fa-solid fa-circle-chevron-up" (click)="scrollToTop()"></i>
</button>
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
.scroll-to-top {
position: fixed;
bottom: 15px;
right: 15px;
opacity: 0;
transition: all .2s ease-in-out;
border: none;
background: none;
cursor: default;
}

#scroll-to-top-button {
font-size: 4rem;
color: var(--ivy-active-color);
}

.show {
opacity: 1;
transition: all .2s ease-in-out;
cursor: pointer;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { BackToTopComponent } from './back-to-top.component';
import { By } from '@angular/platform-browser';

describe('BackToTopComponent', () => {
let component: BackToTopComponent;
let fixture: ComponentFixture<BackToTopComponent>;

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

fixture = TestBed.createComponent(BackToTopComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});

it('should create', () => {
expect(component).toBeTruthy();
});

it('should show the button when scroll threshold is reached', () => {
// Simulate scrolling past the threshold
window.scrollY = component.backToTopShowThreshold + 1;
window.dispatchEvent(new Event('scroll'));

expect(component.showScrollButton).toBeTrue();
});

it('should hide the button when scrolling above the threshold', () => {
// Simulate scrolling past the threshold, then scroll back up
window.scrollY = component.backToTopShowThreshold + 1;
window.dispatchEvent(new Event('scroll'));
expect(component.showScrollButton).toBeTrue();

// Scroll up above the threshold
window.scrollY = component.backToTopShowThreshold - 1;
window.dispatchEvent(new Event('scroll'));
expect(component.showScrollButton).toBeFalse();
});

it('should scroll to top when button is clicked and showScrollButton is true', () => {
const scrollToSpy = spyOn<Window>(window, 'scrollTo');

const mockScrollOption = {
top: 0,
behavior: "smooth"
}
component.showScrollButton = true;
component.scrollToTop();

expect(scrollToSpy.calls.argsFor(0)).toEqual([mockScrollOption]);
});

it('should not scroll to top when button is clicked and showScrollButton is false', () => {
const scrollToSpy = spyOn(window, 'scrollTo');
component.showScrollButton = false;
component.scrollToTop();

expect(scrollToSpy).not.toHaveBeenCalled();
});

it('should render the button when showScrollButton is true', () => {
component.showScrollButton = true;
fixture.detectChanges();

const scrollToTopButtonElement = fixture.debugElement.query(By.css('.scroll-to-top'));
expect(getComputedStyle(scrollToTopButtonElement.nativeElement).opacity).toEqual('1');
});

it('should not render the button when showScrollButton is false', () => {
component.showScrollButton = false;
fixture.detectChanges();

const scrollToTopButtonElement = fixture.debugElement.query(By.css('.scroll-to-top'));
expect(getComputedStyle(scrollToTopButtonElement.nativeElement).opacity).toEqual('0');
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { CommonModule, DOCUMENT } from '@angular/common';
import { Component, HostListener, Inject } from '@angular/core';
import { TranslateModule } from '@ngx-translate/core';

@Component({
selector: 'app-back-to-top',
standalone: true,
imports: [
CommonModule,
TranslateModule,
],
templateUrl: './back-to-top.component.html',
styleUrl: './back-to-top.component.scss'
})
export class BackToTopComponent {
backToTopShowThreshold: number = 500;
scrollBehavior: ScrollBehavior = 'smooth';
showScrollButton: boolean = false;

@HostListener("window:scroll", [])
onWindowScroll() {
const isWindowScrollTopOverThreshold = window.scrollY >= this.backToTopShowThreshold;
const isDocumentScrollTopOverThreshold = document.documentElement.scrollTop >= this.backToTopShowThreshold;
this.showScrollButton = isWindowScrollTopOverThreshold || isDocumentScrollTopOverThreshold;
}

scrollToTop() {
if (this.showScrollButton) {
window.scrollTo({ top: 0, behavior: this.scrollBehavior });
}
}
}

0 comments on commit 48ff426

Please sign in to comment.