Skip to content
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

GN-101 Issues #122

Merged
merged 10 commits into from
Sep 21, 2021
16 changes: 16 additions & 0 deletions src/Header/Account/Account.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ export default class Account extends Component {
this.handleMyAccountButtonClick.bind(this);
this.handleKeyDown = this.handleKeyDown.bind(this);
this.handleMenuItemClick = this.handleMenuItemClick.bind(this);
this.handleMegaMenuClick = this.handleMegaMenuClick.bind(this);
}

handleMyAccountButtonClick(e) {
Expand Down Expand Up @@ -56,6 +57,19 @@ export default class Account extends Component {
}
}

// NOTE: We would benefit from managing the state higher up
handleMegaMenuClick(event) {
let megaMenu = document.querySelector("#header-menu");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i'm not sure whether it's worth using a ref here rather than querying the dom directly.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe context?


if (megaMenu.contains(event.target)) {
event.preventDefault();
this.setState({
isExpanded: false,
});
megaMenu.focus();
}
}

handleMenuItemClick(e) {
const href = e.currentTarget.getAttribute("href");

Expand Down Expand Up @@ -133,6 +147,8 @@ export default class Account extends Component {
}.bind(this)
);
}

document.addEventListener("click", this.handleMegaMenuClick);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

generally in the react world, wherever you add an event listener, you need to remove it if the component unmounts. it's not necessarily always an issue, cos possibly the component doesn't ever unmount. i'm going to test something out with this.

}

render() {
Expand Down
163 changes: 91 additions & 72 deletions src/Header/Header.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,13 @@ export class Header extends Component {
isExpanded: false,
isLoggedIn: false,
accountsData: null,
scrimIsActive: false,
};

this.handleMobileMenuBtnClick = this.handleMobileMenuBtnClick.bind(this);
this.handleLoginStatusChecked = this.handleLoginStatusChecked.bind(this);
this.handleLogoClick = this.handleLogoClick.bind(this);
this.handleScrim = this.handleScrim.bind(this);
}

componentDidMount() {
Expand Down Expand Up @@ -67,6 +69,10 @@ export class Header extends Component {
);
}

handleScrim(scrim) {
emollett marked this conversation as resolved.
Show resolved Hide resolved
this.setState({ scrimIsActive: Boolean(scrim === true) });
}

