From 0c35ac463d57e9bc7af20fc529046813e89aad0e Mon Sep 17 00:00:00 2001 From: "alex.saiannyi" Date: Mon, 15 Feb 2021 15:47:48 +0200 Subject: [PATCH] feat(storefront): BCTHEME-200 add notifications announcement on carousel change --- CHANGELOG.md | 1 + assets/js/theme/common/carousel/index.js | 50 +++++++++++++------ .../common/carousel/utils/arrowAriaLabling.js | 17 ++----- .../js/theme/common/carousel/utils/index.js | 1 + .../common/carousel/utils/tooltipSetup.js | 3 +- .../carousel/utils/updateTextWithLiveData.js | 11 ++++ assets/js/theme/global/quick-view.js | 6 +-- lang/en.json | 1 + .../carousel-content-announcement.html | 6 +++ templates/components/carousel.html | 7 ++- templates/components/products/carousel.html | 1 + .../components/products/product-view.html | 3 ++ 12 files changed, 72 insertions(+), 35 deletions(-) create mode 100644 assets/js/theme/common/carousel/utils/updateTextWithLiveData.js create mode 100644 templates/components/carousel-content-announcement.html diff --git a/CHANGELOG.md b/CHANGELOG.md index 09ea7f77d8..6ffccf3759 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ - Fixed required checkbox message displaying. [1963](https://github.com/bigcommerce/cornerstone/pull/1963) ## Draft +- Added notifications on Carousel's content cahnge through 'Next/Prev' buttons. [#1986](https://github.com/bigcommerce/cornerstone/pull/1986) - Provided sufficient & informative text along with the color swatches [#1976](https://github.com/bigcommerce/cornerstone/pull/1976) - If multiple Pick List Options are applied, customers cannot select "none" on both. [#1975](https://github.com/bigcommerce/cornerstone/pull/1975) - Moved phrase from compare.html to en.json for increasing localization. [#1972](https://github.com/bigcommerce/cornerstone/pull/1972) diff --git a/assets/js/theme/common/carousel/index.js b/assets/js/theme/common/carousel/index.js index 3b975e65db..a6b7ef8e1b 100644 --- a/assets/js/theme/common/carousel/index.js +++ b/assets/js/theme/common/carousel/index.js @@ -5,35 +5,54 @@ import { tooltipSetup, setTabindexes, arrowAriaLabling, + updateTextWithLiveData, heroCarouselSetup, getRealSlidesQuantityAndCurrentSlide, } from './utils'; +/** + * returns actualSlide and actualSlideCount + * based on provided carousel settings + * @param {Object} $slickSettings + * @returns {Object} + */ +const extractSlidesDetails = ({ + slideCount, currentSlide, breakpointSettings, activeBreakpoint, $slider, +}) => getRealSlidesQuantityAndCurrentSlide( + breakpointSettings, + activeBreakpoint, + currentSlide, + slideCount, + $slider.data('slick').slidesToScroll, +); + +export const onCarouselClick = ({ + data: $activeSlider, +}) => { + const $parentContainer = $activeSlider.hasClass('productView-thumbnails') ? $('.productView-images') : $activeSlider; + const { actualSlideCount, actualSlide } = extractSlidesDetails($activeSlider[0].slick); + const $carouselContentElement = $('.js-carousel-content-change-message', $parentContainer); + const carouselContentInitText = $carouselContentElement.text(); + const carouselContentAnnounceMessage = updateTextWithLiveData(carouselContentInitText, (actualSlide + 1), actualSlideCount); + + $carouselContentElement.text(carouselContentAnnounceMessage); +}; + export const onCarouselChange = (event, carousel) => { const { options: { prevArrow, nextArrow }, - currentSlide, - slideCount, $prevArrow, $nextArrow, $dots, $slider, - breakpointSettings, - activeBreakpoint, } = carousel; - const { actualSlideCount, actualSlide } = getRealSlidesQuantityAndCurrentSlide( - breakpointSettings, - activeBreakpoint, - currentSlide, - slideCount, - $slider.data('slick').slidesToScroll, - ); - + const { actualSlideCount, actualSlide } = extractSlidesDetails(carousel); const $prevArrowNode = $prevArrow || $slider.find(prevArrow); const $nextArrowNode = $nextArrow || $slider.find(nextArrow); const dataArrowLabel = $slider.data('arrow-label'); + if (dataArrowLabel) { $prevArrowNode.attr('aria-label', dataArrowLabel); $nextArrowNode.attr('aria-label', dataArrowLabel); @@ -54,14 +73,13 @@ export default function () { $carouselCollection.each((index, carousel) => { // getting element using find to pass jest test const $carousel = $(document).find(carousel); - - $carousel.on('init', onCarouselChange); - $carousel.on('afterChange', onCarouselChange); + $carousel.on('init afterChange', onCarouselChange); + $carousel.on('click', '.slick-arrow, .js-carousel-dot', $carousel, onCarouselClick); const isMultipleSlides = $carousel.children().length > 1; const customPaging = isMultipleSlides ? () => ( - '' + '' ) : () => {}; diff --git a/assets/js/theme/common/carousel/utils/arrowAriaLabling.js b/assets/js/theme/common/carousel/utils/arrowAriaLabling.js index 1e449ce00d..8ae8982471 100644 --- a/assets/js/theme/common/carousel/utils/arrowAriaLabling.js +++ b/assets/js/theme/common/carousel/utils/arrowAriaLabling.js @@ -1,26 +1,19 @@ -const NUMBER = '[NUMBER]'; -const integerRegExp = /[0-9]+/; -const lastIntegerRegExp = /(\d+)(?!.*\d)/; +import updateTextWithLiveData from './updateTextWithLiveData'; export default ($prevArrow, $nextArrow, actualSlide, actualSlideCount) => { if (actualSlideCount < 2) return; if ($prevArrow.length === 0 || $nextArrow.length === 0) return; const arrowAriaLabelBaseText = $prevArrow.attr('aria-label'); - - const isInit = arrowAriaLabelBaseText.includes(NUMBER); - const valueToReplace = isInit ? NUMBER : integerRegExp; const currentSlideNumber = actualSlide + 1; const prevSlideNumber = actualSlide === 0 ? actualSlideCount : currentSlideNumber - 1; - const arrowLeftText = arrowAriaLabelBaseText - .replace(valueToReplace, prevSlideNumber) - .replace(lastIntegerRegExp, actualSlideCount); + const arrowLeftText = updateTextWithLiveData(arrowAriaLabelBaseText, prevSlideNumber, actualSlideCount); + $prevArrow.attr('aria-label', arrowLeftText); const nextSlideNumber = actualSlide === actualSlideCount - 1 ? 1 : currentSlideNumber + 1; - const arrowRightText = arrowAriaLabelBaseText - .replace(valueToReplace, nextSlideNumber) - .replace(lastIntegerRegExp, actualSlideCount); + const arrowRightText = updateTextWithLiveData(arrowAriaLabelBaseText, nextSlideNumber, actualSlideCount); + $nextArrow.attr('aria-label', arrowRightText); }; diff --git a/assets/js/theme/common/carousel/utils/index.js b/assets/js/theme/common/carousel/utils/index.js index 3227c05446..4c15ee62f4 100644 --- a/assets/js/theme/common/carousel/utils/index.js +++ b/assets/js/theme/common/carousel/utils/index.js @@ -1,5 +1,6 @@ export { default as heroCarouselSetup } from './heroCarouselSetup'; export { default as arrowAriaLabling } from './arrowAriaLabling'; +export { default as updateTextWithLiveData } from './updateTextWithLiveData'; export { default as dotsSetup } from './dotsSetup'; export { default as getRealSlidesQuantityAndCurrentSlide } from './getRealSlidesQuantityAndCurrentSlide'; export { default as setTabindexes } from './setTabindexes'; diff --git a/assets/js/theme/common/carousel/utils/tooltipSetup.js b/assets/js/theme/common/carousel/utils/tooltipSetup.js index 8c14c8b35e..acb1bf4526 100644 --- a/assets/js/theme/common/carousel/utils/tooltipSetup.js +++ b/assets/js/theme/common/carousel/utils/tooltipSetup.js @@ -1,6 +1,5 @@ const carouselTooltipClass = 'carousel-tooltip'; const carouselTooltip = ``; - const setupTooltipAriaLabel = ($node) => { const $existedTooltip = $node.find(`.${carouselTooltipClass}`); @@ -17,7 +16,7 @@ const setupArrowTooltips = (...arrowNodes) => { }; const setupDotTooltips = ($dots) => { - $dots.children().each((idx, dot) => setupTooltipAriaLabel($(dot).find('button'))); + $dots.children().each((idx, dot) => setupTooltipAriaLabel($('.js-carousel-dot', dot))); }; export default ($prevArrow, $nextArrow, $dots) => { diff --git a/assets/js/theme/common/carousel/utils/updateTextWithLiveData.js b/assets/js/theme/common/carousel/utils/updateTextWithLiveData.js new file mode 100644 index 0000000000..facff645a6 --- /dev/null +++ b/assets/js/theme/common/carousel/utils/updateTextWithLiveData.js @@ -0,0 +1,11 @@ +const NUMBER = '[NUMBER]'; +const integerRegExp = /[0-9]+/; +const lastIntegerRegExp = /(\d+)(?!.*\d)/; + +export default (textForChange, slideNumber, slideCount) => { + const valueToReplace = textForChange.includes(NUMBER) ? NUMBER : integerRegExp; + + return textForChange + .replace(valueToReplace, slideNumber) + .replace(lastIntegerRegExp, slideCount); +}; diff --git a/assets/js/theme/global/quick-view.js b/assets/js/theme/global/quick-view.js index dc671bc087..8c3fb87129 100644 --- a/assets/js/theme/global/quick-view.js +++ b/assets/js/theme/global/quick-view.js @@ -4,7 +4,7 @@ import utils from '@bigcommerce/stencil-utils'; import ProductDetails from '../common/product-details'; import { defaultModal, modalTypes } from './modal'; import 'slick-carousel'; -import { onCarouselChange } from '../common/carousel'; +import { onCarouselChange, onCarouselClick } from '../common/carousel'; export default function (context) { const modal = defaultModal(); @@ -24,8 +24,8 @@ export default function (context) { const $carousel = modal.$content.find('[data-slick]'); if ($carousel.length) { - $carousel.on('init', onCarouselChange); - $carousel.on('afterChange', onCarouselChange); + $carousel.on('init afterChange', $carousel, onCarouselChange); + $carousel.on('click', '.slick-arrow', $carousel, onCarouselClick); $carousel.slick(); } diff --git a/lang/en.json b/lang/en.json index 0666215ed8..56e5535e76 100755 --- a/lang/en.json +++ b/lang/en.json @@ -902,6 +902,7 @@ }, "carousel": { "arrowAriaLabel": "Go to slide [NUMBER] of", + "contentAnnounceMessage": "You are currently on slide [NUMBER] of", "dotAriaLabel": "Slide number", "activeDotAriaLabel": "active", "playPauseButtonPlay": "Play", diff --git a/templates/components/carousel-content-announcement.html b/templates/components/carousel-content-announcement.html new file mode 100644 index 0000000000..e3e0eab7bb --- /dev/null +++ b/templates/components/carousel-content-announcement.html @@ -0,0 +1,6 @@ + + {{lang 'carousel.contentAnnounceMessage'}} {{slides_length}} + diff --git a/templates/components/carousel.html b/templates/components/carousel.html index 5781bc000e..b580b4fce5 100644 --- a/templates/components/carousel.html +++ b/templates/components/carousel.html @@ -60,9 +60,11 @@ {{/if}} {{/each}} - {{#and arrows (if carousel.slides.length '>' 1)}} + {{#if carousel.slides.length '>' 1}} + {{> components/carousel-content-announcement slides_length=carousel.slides.length}} + {{#if arrows}} - {{/and}} + {{/if}} {{#if play_pause_button}} + {{> components/carousel-content-announcement slides_length=products.length}} {{/if}} diff --git a/templates/components/products/product-view.html b/templates/components/products/product-view.html index a944da3836..8efe4ca8b5 100644 --- a/templates/components/products/product-view.html +++ b/templates/components/products/product-view.html @@ -13,6 +13,9 @@ Note that these image sizes are coupled to image sizes used in /assets/js/theme/common/product-details.js for variant/rule image replacement --}} + {{#if product.images.length '>' 1 }} + {{> components/carousel-content-announcement slides_length=product.images.length}} + {{/if}}