From ad2e630dab3293ce59890ae4209da4831b72f4e0 Mon Sep 17 00:00:00 2001 From: yurytut1993 <66325265+yurytut1993@users.noreply.github.com> Date: Wed, 26 Aug 2020 15:49:28 +0300 Subject: [PATCH] feature(storefront): BCTHEME-156 Selected state on search page not announced (#1788) Co-authored-by: BC-tymurbiedukhin <66319629+BC-tymurbiedukhin@users.noreply.github.com> --- CHANGELOG.md | 1 + assets/js/theme/search.js | 67 +++++++++++++++++-- .../components/stencil/search/_search.scss | 8 +++ assets/scss/tools/_mixins.scss | 9 +++ assets/scss/tools/_tools.scss | 1 + lang/en.json | 3 +- templates/pages/search.html | 19 +++--- 7 files changed, 93 insertions(+), 15 deletions(-) create mode 100644 assets/scss/tools/_mixins.scss diff --git a/CHANGELOG.md b/CHANGELOG.md index 30180954d7..eed32c1ec8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ # Changelog ## Draft +Selected state on search page not announced. [#1788](https://github.com/bigcommerce/cornerstone/pull/1788) - Added additional focusable element for rating announcing. [#1769](https://github.com/bigcommerce/cornerstone/pull/1769) - Error messages from Write a Review should be announced. [#1777](https://github.com/bigcommerce/cornerstone/pull/1777) - Fixed issue with missing logo on mobile and tablet. [#1767](https://github.com/bigcommerce/cornerstone/pull/1767) diff --git a/assets/js/theme/search.js b/assets/js/theme/search.js index 9ee6f3a589..954b278f19 100644 --- a/assets/js/theme/search.js +++ b/assets/js/theme/search.js @@ -8,6 +8,9 @@ import collapsibleFactory from './common/collapsible'; import 'jstree'; import nod from './common/nod'; +const leftArrowKey = 37; +const rightArrowKey = 39; + export default class Search extends CatalogPage { formatCategoryTreeForJSTree(node) { const nodeData = { @@ -34,9 +37,9 @@ export default class Search extends CatalogPage { } showProducts(navigate = true) { - this.$productListingContainer.removeClass('u-hiddenVisually'); - this.$facetedSearchContainer.removeClass('u-hiddenVisually'); - this.$contentResultsContainer.addClass('u-hiddenVisually'); + this.$productListingContainer.removeClass('u-hidden'); + this.$facetedSearchContainer.removeClass('u-hidden'); + this.$contentResultsContainer.addClass('u-hidden'); $('[data-content-results-toggle]').removeClass('navBar-action-color--active'); $('[data-content-results-toggle]').addClass('navBar-action'); @@ -44,6 +47,8 @@ export default class Search extends CatalogPage { $('[data-product-results-toggle]').removeClass('navBar-action'); $('[data-product-results-toggle]').addClass('navBar-action-color--active'); + this.activateTab($('[data-product-results-toggle]')); + if (!navigate) { return; } @@ -57,9 +62,9 @@ export default class Search extends CatalogPage { } showContent(navigate = true) { - this.$contentResultsContainer.removeClass('u-hiddenVisually'); - this.$productListingContainer.addClass('u-hiddenVisually'); - this.$facetedSearchContainer.addClass('u-hiddenVisually'); + this.$contentResultsContainer.removeClass('u-hidden'); + this.$productListingContainer.addClass('u-hidden'); + this.$facetedSearchContainer.addClass('u-hidden'); $('[data-product-results-toggle]').removeClass('navBar-action-color--active'); $('[data-product-results-toggle]').addClass('navBar-action'); @@ -67,6 +72,8 @@ export default class Search extends CatalogPage { $('[data-content-results-toggle]').removeClass('navBar-action'); $('[data-content-results-toggle]').addClass('navBar-action-color--active'); + this.activateTab($('[data-content-results-toggle]')); + if (!navigate) { return; } @@ -79,6 +86,52 @@ export default class Search extends CatalogPage { urlUtils.goToUrl(url); } + activateTab($tabToActivate) { + const $tabsCollection = $('[data-search-page-tabs]').find('[role="tab"]'); + + $tabsCollection.each((idx, tab) => { + const $tab = $(tab); + + if ($tab.is($tabToActivate)) { + $tab.removeAttr('tabindex'); + $tab.attr('aria-selected', true); + return; + } + + $tab.attr('tabindex', '-1'); + $tab.attr('aria-selected', false); + }); + } + + onTabChangeWithArrows(event) { + const eventKey = event.which; + const isLeftOrRightArrowKeydown = eventKey === leftArrowKey + || eventKey === rightArrowKey; + if (!isLeftOrRightArrowKeydown) return; + + const $tabsCollection = $('[data-search-page-tabs]').find('[role="tab"]'); + + const isActiveElementNotTab = $tabsCollection.index($(document.activeElement)) === -1; + if (isActiveElementNotTab) return; + + const $activeTab = $(`#${document.activeElement.id}`); + const activeTabIdx = $tabsCollection.index($activeTab); + const lastTabIdx = $tabsCollection.length - 1; + + let nextTabIdx; + switch (eventKey) { + case leftArrowKey: + nextTabIdx = activeTabIdx === 0 ? lastTabIdx : activeTabIdx - 1; + break; + case rightArrowKey: + nextTabIdx = activeTabIdx === lastTabIdx ? 0 : activeTabIdx + 1; + break; + default: break; + } + + $($tabsCollection.get(nextTabIdx)).focus().trigger('click'); + } + onReady() { compareProducts(this.context.urls); @@ -111,6 +164,8 @@ export default class Search extends CatalogPage { this.showContent(); }); + $('[data-search-page-tabs]').on('keyup', this.onTabChangeWithArrows); + if (this.$productListingContainer.find('li.product').length === 0 || url.query.section === 'content') { this.showContent(false); } else { diff --git a/assets/scss/components/stencil/search/_search.scss b/assets/scss/components/stencil/search/_search.scss index 9b476b07d7..0c96d5a3ca 100644 --- a/assets/scss/components/stencil/search/_search.scss +++ b/assets/scss/components/stencil/search/_search.scss @@ -93,3 +93,11 @@ margin: spacing("single") 0 0; text-align: center; } + +.search-nav { + position: relative; + + .search-tabs-widget-description { + @include ariaDescribeElement + } +} diff --git a/assets/scss/tools/_mixins.scss b/assets/scss/tools/_mixins.scss new file mode 100644 index 0000000000..f407c672bd --- /dev/null +++ b/assets/scss/tools/_mixins.scss @@ -0,0 +1,9 @@ +@mixin ariaDescribeElement { + position: absolute; + top: 0; + left: 0; + height: 1px; + width: 1px; + overflow: hidden; + margin-left: -10000px; +} diff --git a/assets/scss/tools/_tools.scss b/assets/scss/tools/_tools.scss index 8258581725..c3b84c53da 100644 --- a/assets/scss/tools/_tools.scss +++ b/assets/scss/tools/_tools.scss @@ -1,3 +1,4 @@ @import "list"; @import "string"; @import "image"; +@import "mixins"; diff --git a/lang/en.json b/lang/en.json index a0ac2f4742..0565447b8d 100755 --- a/lang/en.json +++ b/lang/en.json @@ -850,7 +850,8 @@ }, "error": { "empty_field": "Search field cannot be empty." - } + }, + "tabs_accesibility_hint": "Use left and right arrows to navigate between tabs." }, "page_not_found": { "page_heading": "404 Error - Page not found", diff --git a/templates/pages/search.html b/templates/pages/search.html index fab5fec268..8467b38546 100644 --- a/templates/pages/search.html +++ b/templates/pages/search.html @@ -21,15 +21,18 @@ {{>components/search/heading}} {{/if}} -