handleLoginStatusChecked(accountsData) {
if (accountsData.display_name) {
this.setState({ isLoggedIn: true, accountsData: accountsData });
Expand All @@ -78,81 +84,94 @@ export class Header extends Component {
render() {
return (
this.props.enabled !== false && (
<div className={styles.header}>
<header aria-label="Site header">
<ul className={styles.a11yLinks} aria-label="Accessibility links">
<li>
<SkipLink to={`#${this.props.skipLinkId}`}>
Skip to content
</SkipLink>
</li>
<li>
<SkipLink to="https://www.nice.org.uk/accessibility">
Accessibility help
</SkipLink>
</li>
</ul>
<div className={styles.container}>
<a
href="https://www.nice.org.uk/"
aria-label="Home"
className={styles.home}
onClick={this.handleLogoClick}
>
<LogoIcon className={styles.icon} width={null} height={null} />
</a>
<div className={styles.wrapper}>
<div className={styles.search}>
{this.props.search && (
<Search
skipLinkId={this.props.skipLinkId}
onNavigating={this.props.onNavigating}
{...this.props.search}
/>
)}
</div>
<button
className={styles.mobileMenuBtn}
id="header-menu-button"
type="button"
aria-controls="header-menu"
aria-expanded={this.state.isExpanded}
aria-haspopup="menu"
aria-label={
this.state.isExpanded
? "Close site menu"
: "Expand site menu"
}
onClick={this.handleMobileMenuBtnClick}
<>
<span
id="scrim"
className={this.state.scrimIsActive && styles.scrim}
aria-hidden="true"
/>

<div className={styles.header}>
<header aria-label="Site header">
<ul className={styles.a11yLinks} aria-label="Accessibility links">
<li>
<SkipLink to={`#${this.props.skipLinkId}`}>
Skip to content
</SkipLink>
</li>
<li>
<SkipLink to="https://www.nice.org.uk/accessibility">
Accessibility help
</SkipLink>
</li>
</ul>
<div className={styles.container}>
<a
href="https://www.nice.org.uk/"
aria-label="Home"
className={styles.home}
onClick={this.handleLogoClick}
>
{this.state.isExpanded ? "Close" : "Menu"}
</button>
{this.props.auth !== false && (
<div className={styles.account}>
<Account
onLoginStatusChecked={this.handleLoginStatusChecked}
isLoggedIn={this.state.isLoggedIn}
accountsData={this.state.accountsData}
{...this.props.auth}
/>
<LogoIcon
className={styles.icon}
width={null}
height={null}
/>
</a>
<div className={styles.wrapper}>
<div className={styles.search}>
{this.props.search && (
<Search
skipLinkId={this.props.skipLinkId}
onNavigating={this.props.onNavigating}
{...this.props.search}
/>
)}
</div>
)}
<button
className={styles.mobileMenuBtn}
id="header-menu-button"
type="button"
aria-controls="header-menu"
aria-expanded={this.state.isExpanded}
aria-haspopup="menu"
aria-label={
this.state.isExpanded
? "Close site menu"
: "Expand site menu"
}
onClick={this.handleMobileMenuBtnClick}
>
{this.state.isExpanded ? "Close" : "Menu"}
</button>
{this.props.auth !== false && (
<div className={styles.account}>
<Account
onLoginStatusChecked={this.handleLoginStatusChecked}
isLoggedIn={this.state.isLoggedIn}
accountsData={this.state.accountsData}
{...this.props.auth}
/>
</div>
)}
</div>
</div>
</div>
<Nav
skipLinkId={this.props.skipLinkId}
service={this.props.service}
isExpanded={this.state.isExpanded}
accountsLinks={
this.state.accountsData && this.state.accountsData.links
}
onNavigating={this.props.onNavigating}
additionalSubMenuItems={this.props.additionalSubMenuItems}
/>
</header>
<CoronaMessage onResize={this.props.onResize} />
<OldIEMessage />
</div>
<Nav
handleScrim={this.handleScrim}
skipLinkId={this.props.skipLinkId}
service={this.props.service}
isExpanded={this.state.isExpanded}
accountsLinks={
this.state.accountsData && this.state.accountsData.links
}
onNavigating={this.props.onNavigating}
additionalSubMenuItems={this.props.additionalSubMenuItems}
/>
</header>
<CoronaMessage onResize={this.props.onResize} />
<OldIEMessage />
</div>
</>
)
);
}
Expand Down
10 changes: 10 additions & 0 deletions src/Header/Header.module.scss
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,16 @@
margin-right: rem($nds-spacing-large);
}

.scrim {
background: rgba(0, 0, 0, 70%);
display: block;
height: 100vh;
left: 0;
margin-top: rem($search-height) + rem($nds-spacing-large);
position: absolute;
width: 100vw;
}

.wrapper {
justify-content: flex-end;
}
Expand Down
9 changes: 9 additions & 0 deletions src/Header/Nav/Dropdown/Components/Components.module.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
@import '~@nice-digital/nds-core/scss/core';

.listUnstyled {
@include nds-list;
list-style: none !important;
margin-left: 0;
max-width: none; // To override max-width of 66ch set on lists
padding: 0;
}
5 changes: 3 additions & 2 deletions src/Header/Nav/Dropdown/Components/Guidance.jsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import React from "react";
import { Button } from "@nice-digital/nds-button";
import { Grid, GridItem } from "@nice-digital/nds-grid";
import styles from "./Components.module.scss";

export function Guidance() {
return (
Expand All @@ -19,7 +20,7 @@ export function Guidance() {
<GridItem cols={12} md={6}>
<h3>Most visited guidance topics</h3>
<p>Guidance grouped by subject, most visited by other people:</p>
<ul className="list list--unstyled" style={{ columnCount: 2 }}>
<ul className={styles.listUnstyled} style={{ columnCount: 2 }}>
wa-rren-dev marked this conversation as resolved.
Show resolved Hide resolved
<li>
<a href="/">Diabetes</a>
</li>
Expand Down Expand Up @@ -49,7 +50,7 @@ export function Guidance() {
<GridItem cols={12} md={6}>
<h3>COVID-19</h3>
<p>Latest COVID-19 guidance:</p>
<ul className="list list--unstyled">
<ul className={styles.listUnstyled}>
<li>
<a href="/">
COVID-19 rapid guideline: vaccine-induced immune
Expand Down
2 changes: 2 additions & 0 deletions src/Header/Nav/Nav.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,7 @@ export default class Nav extends Component {
<nav className={styles.nav} aria-label="primary navigation">
<div className={styles.menuWrapper}>
<NavLinks
handleScrim={this.props.handleScrim}
skipLinkId={this.props.skipLinkId}
servicesToDisplay={servicesToDisplay}
currentService={this.props.service}
Expand Down Expand Up @@ -179,6 +180,7 @@ Nav.propTypes = {
accountsLinks: PropTypes.object,
onNavigating: PropTypes.oneOfType([PropTypes.func, PropTypes.string]),
additionalSubMenuItems: PropTypes.arrayOf(PropTypes.object),
handleScrim: PropTypes.func,
};

Nav.defaultProps = {
Expand Down
40 changes: 26 additions & 14 deletions src/Header/Nav/NavLinks/NavLinks.jsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useState } from "react";
import React, { useState, useEffect } from "react";
import classnames from "classnames";
import FocusTrap from "focus-trap-react";
import PropTypes from "prop-types";
Expand All @@ -21,11 +21,17 @@ export function NavLinks({
subLinks,
onNavigating,
skipLinkId,
handleScrim,
}) {
const [idOfOpenDropdown, setidOfOpenDropdown] = useState(null);
const [focusTrapActive] = useState(idOfOpenDropdown !== null);

const ESCAPE_KEYS = ["27", "Escape"];

useEffect(() => {
emollett marked this conversation as resolved.
Show resolved Hide resolved
handleScrim(Boolean(idOfOpenDropdown !== null));
}, [idOfOpenDropdown]);

function handleNavButtonClick(id) {
setidOfOpenDropdown(id === idOfOpenDropdown ? null : id);
// TODO: Track dropdown opening
Expand All @@ -34,6 +40,7 @@ export function NavLinks({
function handleNavLinkClick(e) {
e.preventDefault();
setidOfOpenDropdown(null);

const href = e.currentTarget.getAttribute("href");
trackEvent(
defaultEventCategory,
Expand All @@ -50,30 +57,31 @@ export function NavLinks({
if (ESCAPE_KEYS.includes(String(key))) setidOfOpenDropdown(null);
}

function clickOutsideNav() {
setidOfOpenDropdown(null);
// NOTE: could the following be solved with context?
function clickOutsideNav(e) {
var thingYouClickedOn = e.target;
var areaToAvoid = document.getElementById("header-menu");
if (!areaToAvoid.contains(thingYouClickedOn)) {
setidOfOpenDropdown(null);
}
}

useEventListener("keydown", escapeDropdown);
useEventListener("click", clickOutsideNav, document.querySelector("main"));
useEventListener(
"click",
clickOutsideNav,
document.querySelector("#global-nav-search-form")
);
useEventListener(
"click",
clickOutsideNav,
document.querySelector("#my-account-button")
document.querySelector("#global-nav-header")
);
// ---------------

useEventListener("keydown", escapeDropdown);

const options = {
clickOutsideDeactivates: true,
initialFocus: false,
};

return (
<FocusTrap active={idOfOpenDropdown !== null} focusTrapOptions={options}>
<FocusTrap active={focusTrapActive} focusTrapOptions={options}>
<ul className={styles.menuList} aria-labelledby="header-menu-button">
{servicesToDisplay.map(
(
Expand Down Expand Up @@ -115,9 +123,12 @@ export function NavLinks({
>
<span aria-label={abbreviation && title}>{text}</span>{" "}
{id === idOfOpenDropdown ? (
<ChevronUp className={styles.icon} />
<ChevronUp className={styles.icon} pointerEvents="none" />
) : (
<ChevronDown className={styles.icon} />
<ChevronDown
className={styles.icon}
pointerEvents="none"
/>
)}
</button>
) : (
Expand Down Expand Up @@ -169,6 +180,7 @@ NavLinks.propTypes = {
currentService: PropTypes.string,
subLinks: PropTypes.array,
onNavigating: PropTypes.oneOfType([PropTypes.func, PropTypes.string]),
handleScrim: PropTypes.func,
};

export default NavLinks;