Skip to content

NCRS-3931 Close mobile menu on lost focus & fix aria toggling #40

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React from 'react';
import { fireEvent, render } from '@testing-library/react';
import { render } from '@testing-library/react';
import HeaderWithLogo from '../HeaderWithLogo';

describe('The header component', () => {
Expand Down Expand Up @@ -127,23 +127,6 @@ describe('The header component', () => {
expect(visuallyHiddenText?.nextSibling?.textContent).toBe(dropdownText ?? 'More');
},
);

it('Invokes the onClick prop when button is clicked', () => {
const clickFn = jest.fn();
const { container } = render(
<HeaderWithLogo>
<HeaderWithLogo.NavDropdownMenu onClick={clickFn}></HeaderWithLogo.NavDropdownMenu>
</HeaderWithLogo>,
);

const buttonElement = container.querySelector('.nhsuk-header__menu-toggle');

expect(clickFn).not.toHaveBeenCalled();

fireEvent.click(buttonElement!);

expect(clickFn).toHaveBeenCalledTimes(1);
});
});

describe('The NavItem component', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,6 @@ exports[`The header component Matches the snapshot 1`] = `
class="nhsuk-mobile-menu-container"
>
<button
aria-expanded="false"
class="nhsuk-header__menu-toggle nhsuk-header__navigation-link"
>
<span
Expand Down
5 changes: 3 additions & 2 deletions src/components/header-with-logo/_headerWithLogo.scss
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@
flex-shrink: 1;
align-items: center;
justify-content: flex-end;

.nhsuk-navigation {
width: 100%;
max-width: 100%;
Expand Down Expand Up @@ -117,9 +117,10 @@
height: 24px;
margin-left: 6px;
transform: rotate(90deg);
fill: inherit;
}

&[aria-expanded='true'] {
&--expanded {
.nhsuk-icon {
transform: rotate(-90deg);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { FC, HTMLProps, useContext, useEffect, MouseEvent } from 'react';
import React, { FC, HTMLProps, useContext, useEffect } from 'react';
import HeaderContext, { IHeaderContext } from '../HeaderContext';
import { ChevronDownIcon } from './LocalChevronDown';
export interface NavDropdownMenuProps extends HTMLProps<HTMLButtonElement> {
Expand All @@ -7,15 +7,7 @@ export interface NavDropdownMenuProps extends HTMLProps<HTMLButtonElement> {
}

const NavMenuDropdown: FC<NavDropdownMenuProps> = ({ onClick, dropdownText = 'More', ...rest }) => {
const { setMenuToggle, toggleMenu, menuOpen } = useContext<IHeaderContext>(HeaderContext);

const onToggleClick = (e: MouseEvent<HTMLButtonElement>) => {
toggleMenu();

if (onClick) {
onClick(e);
}
};
const { setMenuToggle } = useContext<IHeaderContext>(HeaderContext);

useEffect(() => {
setMenuToggle(true);
Expand All @@ -26,8 +18,6 @@ const NavMenuDropdown: FC<NavDropdownMenuProps> = ({ onClick, dropdownText = 'Mo
<li className="nhsuk-mobile-menu-container">
<button
className="nhsuk-header__menu-toggle nhsuk-header__navigation-link "
aria-expanded={menuOpen ? 'true' : 'false'}
onClick={onToggleClick}
{...rest}
>
<span className="nhsuk-u-visually-hidden">Browse</span>
Expand Down
82 changes: 56 additions & 26 deletions src/components/header-with-logo/header.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,13 @@
* Lifted from nhsuk-frontend and brought into this repo to enable compilation to CJS if required
* See Github issue https://github.com/nhsuk/nhsuk-frontend/issues/937
*/


class Header {
constructor() {
this.menuIsOpen = false;
this.navigation = document.querySelector('.nhsuk-navigation');
this.navigationList = document.querySelector('.nhsuk-header__navigation-list');
this.mobileMenu = document.createElement('ul');
this.mobileMenuToggleButton = document.querySelector('.nhsuk-header__menu-toggle');
this.mobileMenuCloseButton = document.createElement('button');
this.mobileMenuContainer = document.querySelector('.nhsuk-mobile-menu-container');
this.breakpoints = [];
this.width = document.body.offsetWidth;
Expand All @@ -29,22 +26,28 @@ class Header {

// calculateBreakpoints and updateNavigtion need to be run twice
// the second run takes into account the width of the logo
this.setupMobileMenu();
this.calculateBreakpoints();
this.updateNavigation();
this.doOnOrientationChange();
this.calculateBreakpoints();
this.updateNavigation();
this.setupMobileMenu();
this.calculateBreakpoints();
this.updateNavigation();
this.doOnOrientationChange();
this.calculateBreakpoints();
this.updateNavigation();

this.handleResize = this.debounce(() => {
this.calculateBreakpoints();
this.updateNavigation();
});


this.mobileMenuToggleButton.addEventListener('click', this.toggleMobileMenu.bind(this));
window.addEventListener('resize', this.handleResize);
window.addEventListener('orientationchange', this.doOnOrientationChange());

// Add new blur listeners to dropdown menu
// It had to be applied to all nav links as removeEventListener wasn't working
const allLinks = [this.mobileMenuToggleButton, ...this.navigation.querySelectorAll('a.nhsuk-header__navigation-link')];
for (let i = 0; i < allLinks.length; i++) {
allLinks[i].addEventListener('blur', this.onBlur.bind(this))
}
}

debounce(func, timeout = 100) {
Expand All @@ -65,14 +68,13 @@ class Header {
*
*/
calculateBreakpoints() {

let childrenWidth = 0;

for (let i = 0; i < this.navigationList.children.length; i++) {
childrenWidth += this.navigationList.children[i].offsetWidth;
this.breakpoints[i] = childrenWidth;
}
}
for (let i = 0; i < this.navigationList.children.length; i++) {
childrenWidth += this.navigationList.children[i].offsetWidth;
this.breakpoints[i] = childrenWidth;
}
}

// Add the mobile menu to the DOM
setupMobileMenu() {
Expand All @@ -85,15 +87,14 @@ class Header {
*
* Closes the mobile menu and updates accessibility state.
*
* Remvoes the margin-bottom from the navigation
* Removes the margin-bottom from the navigation
*/
closeMobileMenu() {
this.menuIsOpen = false;
this.mobileMenu.classList.add('nhsuk-header__drop-down--hidden');
this.navigation.style.marginBottom = 0;
this.mobileMenuToggleButton.classList.remove('nhsuk-header__menu-toggle--expanded');
this.mobileMenuToggleButton.setAttribute('aria-expanded', 'false');
this.mobileMenuToggleButton.focus();
this.mobileMenuCloseButton.removeEventListener('click', this.closeMobileMenu.bind(this));
document.removeEventListener('keydown', this.handleEscapeKey.bind(this));
}

Expand All @@ -105,8 +106,40 @@ class Header {
*
*/
handleEscapeKey(e) {
if (e.key === 'Escape') {
if (e.key === 'Escape' && this.menuIsOpen) {
this.closeMobileMenu();
this.mobileMenuToggleButton.focus();
}
}

/**
* Blur Listening
*
* Listener for when the focus leaves a nav link / toggle
*
* If the currentTarget is dropdown-related (toggle or child link), and the new one isn't, we want to close the
* mobileMenu.
*/
onBlur(e) {
const grandchildren = this.mobileMenu.querySelectorAll('li a');
// Checks the current link even qualifies
const currentFocusIsInDropdown = Array.from(grandchildren).includes(e.currentTarget);
const currentFocusIsToggleOrSubLink = e.currentTarget.classList.contains('nhsuk-header__menu-toggle') || currentFocusIsInDropdown

// If the menu isn't open, or the old link isn't one of the toggles or sub links, return;
if (!this.menuIsOpen || !currentFocusIsToggleOrSubLink) {
return;
}

// Focus left any link
if (e.relatedTarget === null) {
this.closeMobileMenu();
return;
}

const newFocusIsInDropdown = Array.from(grandchildren).includes(e.relatedTarget);
if (!e.relatedTarget.classList.contains('nhsuk-header__menu-toggle') && !newFocusIsInDropdown) {
this.closeMobileMenu();
}
}

Expand All @@ -126,13 +159,11 @@ class Header {
this.mobileMenu.classList.remove('nhsuk-header__drop-down--hidden');
const marginBody = this.mobileMenu.offsetHeight;
this.navigation.style.marginBottom = `${marginBody}px`;
this.mobileMenuToggleButton.classList.add('nhsuk-header__menu-toggle--expanded');
this.mobileMenuToggleButton.setAttribute('aria-expanded', 'true');

// add event listerer for esc key to close menu
// add event listener for esc key to close menu
document.addEventListener('keydown', this.handleEscapeKey.bind(this));

// add event listener for close icon to close menu
this.mobileMenuCloseButton.addEventListener('click', this.closeMobileMenu.bind(this));
}

/**
Expand All @@ -158,7 +189,7 @@ class Header {
* If the available space is greater than the current breakpoint,
* remove the mobile menu toggle button and move the first item in the
*
* Additionaly will close the mobile menu if the window gets resized
* Additionally will close the mobile menu if the window gets resized
* and the menu is open.
*/

Expand Down Expand Up @@ -196,7 +227,6 @@ class Header {
if (document.body.offsetWidth !== this.width && this.menuIsOpen) {
this.closeMobileMenu();
}

}

/**
Expand Down