Skip to content

Commit

Permalink
feature: Implements the "Me Menu" (#8985)
Browse files Browse the repository at this point in the history
* Adds the Me Menu (WIP)

* Allows for submenu on mobile version

* Moves me-menu's html to nav_auth

* Ensures the dropdowns are aligned after altering perspective
  • Loading branch information
gdixon authored Jun 7, 2021
1 parent fd4e792 commit dc40144
Show file tree
Hide file tree
Showing 4 changed files with 482 additions and 155 deletions.
162 changes: 129 additions & 33 deletions app/assets/v2/js/navbar.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
document.addEventListener('DOMContentLoaded', () => {
const navbarEls = document.querySelectorAll('.gc-navbar-nav');

[...navbarEls].forEach((navbarEl) => {
makeMenu(navbarEl);
});
});

// feed in the navBarEl
const makeMenu = (navbarEl) => {
// debounce actions to avoid measuring too often/losing focus
let debounceClose = false;
let debounceMeasure = false;
Expand All @@ -18,6 +26,9 @@ document.addEventListener('DOMContentLoaded', () => {
},
company: {
moveX: -80
},
profile: {
moveX: -300
}
};

Expand All @@ -28,23 +39,28 @@ document.addEventListener('DOMContentLoaded', () => {
const $navbarSupportedContent = $('#navbarSupportedContent');

// reference constant els used in the menu
const navbarEl = document.querySelector('.gc-navbar-nav');
const navbarContainerEl = document.querySelector('.navbar');
const menuContainerEl = document.querySelector('.gc-menu-container');
const navItemEls = document.querySelectorAll('.gc-nav-item');
const navLinkEls = document.querySelectorAll('.gc-nav-link');
const menuEls = document.querySelectorAll('.gc-menu-wrap');
const spacerEls = document.querySelectorAll('.gc-mobile-spacer');
const contentEl = document.querySelector('.gc-menu-content');
const caretEl = document.querySelector('.gc-menu-caret');
const backgroundEl = document.querySelector('.gc-menu-background');
const submenuEls = document.querySelectorAll('.gc-menu-submenu-wrap');
const menuEls = document.querySelectorAll('.gc-menu-wrap');

const menuContainerEl = navbarEl.querySelector('.gc-menu-container');
const navLinkEls = navbarEl.querySelectorAll('.gc-nav-link');
const contentEl = navbarEl.querySelector('.gc-menu-content');

const caretEl = navbarEl.querySelector('.gc-menu-caret');
const backgroundEl = navbarEl.querySelector('.gc-menu-background');

const submenuEls = navbarEl.querySelectorAll('.gc-menu-submenu-wrap');

const mobileToggleEl = document.querySelector('.navbar-toggler-icon');

// fill these based on what we find in the submenuEls
const submenuToggleEls = {};
const submenuMenuEls = {};
const submenuSpacerEls = {};
const submenuMenuElsByName = {};
const submenuSpacerElsByName = {};
const debounceSubmenuFocus = [];

// record mousePositions when interacting with submenus
Expand All @@ -55,7 +71,7 @@ document.addEventListener('DOMContentLoaded', () => {

return [...from].reduce((carr, el) => {
const attr = el.dataset[dataAttr];
const dest = document.querySelector(`.${className}-${attr}`);
const dest = navbarEl.querySelector(`.${className}-${attr}`);

// record the dest by attr value
carr[attr] = dest;
Expand All @@ -72,7 +88,9 @@ document.addEventListener('DOMContentLoaded', () => {
[...submenuEls].forEach((el) => {
submenuToggleEls[el.dataset.submenu] = el.querySelectorAll('.gc-menu-submenu-toggle');
submenuMenuEls[el.dataset.submenu] = el.querySelectorAll('.gc-menu-submenu');
submenuSpacerEls[el.dataset.submenu] = el.querySelectorAll('.gc-mobile-submenu-spacer');
submenuMenuElsByName[el.dataset.submenu] = indexElsByName(submenuToggleEls[el.dataset.submenu], 'gc-menu-submenu', 'submenu');
submenuSpacerElsByName[el.dataset.submenu] = indexElsByName(submenuToggleEls[el.dataset.submenu], 'gc-mobile-submenu-spacer', 'submenu');
});

// remove transform (rotate()) during measure so that the height/width isn't skewed
Expand Down Expand Up @@ -119,20 +137,32 @@ document.addEventListener('DOMContentLoaded', () => {
};

// measure the submenu's dimensions
const getSubmenuDimensions = (menu) => {
const getSubmenuDimensions = (menu, direction) => {
showMenuContainerEl();

// position of the upperRight boundary
dimensions[`${menu}Submenu-decreasingCorner`] = {
x: dimensions[menu].menuX + menuElsByName[menu].clientWidth,
y: dimensions[menu].menuY - 400
};

// position of the lowerRight boundary
dimensions[`${menu}Submenu-increasingCorner`] = {
x: dimensions[menu].menuX + menuElsByName[menu].clientWidth,
y: dimensions[menu].menuY + menuElsByName[menu].clientHeight + 400
};
if (direction === false) {
// position of the lowerLeft boundary
dimensions[`${menu}Submenu-decreasingCorner`] = {
x: dimensions[menu].menuX + navbarEl.offsetLeft,
y: dimensions[menu].menuY + menuElsByName[menu].clientHeight + 400
};
// position of the upperLeft boundary
dimensions[`${menu}Submenu-increasingCorner`] = {
x: dimensions[menu].menuX + navbarEl.offsetLeft,
y: dimensions[menu].menuY - 400
};
} else {
// position of the upperRight boundary
dimensions[`${menu}Submenu-decreasingCorner`] = {
x: dimensions[menu].menuX + navbarEl.offsetLeft + menuElsByName[menu].clientWidth,
y: dimensions[menu].menuY - 400
};
// position of the lowerRight boundary
dimensions[`${menu}Submenu-increasingCorner`] = {
x: dimensions[menu].menuX + navbarEl.offsetLeft + menuElsByName[menu].clientWidth,
y: dimensions[menu].menuY + menuElsByName[menu].clientHeight + 400
};
}

hideMenuContainerEl();
};
Expand All @@ -151,7 +181,7 @@ document.addEventListener('DOMContentLoaded', () => {
};
});
// get the dimensions for each submenu
[...submenuEls].forEach((el) => getSubmenuDimensions(el.dataset.submenu));
[...submenuEls].forEach((el) => getSubmenuDimensions(el.dataset.submenu, el.dataset.direction == 'ltr'));
};

// remove isVisible transitions to reduce jank
Expand Down Expand Up @@ -189,6 +219,25 @@ document.addEventListener('DOMContentLoaded', () => {
caretEl.style.cssText = 'transition:unset;';
};

// clean up the state of a submenu selection on mobile
const cleanUpSubmenu = (menu, keepSelection) => {
if (!keepSelection) {
[...submenuMenuEls[menu]].forEach((el) => {
el.classList.remove('show');
el.classList.remove('active');
});
}
[...submenuToggleEls[menu]].forEach((el) => {
el.classList.remove('active');
el.classList.remove('gc-menu-submenu-toggle-focus');
el.classList.remove('gc-menu-submenu-toggle-active');
});
[...submenuSpacerEls[menu]].forEach((el) => {
el.classList.remove('active');
el.style.height = '0px';
});
};

// toggle display of menu dropdown (triggered on hover - content is moved and background is scaled)
const showMenu = (navLink) => {
// get menu name from navLink
Expand All @@ -205,12 +254,21 @@ document.addEventListener('DOMContentLoaded', () => {
menuElsByName[menu].querySelector('.gc-menu-submenu').classList.add('active');
}

// remove active state on first load
if (submenuToggleEls[menu]) {
cleanUpSubmenu(menu, true);
}

// wait for .show paint (waiting for display: block)
window.requestAnimationFrame(() => {
// add open to set opacity and to transition the rotateX
menuContainerEl.classList.add('open');
// remove prev active state
menuEls.forEach(el => el.classList.remove('active'));
menuEls.forEach(el => {
if (el.parentElement == menuElsByName[menu].parentElement) {
el.classList.remove('active');
}
});
// mark this menu as active
menuElsByName[menu].classList.add('active');

Expand Down Expand Up @@ -250,17 +308,36 @@ document.addEventListener('DOMContentLoaded', () => {
};

// toggle display of menus for mobile
const showMenuMobile = (navLink) => {
const showMenuMobile = (navLink, isSubMenu) => {
// discover the parents details for subMenus
let parentWrap; let
parentMenu;

// grab the parent if working a submenu
if (isSubMenu) {
parentWrap = navLink.closest('.gc-menu-wrap');
parentMenu = parentWrap.dataset.submenu;
}

// get menu name from navLink
const menu = navLink.dataset.menu;
const menu = (isSubMenu ? navLink.dataset.submenu : navLink.dataset.menu);
// get menuEl + menuSpacer so we can move menuEl into menuSpacer space
const menuEl = menuElsByName[menu];
const menuSpacer = spacerElsByName[menu];
const menuEl = (isSubMenu ? submenuMenuElsByName[parentMenu][menu] : menuElsByName[menu]);
const menuSpacer = (isSubMenu ? submenuSpacerElsByName[parentMenu][menu] : spacerElsByName[menu]);
// check if its already been opened (is .active)
const isActive = menuSpacer.classList.contains('active');

// remove prev .active state(s)
cleanUp();
// cleanup the prev state
if (!isSubMenu) {
// remove prev .active state(s)
cleanUp();
// remove active state on first load
if (submenuToggleEls[menu]) {
cleanUpSubmenu(menu);
}
} else {
cleanUpSubmenu(parentMenu);
}

// hide bs dropdowns
$('.nav-link.dropdown-toggle').dropdown('hide');
Expand All @@ -272,20 +349,33 @@ document.addEventListener('DOMContentLoaded', () => {
// get the dimensions for just this menu (doing this each call so that the expanded (active) state is measured)
const dimension = getDimension(navLink, menuEl);

// mark the submenu
if (isSubMenu && spacerElsByName[parentMenu]) {
// fix/remove hover state on toggle
navLink.classList.add('gc-menu-submenu-toggle-active');
// set the height according to parent
spacerElsByName[parentMenu].style.height = `${ dimension.height + getDimension(parentWrap, menuElsByName[parentMenu]).height }px`;
}
// set spacers height
menuSpacer.style.height = `${ dimension.height }px`;
// cleanUp menuEl before adding new css
menuEl.style.cssText = '';
// resize and position content (on top of the spacer)
menuEl.style.top = `${ dimension.menuY + navbarContainerEl.scrollTop }px`;
menuEl.style.height = `${ dimension.height }px`;
// wait for .show to paint
window.requestAnimationFrame(() => {
// mark this menu as active
menuEl.classList.add('active');
menuSpacer.classList.add('active');
navLink.parentElement.classList.add('active');
// cleanUp menuEl before adding new css
menuEl.style.cssText = '';
// position the top after showing to gather the right position
menuEl.style.top = (isSubMenu ? `${ menuSpacer.offsetTop }px` : `${ dimension.menuY + navbarContainerEl.scrollTop }px`);
});
} else if (spacerElsByName[parentMenu]) {
// set the height according to parent (close the gap)
spacerElsByName[parentMenu].style.height = `${ getDimension(parentWrap, menuElsByName[parentMenu]).height }px`;
}
};

Expand All @@ -296,8 +386,8 @@ document.addEventListener('DOMContentLoaded', () => {
const pushMousePosition = (e, menu) => {
// ensure that the mouse is always offset by whatever is above the nav
const navbarOffset = navbarContainerEl.getBoundingClientRect();
// push the positions taken from the event

// push the positions taken from the event
mousePositions[menu].push({
x: e.clientX,
y: e.clientY - navbarOffset.y
Expand Down Expand Up @@ -335,7 +425,7 @@ document.addEventListener('DOMContentLoaded', () => {

// check if we should delay the change of menu
if (decreasingSlope < prevDecreasingSlope && increasingSlope > prevIncreasingSlope) {
debounceSubmenuFocus[menu] = setTimeout(() => showSubmenu(e, menu, submenuToggle), 300);
debounceSubmenuFocus[menu] = setTimeout(() => showSubmenu(e, menu, submenuToggle), 1000);
} else {
showSubmenu(e, menu, submenuToggle);
}
Expand Down Expand Up @@ -479,7 +569,13 @@ document.addEventListener('DOMContentLoaded', () => {
submenuToggle.addEventListener('mouseleave', (e) => {
clearTimeout(debounceSubmenuFocus[menu]);
});
// click event for mobile submenus
submenuToggle.addEventListener('click', (e) => {
if (window.innerWidth < breakpoint_md) {
showMenuMobile(submenuToggle, true);
}
});
});
});

});
};
Loading

0 comments on commit dc40144

Please sign in to comment.