From ce1a0f65d0bfcc165d0610c8dddbf73ed137361b Mon Sep 17 00:00:00 2001 From: sdrozdsap <163305268+sdrozdsap@users.noreply.github.com> Date: Thu, 5 Dec 2024 10:07:18 +0100 Subject: [PATCH] fix: elevate scroll to top button when overlaps with banner (#19711) --- .../feature-toggles/config/feature-toggles.ts | 7 +++++++ .../app/spartacus/spartacus-features.module.ts | 1 + .../scroll-to-top/scroll-to-top.component.html | 1 + .../scroll-to-top.component.spec.ts | 11 +++++++++++ .../scroll-to-top/scroll-to-top.component.ts | 18 ++++++++++++++++-- .../content/navigation/_scroll-to-top.scss | 6 ++++++ 6 files changed, 42 insertions(+), 2 deletions(-) diff --git a/projects/core/src/features-config/feature-toggles/config/feature-toggles.ts b/projects/core/src/features-config/feature-toggles/config/feature-toggles.ts index 9881d271bc3..8f50a75d697 100644 --- a/projects/core/src/features-config/feature-toggles/config/feature-toggles.ts +++ b/projects/core/src/features-config/feature-toggles/config/feature-toggles.ts @@ -829,6 +829,12 @@ export interface FeatureTogglesInterface { */ showRealTimeStockInPDP?: boolean; + /** + * When enabled, the scroll-to-top button adjusts its position when other UI elements + * (like cookie consent banner) appear at the bottom of the page to prevent overlapping + */ + a11yScrollToTopPositioning?: boolean; + /** * Creates a section element with applied aria-label in "Review Order" page of the checkout. * Moves components to be children of this section element. @@ -964,6 +970,7 @@ export const defaultFeatureToggles: Required = { allPageMetaResolversEnabledInCsr: false, a11yPdpGridArrangement: false, useExtendedMediaComponentConfiguration: false, + a11yScrollToTopPositioning: false, showRealTimeStockInPDP: false, enableSecurePasswordValidation: false, }; diff --git a/projects/storefrontapp/src/app/spartacus/spartacus-features.module.ts b/projects/storefrontapp/src/app/spartacus/spartacus-features.module.ts index bf600ccebb4..b0a753cedd8 100644 --- a/projects/storefrontapp/src/app/spartacus/spartacus-features.module.ts +++ b/projects/storefrontapp/src/app/spartacus/spartacus-features.module.ts @@ -413,6 +413,7 @@ if (environment.cpq) { allPageMetaResolversEnabledInCsr: true, a11yPdpGridArrangement: true, useExtendedMediaComponentConfiguration: true, + a11yScrollToTopPositioning: true, showRealTimeStockInPDP: false, a11yWrapReviewOrderInSection: true, enableSecurePasswordValidation: true, diff --git a/projects/storefrontlib/cms-components/navigation/scroll-to-top/scroll-to-top.component.html b/projects/storefrontlib/cms-components/navigation/scroll-to-top/scroll-to-top.component.html index 5da35d2699b..8dfb220c8a3 100644 --- a/projects/storefrontlib/cms-components/navigation/scroll-to-top/scroll-to-top.component.html +++ b/projects/storefrontlib/cms-components/navigation/scroll-to-top/scroll-to-top.component.html @@ -3,6 +3,7 @@ [attr.aria-label]="'navigation.scrollToTop' | cxTranslate" [title]="'navigation.scrollToTop' | cxTranslate" class="cx-scroll-to-top-btn" + [class.elevated-position]="elevatedPosition$ | async" (click)="scrollToTop($event)" (focusout)="onFocusOut()" (keydown.Tab)="onTab($event)" diff --git a/projects/storefrontlib/cms-components/navigation/scroll-to-top/scroll-to-top.component.spec.ts b/projects/storefrontlib/cms-components/navigation/scroll-to-top/scroll-to-top.component.spec.ts index 1c8cb9ca0ab..492243b83bf 100644 --- a/projects/storefrontlib/cms-components/navigation/scroll-to-top/scroll-to-top.component.spec.ts +++ b/projects/storefrontlib/cms-components/navigation/scroll-to-top/scroll-to-top.component.spec.ts @@ -2,6 +2,7 @@ import { DebugElement } from '@angular/core'; import { ComponentFixture, TestBed } from '@angular/core/testing'; import { By } from '@angular/platform-browser'; import { + AnonymousConsentsService, CmsScrollToTopComponent, FeatureConfigService, I18nTestingModule, @@ -28,6 +29,12 @@ class MockFeatureConfigService { } } +class MockAnonymousConsentsService { + isBannerVisible() { + return of(false); + } +} + describe('ScrollToTopComponent', () => { let component: ScrollToTopComponent; let fixture: ComponentFixture; @@ -48,6 +55,10 @@ describe('ScrollToTopComponent', () => { provide: FeatureConfigService, useClass: MockFeatureConfigService, }, + { + provide: AnonymousConsentsService, + useClass: MockAnonymousConsentsService, + }, ], }).compileComponents(); diff --git a/projects/storefrontlib/cms-components/navigation/scroll-to-top/scroll-to-top.component.ts b/projects/storefrontlib/cms-components/navigation/scroll-to-top/scroll-to-top.component.ts index 2a450ff2af8..99d9655b136 100644 --- a/projects/storefrontlib/cms-components/navigation/scroll-to-top/scroll-to-top.component.ts +++ b/projects/storefrontlib/cms-components/navigation/scroll-to-top/scroll-to-top.component.ts @@ -20,8 +20,10 @@ import { FeatureConfigService, ScrollBehavior, WindowRef, + AnonymousConsentsService, + useFeatureStyles, } from '@spartacus/core'; -import { take } from 'rxjs/operators'; +import { take, Observable } from 'rxjs'; import { CmsComponentData } from '../../../cms-structure/page/model/cms-component-data'; import { SelectFocusUtility } from '../../../layout/a11y/index'; import { ICON_TYPE } from '../../misc/icon/icon.model'; @@ -37,6 +39,7 @@ export class ScrollToTopComponent implements OnInit { @HostBinding('class.display') display: boolean | undefined; + protected elevatedPosition$: Observable | undefined; protected window: Window | undefined = this.winRef.nativeWindow; protected scrollBehavior: ScrollBehavior = ScrollBehavior.SMOOTH; protected displayThreshold: number = (this.window?.innerHeight ?? 400) / 2; @@ -49,15 +52,26 @@ export class ScrollToTopComponent implements OnInit { @Optional() protected featureConfigService = inject(FeatureConfigService, { optional: true, }); + @Optional() protected anonymousConsentsService = inject( + AnonymousConsentsService, + { + optional: true, + } + ); constructor( protected winRef: WindowRef, protected componentData: CmsComponentData, protected selectFocusUtility: SelectFocusUtility - ) {} + ) { + useFeatureStyles('a11yScrollToTopPositioning'); + } ngOnInit(): void { this.setConfig(); + if (this.featureConfigService?.isEnabled('a11yScrollToTopPositioning')) { + this.elevatedPosition$ = this.anonymousConsentsService?.isBannerVisible(); + } } @HostListener('window:scroll', ['$event']) diff --git a/projects/storefrontstyles/scss/components/content/navigation/_scroll-to-top.scss b/projects/storefrontstyles/scss/components/content/navigation/_scroll-to-top.scss index deeb4cdda3b..7828e563f4d 100644 --- a/projects/storefrontstyles/scss/components/content/navigation/_scroll-to-top.scss +++ b/projects/storefrontstyles/scss/components/content/navigation/_scroll-to-top.scss @@ -12,6 +12,12 @@ animation: popup 1s 1; } + @include forFeature('a11yScrollToTopPositioning') { + &:has(.elevated-position) { + bottom: 180px; + } + } + button { height: inherit; width: inherit;