✨feature: Add sliding viewport mobile menu support
This commit is contained in:
@@ -36,6 +36,24 @@ class Navigation {
|
|||||||
*/
|
*/
|
||||||
#subMenuElementIdentifier;
|
#subMenuElementIdentifier;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Current navigation level in sliding viewport
|
||||||
|
* @type {number}
|
||||||
|
*/
|
||||||
|
#currentLevel = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Navigation stack for breadcrumb functionality
|
||||||
|
* @type {Array}
|
||||||
|
*/
|
||||||
|
#navigationStack = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether sliding viewport mode is enabled
|
||||||
|
* @type {boolean}
|
||||||
|
*/
|
||||||
|
#slidingViewportEnabled = false;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
constructor(mobileMenuButtonId, dropDownClass, subMenuLinkClass = "sub-menu-item") {
|
constructor(mobileMenuButtonId, dropDownClass, subMenuLinkClass = "sub-menu-item") {
|
||||||
@@ -44,6 +62,9 @@ class Navigation {
|
|||||||
this.#subMenuElementIdentifier = subMenuLinkClass;
|
this.#subMenuElementIdentifier = subMenuLinkClass;
|
||||||
|
|
||||||
this.handleEscapeKey();
|
this.handleEscapeKey();
|
||||||
|
|
||||||
|
// Initialize sliding viewport immediately if styles are detected
|
||||||
|
this.initializeSlidingViewport();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -93,7 +114,16 @@ class Navigation {
|
|||||||
* @param {HTMLButtonElement} button
|
* @param {HTMLButtonElement} button
|
||||||
*/
|
*/
|
||||||
toggleMobileMenu(event, button) {
|
toggleMobileMenu(event, button) {
|
||||||
this.toggleAriaExpanded(event, button)
|
this.toggleAriaExpanded(event, button);
|
||||||
|
|
||||||
|
const isExpanded = button.getAttribute("aria-expanded") === "true";
|
||||||
|
|
||||||
|
if (!isExpanded) {
|
||||||
|
// Reset sliding navigation when menu is closed
|
||||||
|
if (this.#slidingViewportEnabled) {
|
||||||
|
this.resetSlidingNavigation();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -138,7 +168,9 @@ 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("menu-vdi__link");
|
&& !event.relatedTarget.classList.contains("sub-menu-item")
|
||||||
|
&& !event.relatedTarget.classList.contains("menu-vdi__link")
|
||||||
|
&& !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);
|
||||||
@@ -158,6 +190,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();
|
||||||
|
|
||||||
|
// Reset sliding navigation on escape
|
||||||
|
if (this.#slidingViewportEnabled) {
|
||||||
|
this.resetSlidingNavigation();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}, { passive: true });
|
}, { passive: true });
|
||||||
}
|
}
|
||||||
@@ -220,6 +257,320 @@ class Navigation {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize sliding viewport navigation
|
||||||
|
* Detects if sliding viewport should be enabled and sets up the structure
|
||||||
|
*/
|
||||||
|
initializeSlidingViewport() {
|
||||||
|
// Check if we should enable sliding viewport (could be based on screen size, user preference, etc.)
|
||||||
|
this.#slidingViewportEnabled = this.shouldEnableSlidingViewport();
|
||||||
|
|
||||||
|
if (this.#slidingViewportEnabled) {
|
||||||
|
console.log('Sliding viewport enabled, setting up structure');
|
||||||
|
this.setupSlidingViewportStructure();
|
||||||
|
} else {
|
||||||
|
console.log('Sliding viewport not enabled');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine if sliding viewport should be enabled
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
shouldEnableSlidingViewport() {
|
||||||
|
// Check if sliding viewport styles are loaded by testing if the CSS rule exists
|
||||||
|
// This allows CSS-based switching between navigation styles
|
||||||
|
const isMobile = window.innerWidth <= 1000; // 62.5rem converted to px
|
||||||
|
|
||||||
|
if (!isMobile) return false;
|
||||||
|
|
||||||
|
// Check if sliding viewport CSS is loaded by testing a specific rule
|
||||||
|
// We need to test the element within the proper context (.nav-main)
|
||||||
|
try {
|
||||||
|
const navMain = document.querySelector('.nav-main');
|
||||||
|
if (!navMain) return false;
|
||||||
|
|
||||||
|
const testElement = document.createElement('div');
|
||||||
|
testElement.className = 'menu-vdi--sliding';
|
||||||
|
testElement.style.position = 'absolute';
|
||||||
|
testElement.style.visibility = 'hidden';
|
||||||
|
testElement.style.height = '1px';
|
||||||
|
testElement.style.width = '1px';
|
||||||
|
|
||||||
|
navMain.appendChild(testElement);
|
||||||
|
|
||||||
|
// Get computed styles to check if sliding viewport CSS is active
|
||||||
|
const computedStyle = window.getComputedStyle(testElement);
|
||||||
|
const hasOverflowHidden = computedStyle.overflow === 'hidden';
|
||||||
|
|
||||||
|
navMain.removeChild(testElement);
|
||||||
|
|
||||||
|
return hasOverflowHidden;
|
||||||
|
} catch (error) {
|
||||||
|
console.warn('Error detecting sliding viewport styles:', error);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Setup the HTML structure for sliding viewport navigation
|
||||||
|
*/
|
||||||
|
setupSlidingViewportStructure() {
|
||||||
|
const menuContainer = document.getElementById('menu-container');
|
||||||
|
if (!menuContainer) {
|
||||||
|
console.warn('Menu container not found');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Don't set up if already configured
|
||||||
|
if (menuContainer.classList.contains('menu-vdi--sliding')) {
|
||||||
|
console.log('Sliding viewport already configured');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('Setting up sliding viewport structure');
|
||||||
|
|
||||||
|
// Add sliding class to enable sliding styles
|
||||||
|
menuContainer.classList.add('menu-vdi--sliding');
|
||||||
|
|
||||||
|
// Create viewport container
|
||||||
|
const viewport = document.createElement('div');
|
||||||
|
viewport.className = 'menu-vdi__viewport';
|
||||||
|
viewport.setAttribute('data-current-level', '0');
|
||||||
|
|
||||||
|
// Create main level
|
||||||
|
const mainLevel = document.createElement('div');
|
||||||
|
mainLevel.className = 'menu-vdi__level menu-vdi__level--main';
|
||||||
|
mainLevel.setAttribute('data-level', '0');
|
||||||
|
|
||||||
|
// Move existing menu items to main level
|
||||||
|
const existingItems = Array.from(menuContainer.children);
|
||||||
|
console.log('Moving', existingItems.length, 'existing items to main level');
|
||||||
|
|
||||||
|
existingItems.forEach(item => {
|
||||||
|
mainLevel.appendChild(item);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Setup click handlers for parent items in sliding mode
|
||||||
|
this.setupSlidingClickHandlers(mainLevel);
|
||||||
|
|
||||||
|
viewport.appendChild(mainLevel);
|
||||||
|
menuContainer.appendChild(viewport);
|
||||||
|
|
||||||
|
console.log('Sliding viewport structure complete');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Setup click handlers for parent menu items in sliding mode
|
||||||
|
* @param {HTMLElement} level
|
||||||
|
*/
|
||||||
|
setupSlidingClickHandlers(level) {
|
||||||
|
const parentButtons = level.querySelectorAll('.menu-vdi__toggle');
|
||||||
|
|
||||||
|
parentButtons.forEach(button => {
|
||||||
|
button.addEventListener('click', (event) => {
|
||||||
|
if (this.#slidingViewportEnabled) {
|
||||||
|
event.preventDefault();
|
||||||
|
event.stopPropagation();
|
||||||
|
|
||||||
|
const parentItem = button.closest('.menu-vdi__item--parent');
|
||||||
|
const submenu = parentItem.querySelector('.menu-vdi__submenu');
|
||||||
|
|
||||||
|
if (submenu) {
|
||||||
|
this.navigateToLevel(button.textContent.trim(), submenu);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, true); // Use capture phase to intercept before other handlers
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Navigate to a specific navigation level
|
||||||
|
* @param {string} levelTitle
|
||||||
|
* @param {HTMLElement} submenuElement
|
||||||
|
*/
|
||||||
|
navigateToLevel(levelTitle, submenuElement) {
|
||||||
|
const viewport = document.querySelector('.menu-vdi__viewport');
|
||||||
|
if (!viewport) return;
|
||||||
|
|
||||||
|
this.#currentLevel++;
|
||||||
|
this.#navigationStack.push({ title: levelTitle, level: this.#currentLevel });
|
||||||
|
|
||||||
|
// Create new level
|
||||||
|
const newLevel = document.createElement('div');
|
||||||
|
newLevel.className = 'menu-vdi__level';
|
||||||
|
newLevel.setAttribute('data-level', this.#currentLevel);
|
||||||
|
|
||||||
|
// Add back button
|
||||||
|
const backButton = this.createBackButton();
|
||||||
|
newLevel.appendChild(backButton);
|
||||||
|
|
||||||
|
// Add level title
|
||||||
|
const levelTitleEl = document.createElement('div');
|
||||||
|
levelTitleEl.className = 'menu-vdi__level-title';
|
||||||
|
levelTitleEl.textContent = levelTitle;
|
||||||
|
newLevel.appendChild(levelTitleEl);
|
||||||
|
|
||||||
|
// Clone and add submenu items
|
||||||
|
const submenuItems = submenuElement.cloneNode(true);
|
||||||
|
submenuItems.className = 'menu-vdi__level-items';
|
||||||
|
|
||||||
|
// Convert submenu items to level items and ensure proper visibility
|
||||||
|
const items = submenuItems.querySelectorAll('.menu-vdi__item');
|
||||||
|
items.forEach(item => {
|
||||||
|
// Remove any nested classes that don't apply to sliding mode
|
||||||
|
item.classList.remove('menu-vdi__item--child', 'menu-vdi__item--grandchild');
|
||||||
|
|
||||||
|
// Handle nested items if they exist
|
||||||
|
const nestedToggle = item.querySelector('.menu-vdi__toggle');
|
||||||
|
if (nestedToggle) {
|
||||||
|
nestedToggle.addEventListener('click', (event) => {
|
||||||
|
event.preventDefault();
|
||||||
|
// Could implement deeper nesting here if needed
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Remove any hidden submenu classes from nested elements to ensure visibility
|
||||||
|
const nestedSubmenus = submenuItems.querySelectorAll('.menu-vdi__submenu');
|
||||||
|
nestedSubmenus.forEach(nestedSubmenu => {
|
||||||
|
nestedSubmenu.classList.remove('menu-vdi__submenu');
|
||||||
|
nestedSubmenu.classList.add('menu-vdi__nested-items');
|
||||||
|
});
|
||||||
|
|
||||||
|
// Ensure all list items are visible in sliding mode
|
||||||
|
const allListItems = submenuItems.querySelectorAll('li');
|
||||||
|
allListItems.forEach(li => {
|
||||||
|
if (!li.classList.contains('menu-vdi__item')) {
|
||||||
|
li.classList.add('menu-vdi__item');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
newLevel.appendChild(submenuItems);
|
||||||
|
viewport.appendChild(newLevel);
|
||||||
|
|
||||||
|
// Animate to new level
|
||||||
|
this.animateToLevel(this.#currentLevel);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a back button for navigation levels
|
||||||
|
* @returns {HTMLElement}
|
||||||
|
*/
|
||||||
|
createBackButton() {
|
||||||
|
const backButton = document.createElement('button');
|
||||||
|
backButton.className = 'menu-vdi__back';
|
||||||
|
backButton.innerHTML = `
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" d="M15.75 19.5L8.25 12l7.5-7.5" />
|
||||||
|
</svg>
|
||||||
|
Back
|
||||||
|
`;
|
||||||
|
|
||||||
|
backButton.addEventListener('click', (event) => {
|
||||||
|
event.preventDefault();
|
||||||
|
event.stopPropagation();
|
||||||
|
this.navigateBack();
|
||||||
|
});
|
||||||
|
|
||||||
|
return backButton;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Navigate back to previous level
|
||||||
|
*/
|
||||||
|
navigateBack() {
|
||||||
|
if (this.#currentLevel > 0) {
|
||||||
|
this.#currentLevel--;
|
||||||
|
this.#navigationStack.pop();
|
||||||
|
|
||||||
|
// Remove the current level element
|
||||||
|
const viewport = document.querySelector('.menu-vdi__viewport');
|
||||||
|
const currentLevelEl = viewport.querySelector(`[data-level="${this.#currentLevel + 1}"]`);
|
||||||
|
if (currentLevelEl) {
|
||||||
|
currentLevelEl.remove();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Animate back to previous level
|
||||||
|
this.animateToLevel(this.#currentLevel);
|
||||||
|
|
||||||
|
// Ensure menu stays open after back navigation
|
||||||
|
setTimeout(() => {
|
||||||
|
if (this.#mobileMenuButton.getAttribute("aria-expanded") === "false") {
|
||||||
|
this.#mobileMenuButton.setAttribute("aria-expanded", "true");
|
||||||
|
}
|
||||||
|
}, 50);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Animate viewport to specific level
|
||||||
|
* @param {number} level
|
||||||
|
*/
|
||||||
|
animateToLevel(level) {
|
||||||
|
const viewport = document.querySelector('.menu-vdi__viewport');
|
||||||
|
if (!viewport) return;
|
||||||
|
|
||||||
|
const translateX = -level * 100;
|
||||||
|
viewport.style.transform = `translateX(${translateX}%)`;
|
||||||
|
viewport.setAttribute('data-current-level', level);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reset sliding navigation to main level
|
||||||
|
*/
|
||||||
|
resetSlidingNavigation() {
|
||||||
|
const menuContainer = document.getElementById('menu-container');
|
||||||
|
if (!menuContainer) return;
|
||||||
|
|
||||||
|
if (this.#slidingViewportEnabled) {
|
||||||
|
this.#currentLevel = 0;
|
||||||
|
this.#navigationStack = [];
|
||||||
|
|
||||||
|
const viewport = document.querySelector('.menu-vdi__viewport');
|
||||||
|
if (viewport) {
|
||||||
|
// Remove all levels except main
|
||||||
|
const levels = viewport.querySelectorAll('.menu-vdi__level:not(.menu-vdi__level--main)');
|
||||||
|
levels.forEach(level => level.remove());
|
||||||
|
|
||||||
|
// Reset position
|
||||||
|
this.animateToLevel(0);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Clean up sliding structure and restore normal menu
|
||||||
|
this.cleanupSlidingStructure();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clean up sliding viewport structure and restore normal menu
|
||||||
|
*/
|
||||||
|
cleanupSlidingStructure() {
|
||||||
|
const menuContainer = document.getElementById('menu-container');
|
||||||
|
if (!menuContainer) return;
|
||||||
|
|
||||||
|
// Remove sliding class
|
||||||
|
menuContainer.classList.remove('menu-vdi--sliding');
|
||||||
|
|
||||||
|
// Find viewport and move items back to container
|
||||||
|
const viewport = menuContainer.querySelector('.menu-vdi__viewport');
|
||||||
|
if (viewport) {
|
||||||
|
const mainLevel = viewport.querySelector('.menu-vdi__level--main');
|
||||||
|
if (mainLevel) {
|
||||||
|
// Move all items back to menu container
|
||||||
|
const items = Array.from(mainLevel.children);
|
||||||
|
items.forEach(item => {
|
||||||
|
menuContainer.appendChild(item);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove viewport structure
|
||||||
|
viewport.remove();
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('Sliding structure cleaned up');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Navigation;
|
export default Navigation;
|
||||||
|
|||||||
@@ -7,13 +7,30 @@
|
|||||||
* - index.php
|
* - index.php
|
||||||
* - nav-functional.php
|
* - nav-functional.php
|
||||||
*
|
*
|
||||||
|
* Desktop Navigation Styles:
|
||||||
* Choose one or the other for the main navigation, based on theme needs.
|
* Choose one or the other for the main navigation, based on theme needs.
|
||||||
|
* @import 'nav-main-default'; (standard dropdown)
|
||||||
|
* @import 'nav-main-mega'; (mega menu style)
|
||||||
*
|
*
|
||||||
* @import 'nav-main-default';
|
* Mobile Navigation Styles:
|
||||||
* @import 'nav-main-mega';
|
* Choose one for mobile navigation behavior:
|
||||||
|
* @import 'nav-mobile-accordion'; (traditional dropdown/accordion style)
|
||||||
|
* @import 'nav-mobile-sliding'; (sliding viewport style)
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@import "./nav-functional.css";
|
@import "./nav-functional.css";
|
||||||
@import "./nav-aux.css";
|
@import "./nav-aux.css";
|
||||||
|
|
||||||
|
/* Mobile Navigation Style - Choose one of the following: */
|
||||||
|
/* Traditional dropdown style */
|
||||||
@import "./nav-main-default.css";
|
@import "./nav-main-default.css";
|
||||||
|
/* Mega menu style */
|
||||||
|
/* @import "./nav-main-mega.css"; */
|
||||||
|
|
||||||
|
/* Mobile Navigation Style - Choose one of the following: */
|
||||||
|
/* Accordion/dropdown style */
|
||||||
|
/* @import "./nav-mobile-accordion.css"; */
|
||||||
|
/* Sliding viewport style */
|
||||||
|
@import "./nav-mobile-sliding.css";
|
||||||
|
|
||||||
@import "./nav-footer.css";
|
@import "./nav-footer.css";
|
||||||
|
|||||||
@@ -1,5 +1,10 @@
|
|||||||
/**
|
/**
|
||||||
* VDI Navs & Menu - Main Navigation + Default Dropdown Menu Styles
|
* VDI Navs & Menu - Main Navigation + Default Dropdown Menu Styles (Desktop Only)
|
||||||
|
*
|
||||||
|
* This file contains only desktop navigation styles.
|
||||||
|
* For mobile navigation, choose one of:
|
||||||
|
* - nav-mobile-accordion.css (traditional dropdown/accordion style)
|
||||||
|
* - nav-mobile-sliding.css (sliding viewport style)
|
||||||
*
|
*
|
||||||
* Please review documentation upon first use, and, as-needed:
|
* Please review documentation upon first use, and, as-needed:
|
||||||
* TODO: Add documenation link here
|
* TODO: Add documenation link here
|
||||||
@@ -22,46 +27,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* mobile */
|
|
||||||
@media screen and (max-width: 62.5rem) {
|
|
||||||
.nav-main {
|
|
||||||
.nav-main__toggle {
|
|
||||||
/* display */
|
|
||||||
@apply text-white p-3;
|
|
||||||
}
|
|
||||||
|
|
||||||
.menu-vdi {
|
|
||||||
@apply flex-col bg-white w-[95%] right-0 z-10 py-6;
|
|
||||||
top: var(--hgtHeader);
|
|
||||||
min-height: calc(100vh - var(--hgtHeader));
|
|
||||||
|
|
||||||
.menu-vdi__submenu {
|
|
||||||
@apply py-2 px-7 flex-col;
|
|
||||||
}
|
|
||||||
|
|
||||||
.menu-vdi__item {
|
|
||||||
a,
|
|
||||||
button {
|
|
||||||
/* text */
|
|
||||||
@apply font-bold text-20px text-black hover:text-light no-underline leading-snug;
|
|
||||||
/* spacing & display */
|
|
||||||
@apply block w-full p-4;
|
|
||||||
/* interaction */
|
|
||||||
@apply focus-visible:bg-secondary-200 hover:bg-secondary-200;
|
|
||||||
}
|
|
||||||
|
|
||||||
a {
|
|
||||||
@apply block w-full;
|
|
||||||
}
|
|
||||||
|
|
||||||
button {
|
|
||||||
@apply flex w-full justify-between;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media screen and (min-width: 62.5rem) {
|
@media screen and (min-width: 62.5rem) {
|
||||||
.menu-vdi {
|
.menu-vdi {
|
||||||
.menu-vdi__toggle {
|
.menu-vdi__toggle {
|
||||||
|
|||||||
52
styles/navigation/nav-mobile-accordion.css
Normal file
52
styles/navigation/nav-mobile-accordion.css
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
/**
|
||||||
|
* VDI Navs & Menu - Mobile Accordion Navigation
|
||||||
|
*
|
||||||
|
* Traditional mobile accordion/dropdown style navigation
|
||||||
|
* Include this file for accordion-style mobile navigation
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* Mobile accordion navigation */
|
||||||
|
@media screen and (max-width: 62.5rem) {
|
||||||
|
.nav-main {
|
||||||
|
.nav-main__toggle {
|
||||||
|
/* display */
|
||||||
|
@apply text-white p-3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu-vdi {
|
||||||
|
@apply flex-col bg-white w-[95%] right-0 z-10 py-6;
|
||||||
|
@apply absolute hidden; /* Hidden by default */
|
||||||
|
top: var(--hgtHeader);
|
||||||
|
min-height: calc(100vh - var(--hgtHeader));
|
||||||
|
|
||||||
|
.menu-vdi__submenu {
|
||||||
|
@apply py-2 px-7 flex-col;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu-vdi__item {
|
||||||
|
a,
|
||||||
|
button {
|
||||||
|
/* text */
|
||||||
|
@apply font-bold text-20px text-black hover:text-light no-underline leading-snug;
|
||||||
|
/* spacing & display */
|
||||||
|
@apply block w-full p-4;
|
||||||
|
/* interaction */
|
||||||
|
@apply focus-visible:bg-secondary-200 hover:bg-secondary-200;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
@apply block w-full;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
@apply flex w-full justify-between;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Show menu when toggle button is expanded */
|
||||||
|
.nav-main__toggle[aria-expanded="true"] ~ .menu-vdi:not(.menu-vdi--sliding) {
|
||||||
|
@apply !flex; /* Use !important to override hidden */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
130
styles/navigation/nav-mobile-sliding.css
Normal file
130
styles/navigation/nav-mobile-sliding.css
Normal file
@@ -0,0 +1,130 @@
|
|||||||
|
/**
|
||||||
|
* VDI Navs & Menu - Mobile Sliding Viewport Navigation
|
||||||
|
*
|
||||||
|
* Sliding viewport style for mobile navigation where
|
||||||
|
* users can navigate through menu levels by sliding between views
|
||||||
|
* Include this file for sliding-style mobile navigation
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* Mobile sliding viewport navigation */
|
||||||
|
@media screen and (max-width: 62.5rem) {
|
||||||
|
.nav-main {
|
||||||
|
.nav-main__toggle {
|
||||||
|
/* display */
|
||||||
|
@apply text-white p-3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu-vdi--sliding {
|
||||||
|
@apply relative overflow-hidden bg-white w-[95%] right-0 z-10 py-6;
|
||||||
|
@apply absolute hidden; /* Hidden by default */
|
||||||
|
top: var(--hgtHeader);
|
||||||
|
min-height: calc(100vh - var(--hgtHeader));
|
||||||
|
|
||||||
|
/* Container for all navigation levels */
|
||||||
|
.menu-vdi__viewport {
|
||||||
|
@apply flex transition-transform duration-300 ease-in-out;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Each navigation level */
|
||||||
|
.menu-vdi__level {
|
||||||
|
@apply w-full flex-shrink-0 flex-col;
|
||||||
|
min-width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Back button for secondary levels */
|
||||||
|
.menu-vdi__back {
|
||||||
|
@apply flex items-center gap-2 p-4 border-b border-gray-200 font-bold text-black hover:bg-secondary-200 focus-visible:bg-secondary-200 cursor-pointer;
|
||||||
|
|
||||||
|
svg {
|
||||||
|
@apply w-5 h-5;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Level indicator for context */
|
||||||
|
.menu-vdi__level-title {
|
||||||
|
@apply p-4 border-b border-gray-200 font-bold text-lg text-center bg-gray-50;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Navigation items in sliding mode */
|
||||||
|
.menu-vdi__item {
|
||||||
|
a,
|
||||||
|
button {
|
||||||
|
/* text */
|
||||||
|
@apply font-bold text-20px text-black hover:text-light no-underline leading-snug;
|
||||||
|
/* spacing & display */
|
||||||
|
@apply block w-full p-4;
|
||||||
|
/* interaction */
|
||||||
|
@apply focus-visible:bg-secondary-200 hover:bg-secondary-200;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
@apply block w-full;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
@apply flex w-full justify-between items-center;
|
||||||
|
|
||||||
|
/* Arrow indicator for items with children */
|
||||||
|
svg {
|
||||||
|
@apply w-5 h-5;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Hide submenu toggles in sliding mode as they become navigation buttons */
|
||||||
|
.menu-vdi__toggle {
|
||||||
|
/* Override the accordion toggle behavior */
|
||||||
|
&[aria-expanded="true"] {
|
||||||
|
svg {
|
||||||
|
@apply rotate-0; /* Don't rotate arrow */
|
||||||
|
}
|
||||||
|
|
||||||
|
+.menu-vdi__submenu {
|
||||||
|
@apply hidden; /* Don't show dropdown */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Hide traditional submenus in sliding mode */
|
||||||
|
.menu-vdi__submenu {
|
||||||
|
@apply hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Ensure menu items in sliding viewport have proper styling */
|
||||||
|
.menu-vdi__level-items {
|
||||||
|
@apply flex-col;
|
||||||
|
|
||||||
|
.menu-vdi__item {
|
||||||
|
@apply w-full;
|
||||||
|
|
||||||
|
a {
|
||||||
|
@apply p-4 block w-full no-underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
span {
|
||||||
|
@apply p-4 block w-full font-bold;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Style nested items that were converted from submenus */
|
||||||
|
.menu-vdi__nested-items {
|
||||||
|
@apply block;
|
||||||
|
|
||||||
|
.menu-vdi__item {
|
||||||
|
@apply w-full;
|
||||||
|
|
||||||
|
a {
|
||||||
|
@apply p-4 pl-8 block w-full no-underline; /* Add indent for nested items */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Show sliding menu when toggle button is expanded and menu has sliding class */
|
||||||
|
.nav-main__toggle[aria-expanded="true"] + .menu-vdi--sliding {
|
||||||
|
@apply !block; /* Use !important to override hidden */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user