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

New Accessible Dropdown Nav Bar Component #478

Draft
wants to merge 25 commits into
base: main
Choose a base branch
from
Draft

Conversation

danparnella
Copy link
Contributor

@danparnella danparnella commented May 10, 2021

Provides a contained and simple to stand up accessible navigation bar with a menu of parent links and optional child links hidden within submenus.

Known Issues

  • If while in mobile view, a user opens some submenus, then closes the mobile menu and expands their browser to show the desktop view, the submenus will still be open and will need to be closed with the arrow buttons. We could allow for something to be passed down from the mobile menu button to trigger closing all submenus when the mobile menu is closed, but this seemed like a far enough out edge case (and a non-breaking issue) to avoid adding more complication to the component.

Author Checklist

  • Add unit test(s)
  • Update version in package.json (see the versioning guidelines)
  • Update documentation (if necessary)
  • Add story to storybook (if necessary)
  • Assign dev reviewer

@dpikt dpikt temporarily deployed to lp-components-pr-478 May 10, 2021 20:05 Inactive
@danparnella danparnella temporarily deployed to lp-components-pr-478 May 10, 2021 20:09 Inactive
@danparnella danparnella temporarily deployed to lp-components-pr-478 May 10, 2021 20:39 Inactive
@danparnella danparnella marked this pull request as draft May 10, 2021 21:18
@danparnella danparnella temporarily deployed to lp-components-pr-478 May 13, 2021 18:25 Inactive
Comment on lines +1 to +8
.dropdown-nav-bar.no-mobile {
@import 'dropdown-nav-bar-no-mobile';
@import 'dropdown-nav-bar-common';
}

.dropdown-nav-bar:not(.no-mobile) {
@import 'dropdown-nav-bar-common';
}
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This allowed me to use the same styles for both implementations and just change the $tablet sass variable for the no-mobile version.

Copy link
Contributor

Choose a reason for hiding this comment

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

Interesting! I'll defer to @marchwiany on best practices in using dynamic variables as this is pretty foreign to me.

For my own edification, would the following not perform a similarly sequenced import? Maybe in the compilation step the second block is executed before the first?

.dropdown-nav-bar.no-mobile {
  @import 'dropdown-nav-bar-no-mobile';
}

.dropdown-nav-bar {
  @import 'dropdown-nav-bar-common';
}

.storybook/styles/base/_typography.scss Show resolved Hide resolved
docs.md Show resolved Hide resolved
src/controls/dropdown-nav-menu.js Show resolved Hide resolved
@danparnella danparnella temporarily deployed to lp-components-pr-478 May 13, 2021 19:54 Inactive
Comment on lines +87 to +99
<input
type="checkbox"
id="mobile-nav-button"
checked={isMobileMenuOpen}
onChange={() => {
setIsMobileMenuOpen(!isMobileMenuOpen)
}}
/>
<label htmlFor="mobile-nav-button" className="mobile-menu">
<span />
<span />
<span />
</label>
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Pulled in the mobile menu button used on AZ, but we might have a more up to date version to swap in.

Copy link
Contributor

Choose a reason for hiding this comment

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

Can you walk me through the decision to not include this as part of the component?

Copy link
Contributor

Choose a reason for hiding this comment

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

@marchwiany Do we typically use buttons or are we still mostly using the checkbox approach?

@danparnella danparnella marked this pull request as ready for review May 13, 2021 20:01
@danparnella danparnella requested a review from chawes13 May 13, 2021 20:01
Copy link
Contributor

@chawes13 chawes13 left a comment

Choose a reason for hiding this comment

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

Looking great so far! I have a handful of questions, so it might be helpful to schedule some time to go through them sync

