-
Notifications
You must be signed in to change notification settings - Fork 63
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
43 changed files
with
2,391 additions
and
1,653 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,162 @@ | ||
/** | ||
* Select all Side Navigation components: ".p-side-navigation" "[class*='p-side-navigation--']" | ||
* - Collapse/Expand side navigation using button ".js-drawer-toggle" | ||
* - Handle active state of links ".p-side-navigation__link" | ||
* - Handle Collapse/Expand for submenus of side navigation ".p-side-navigation__expand" | ||
*/ | ||
|
||
/** | ||
Toggles the expanded/collapsed classed on side navigation element. | ||
@param {HTMLElement} sideNavigation The side navigation element. | ||
@param {Boolean} show Whether to show or hide the drawer. | ||
@param {Boolean} ignoreTogglerFocus when we click on menu there is no redirect, the focus should jump into selected section | ||
*/ | ||
function toggleDrawer(sideNavigation, show, ignoreTogglerFocus = false) { | ||
const toggleButtonOutsideDrawer = sideNavigation.querySelector( | ||
".p-side-navigation__toggle" | ||
); | ||
const toggleButtonInsideDrawer = sideNavigation.querySelector( | ||
".p-side-navigation__toggle--in-drawer" | ||
); | ||
|
||
if (sideNavigation) { | ||
if (show) { | ||
sideNavigation.classList.remove("is-collapsed"); | ||
sideNavigation.classList.add("is-expanded"); | ||
|
||
if (!ignoreTogglerFocus) { | ||
toggleButtonInsideDrawer.focus(); | ||
} | ||
toggleButtonOutsideDrawer.setAttribute("aria-expanded", "true"); | ||
toggleButtonInsideDrawer.setAttribute("aria-expanded", "true"); | ||
} else { | ||
sideNavigation.classList.remove("is-expanded"); | ||
sideNavigation.classList.add("is-collapsed"); | ||
|
||
if (!ignoreTogglerFocus) { | ||
toggleButtonOutsideDrawer.focus(); | ||
} | ||
toggleButtonOutsideDrawer.setAttribute("aria-expanded", "false"); | ||
toggleButtonInsideDrawer.setAttribute("aria-expanded", "false"); | ||
} | ||
} | ||
} | ||
|
||
/** | ||
Setup default values of aria-expanded for the toggle button, list title and list itself | ||
@param {HTMLButtonElement} toggleMenu | ||
*/ | ||
const setupToggleMenu = (toggleMenu) => { | ||
const isExpanded = toggleMenu.getAttribute("aria-expanded") === "true"; | ||
if (!isExpanded) { | ||
toggleMenu.setAttribute("aria-expanded", isExpanded); | ||
} | ||
const item = toggleMenu.closest(".p-side-navigation__item"); | ||
const link = item.querySelector(".p-side-navigation__link"); | ||
const nestedList = item.querySelector(".p-side-navigation__list"); | ||
if (!link?.hasAttribute("aria-expanded")) { | ||
link.setAttribute("aria-expanded", isExpanded); | ||
} | ||
if (!nestedList?.hasAttribute("aria-expanded")) { | ||
nestedList.setAttribute("aria-expanded", isExpanded); | ||
} | ||
}; | ||
|
||
/** | ||
Handle toggle button to show/hide submenu | ||
@param {Event} e | ||
*/ | ||
const handleToggleMenu = (e) => { | ||
const item = e.currentTarget.closest(".p-side-navigation__item"); | ||
const button = item.querySelector(".p-side-navigation__expand"); | ||
const link = item.querySelector(".p-side-navigation__link"); | ||
const nestedList = item.querySelector(".p-side-navigation__list"); | ||
[button, link, nestedList].forEach((el) => | ||
el.setAttribute( | ||
"aria-expanded", | ||
el.getAttribute("aria-expanded") === "true" ? "false" : "true" | ||
) | ||
); | ||
}; | ||
|
||
/** | ||
Attaches event listeners for the side navigation and submenu toggles | ||
@param {HTMLElement} sideNavigation The side navigation element. | ||
*/ | ||
function setupSideNavigation(sideNavigation) { | ||
const toggles = [...sideNavigation.querySelectorAll(".js-drawer-toggle")]; | ||
|
||
// setup toggle buttons for sidenav | ||
toggles.forEach(function (toggle) { | ||
toggle.addEventListener("click", function (event) { | ||
event.preventDefault(); | ||
toggleDrawer( | ||
sideNavigation, | ||
!sideNavigation.classList.contains("is-expanded") | ||
); | ||
}); | ||
}); | ||
|
||
window.addEventListener("keydown", (e) => { | ||
if (e.key === "Escape") { | ||
toggleDrawer(sideNavigation, false); | ||
} | ||
}); | ||
|
||
// Setup expandable submenus side navigation | ||
const expandMenuToggles = [ | ||
...sideNavigation.querySelectorAll(".p-side-navigation__expand"), | ||
]; | ||
expandMenuToggles.forEach((toggleMenu) => { | ||
setupToggleMenu(toggleMenu); | ||
toggleMenu.addEventListener("click", (e) => { | ||
handleToggleMenu(e); | ||
}); | ||
}); | ||
|
||
// SETUP menu links click when expand/collapse side nav is not available | ||
if (expandMenuToggles.length === 0) { | ||
const currentHash = window.location.hash; | ||
const currentPath = window.location.pathname; | ||
const currentUrl = currentPath + currentHash; | ||
const links = [ | ||
...sideNavigation.querySelectorAll(".p-side-navigation__link"), | ||
]; | ||
links.forEach(function (link) { | ||
link.addEventListener("click", function () { | ||
links.forEach(function (link) { | ||
link.removeAttribute("aria-current"); | ||
}); | ||
this.setAttribute("aria-current", "page"); | ||
this.blur(); | ||
const isExpanded = sideNavigation.classList.contains("is-expanded"); | ||
if (isExpanded) { | ||
toggleDrawer(sideNavigation, !isExpanded, true); | ||
} | ||
}); | ||
|
||
const linkUrl = link.getAttribute("href"); | ||
if (linkUrl === currentUrl || linkUrl === currentHash) { | ||
link.setAttribute("aria-current", "page"); | ||
} | ||
}); | ||
} | ||
} | ||
|
||
/** | ||
Attaches event listeners for all the side navigations in the document. | ||
@param {String} sideNavigationSelector The CSS selector matching side navigation elements. | ||
*/ | ||
function setupSideNavigations(sideNavigationSelector) { | ||
// Setup all side navigations on the page. | ||
const sideNavigations = [ | ||
...document.querySelectorAll(sideNavigationSelector), | ||
]; | ||
sideNavigations.forEach(setupSideNavigation); | ||
} | ||
|
||
setupSideNavigations(".p-side-navigation, [class*='p-side-navigation--']"); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,162 @@ | ||
(function () { | ||
const keys = { | ||
left: "ArrowLeft", | ||
right: "ArrowRight", | ||
}; | ||
|
||
const direction = { | ||
ArrowLeft: -1, | ||
ArrowRight: 1, | ||
}; | ||
|
||
// IE11 doesn't support event.code, but event.keyCode is | ||
// deprecated in most modern browsers, so we should support | ||
// both for the time being. | ||
const IEKeys = { | ||
left: 37, | ||
right: 39, | ||
}; | ||
|
||
const IEDirection = { | ||
37: direction["ArrowLeft"], | ||
39: direction["ArrowRight"], | ||
}; | ||
|
||
/** | ||
Determine which tab to show when an arrow key is pressed | ||
@param {KeyboardEvent} event | ||
@param {Array} tabs an array of tabs within a container | ||
*/ | ||
const switchTabOnArrowPress = (event, tabs) => { | ||
let compatibleKeys = IEKeys; | ||
let compatibleDirection = IEDirection; | ||
let pressed = event.keyCode; | ||
|
||
if (event.code) { | ||
compatibleKeys = keys; | ||
compatibleDirection = direction; | ||
pressed = event.code; | ||
} | ||
|
||
if (compatibleDirection[pressed]) { | ||
const target = event.target; | ||
if (target.index !== undefined) { | ||
if (tabs[target.index + compatibleDirection[pressed]]) { | ||
tabs[target.index + compatibleDirection[pressed]].focus(); | ||
} else if (pressed === compatibleKeys.left) { | ||
tabs[tabs.length - 1].focus(); | ||
} else if (pressed === compatibleKeys.right) { | ||
tabs[0].focus(); | ||
} | ||
} | ||
} | ||
}; | ||
|
||
/** | ||
Attaches a number of events that each trigger | ||
the reveal of the chosen tab content | ||
@param {Array} tabs an array of tabs within a container | ||
*/ | ||
const attachEvents = (tabs, persistURLHash) => { | ||
tabs.forEach(function (tab, index) { | ||
tab.addEventListener("keyup", function (e) { | ||
let compatibleKeys = IEKeys; | ||
let key = e.keyCode; | ||
|
||
if (e.code) { | ||
compatibleKeys = keys; | ||
key = e.code; | ||
} | ||
|
||
if (key === compatibleKeys.left || key === compatibleKeys.right) { | ||
switchTabOnArrowPress(e, tabs); | ||
} | ||
}); | ||
|
||
tab.addEventListener("click", (e) => { | ||
e.preventDefault(); | ||
|
||
if (persistURLHash) { | ||
// if we're adding the ID of the tab to the URL | ||
// this prevents the page attempting to jump to | ||
// the section with that ID | ||
history.pushState({}, "", tab.href); | ||
|
||
// Update the URL again with the same hash, then go back | ||
history.pushState({}, "", tab.href); | ||
history.back(); | ||
} | ||
|
||
setActiveTab(tab, tabs); | ||
}); | ||
|
||
tab.addEventListener("focus", () => { | ||
setActiveTab(tab, tabs); | ||
}); | ||
|
||
tab.index = index; | ||
}); | ||
}; | ||
|
||
/** | ||
Cycles through an array of tab elements and ensures | ||
only the target tab and its content are selected | ||
@param {HTMLElement} tab the tab whose content will be shown | ||
@param {Array} tabs an array of tabs within a container | ||
*/ | ||
const setActiveTab = (tab, tabs) => { | ||
tabs.forEach((tabElement) => { | ||
var tabContent = document.querySelectorAll( | ||
"#" + tabElement.getAttribute("aria-controls") | ||
); | ||
tabContent.forEach((content) => { | ||
if (tabElement === tab) { | ||
tabElement.setAttribute("aria-selected", true); | ||
content.classList.remove("u-hide"); | ||
} else { | ||
tabElement.setAttribute("aria-selected", false); | ||
content.classList.add("u-hide"); | ||
} | ||
}); | ||
}); | ||
}; | ||
|
||
/** | ||
Attaches events to tab links within a given parent element, | ||
and sets the active tab if the current hash matches the id | ||
of an element controlled by a tab link | ||
@param {String} selector class name of the element | ||
containing the tabs we want to attach events to | ||
*/ | ||
const initTabs = (selector) => { | ||
var tabContainers = [].slice.call(document.querySelectorAll(selector)); | ||
|
||
tabContainers.forEach((tabContainer) => { | ||
// if the tab container has this data attribute, the id of the tab | ||
// is added to the URL, and a particular tab can be directly linked | ||
var persistURLHash = tabContainer.getAttribute("data-maintain-hash"); | ||
var currentHash = window.location.hash; | ||
|
||
var tabs = [].slice.call( | ||
tabContainer.querySelectorAll("[aria-controls]") | ||
); | ||
attachEvents(tabs, persistURLHash); | ||
|
||
if (persistURLHash && currentHash) { | ||
var activeTab = document.querySelector( | ||
".p-tabs__link[href='" + currentHash + "']" | ||
); | ||
|
||
if (activeTab) { | ||
setActiveTab(activeTab, tabs); | ||
} | ||
} else { | ||
setActiveTab(tabs[0], tabs); | ||
} | ||
}); | ||
}; | ||
|
||
document.addEventListener("DOMContentLoaded", () => { | ||
initTabs(".js-tabbed-content"); | ||
}); | ||
})(); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
@mixin canonical-p-footer { | ||
.p-footer { | ||
@extend %vf-strip; | ||
|
||
background: $colors--dark-theme--background-alt; | ||
color: $colors--dark-theme--text-default; | ||
|
||
.p-list__item--condensed { | ||
@extend %vf-list-item; | ||
|
||
padding-bottom: 0; | ||
padding-top: 0; | ||
} | ||
|
||
a { | ||
color: $color-link-dark; | ||
|
||
&:visited { | ||
color: $color-link-visited-dark; | ||
} | ||
} | ||
} | ||
} |
Oops, something went wrong.