Skip to content

Commit

Permalink
Fix/11606 pni category nav not accessible and invalid (#11869)
Browse files Browse the repository at this point in the history
* Refactor dropwdown link for accessibility

* Minor improvement tweaks

* Lint

* fix linting error

---------

Co-authored-by: Ben Morse <[email protected]>
Co-authored-by: Mavis Ou <[email protected]>
  • Loading branch information
3 people authored Feb 22, 2024
1 parent f0e666a commit a1e19da
Show file tree
Hide file tree
Showing 2 changed files with 147 additions and 85 deletions.
Original file line number Diff line number Diff line change
@@ -1,51 +1,32 @@
{% load static wagtailcore_tags localization bg_nav_tags wagtailimages_tags i18n %}

<button
class="
pni-category-dropdown
tw-hidden
tw-bg-transparent
tw-items-center
tw-relative
tw-pr-10
tw-py-4
tw-mr-auto
tw-font-sans
tw-font-bold
tw-text-sm
tw-group
hover:tw-text-black
tw-text-gray-40
"
>
<span>{% trans "More Categories" %}</span>
<svg width="11" height="7" viewBox="0 0 11 7" fill="none" xmlns="http://www.w3.org/2000/svg" class="tw-ml-4 tw-origin-center tw-stroke-gray-40 group-hover:tw-stroke-black">
<title>{% trans "Open category drop down" %}</title>
<path d="M1 1L5.02504 5.02504L9.05007 1" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
<ul id="pni-category-dropdown-select" class="tw-hidden tw-list-none tw-absolute tw-right-0 tw-p-0 tw-top-0 tw-z-50 tw-border-gray-20 tw-border tw-pb-4 tw-bg-white">
<li class="
tw-bg-white
tw-w-full
tw-mr-0
tw-font-sans
tw-font-bold
tw-no-underline
tw-px-8
tw-py-4
tw-mb-0
tw-text-black
tw-flex
tw-items-center
tw-justify-end
tw-text-sm
">
<div class="pni-category-dropdown tw-hidden tw-relative">
<button
class="
pni-category-dropdown-button
tw-bg-transparent
tw-relative
tw-pr-10
tw-py-4
tw-mr-auto
tw-font-sans
tw-font-bold
tw-text-sm
tw-group
hover:tw-text-black
tw-text-gray-40
"
aria-controls="pni-category-dropdown-select"
aria-expanded="false"
aria-label="{% trans "More Categories" %}"
>
<span aria-hidden="true" class="tw-inline-flex tw-items-center">
<span>{% trans "More Categories" %}</span>
<svg width="11" height="7" viewBox="0 0 11 7" fill="none" xmlns="http://www.w3.org/2000/svg" class="tw-ml-4 tw-origin-center tw-stroke-black tw-rotate-180">
<title>{% trans "Close category drop down" %}</title>
<svg width="11" height="7" viewBox="0 0 11 7" fill="none" xmlns="http://www.w3.org/2000/svg" class="tw-ml-4 tw-origin-center tw-stroke-gray-40 group-hover:tw-stroke-black">
<path d="M1 1L5.02504 5.02504L9.05007 1" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</li>
</ul>
</span>
</button>
<ul id="pni-category-dropdown-select" class="tw-hidden tw-list-none tw-absolute tw-top-full tw-w-auto tw-right-0 tw-p-0 tw-z-50 tw-border-gray-20 tw-border tw-pb-4 tw-bg-white">

</button>
</ul>
</div>
159 changes: 120 additions & 39 deletions source/js/buyers-guide/template-js-handler/pni-category-dropdown.js
Original file line number Diff line number Diff line change
@@ -1,26 +1,24 @@
// TODO Refactor to ES6 Class in future to match other components and include aria/keyboard shortcuts

export default () => {
const dropdown = document.querySelector(".pni-category-dropdown");
const dropdownButton = dropdown.querySelector(
".pni-category-dropdown-button"
);
const dropdownButtonContent = dropdownButton.querySelector("span");
const dropdownButtonChevron = dropdownButton.querySelector("svg");
const dropdownSelect = dropdown.querySelector(
"#pni-category-dropdown-select"
);
let defaultDropdownHeaderText; // Keep track of dropdown button's default text for different locales

// Need this to keep track of the default text of the dropdown button when using different locales
let defaultDropdownHeaderText;

// Needed for calculating which items go into the dropdown
const navLinkMargin = 20;
const navLinkMargin = 20; // Calculate which items go into the dropdown
const categoryWrapper = document.querySelector("#pni-category-wrapper");
const categoryNav = document.querySelector("#product-review");

// Calculate width and margin
const calculateWidthAndMargin = (ele) => ele.clientWidth + navLinkMargin;

const dropdownSelect = document.querySelector(
"#pni-category-dropdown-select"
);
const dropdownSelectItems = document.querySelectorAll(
"#pni-category-dropdown-select > li, #pni-category-dropdown-select > li > a"
);

function resizeCategoryNavigation() {
// Resize category navigation
const resizeCategoryNavigation = () => {
const categoryLinks = [
...document.querySelectorAll(
"#buyersguide-category-link-container > .multipage-link"
Expand All @@ -45,9 +43,10 @@ export default () => {
}

linksForDropdown.forEach((e) => addCategoryToDropdown(e));
}
};

function addCategoryToDropdown(category) {
// Add category to dropdown
const addCategoryToDropdown = (category) => {
const el = document.createElement("li");
el.classList.add(
"tw-bg-white",
Expand All @@ -66,29 +65,50 @@ export default () => {
category.classList.remove("tw-block");
el.append(category);
dropdownSelect.append(el);
}
};

function highlightSelectedCategory() {
// Highlight selected category
const highlightSelectedCategory = () => {
const activeCategory = document.querySelector(
"#pni-category-dropdown-select .active"
);

const dropdownHeaderText = document.querySelector(
".pni-category-dropdown > span"
);

const dropdownHeaderText = dropdownButtonContent.querySelector("span");
if (activeCategory) {
dropdownHeaderText.innerText = activeCategory.innerText;
dropdownHeaderText.classList.add("tw-text-black");
dropdownHeaderText.setAttribute("aria-current", "page");
} else {
dropdownHeaderText.innerText = defaultDropdownHeaderText;
dropdownHeaderText.classList.remove("tw-text-black");
dropdownHeaderText.removeAttribute("aria-current");
}
}

if (dropdown) {
// removing styling that are exclusive used when JS is disabled or before it is loaded
dropdown.classList.add("tw-inline-flex");
};

// Open menu
const openMenu = (withFocus = false) => {
dropdownSelect.classList.remove("tw-hidden");
dropdownButton.setAttribute("aria-expanded", "true");
dropdownButtonChevron.classList.add("tw-stroke-black", "tw-rotate-180");

if (withFocus) {
const firstOption = dropdownSelect.querySelector("li > a");
if (firstOption) {
firstOption.focus();
}
}
};

// Close menu
const closeMenu = () => {
dropdownSelect.classList.add("tw-hidden");
dropdownButton.setAttribute("aria-expanded", "false");
dropdownButtonChevron.classList.remove("tw-stroke-black", "tw-rotate-180");
};

if (dropdown && dropdownButton && dropdownSelect) {
// removing styling that are exclusively used when JS is disabled or before it is loaded
dropdown.classList.add("tw-block");
dropdown.classList.remove("tw-hidden");
categoryWrapper.classList.add("tw-w-max", "tw-min-w-full");
document
Expand All @@ -100,17 +120,16 @@ export default () => {
"tw-mr-8"
);

defaultDropdownHeaderText = document.querySelector(
".pni-category-dropdown > span"
).innerText;
defaultDropdownHeaderText =
dropdownButtonContent.querySelector("span").innerText;

// If there is an overflow of categories lets start moving them to the category dropdown
if (categoryWrapper.clientWidth > categoryNav.clientWidth) {
resizeCategoryNavigation();
highlightSelectedCategory();
}

// Using to detect classes changes within the links
// Detect class changes within links
new MutationObserver(() => {
highlightSelectedCategory();
}).observe(dropdownSelect, {
Expand All @@ -119,23 +138,85 @@ export default () => {
attributes: true,
});

// So people can test going to smaller screen sizes without having to refresh the page
// Support adjusting screen size (avoid having to refresh the page)
window.onresize = function () {
if (categoryWrapper.clientWidth > categoryNav.clientWidth) {
resizeCategoryNavigation();
highlightSelectedCategory();
}
};

dropdown.addEventListener("click", function (event) {
// Event listener for keyboard events on dropdown button
dropdownButton.addEventListener("keydown", function (event) {
if (event.key === "Enter" || event.key === " ") {
event.preventDefault();
if (dropdownButton.getAttribute("aria-expanded") === "false") {
openMenu(true);
} else {
closeMenu();
}
}

if (event.key === "ArrowDown") {
if (dropdownButton.getAttribute("aria-expanded") === "true") {
event.preventDefault();
const firstOption = dropdownSelect.querySelector("li > a");
if (firstOption) {
firstOption.focus();
}
} else {
openMenu(true);
}
}
});

// Event listener for click events on dropdown button
dropdownButton.addEventListener("click", function (event) {
event.stopPropagation();
dropdownSelect.classList.remove("tw-hidden");
if (dropdownButton.getAttribute("aria-expanded") === "false") {
openMenu();
} else {
closeMenu();
}
});

dropdownSelectItems.forEach((item) => {
item.addEventListener("click", function (event) {
event.stopPropagation();
dropdownSelect.classList.add("tw-hidden");
// Event listener for keyboard events on dropdown container
dropdownSelect.addEventListener("keydown", function (event) {
if (event.key === "Escape") {
closeMenu();
}
});

// Event listener for keyboard and click events on dropdown options
dropdownSelect.querySelectorAll("li").forEach(function (option) {
// Event listener for click events on dropdown options
option.querySelector("a")?.addEventListener("click", function () {
closeMenu();
});

// Event listener for keyboard events on dropdown options
option.addEventListener("keydown", function (event) {
if (event.key === " " || event.key === "Enter") {
event.preventDefault();
option.querySelector("a").click();
closeMenu();
}

if (event.key === "ArrowDown") {
event.preventDefault();
const nextOption = option?.nextElementSibling?.querySelector("a");
if (nextOption) {
nextOption.focus();
}
} else if (event.key === "ArrowUp") {
event.preventDefault();
const prevOption = option?.previousElementSibling?.querySelector("a");
if (prevOption) {
prevOption.focus();
} else {
dropdownButton.focus();
}
}
});
});
}
Expand Down

0 comments on commit a1e19da

Please sign in to comment.