@@ -0,0 +1,91 @@
& {
Copy link
Contributor

Choose a reason for hiding this comment

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

I haven't seen this usage before! What does & signify when it's used at the top-level?

@@ -64,6 +64,7 @@
@import "components/search-results-and-filtering";
@import "components/recruiter-search";
@import "components/tabs";
@import "components/dropdown-nav-bar/dropdown-nav-bar";
Copy link
Contributor

Choose a reason for hiding this comment

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

Will this need to be ported over to the client template as well or will the styles automatically be applied when a user imports the component into their project?

Comment on lines +1 to +8
.dropdown-nav-bar.no-mobile {
@import 'dropdown-nav-bar-no-mobile';
@import 'dropdown-nav-bar-common';
}

.dropdown-nav-bar:not(.no-mobile) {
@import 'dropdown-nav-bar-common';
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Interesting! I'll defer to @marchwiany on best practices in using dynamic variables as this is pretty foreign to me.

For my own edification, would the following not perform a similarly sequenced import? Maybe in the compilation step the second block is executed before the first?

.dropdown-nav-bar.no-mobile {
  @import 'dropdown-nav-bar-no-mobile';
}

.dropdown-nav-bar {
  @import 'dropdown-nav-bar-common';
}

Comment on lines +10 to +57
#mobile-nav-button {
position: absolute;
top: -9999px;
left: -9999px;

&:checked {
& ~ .dropdown-nav-bar .dropdown-nav-menu {
top: 0;
position: fixed;
overflow: auto;
display: block;
height: 100%;
width: 100%;
}

& ~ .mobile-menu {
span:nth-child(1) {
transform: translateY(8px) rotate(45deg);
}
span:nth-child(2) {
opacity: 0;
}
span:nth-child(3) {
transform: translateY(-8px) rotate(-45deg);
}
}
}
}

.mobile-menu {
position: absolute;
right: 15px;
z-index: 3;
cursor: pointer;

@include media($tablet) {
display: none;
}

span {
display: block;
margin: 4px auto;
height: 4px;
width: 25px;
background: #333;
transition: 0.5s;
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

What's the distinction between these styles and the ones included in _dropdown-nav-bar-common?

docs.md Show resolved Hide resolved
}
const menuItemOnBlur = () => {
if (hideSubmenuButtonBeforeFocus) {
// timeout needed to move from link to button without it disappearing
Copy link
Contributor

Choose a reason for hiding this comment

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

Could you expand on this a bit? What's moving from the link to the button? What is "it"?

Comment on lines +65 to +73
{isExternalPath ? (
<a href={path} {...menuItemProps}>
{name}
</a>
) : (
<Link to={path} {...menuItemProps}>
{name}
</Link>
)}
Copy link
Contributor

Choose a reason for hiding this comment

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

There might be a useful abstraction here. Something like IsomorphicLink (inspired by https://github.com/streamich/react-use/blob/master/src/useIsomorphicLayoutEffect.ts)?

function IsomorphicLink ({ path, ...rest }) {
  const isExternalPath = path.startsWith('http') // or whatever the logic ends up being
  return isExternalPath ? <a href={path} {...rest} /> : <Link to={path} {...rest} />
}

*
* @name DropdownNavBar
* @type Function
* @description A control component for navigating through multiple navigation menu items that can include dropdowns with submenu items along with a mobile view option
Copy link
Contributor

Choose a reason for hiding this comment

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

Can you document the expected keyboard functionality here? If you have any links to ARIA authoring practices or other resources that inspired you, that'd be helpful as well!

Copy link
Contributor

Choose a reason for hiding this comment

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

Is there any need for arrow key support? It looks like the authoring practices specifically call out the situation where we have a menuItem that also performs a function:
Image 2021-05-19 at 10 15 07 PM

As well as some other keyboard requirements:
Image 2021-05-19 at 10 17 51 PM

Reference: https://www.w3.org/TR/wai-aria-practices-1.1/#menu

Copy link
Contributor

Choose a reason for hiding this comment

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

There might be some room for interpretation here, but from the following quotes and this example, it seems like using tab to navigate between the items may not be appropriate?

When a menu opens, or when a menubar receives focus, keyboard focus is placed on the first item. All items are focusable as described in § 6.6 Keyboard Navigation Inside Components.

6.6 Keyboard Navigation Inside Components

As described in section § 6.1 Fundamental Keyboard Navigation Conventions, the tab sequence should include only one focusable element of a composite UI component.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ah, that number 4 note is the pattern I had trouble finding! I had moved away from using the arrow navigation vs tab navigation as I wasn't sure that was a common pattern when the parent menu item was also a link, but this sounds pretty clear to me. I'm working on that refactor now.

})}
role="none"
>
<OutsideClickHandler onOutsideClick={closeDesktopSubmenus}>
Copy link
Contributor

Choose a reason for hiding this comment

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

Similar question as before - are there use cases for closing all submenus vs. just the relevant submenu? Or should all submenus always be closed here?

If only one submenu can be open at a time, does that suggest that the value captured should be submenuId and not submenuIds?

const menuItemProps = {
onFocus: menuItemOnFocus,
onBlur: menuItemOnBlur,
role: 'menuItem',
Copy link
Contributor

Choose a reason for hiding this comment

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

Should this be 'menuitem'? Reference: https://www.digitala11y.com/menuitem-role/

@chawes13 chawes13 linked an issue Sep 17, 2021 that may be closed by this pull request
@chawes13 chawes13 marked this pull request as draft September 21, 2021 15:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Add NavBar component
3 participants