From bad30020062f6dd52738125406ba81132138e7b7 Mon Sep 17 00:00:00 2001 From: sdarbinyan Date: Thu, 13 Aug 2026 10:12:41 +0400 Subject: [PATCH] fix: WCAG 2.2.2 hero autoplay pause control, invisible keyboard-focusable cart button, literal hex token - Hero widget autoplay had no pause control and ignored prefers-reduced-motion (WCAG 2.2.2 requires a way to pause auto-updating content lasting >5s). Added a pause/resume toggle button and skip autoplay entirely when the OS prefers reduced motion. - Cart's swipe-reveal delete-btn-mobile was reachable by Tab even while invisible (opacity: 0, only the touch-swipe gesture could reveal it) - a confusing, unusable focus stop for keyboard users. Now tabindex=-1 + aria-hidden until swiped. Keyboard users already had a full removal path via the always-visible header remove button; this just stops the redundant hidden button from being a dead tab stop. - stars.component.scss hardcoded #cdd6d5 for the unfilled-star color instead of the --border-color design token. Co-Authored-By: Claude Sonnet 5 --- .../components/stars/stars.component.scss | 2 +- src/app/i18n/en.ts | 2 + src/app/i18n/hy.ts | 2 + src/app/i18n/ru.ts | 2 + src/app/i18n/translations.ts | 2 + src/app/pages/cart/cart.component.html | 8 +++- src/app/widgets/ui/hero-widget.component.ts | 40 ++++++++++++++++++- 7 files changed, 55 insertions(+), 3 deletions(-) diff --git a/src/app/features/website/product/engagement/components/stars/stars.component.scss b/src/app/features/website/product/engagement/components/stars/stars.component.scss index 1bdf602..49e3740 100644 --- a/src/app/features/website/product/engagement/components/stars/stars.component.scss +++ b/src/app/features/website/product/engagement/components/stars/stars.component.scss @@ -7,7 +7,7 @@ } .star { - color: #cdd6d5; + color: var(--border-color); } .star.filled { diff --git a/src/app/i18n/en.ts b/src/app/i18n/en.ts index 38f4758..4cbdca0 100644 --- a/src/app/i18n/en.ts +++ b/src/app/i18n/en.ts @@ -1129,6 +1129,8 @@ export const en: Translations = { nextProducts: 'Next products', previousSlide: 'Previous slide', nextSlide: 'Next slide', + pauseAutoplay: 'Pause slideshow', + resumeAutoplay: 'Resume slideshow', closeDialog: 'Close dialog', dismiss: 'Dismiss', qrCode: 'QR Code', diff --git a/src/app/i18n/hy.ts b/src/app/i18n/hy.ts index cd580e6..c270425 100644 --- a/src/app/i18n/hy.ts +++ b/src/app/i18n/hy.ts @@ -1129,6 +1129,8 @@ export const hy: Translations = { nextProducts: 'Հաջորդ ապրանքները', previousSlide: 'Նախորդ սլայդը', nextSlide: 'Հաջորդ սլայդը', + pauseAutoplay: 'Դադարեցնել սլայդշոուն', + resumeAutoplay: 'Վերսկսել սլայդշոուն', closeDialog: 'Փակել պատուհանը', dismiss: 'Փակել', qrCode: 'QR կոդ', diff --git a/src/app/i18n/ru.ts b/src/app/i18n/ru.ts index 25f701f..e2d09de 100644 --- a/src/app/i18n/ru.ts +++ b/src/app/i18n/ru.ts @@ -1129,6 +1129,8 @@ export const ru: Translations = { nextProducts: 'Следующие товары', previousSlide: 'Предыдущий слайд', nextSlide: 'Следующий слайд', + pauseAutoplay: 'Приостановить слайд-шоу', + resumeAutoplay: 'Возобновить слайд-шоу', closeDialog: 'Закрыть диалог', dismiss: 'Скрыть', qrCode: 'QR-код', diff --git a/src/app/i18n/translations.ts b/src/app/i18n/translations.ts index f41addb..a301214 100644 --- a/src/app/i18n/translations.ts +++ b/src/app/i18n/translations.ts @@ -1128,6 +1128,8 @@ export interface Translations { nextProducts: string; previousSlide: string; nextSlide: string; + pauseAutoplay: string; + resumeAutoplay: string; closeDialog: string; dismiss: string; qrCode: string; diff --git a/src/app/pages/cart/cart.component.html b/src/app/pages/cart/cart.component.html index 2bfb1b9..2c011a3 100644 --- a/src/app/pages/cart/cart.component.html +++ b/src/app/pages/cart/cart.component.html @@ -98,7 +98,13 @@ - diff --git a/src/app/widgets/ui/hero-widget.component.ts b/src/app/widgets/ui/hero-widget.component.ts index 72aa132..c67e018 100644 --- a/src/app/widgets/ui/hero-widget.component.ts +++ b/src/app/widgets/ui/hero-widget.component.ts @@ -61,6 +61,15 @@ const SWIPE_THRESHOLD_PX = 50; (click)="goTo($index)" > } + @if (data?.autoplay) { + + } } } @@ -171,6 +180,23 @@ const SWIPE_THRESHOLD_PX = 50; &:focus-visible { outline: 2px solid var(--primary-color, #497671); outline-offset: 2px; } } + .hero-widget__pause { + margin-left: var(--space-sm, 8px); + width: 24px; + height: 24px; + border-radius: 50%; + border: 1px solid var(--border-color, #d3dad9); + background: #fff; + cursor: pointer; + font-size: 0.7rem; + line-height: 1; + display: inline-flex; + align-items: center; + justify-content: center; + + &:focus-visible { outline: 2px solid var(--primary-color, #497671); outline-offset: 2px; } + } + @keyframes hero-widget-in { from { opacity: 0; transform: translateY(12px); } to { opacity: 1; transform: translateY(0); } @@ -199,6 +225,7 @@ export class HeroWidgetComponent implements OnChanges, OnDestroy { @Output() ctaClicked = new EventEmitter(); readonly activeIndex = signal(0); + readonly isPaused = signal(false); private readonly dataSignal = signal(null); private autoplayHandle: ReturnType | null = null; private swipeStartX: number | null = null; @@ -237,6 +264,7 @@ export class HeroWidgetComponent implements OnChanges, OnDestroy { if (changes['data']) { this.dataSignal.set(this.data); this.activeIndex.set(0); + this.isPaused.set(false); this.setupAutoplay(); } } @@ -289,10 +317,20 @@ export class HeroWidgetComponent implements OnChanges, OnDestroy { this.ctaClicked.emit(); } + /** WCAG 2.2.2: auto-updating content lasting >5s needs a way to pause it. */ + toggleAutoplay(): void { + this.isPaused.update(paused => !paused); + this.setupAutoplay(); + } + + private prefersReducedMotion(): boolean { + return typeof window !== 'undefined' && !!window.matchMedia?.('(prefers-reduced-motion: reduce)').matches; + } + private setupAutoplay(): void { this.clearAutoplay(); const slides = this.allSlides(); - if (!this.data?.autoplay || slides.length <= 1) { + if (!this.data?.autoplay || slides.length <= 1 || this.isPaused() || this.prefersReducedMotion()) { return; } this.autoplayHandle = setInterval(() => {