✨feature: Enhance navigation components with scroll control and styling updates
This commit is contained in:
+1
-1
@@ -49,7 +49,7 @@ $showHero = in_array(
|
|||||||
<?php get_template_part( 'views/partials/aura-bg' ); ?>
|
<?php get_template_part( 'views/partials/aura-bg' ); ?>
|
||||||
|
|
||||||
<header role="banner" class="site-header bg-secondary flex flex-col items-center justify-start">
|
<header role="banner" class="site-header bg-secondary flex flex-col items-center justify-start">
|
||||||
<div class="header__nav-main container py-4 items-center grid gap-x-8 gap-y-0 grid-cols-[auto_1fr_auto] grid-rows-[1fr] justify-between">
|
<div class="header__nav-main lg:container py-4 items-center flex gap-x-8 gap-y-0 justify-between">
|
||||||
<a href="<?php echo esc_url( home_url( '/' ) ); ?>" class="site-header__logo m-0 p-0 flex gap-x-4 items-center justify-start text-white! text-45px font-bold leading-none hover:underline">
|
<a href="<?php echo esc_url( home_url( '/' ) ); ?>" class="site-header__logo m-0 p-0 flex gap-x-4 items-center justify-start text-white! text-45px font-bold leading-none hover:underline">
|
||||||
<img class="site-header__logo-img aspect-square h-full w-full max-h-12 max-w-12" src="<?php echo esc_url( $headerLogo ); ?>" alt="<?php echo esc_attr( get_bloginfo( 'name' ) ); ?> logo" /> Keith Solomon
|
<img class="site-header__logo-img aspect-square h-full w-full max-h-12 max-w-12" src="<?php echo esc_url( $headerLogo ); ?>" alt="<?php echo esc_attr( get_bloginfo( 'name' ) ); ?> logo" /> Keith Solomon
|
||||||
</a>
|
</a>
|
||||||
|
|||||||
@@ -54,6 +54,12 @@ class Navigation {
|
|||||||
*/
|
*/
|
||||||
#slidingViewportEnabled = false;
|
#slidingViewportEnabled = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Function reference for preventing scroll (needed to remove listener)
|
||||||
|
* @type {Function}
|
||||||
|
*/
|
||||||
|
#preventScrollHandler = (e) => e.preventDefault();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
constructor(mobileMenuButtonId, dropDownClass, subMenuLinkClass = "sub-menu-item") {
|
constructor(mobileMenuButtonId, dropDownClass, subMenuLinkClass = "sub-menu-item") {
|
||||||
@@ -62,7 +68,7 @@ class Navigation {
|
|||||||
this.#subMenuElementIdentifier = subMenuLinkClass;
|
this.#subMenuElementIdentifier = subMenuLinkClass;
|
||||||
|
|
||||||
this.handleEscapeKey();
|
this.handleEscapeKey();
|
||||||
|
|
||||||
// Initialize sliding viewport immediately if styles are detected
|
// Initialize sliding viewport immediately if styles are detected
|
||||||
this.initializeSlidingViewport();
|
this.initializeSlidingViewport();
|
||||||
}
|
}
|
||||||
@@ -115,10 +121,17 @@ class Navigation {
|
|||||||
*/
|
*/
|
||||||
toggleMobileMenu(event, button) {
|
toggleMobileMenu(event, button) {
|
||||||
this.toggleAriaExpanded(event, button);
|
this.toggleAriaExpanded(event, button);
|
||||||
|
|
||||||
const isExpanded = button.getAttribute("aria-expanded") === "true";
|
const isExpanded = button.getAttribute("aria-expanded") === "true";
|
||||||
|
|
||||||
if (!isExpanded) {
|
if (isExpanded) {
|
||||||
|
// Prevent scroll when menu is open
|
||||||
|
document.documentElement.style.overflow = "hidden";
|
||||||
|
document.body.style.overflow = "hidden";
|
||||||
|
} else {
|
||||||
|
// Re-enable scroll when menu is closed
|
||||||
|
document.documentElement.style.overflow = "";
|
||||||
|
document.body.style.overflow = "";
|
||||||
// Reset sliding navigation when menu is closed
|
// Reset sliding navigation when menu is closed
|
||||||
if (this.#slidingViewportEnabled) {
|
if (this.#slidingViewportEnabled) {
|
||||||
this.resetSlidingNavigation();
|
this.resetSlidingNavigation();
|
||||||
@@ -168,16 +181,27 @@ class Navigation {
|
|||||||
// Note: cannot group negation -1*-1*-1 != -(1*1*1)
|
// Note: cannot group negation -1*-1*-1 != -(1*1*1)
|
||||||
const isNull = event.relatedTarget == null;
|
const isNull = event.relatedTarget == null;
|
||||||
const isNotMenuItem = !isNull && !event.relatedTarget.classList.contains("menu-vdi__toggle")
|
const isNotMenuItem = !isNull && !event.relatedTarget.classList.contains("menu-vdi__toggle")
|
||||||
&& !event.relatedTarget.classList.contains("sub-menu-item")
|
&& !event.relatedTarget.classList.contains("sub-menu-item")
|
||||||
&& !event.relatedTarget.classList.contains("menu-vdi__link")
|
&& !event.relatedTarget.classList.contains("menu-vdi__link")
|
||||||
&& !event.relatedTarget.classList.contains("menu-vdi__back"); // Don't close on back button click
|
&& !event.relatedTarget.classList.contains("menu-vdi__back"); // Don't close on back button click
|
||||||
|
|
||||||
if (isNull || isNotMenuItem)
|
if (isNull || isNotMenuItem) {
|
||||||
button.setAttribute("aria-expanded", false);
|
button.setAttribute("aria-expanded", false);
|
||||||
|
// Re-enable scroll when menu is closed
|
||||||
|
document.documentElement.style.overflow = "";
|
||||||
|
document.body.style.overflow = "";
|
||||||
|
}
|
||||||
}, true)
|
}, true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles escape behaviour
|
||||||
|
* Closes all dropdown when user hits
|
||||||
|
* escape key
|
||||||
|
*
|
||||||
|
* @link {https://developer.mozilla.org/en-US/docs/Web/API/Element/keyup_event}
|
||||||
|
*/
|
||||||
/**
|
/**
|
||||||
* Handles escape behaviour
|
* Handles escape behaviour
|
||||||
* Closes all dropdown when user hits
|
* Closes all dropdown when user hits
|
||||||
@@ -190,7 +214,11 @@ class Navigation {
|
|||||||
if (event.key === "Escape") {
|
if (event.key === "Escape") {
|
||||||
this.#mobileMenuButton.setAttribute("aria-expanded", false);
|
this.#mobileMenuButton.setAttribute("aria-expanded", false);
|
||||||
this.closeAllDropDowns();
|
this.closeAllDropDowns();
|
||||||
|
|
||||||
|
// Re-enable scroll when menu is closed
|
||||||
|
document.documentElement.style.overflow = "";
|
||||||
|
document.body.style.overflow = "";
|
||||||
|
|
||||||
// Reset sliding navigation on escape
|
// Reset sliding navigation on escape
|
||||||
if (this.#slidingViewportEnabled) {
|
if (this.#slidingViewportEnabled) {
|
||||||
this.resetSlidingNavigation();
|
this.resetSlidingNavigation();
|
||||||
@@ -265,7 +293,7 @@ class Navigation {
|
|||||||
initializeSlidingViewport() {
|
initializeSlidingViewport() {
|
||||||
// Check if we should enable sliding viewport (could be based on screen size, user preference, etc.)
|
// Check if we should enable sliding viewport (could be based on screen size, user preference, etc.)
|
||||||
this.#slidingViewportEnabled = this.shouldEnableSlidingViewport();
|
this.#slidingViewportEnabled = this.shouldEnableSlidingViewport();
|
||||||
|
|
||||||
if (this.#slidingViewportEnabled) {
|
if (this.#slidingViewportEnabled) {
|
||||||
console.log('Sliding viewport enabled, setting up structure');
|
console.log('Sliding viewport enabled, setting up structure');
|
||||||
this.setupSlidingViewportStructure();
|
this.setupSlidingViewportStructure();
|
||||||
@@ -282,30 +310,30 @@ class Navigation {
|
|||||||
// Check if sliding viewport styles are loaded by testing if the CSS rule exists
|
// Check if sliding viewport styles are loaded by testing if the CSS rule exists
|
||||||
// This allows CSS-based switching between navigation styles
|
// This allows CSS-based switching between navigation styles
|
||||||
const isMobile = window.innerWidth <= 1000; // 62.5rem converted to px
|
const isMobile = window.innerWidth <= 1000; // 62.5rem converted to px
|
||||||
|
|
||||||
if (!isMobile) return false;
|
if (!isMobile) return false;
|
||||||
|
|
||||||
// Check if sliding viewport CSS is loaded by testing a specific rule
|
// Check if sliding viewport CSS is loaded by testing a specific rule
|
||||||
// We need to test the element within the proper context (.nav-main)
|
// We need to test the element within the proper context (.nav-main)
|
||||||
try {
|
try {
|
||||||
const navMain = document.querySelector('.nav-main');
|
const navMain = document.querySelector('.nav-main');
|
||||||
if (!navMain) return false;
|
if (!navMain) return false;
|
||||||
|
|
||||||
const testElement = document.createElement('div');
|
const testElement = document.createElement('div');
|
||||||
testElement.className = 'menu-vdi--sliding';
|
testElement.className = 'menu-vdi--sliding';
|
||||||
testElement.style.position = 'absolute';
|
testElement.style.position = 'absolute';
|
||||||
testElement.style.visibility = 'hidden';
|
testElement.style.visibility = 'hidden';
|
||||||
testElement.style.height = '1px';
|
testElement.style.height = '1px';
|
||||||
testElement.style.width = '1px';
|
testElement.style.width = '1px';
|
||||||
|
|
||||||
navMain.appendChild(testElement);
|
navMain.appendChild(testElement);
|
||||||
|
|
||||||
// Get computed styles to check if sliding viewport CSS is active
|
// Get computed styles to check if sliding viewport CSS is active
|
||||||
const computedStyle = window.getComputedStyle(testElement);
|
const computedStyle = window.getComputedStyle(testElement);
|
||||||
const hasOverflowHidden = computedStyle.overflow === 'hidden';
|
const hasOverflowHidden = computedStyle.overflow === 'hidden';
|
||||||
|
|
||||||
navMain.removeChild(testElement);
|
navMain.removeChild(testElement);
|
||||||
|
|
||||||
return hasOverflowHidden;
|
return hasOverflowHidden;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.warn('Error detecting sliding viewport styles:', error);
|
console.warn('Error detecting sliding viewport styles:', error);
|
||||||
@@ -347,7 +375,7 @@ class Navigation {
|
|||||||
// Move existing menu items to main level
|
// Move existing menu items to main level
|
||||||
const existingItems = Array.from(menuContainer.children);
|
const existingItems = Array.from(menuContainer.children);
|
||||||
console.log('Moving', existingItems.length, 'existing items to main level');
|
console.log('Moving', existingItems.length, 'existing items to main level');
|
||||||
|
|
||||||
existingItems.forEach(item => {
|
existingItems.forEach(item => {
|
||||||
mainLevel.appendChild(item);
|
mainLevel.appendChild(item);
|
||||||
});
|
});
|
||||||
@@ -357,7 +385,7 @@ class Navigation {
|
|||||||
|
|
||||||
viewport.appendChild(mainLevel);
|
viewport.appendChild(mainLevel);
|
||||||
menuContainer.appendChild(viewport);
|
menuContainer.appendChild(viewport);
|
||||||
|
|
||||||
console.log('Sliding viewport structure complete');
|
console.log('Sliding viewport structure complete');
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -367,16 +395,16 @@ class Navigation {
|
|||||||
*/
|
*/
|
||||||
setupSlidingClickHandlers(level) {
|
setupSlidingClickHandlers(level) {
|
||||||
const parentButtons = level.querySelectorAll('.menu-vdi__toggle');
|
const parentButtons = level.querySelectorAll('.menu-vdi__toggle');
|
||||||
|
|
||||||
parentButtons.forEach(button => {
|
parentButtons.forEach(button => {
|
||||||
button.addEventListener('click', (event) => {
|
button.addEventListener('click', (event) => {
|
||||||
if (this.#slidingViewportEnabled) {
|
if (this.#slidingViewportEnabled) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
|
|
||||||
const parentItem = button.closest('.menu-vdi__item--parent');
|
const parentItem = button.closest('.menu-vdi__item--parent');
|
||||||
const submenu = parentItem.querySelector('.menu-vdi__submenu');
|
const submenu = parentItem.querySelector('.menu-vdi__submenu');
|
||||||
|
|
||||||
if (submenu) {
|
if (submenu) {
|
||||||
this.navigateToLevel(button.textContent.trim(), submenu);
|
this.navigateToLevel(button.textContent.trim(), submenu);
|
||||||
}
|
}
|
||||||
@@ -415,13 +443,13 @@ class Navigation {
|
|||||||
// Clone and add submenu items
|
// Clone and add submenu items
|
||||||
const submenuItems = submenuElement.cloneNode(true);
|
const submenuItems = submenuElement.cloneNode(true);
|
||||||
submenuItems.className = 'menu-vdi__level-items';
|
submenuItems.className = 'menu-vdi__level-items';
|
||||||
|
|
||||||
// Convert submenu items to level items and ensure proper visibility
|
// Convert submenu items to level items and ensure proper visibility
|
||||||
const items = submenuItems.querySelectorAll('.menu-vdi__item');
|
const items = submenuItems.querySelectorAll('.menu-vdi__item');
|
||||||
items.forEach(item => {
|
items.forEach(item => {
|
||||||
// Remove any nested classes that don't apply to sliding mode
|
// Remove any nested classes that don't apply to sliding mode
|
||||||
item.classList.remove('menu-vdi__item--child', 'menu-vdi__item--grandchild');
|
item.classList.remove('menu-vdi__item--child', 'menu-vdi__item--grandchild');
|
||||||
|
|
||||||
// Handle nested items if they exist
|
// Handle nested items if they exist
|
||||||
const nestedToggle = item.querySelector('.menu-vdi__toggle');
|
const nestedToggle = item.querySelector('.menu-vdi__toggle');
|
||||||
if (nestedToggle) {
|
if (nestedToggle) {
|
||||||
@@ -494,7 +522,7 @@ class Navigation {
|
|||||||
|
|
||||||
// Animate back to previous level
|
// Animate back to previous level
|
||||||
this.animateToLevel(this.#currentLevel);
|
this.animateToLevel(this.#currentLevel);
|
||||||
|
|
||||||
// Ensure menu stays open after back navigation
|
// Ensure menu stays open after back navigation
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
if (this.#mobileMenuButton.getAttribute("aria-expanded") === "false") {
|
if (this.#mobileMenuButton.getAttribute("aria-expanded") === "false") {
|
||||||
@@ -523,17 +551,17 @@ class Navigation {
|
|||||||
resetSlidingNavigation() {
|
resetSlidingNavigation() {
|
||||||
const menuContainer = document.getElementById('menu-container');
|
const menuContainer = document.getElementById('menu-container');
|
||||||
if (!menuContainer) return;
|
if (!menuContainer) return;
|
||||||
|
|
||||||
if (this.#slidingViewportEnabled) {
|
if (this.#slidingViewportEnabled) {
|
||||||
this.#currentLevel = 0;
|
this.#currentLevel = 0;
|
||||||
this.#navigationStack = [];
|
this.#navigationStack = [];
|
||||||
|
|
||||||
const viewport = document.querySelector('.menu-vdi__viewport');
|
const viewport = document.querySelector('.menu-vdi__viewport');
|
||||||
if (viewport) {
|
if (viewport) {
|
||||||
// Remove all levels except main
|
// Remove all levels except main
|
||||||
const levels = viewport.querySelectorAll('.menu-vdi__level:not(.menu-vdi__level--main)');
|
const levels = viewport.querySelectorAll('.menu-vdi__level:not(.menu-vdi__level--main)');
|
||||||
levels.forEach(level => level.remove());
|
levels.forEach(level => level.remove());
|
||||||
|
|
||||||
// Reset position
|
// Reset position
|
||||||
this.animateToLevel(0);
|
this.animateToLevel(0);
|
||||||
}
|
}
|
||||||
@@ -564,13 +592,51 @@ class Navigation {
|
|||||||
menuContainer.appendChild(item);
|
menuContainer.appendChild(item);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove viewport structure
|
// Remove viewport structure
|
||||||
viewport.remove();
|
viewport.remove();
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('Sliding structure cleaned up');
|
console.log('Sliding structure cleaned up');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Disable scroll on the page
|
||||||
|
* Prevents scrolling in all directions
|
||||||
|
*/
|
||||||
|
disableScroll() {
|
||||||
|
try {
|
||||||
|
// Apply overflow hidden to both html and body for better compatibility
|
||||||
|
document.documentElement.style.overflow = "hidden";
|
||||||
|
document.body.style.overflow = "hidden";
|
||||||
|
|
||||||
|
// Prevent touch scroll on mobile devices
|
||||||
|
if (this.#preventScrollHandler) {
|
||||||
|
document.addEventListener("touchmove", this.#preventScrollHandler, { passive: false });
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.warn("Error disabling scroll:", error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enable scroll on the page
|
||||||
|
* Restores normal scrolling behavior
|
||||||
|
*/
|
||||||
|
enableScroll() {
|
||||||
|
try {
|
||||||
|
// Restore overflow behavior
|
||||||
|
document.documentElement.style.overflow = "";
|
||||||
|
document.body.style.overflow = "";
|
||||||
|
|
||||||
|
// Re-enable touch scroll on mobile devices
|
||||||
|
if (this.#preventScrollHandler) {
|
||||||
|
document.removeEventListener("touchmove", this.#preventScrollHandler);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.warn("Error enabling scroll:", error);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Navigation;
|
export default Navigation;
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ $GLOBALS['ks_portfolio_link_resume'] = isset( $GLOBALS['ks_portfolio_link_resume
|
|||||||
?>
|
?>
|
||||||
|
|
||||||
<nav class="nav-main" role="navigation" aria-label="Main">
|
<nav class="nav-main" role="navigation" aria-label="Main">
|
||||||
<?php require_once __DIR__ . '/nav-main__toggle.php'; ?>
|
<?php require_once __DIR__ . '/nav-main__toggle.php'; ?>
|
||||||
|
|
||||||
<?php
|
<?php
|
||||||
if ( has_nav_menu( 'main_navigation' ) ) {
|
if ( has_nav_menu( 'main_navigation' ) ) {
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ $navIcon = isset( get_field( 'header', 'option' )['nav_icon'] ) ? get_field( 'he
|
|||||||
|
|
||||||
// Generate toggle classes
|
// Generate toggle classes
|
||||||
$toggleClasses = array( 'nav-main__toggle' );
|
$toggleClasses = array( 'nav-main__toggle' );
|
||||||
|
|
||||||
if ( $navIcon ) {
|
if ( $navIcon ) {
|
||||||
$toggleClasses[] = 'custom-icon';
|
$toggleClasses[] = 'custom-icon';
|
||||||
}
|
}
|
||||||
@@ -29,17 +30,18 @@ if ( $navIcon ) {
|
|||||||
<?php else : ?>
|
<?php else : ?>
|
||||||
<!-- Hamburger Icon -->
|
<!-- Hamburger Icon -->
|
||||||
<span class="nav-toggle-icon nav-toggle-hamburger">
|
<span class="nav-toggle-icon nav-toggle-hamburger">
|
||||||
<svg class="text-current fill-current" viewBox="0 0 100 80" width="40" height="40">
|
<svg class="text-current fill-[#1A2A45]" viewBox="0 0 100 60" width="40" height="40">
|
||||||
<rect y="0" width="100" height="15"></rect>
|
<rect y="0" width="100" height="15"></rect>
|
||||||
<rect y="30" width="100" height="15"></rect>
|
<rect y="30" width="100" height="15"></rect>
|
||||||
<rect y="60" width="100" height="15"></rect>
|
<rect y="60" width="100" height="15"></rect>
|
||||||
</svg>
|
</svg>
|
||||||
</span>
|
</span>
|
||||||
<!-- X Icon -->
|
|
||||||
|
<!-- X Icon -->
|
||||||
<span class="nav-toggle-icon nav-toggle-x">
|
<span class="nav-toggle-icon nav-toggle-x">
|
||||||
<svg class="text-current stroke-current" viewBox="0 0 100 80" width="40" height="40">
|
<svg class="text-current stroke-[#1A2A45]" viewBox="0 0 100 100" width="40" height="40">
|
||||||
<line x1="20" y1="20" x2="80" y2="60" stroke-width="15" stroke-linecap="round" />
|
<line x1="15" y1="15" x2="85" y2="85" stroke-width="15" stroke-linecap="round" />
|
||||||
<line x1="80" y1="20" x2="20" y2="60" stroke-width="15" stroke-linecap="round" />
|
<line x1="85" y1="15" x2="15" y2="85" stroke-width="15" stroke-linecap="round" />
|
||||||
</svg>
|
</svg>
|
||||||
</span>
|
</span>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
|
|||||||
Reference in New Issue
Block a user