Skip to content

Commit

Permalink
Arf 81499 create user nav (#29382)
Browse files Browse the repository at this point in the history
* 81499 Create User Nav

* 81499 Integrate UserNav into Header

* Styling fixes, DRY code, revert icon autofix

* Add TODO comment and included in issue

* Add unit testing

* Add user nav dropdown and update styling

* Update testing for user nav dropdown

* Set default state to not logged in and not loading

* Update so event listener only present when dropdown is open

* Add white-space: nowrap

* Update LogoRow unit tests based on UserNav addition

* Update header e2e tests with UserNav changes
  • Loading branch information
williamphelps13 authored May 1, 2024
1 parent 976275c commit 0216853
Show file tree
Hide file tree
Showing 8 changed files with 262 additions and 31 deletions.
Original file line number Diff line number Diff line change
@@ -1,6 +1,37 @@
@import "~@department-of-veterans-affairs/formation/sass/shared-variables";

.arp-header {
.va-header-logo-wrapper {
display: flex;
align-items: center;
}

.user-nav {
white-space: nowrap;

.loading-icon-container {
color: var(--vads-color-white);
display: flex;
justify-content: center;
align-items: center;
flex-grow: 1;
}

.icon {
width: 26px;
height: 24px;
}

.user-dropdown-email {
margin-left: 4px;
display: block;
max-width: 120px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
}

.mobile {
.usa-banner-header {
display: flex;
Expand All @@ -14,6 +45,14 @@
width: 320px;
}

.user-nav {
margin-left: 1rem;

.user-dropdown-email {
max-width: 64px;
}
}

@media (min-width: $medium-screen) {
display: none !important;
}
Expand All @@ -26,12 +65,21 @@
display: block;

.arp-logo {
width: 496px;
width: 480px;
}

.vet-toolbar {
display: flex;
flex-basis: 198px;
justify-content: flex-end;
flex: 0 1 30rem;

.user-nav {
margin-inline: 1rem;

.loading-icon-container {
width: 88px;
}
}
}

.va-header-logo-menu {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import React from 'react';
import { Link } from 'react-router-dom-v5-compat';

import { SIGN_IN_URL } from '../../../../constants';
import UserNav from '../common/UserNav';

export const MobileLogoRow = () => {
return (
Expand All @@ -23,17 +23,7 @@ export const MobileLogoRow = () => {
<div className="vads-u-display--flex vads-u-flex-direction--row vads-u-align-items--center">
<div className="profile-nav-container">
<div className="hidden-header vads-u-display--flex vads-u-align-items--center">
<div className="sign-in-nav">
<div className="sign-in-links">
<a
data-testid="mobile-logo-row-sign-in-link"
href={SIGN_IN_URL}
className="sign-in-link"
>
Sign in
</a>
</div>
</div>
<UserNav isMobile />
</div>
</div>
{/* eslint-disable-next-line @department-of-veterans-affairs/prefer-button-component, react/button-has-type */}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import React from 'react';
import { Link } from 'react-router-dom-v5-compat';

import { SIGN_IN_URL } from '../../../../constants';
import UserNav from '../common/UserNav';

const WiderThanMobileLogoRow = () => {
return (
Expand All @@ -27,18 +27,7 @@ const WiderThanMobileLogoRow = () => {
>
Contact us
</a>
<div className="sign-in-nav">
<div className="sign-in-links">
{/* eslint-disable-next-line @department-of-veterans-affairs/prefer-button-component, react/button-has-type */}
<a
data-testid="wider-than-mobile-logo-row-sign-in-link"
className="usa-button usa-button-primary"
href={SIGN_IN_URL}
>
Sign in
</a>
</div>
</div>
<UserNav />
</div>
</div>
);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
import React, { useState, useRef, useEffect } from 'react';
import PropTypes from 'prop-types';

import { SIGN_IN_URL, SIGN_OUT_URL } from '../../../../constants';

const testProfile = null;
// const testProfile = { firstName: 'James', lastName: 'Smith' };

const UserNav = ({ isMobile, isLoading = false, profile = testProfile }) => {
let content = null;

const [isDropdownOpen, setDropdownOpen] = useState(false);
const dropdownRef = useRef(null);

const toggleDropdown = () => setDropdownOpen(!isDropdownOpen);

useEffect(
() => {
const handleClickOutside = event => {
if (
dropdownRef.current &&
!dropdownRef.current.contains(event.target)
) {
setDropdownOpen(false);
}
};

if (isDropdownOpen) {
document.addEventListener('mousedown', handleClickOutside);
}

return () => {
document.removeEventListener('mousedown', handleClickOutside);
};
},
[isDropdownOpen],
);

if (isLoading) {
content = (
<div className="loading-icon-container">
{/* eslint-disable-next-line @department-of-veterans-affairs/prefer-icon-component */}
<i
data-testid="user-nav-loading-icon"
className="fa fa-spinner fa-spin fa-lg"
aria-hidden="true"
role="presentation"
/>
</div>
);
} else if (!profile && isMobile) {
content = (
<a
href={SIGN_IN_URL}
data-testid="user-nav-mobile-sign-in-link"
className="sign-in-link"
>
Sign in
</a>
);
} else if (!profile && !isMobile) {
content = (
<a
data-testid="user-nav-wider-than-mobile-sign-in-link"
className="usa-button usa-button-primary"
href={SIGN_IN_URL}
>
Sign in
</a>
);
} else if (profile) {
content = (
<div className="va-dropdown" ref={dropdownRef}>
{/* eslint-disable-next-line @department-of-veterans-affairs/prefer-button-component, react/button-has-type */}
<button
data-testid="user-nav-dropdown-panel-button"
className="sign-in-drop-down-panel-button va-btn-withicon va-dropdown-trigger"
aria-controls="account-menu"
aria-expanded={isDropdownOpen}
onClick={toggleDropdown}
type="button"
>
<span>
<svg
aria-hidden="true"
focusable="false"
className="vads-u-display--block vads-u-margin-right--0 medium-screen:vads-u-margin-right--0p5 icon"
viewBox="0 2 21 21"
xmlns="http://www.w3.org/2000/svg"
>
<path
fill="#fff"
d="M12 12c-1.1 0-2.04-.4-2.82-1.18A3.85 3.85 0 0 1 8 8c0-1.1.4-2.04 1.18-2.83A3.85 3.85 0 0 1 12 4c1.1 0 2.04.4 2.82 1.17A3.85 3.85 0 0 1 16 8c0 1.1-.4 2.04-1.18 2.82A3.85 3.85 0 0 1 12 12Zm-8 8v-2.8c0-.57.15-1.09.44-1.56a2.9 2.9 0 0 1 1.16-1.09 13.76 13.76 0 0 1 9.65-1.16c1.07.26 2.12.64 3.15 1.16.48.25.87.61 1.16 1.09.3.47.44 1 .44 1.56V20H4Z"
/>
</svg>
<div
data-testid="user-nav-user-name"
className="user-dropdown-email"
data-dd-privacy="mask"
data-dd-action-name="First Name"
>
{`${profile.firstName}${isMobile ? '' : ` ${profile.lastName}`}`}
</div>
</span>
</button>
<div
className={`va-dropdown-panel ${isDropdownOpen ? '' : 'hidden'}`}
id="account-menu"
>
<ul>
<li>
<a data-testid="user-nav-sign-out-link" href={SIGN_OUT_URL}>
Sign Out
</a>
</li>
</ul>
</div>
</div>
);
}

return <div className="user-nav">{content}</div>;
};

UserNav.propTypes = {
isLoading: PropTypes.bool,
isMobile: PropTypes.bool,
profile: PropTypes.shape({
firstName: PropTypes.string,
lastName: PropTypes.string,
}),
};

export default UserNav;
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ describe('MobileLogoRow', () => {

it('renders sign in link', () => {
const { getByTestId } = getMobileLogoRow();
expect(getByTestId('mobile-logo-row-sign-in-link').textContent).to.eq(
expect(getByTestId('user-nav-mobile-sign-in-link').textContent).to.eq(
'Sign in',
);
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ describe('WiderThanMobileLogoRow', () => {
it('renders sign in link', () => {
const { getByTestId } = getWiderThanMobileLogoRow();
expect(
getByTestId('wider-than-mobile-logo-row-sign-in-link').textContent,
getByTestId('user-nav-wider-than-mobile-sign-in-link').textContent,
).to.eq('Sign in');
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import React from 'react';
import { fireEvent, render } from '@testing-library/react';
import { expect } from 'chai';

import UserNav from '../../../../../components/common/Header/common/UserNav';
import { SIGN_IN_URL, SIGN_OUT_URL } from '../../../../../constants';

describe('UserNav mobile', () => {
const getUserNavMobile = (isLoading, profile) =>
render(<UserNav isMobile isLoading={isLoading} profile={profile} />);

it('renders as Loading', () => {
const { getByTestId } = getUserNavMobile(true, null);
expect(getByTestId('user-nav-loading-icon')).to.exist;
});

it('renders as Sign in link when no profile exists', () => {
const { getByTestId } = getUserNavMobile(false, null);
const signInLink = getByTestId('user-nav-mobile-sign-in-link');

expect(signInLink.textContent).to.eq('Sign in');
expect(signInLink.getAttribute('href')).to.eq(SIGN_IN_URL.toString());
});

it('renders with first name when has profile', () => {
const profile = { firstName: 'First', lastName: 'Last' };
const { getByTestId } = getUserNavMobile(false, profile);

expect(getByTestId('user-nav-user-name').textContent).to.eq(
profile.firstName,
expect,
);
fireEvent.click(getByTestId('user-nav-dropdown-panel-button'));
expect(getByTestId('user-nav-sign-out-link').getAttribute('href')).to.eq(
SIGN_OUT_URL.toString(),
);
});
});

describe('UserNav wider than mobile', () => {
const getUserNavWider = (isLoading, profile) =>
render(<UserNav isLoading={isLoading} profile={profile} />);

it('renders as Loading', () => {
const { getByTestId } = getUserNavWider(true, null);
expect(getByTestId('user-nav-loading-icon')).to.exist;
});

it('renders as Sign in link when no profile exists', () => {
const { getByTestId } = getUserNavWider(false, null);
const signInLink = getByTestId('user-nav-wider-than-mobile-sign-in-link');

expect(signInLink.textContent).to.eq('Sign in');
expect(signInLink.getAttribute('href')).to.eq(SIGN_IN_URL.toString());
});

it('renders with first and last name when has profile', () => {
const profile = { firstName: 'First', lastName: 'Last' };
const { getByTestId } = getUserNavWider(false, profile);

expect(getByTestId('user-nav-user-name').textContent).to.eq(
`${profile.firstName} ${profile.lastName}`,
expect,
);
fireEvent.click(getByTestId('user-nav-dropdown-panel-button'));
expect(getByTestId('user-nav-sign-out-link').getAttribute('href')).to.eq(
SIGN_OUT_URL.toString(),
);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ describe('Header on mobile', () => {
it('allows navigating from the Landing Page to unified sign-in page', () => {
cy.axeCheck();

cy.get('[data-testid=mobile-logo-row-sign-in-link]')
cy.get('[data-testid=user-nav-mobile-sign-in-link]')
.contains('Sign in')
.click();
cy.location('pathname').should('equal', '/sign-in/');
Expand Down Expand Up @@ -61,7 +61,7 @@ describe('Header on screens wider than mobile', () => {
it('allows navigating from the Landing Page to unified sign-in page', () => {
cy.axeCheck();

cy.get('[data-testid=wider-than-mobile-logo-row-sign-in-link]')
cy.get('[data-testid=user-nav-wider-than-mobile-sign-in-link]')
.contains('Sign in')
.click();
cy.location('pathname').should('equal', '/sign-in/');
Expand Down

0 comments on commit 0216853

Please sign in to comment.