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-117 Remove need to assert on dom #148

Merged
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
b2958bf
context experiments
johndavey72 Nov 11, 2021
26594c1
basic context setup
johndavey72 Nov 12, 2021
3fe0e51
tidy up debugging
johndavey72 Nov 12, 2021
20a1974
uncommen event listener for expected closing behaviour
johndavey72 Nov 12, 2021
d03b29d
make debug clearer
johndavey72 Nov 12, 2021
8930967
clean up console logging for clarity
johndavey72 Nov 12, 2021
0a35a82
move globalnavcontext provider up to header component
johndavey72 Nov 12, 2021
1da7119
GN-117 add useCallback to clickOutside hook to maintain reference to …
johndavey72 Nov 12, 2021
eed5e6e
GN-117 Refactor Nav component to class. Add custom hook and context f…
johndavey72 Nov 12, 2021
4d61eb6
GN-117 Begin conversion of Account class component to functional comp…
johndavey72 Nov 16, 2021
f748f72
GN-117 Remove commented out lines
johndavey72 Nov 16, 2021
21e7714
GN-117 Reinstate propTypes for Naccount placeholder component
johndavey72 Nov 16, 2021
356e5df
Side effect sideline (#150)
wa-rren-dev Nov 17, 2021
e898e7f
Tests needed
wa-rren-dev Nov 17, 2021
2081a88
GN-117 Tidy up redundant arguments and commented out logic for useCli…
johndavey72 Nov 18, 2021
1a29976
GN-117 Change directory structure for hooks and react context. Make u…
johndavey72 Nov 18, 2021
f105916
GN-117 Import GlobalNavContextProvider from new location for Account …
johndavey72 Nov 18, 2021
2ff0d29
GN-117 Fixing broken unit tests
johndavey72 Nov 18, 2021
5d23e1e
GN-117 Fixing broken tests
johndavey72 Nov 18, 2021
993c43c
GN-117 Fixing broken tests
johndavey72 Nov 18, 2021
6cc73d3
Replace simulate click
wa-rren-dev Nov 18, 2021
be3d88c
GN-117 Revert some tests to shallow rendering after adding defaults t…
johndavey72 Nov 19, 2021
edab694
GN-117 Revert to shallow mounting for a previously failing unit test
johndavey72 Nov 19, 2021
eb1edec
GN-117 Convert unit test from shallow to mount due to issues around e…
johndavey72 Nov 19, 2021
d15dfa5
GN-117 Rename GlobalNavContext to HeaderContext
johndavey72 Nov 19, 2021
c9d6650
GN-117 remove unused dependencies from account test
johndavey72 Nov 19, 2021
5ac4075
GN-117 Tidy up unit tests
johndavey72 Nov 19, 2021
adf3196
Merge branch 'GN-115-Mega-nav-production-ready' into GN-117-Remove-ne…
johndavey72 Nov 19, 2021
ebc92be
testing custom hook useClickOutside
johndavey72 Nov 19, 2021
6b6e3cd
basic setup
johndavey72 Nov 19, 2021
1375de4
Custom hook test
wa-rren-dev Nov 19, 2021
79fe740
Update test
wa-rren-dev Nov 19, 2021
58ba03d
Rename test
wa-rren-dev Nov 19, 2021
a7af813
Merge branch 'GN-115-Mega-nav-production-ready' into GN-117-Remove-ne…
wa-rren-dev Nov 22, 2021
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
23 changes: 23 additions & 0 deletions src/GlobalNavContext.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import React, { useState, createContext } from "react";
import PropTypes from "prop-types";

export const GlobalNavContext = createContext();

export const GlobalNavContextProvider = function ({ children }) {
const [idOfOpenDropdown, setidOfOpenDropdown] = useState(null);

const value = {
idOfOpenDropdown,
setidOfOpenDropdown,
};

return (
<GlobalNavContext.Provider value={value}>
{children}
</GlobalNavContext.Provider>
);
};

GlobalNavContextProvider.propTypes = {
children: PropTypes.array,
};
5 changes: 3 additions & 2 deletions src/Header/Header.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import Account from "./Account";
import SkipLink from "./SkipLink";

import styles from "./Header.module.scss";
import { GlobalNavContextProvider } from "../GlobalNavContext";

export class Header extends Component {
constructor(props) {
Expand Down Expand Up @@ -84,7 +85,7 @@ export class Header extends Component {
render() {
return (
this.props.enabled !== false && (
<>
<GlobalNavContextProvider>
<span
id="scrim"
className={this.state.scrimIsActive ? styles.scrim : undefined}
Expand Down Expand Up @@ -171,7 +172,7 @@ export class Header extends Component {
<CoronaMessage onResize={this.props.onResize} />
<OldIEMessage />
</div>
</>
</GlobalNavContextProvider>
)
);
}
Expand Down
217 changes: 110 additions & 107 deletions src/Header/Nav/Nav.jsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { Component } from "react";
import React, { useContext } from "react";
import PropTypes from "prop-types";
import classnames from "classnames";

Expand All @@ -11,14 +11,18 @@ import {
headerClickEventAction,
} from "../../tracker";

export default class Nav extends Component {
constructor(props) {
super(props);
import useClickOutside from "../../useClickOutside";
import { GlobalNavContext } from "../../GlobalNavContext";

this.handleAccountNavItemClick = this.handleAccountNavItemClick.bind(this);
}
function Nav(props) {
const { accountsLinks } = props;
const context = useContext(GlobalNavContext);
const { ref } = useClickOutside(
context.idOfOpenDropdown,
context.setidOfOpenDropdown
);

handleNavItemClick(e) {
function handleNavItemClick(e) {
e.preventDefault();

const href = e.currentTarget.getAttribute("href");
Expand All @@ -38,7 +42,7 @@ export default class Nav extends Component {
);
}

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

let eventLabel;
Expand All @@ -65,114 +69,113 @@ export default class Nav extends Component {
}
}

render() {
const { accountsLinks } = this.props;

// Links from NICE Accounts is an object so flatten to a loop for easier traversal
const accountsLinksArray =
accountsLinks &&
Object.keys(accountsLinks).map((text) => ({
text: text,
href: accountsLinks[text],
}));

// Would need to polyfill Array.prototype.find to rewrite these loops, whilst we support IE
// See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/find for support
let activeService = null;
let internalService = false;
let servicesToDisplay = services.external; //default to displaying external services.
for (let i = 0; i < services.internal.length; i++) {
const internalRootLink = services.internal[i];
if (this.props.service && internalRootLink.id === this.props.service) {
internalService = true;
activeService = internalRootLink;
servicesToDisplay = [internalRootLink]; //unlike external, internal services dosn't display other internal services.
break;
}
// Links from NICE Accounts is an object so flatten to a loop for easier traversal
const accountsLinksArray =
accountsLinks &&
Object.keys(accountsLinks).map((text) => ({
text: text,
href: accountsLinks[text],
}));

// Would need to polyfill Array.prototype.find to rewrite these loops, whilst we support IE
// See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/find for support
let activeService = null;
let internalService = false;
let servicesToDisplay = services.external; //default to displaying external services.
for (let i = 0; i < services.internal.length; i++) {
const internalRootLink = services.internal[i];
if (props.service && internalRootLink.id === props.service) {
internalService = true;
activeService = internalRootLink;
servicesToDisplay = [internalRootLink]; //unlike external, internal services dosn't display other internal services.
break;
}
if (!internalService) {
for (let i = 0; i < services.external.length; i++) {
const externalService = services.external[i];
if (this.props.service && externalService.id === this.props.service) {
activeService = externalService;
break;
}
}
if (!internalService) {
for (let i = 0; i < services.external.length; i++) {
const externalService = services.external[i];
if (props.service && externalService.id === props.service) {
activeService = externalService;
break;
}
}
let additionalSubMenuLinks = [];
for (let i = 0; i < this.props.additionalSubMenuItems.length; i++) {
const additionalSubMenuItem = this.props.additionalSubMenuItems[i];
if (
typeof additionalSubMenuItem !== "undefined" &&
additionalSubMenuItem.service === this.props.service &&
Array.isArray(additionalSubMenuItem.links)
) {
additionalSubMenuLinks = additionalSubMenuItem.links.map((link) => ({
text: link.text,
href: link.url,
}));
}
}
let additionalSubMenuLinks = [];
for (let i = 0; i < props.additionalSubMenuItems.length; i++) {
const additionalSubMenuItem = props.additionalSubMenuItems[i];
if (
typeof additionalSubMenuItem !== "undefined" &&
additionalSubMenuItem.service === props.service &&
Array.isArray(additionalSubMenuItem.links)
) {
additionalSubMenuLinks = additionalSubMenuItem.links.map((link) => ({
text: link.text,
href: link.url,
}));
}
const subLinks =
activeService &&
activeService.links &&
activeService.links.concat(additionalSubMenuLinks);

return (
<div
id="header-menu"
className={classnames(
styles.wrapper,
{
[styles.wrapperExpanded]: this.props.isExpanded,
},
{
[styles.wrapperWithSubLinks]: subLinks,
}
)}
>
<nav className={styles.nav} aria-label="primary navigation">
}
const subLinks =
activeService &&
activeService.links &&
activeService.links.concat(additionalSubMenuLinks);

return (
<div
id="header-menu"
className={classnames(
styles.wrapper,
{
[styles.wrapperExpanded]: props.isExpanded,
},
{
[styles.wrapperWithSubLinks]: subLinks,
}
)}
ref={ref}
>
<nav className={styles.nav} aria-label="primary navigation">
<div className={styles.menuWrapper}>
<NavLinks
handleScrim={props.handleScrim}
skipLinkId={props.skipLinkId}
servicesToDisplay={servicesToDisplay}
currentService={props.service}
subLinks={subLinks}
onNavigating={props.onNavigating}
/>
</div>
</nav>
{accountsLinksArray && (
<nav
aria-label="My account"
className={classnames(styles.nav, styles.myAccount)}
>
{accountsLinksArray.length > 1 && (
<h2 className={styles.myAccountHeading}>My account</h2>
)}
<div className={styles.menuWrapper}>
<NavLinks
handleScrim={this.props.handleScrim}
skipLinkId={this.props.skipLinkId}
servicesToDisplay={servicesToDisplay}
currentService={this.props.service}
subLinks={subLinks}
onNavigating={this.props.onNavigating}
/>
<ul className={styles.menuList}>
{accountsLinksArray.map(({ href, text }) => (
<li key={href}>
<a
href={href}
className={styles.link}
onClick={handleAccountNavItemClick}
>
{text}
</a>
</li>
))}
</ul>
</div>
</nav>
{accountsLinksArray && (
<nav
aria-label="My account"
className={classnames(styles.nav, styles.myAccount)}
>
{accountsLinksArray.length > 1 && (
<h2 className={styles.myAccountHeading}>My account</h2>
)}
<div className={styles.menuWrapper}>
<ul className={styles.menuList}>
{accountsLinksArray.map(({ href, text }) => (
<li key={href}>
<a
href={href}
className={styles.link}
onClick={this.handleAccountNavItemClick}
>
{text}
</a>
</li>
))}
</ul>
</div>
</nav>
)}
</div>
);
}
)}
</div>
);
}

export default Nav;

Nav.propTypes = {
skipLinkId: PropTypes.string,
service: PropTypes.string,
Expand Down
35 changes: 11 additions & 24 deletions src/Header/Nav/NavLinks/NavLinks.jsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useState, useEffect } from "react";
import React, { useState, useEffect, useContext } from "react";
import classnames from "classnames";
import FocusTrap from "focus-trap-react";
import PropTypes from "prop-types";
Expand All @@ -7,6 +7,7 @@ import ChevronDown from "@nice-digital/icons/lib/ChevronDown";
import ChevronUp from "@nice-digital/icons/lib/ChevronUp";
import SubNav from "../SubNav";
import Dropdown from "../Dropdown";
import { GlobalNavContext } from "../../../GlobalNavContext";

import styles from "./NavLinks.module.scss";
import {
Expand All @@ -23,9 +24,9 @@ export function NavLinks({
skipLinkId,
handleScrim,
}) {
const [idOfOpenDropdown, setidOfOpenDropdown] = useState(null);
const [focusTrapActive] = useState(idOfOpenDropdown !== null);
const [canUseDOM, setCanUseDOM] = useState(false);
const { idOfOpenDropdown, setidOfOpenDropdown } =
useContext(GlobalNavContext);

useEffect(() => {
setCanUseDOM(true);
Expand Down Expand Up @@ -62,22 +63,6 @@ export function NavLinks({
if (ESCAPE_KEYS.includes(String(key))) 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(
"click",
clickOutsideNav,
document.querySelector("#global-nav-header")
);
// ---------------

useEventListener("keydown", escapeDropdown);

const options = {
Expand Down Expand Up @@ -126,14 +111,16 @@ export function NavLinks({
aria-controls={`dropdown-${id}`}
aria-expanded={id === idOfOpenDropdown ? true : false}
>
<span aria-label={abbreviation && title}>{text}</span>{" "}
<span aria-label={abbreviation && title}>{text}</span>
{id === idOfOpenDropdown ? (
<ChevronUp className={styles.icon} pointerEvents="none" />
) : (
<ChevronDown
className={styles.icon}
pointerEvents="none"
/>
<>
<ChevronDown
className={styles.icon}
pointerEvents="none"
/>
</>
)}
</button>
) : (
Expand Down
Loading