Skip to content
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
@@ -0,0 +1,48 @@
# PFE-Navigation Architecture

## Light DOM & Shadow DOM Relationship

This component is (mostly) using a 'light DOM as data' approach. Meaning there is a lot of content that is added in the `<pfe-navigation>` tag that is not slotted, so it doesn't directly show up in the shadow DOM.

We're doing this so we can tightly manage the CSS and behaviors of certain parts of the component.

Slotted content (e.g. the search form) we're reserving for parts of the navigation we expect the site owner to manage the markup, styles, and behavior.

The main navigation and the logo are all cloned into the shadow DOM when the component connects (in a function called `_processLightDom()`), and is updated if there are changes in the light DOM content that needs to be copied over.

We're cloning (not moving) the light DOM elements so the site owner can make dynamic updates to the navigation if needed, and a mutation observer can catch the changes so we can bring them in.

The downside of cloning the content is no javascript behavior added to the light DOM elements will follow it into the shadow DOM version.

Our goal is to handle all of the standard analytics events, and provide custom events for anything else that needs to be caught by the light DOM.

## Navigation State

Because there is a lot of classes, aria attributes, and state to manage in the menu we've made one function to rule all navigation state `_changeNavigationState()`.

It handles almost all† of the UI logic, ensuring things like:
* only one dropdown is open at a time
* allowing a user to close the currently open toggle but keeping a parent one open
* Adding/removing any aria attributes or classes that need to be updated after the state change
* Manages focus state (but only when needed for assistive tech)

> † The exception is toggle buttons that have 'children toggles' have a little logic in their click handler. For example, the `mobile__button`'s dropdown has the main menu dropdowns, and at some breakpoints the All Red Hat dropdown.

Any time a toggle needs to be opened or closed, it should go through `_changeNavigationState`.

There are two internal helper functions in `_changeNavigationState`:
* `_openDropdown`
* `_closeDropdown`

These were made internal because they don't have all of the logic neccesary to manage the navigation's state, but do have the self contained logic for opening/closing a single dropdown.

There are also three helper functions on `PfeNavigation` that manage the attributes related to dropdowns:
* `_removeDropdownAttributes`
* `_addOpenDropdownAttributes`
* `_addCloseDropdownAttributes`

These only manage the attributes, and should only be used inside of `_changeNavigationState`, or in the case of interactions that change the properties of a dropdown, but not the navigation state (e.g. resizing the window to a point where an element isn't a dropdown).

@todo JS breakpoints

@todo Sass breakpoint & collapse mixins
11 changes: 9 additions & 2 deletions elements/pfe-navigation/src/pfe-navigation.js
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,13 @@ class PfeNavigation extends PFElement {
if (openToggleId.startsWith("main-menu") && toggleId === "mobile__button") {
return true;
}
if (
openToggleId === "secondary-links__button--all-red-hat" &&
toggleId === "mobile__button" &&
this.isSecondaryLinksSectionCollapsed()
) {
return true;
}

// Only checks for prefix so if main-menu is queried and main-menu__dropdown--Link-Name is open it still evaluates as true
// This prevents the main-menu toggle shutting at mobile when a sub-section is opened
Expand Down Expand Up @@ -454,7 +461,7 @@ class PfeNavigation extends PFElement {
* @return {boolean} True if the final state is open, false if closed
*/
_changeNavigationState(toggleId, toState) {
const debugNavigationState = true; // Should never be committed as true
const debugNavigationState = false; // Should never be committed as true

if (debugNavigationState) {
console.log("_changeNavigationState", toggleId, toState);
Expand Down Expand Up @@ -524,7 +531,7 @@ class PfeNavigation extends PFElement {
}
};

// Shut any open dropdowns
// Shut any open dropdowns before we open any other
if (currentlyOpenToggleId) {
const openToggle = this.shadowRoot.getElementById(currentlyOpenToggleId);

Expand Down
43 changes: 35 additions & 8 deletions elements/pfe-navigation/src/pfe-navigation.scss
Original file line number Diff line number Diff line change
Expand Up @@ -201,7 +201,7 @@ See mixin blocks before selectors that contain the selector name and the three l
}
}

// @todo Fix my specificity issues
// @todo Fix specificity issues with mobile dropdown selector
.pfe-navigation__mobile-dropdown[class][class][class] {
position: absolute;
top: 100%;
Expand Down Expand Up @@ -258,8 +258,6 @@ See mixin blocks before selectors that contain the selector name and the three l
}
}

// Layout changes per breakpoint
// See comment at top of file
.pfe-navigation__outer-menu-wrapper {
height: auto;

Expand All @@ -277,6 +275,20 @@ See mixin blocks before selectors that contain the selector name and the three l

.pfe-navigation__outer-menu-wrapper__inner {
width: 100%;
transition: transform 0.25s ease-in-out;

[pfe-navigation-open-toggle='secondary-links__button--all-red-hat'] & {
position: relative;
transform: translate(-100vw);
@include breakpoint('secondary-links-expand') {
position: static;
transform: none;
}
@include collapse('secondary-links') {
position: relative;
transform: translate(-100vw);
}
}
}

// Menu, List, & Items
Expand Down Expand Up @@ -911,12 +923,27 @@ See mixin blocks before selectors that contain the selector name and the three l
}
}

@mixin pfe-navigation__all-red-hat-wrapper--mobile--active {
position: absolute;
// A parent element has padding: 16px 32px;, the offsets below are to counteract that
top: -16px;
left: calc(100vw - 32px);
width: calc(100% + 64px);
height: calc(100% + 32px);
overflow-y: auto;
}
.pfe-navigation__all-red-hat-wrapper {
// JS Adds aria-hidden at the appropriate time before open, or after close
// This prevents the dropdown from showing to anyone, but also lets JS get the dropdown height
// Unlike display: none
&[aria-hidden='true'] {
visibility: hidden;
[pfe-navigation-open-toggle='secondary-links__button--all-red-hat'] & {
@include pfe-navigation__all-red-hat-wrapper--mobile--active;
@include breakpoint('secondary-links-expand') {
position: static;
width: auto;
height: auto;
overflow: visible;
}
@include collapse('secondary-links') {
@include pfe-navigation__all-red-hat-wrapper--mobile--active;
}
}
}

Expand Down