CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-material-design-lite

Material Design Components in CSS, JS and HTML providing a comprehensive implementation of Google's Material Design specification for web applications.

Pending
Quality

Pending

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

SecuritybySnyk

Pending

The risk profile of this skill

Overview
Eval results
Files

navigation-components.mddocs/

Navigation Components

Menu systems and navigation components including dropdown menus and contextual navigation. These components provide user interface patterns for organizing and accessing application functionality.

Capabilities

Material Menu

Dropdown menu component with positioning, keyboard navigation, and smooth animations.

/**
 * Material Design menu component
 * CSS Class: mdl-js-menu
 * Widget: true
 */
interface MaterialMenu {
  /** 
   * Display the menu at calculated position
   * @param evt - Optional event object for positioning context
   */
  show(evt?: Event): void;
  
  /** Hide the menu with animation */
  hide(): void;
  
  /** 
   * Toggle menu visibility
   * @param evt - Optional event object for positioning context
   */
  toggle(evt?: Event): void;
}

HTML Structure:

<!-- Basic menu -->
<button id="demo-menu-lower-left" 
        class="mdl-button mdl-js-button mdl-button--icon">
  <i class="material-icons">more_vert</i>
</button>

<ul class="mdl-menu mdl-menu--bottom-left mdl-js-menu mdl-js-ripple-effect"
    for="demo-menu-lower-left">
  <li class="mdl-menu__item">Some Action</li>
  <li class="mdl-menu__item">Another Action</li>
  <li class="mdl-menu__item" disabled>Disabled Action</li>
  <li class="mdl-menu__item">Yet Another Action</li>
</ul>

<!-- Menu with icons -->
<button id="demo-menu-with-icons" 
        class="mdl-button mdl-js-button mdl-button--icon">
  <i class="material-icons">settings</i>
</button>

<ul class="mdl-menu mdl-menu--bottom-right mdl-js-menu"
    for="demo-menu-with-icons">
  <li class="mdl-menu__item">
    <i class="material-icons">edit</i>Edit
  </li>
  <li class="mdl-menu__item">
    <i class="material-icons">delete</i>Delete
  </li>
  <li class="mdl-menu__item">
    <i class="material-icons">share</i>Share
  </li>
</ul>

Menu Positioning:

<!-- Top positioning -->
<ul class="mdl-menu mdl-menu--top-left mdl-js-menu">
<ul class="mdl-menu mdl-menu--top-right mdl-js-menu">

<!-- Bottom positioning -->
<ul class="mdl-menu mdl-menu--bottom-left mdl-js-menu">
<ul class="mdl-menu mdl-menu--bottom-right mdl-js-menu">

<!-- Unaligned (centered) -->
<ul class="mdl-menu mdl-menu--unaligned mdl-js-menu">

Usage Examples:

// Access menu instance
const menuButton = document.querySelector('#demo-menu-lower-left');
const menu = document.querySelector('[for="demo-menu-lower-left"]').MaterialMenu;

// Show menu programmatically
menu.show();

// Hide menu programmatically
menu.hide();

// Toggle menu visibility
menuButton.addEventListener('click', (event) => {
  menu.toggle(event);
});

// Handle menu item clicks
document.addEventListener('click', (event) => {
  if (event.target.matches('.mdl-menu__item')) {
    const menuItem = event.target;
    const menuContainer = menuItem.closest('.mdl-js-menu');
    
    console.log('Menu item clicked:', menuItem.textContent);
    
    // Hide menu after selection
    menuContainer.MaterialMenu.hide();
    
    // Perform action based on menu item
    handleMenuItemAction(menuItem);
  }
});

function handleMenuItemAction(menuItem) {
  const action = menuItem.textContent.trim();
  
  switch (action) {
    case 'Edit':
      editItem();
      break;
    case 'Delete':
      deleteItem();
      break;
    case 'Share':
      shareItem();
      break;
  }
}

Dynamic Menu Management

// Create menu dynamically
function createMenu(buttonId, items) {
  const button = document.getElementById(buttonId);
  
  // Create menu element
  const menu = document.createElement('ul');
  menu.className = 'mdl-menu mdl-menu--bottom-left mdl-js-menu mdl-js-ripple-effect';
  menu.setAttribute('for', buttonId);
  
  // Add menu items
  items.forEach(item => {
    const li = document.createElement('li');
    li.className = 'mdl-menu__item';
    li.textContent = item.text;
    
    if (item.disabled) {
      li.setAttribute('disabled', '');
    }
    
    if (item.icon) {
      const icon = document.createElement('i');
      icon.className = 'material-icons';
      icon.textContent = item.icon;
      li.insertBefore(icon, li.firstChild);
    }
    
    menu.appendChild(li);
  });
  
  // Insert menu after button
  button.parentNode.insertBefore(menu, button.nextSibling);
  
  // Upgrade the menu
  componentHandler.upgradeElement(menu);
  
  return menu.MaterialMenu;
}

// Usage
const dynamicMenu = createMenu('my-button', [
  { text: 'Edit', icon: 'edit' },
  { text: 'Delete', icon: 'delete', disabled: true },
  { text: 'Share', icon: 'share' }
]);

Keyboard Navigation

Menus support full keyboard navigation:

// Keyboard navigation is automatic, but you can listen for events
document.addEventListener('keydown', (event) => {
  const activeMenu = document.querySelector('.mdl-menu.is-visible');
  
  if (activeMenu) {
    const menu = activeMenu.MaterialMenu;
    
    switch (event.key) {
      case 'Escape':
        menu.hide();
        event.preventDefault();
        break;
        
      case 'ArrowUp':
        navigateMenuUp(activeMenu);
        event.preventDefault();
        break;
        
      case 'ArrowDown':
        navigateMenuDown(activeMenu);
        event.preventDefault();
        break;
        
      case 'Enter':
      case ' ':
        const focusedItem = activeMenu.querySelector('.mdl-menu__item:focus');
        if (focusedItem && !focusedItem.hasAttribute('disabled')) {
          focusedItem.click();
        }
        event.preventDefault();
        break;
    }
  }
});

function navigateMenuUp(menu) {
  const items = Array.from(menu.querySelectorAll('.mdl-menu__item:not([disabled])'));
  const currentIndex = items.indexOf(document.activeElement);
  const nextIndex = currentIndex > 0 ? currentIndex - 1 : items.length - 1;
  items[nextIndex].focus();
}

function navigateMenuDown(menu) {
  const items = Array.from(menu.querySelectorAll('.mdl-menu__item:not([disabled])'));
  const currentIndex = items.indexOf(document.activeElement);
  const nextIndex = currentIndex < items.length - 1 ? currentIndex + 1 : 0;
  items[nextIndex].focus();
}

Menu Constants

/**
 * Material Menu constants and configuration
 */
interface MenuConstants {
  /** Total transition duration in seconds */
  TRANSITION_DURATION_SECONDS: 0.3;
  
  /** Fraction of transition used for main animation */
  TRANSITION_DURATION_FRACTION: 0.8;
  
  /** Timeout before closing menu after selection */
  CLOSE_TIMEOUT: 150;
}

/**
 * Keyboard codes used by menu navigation
 */
interface MenuKeyCodes {
  ENTER: 13;
  ESCAPE: 27;
  SPACE: 32;
  UP_ARROW: 38;
  DOWN_ARROW: 40;
}

/**
 * Menu positioning classes
 */
interface MenuPositions {
  BOTTOM_LEFT: 'mdl-menu--bottom-left';
  BOTTOM_RIGHT: 'mdl-menu--bottom-right';
  TOP_LEFT: 'mdl-menu--top-left';
  TOP_RIGHT: 'mdl-menu--top-right';
  UNALIGNED: 'mdl-menu--unaligned';
}

Context Menus

Create context menus that appear on right-click:

// Context menu implementation
function createContextMenu(targetSelector, menuItems) {
  let contextMenu = null;
  
  document.addEventListener('contextmenu', (event) => {
    if (event.target.matches(targetSelector)) {
      event.preventDefault();
      
      // Remove existing context menu
      if (contextMenu) {
        contextMenu.remove();
      }
      
      // Create new context menu
      contextMenu = document.createElement('ul');
      contextMenu.className = 'mdl-menu mdl-js-menu mdl-menu--unaligned';
      contextMenu.style.position = 'fixed';
      contextMenu.style.left = event.clientX + 'px';
      contextMenu.style.top = event.clientY + 'px';
      
      // Add menu items
      menuItems.forEach(item => {
        const li = document.createElement('li');
        li.className = 'mdl-menu__item';
        li.textContent = item.text;
        li.addEventListener('click', () => {
          item.action(event.target);
          contextMenu.MaterialMenu.hide();
        });
        contextMenu.appendChild(li);
      });
      
      document.body.appendChild(contextMenu);
      componentHandler.upgradeElement(contextMenu);
      
      // Show menu
      contextMenu.MaterialMenu.show();
    }
  });
  
  // Hide context menu on regular click
  document.addEventListener('click', () => {
    if (contextMenu) {
      contextMenu.MaterialMenu.hide();
    }
  });
}

// Usage
createContextMenu('.data-item', [
  {
    text: 'Edit',
    action: (target) => editDataItem(target)
  },
  {
    text: 'Delete',
    action: (target) => deleteDataItem(target)
  },
  {
    text: 'Duplicate',
    action: (target) => duplicateDataItem(target)
  }
]);

Menu State Management

// Track menu states across the application
class MenuManager {
  constructor() {
    this.openMenus = new Set();
    this.setupGlobalListeners();
  }
  
  setupGlobalListeners() {
    // Track menu opening/closing
    document.addEventListener('click', (event) => {
      if (event.target.matches('.mdl-menu__item')) {
        const menu = event.target.closest('.mdl-js-menu');
        this.closeMenu(menu);
      }
    });
    
    // Close all menus on escape
    document.addEventListener('keydown', (event) => {
      if (event.key === 'Escape') {
        this.closeAllMenus();
      }
    });
    
    // Close menus when clicking outside
    document.addEventListener('click', (event) => {
      if (!event.target.closest('.mdl-menu') && 
          !event.target.matches('[class*="mdl-button"]')) {
        this.closeAllMenus();
      }
    });
  }
  
  openMenu(menu) {
    this.openMenus.add(menu);
    menu.MaterialMenu.show();
  }
  
  closeMenu(menu) {
    this.openMenus.delete(menu);
    menu.MaterialMenu.hide();
  }
  
  closeAllMenus() {
    this.openMenus.forEach(menu => {
      menu.MaterialMenu.hide();
    });
    this.openMenus.clear();
  }
  
  toggleMenu(menu, event) {
    if (this.openMenus.has(menu)) {
      this.closeMenu(menu);
    } else {
      this.closeAllMenus(); // Close other menus first
      this.openMenu(menu);
    }
  }
}

// Global menu manager instance
const menuManager = new MenuManager();

// Use with buttons
document.addEventListener('click', (event) => {
  if (event.target.matches('[data-menu-trigger]')) {
    const menuId = event.target.getAttribute('data-menu-trigger');
    const menu = document.getElementById(menuId);
    if (menu) {
      menuManager.toggleMenu(menu, event);
    }
  }
});

Menu Customization

// Custom menu themes and styles
function applyMenuTheme(menu, theme) {
  const themes = {
    dark: {
      backgroundColor: '#333',
      color: '#fff',
      itemHover: '#555'
    },
    light: {
      backgroundColor: '#fff',
      color: '#333',
      itemHover: '#f5f5f5'
    },
    colored: {
      backgroundColor: '#3f51b5',
      color: '#fff',
      itemHover: '#5c6bc0'
    }
  };
  
  const themeConfig = themes[theme];
  if (!themeConfig) return;
  
  menu.style.backgroundColor = themeConfig.backgroundColor;
  menu.style.color = themeConfig.color;
  
  const items = menu.querySelectorAll('.mdl-menu__item');
  items.forEach(item => {
    item.addEventListener('mouseenter', () => {
      item.style.backgroundColor = themeConfig.itemHover;
    });
    
    item.addEventListener('mouseleave', () => {
      item.style.backgroundColor = '';
    });
  });
}

// Animation customization
function customMenuAnimation(menu) {
  // Override default animation
  menu.addEventListener('mdl-componentupgraded', () => {
    const menuInstance = menu.MaterialMenu;
    
    // Custom show animation
    const originalShow = menuInstance.show;
    menuInstance.show = function(evt) {
      originalShow.call(this, evt);
      
      // Add custom animation class
      menu.classList.add('custom-menu-animation');
      
      setTimeout(() => {
        menu.classList.add('custom-menu-visible');
      }, 10);
    };
    
    // Custom hide animation
    const originalHide = menuInstance.hide;
    menuInstance.hide = function() {
      menu.classList.remove('custom-menu-visible');
      
      setTimeout(() => {
        originalHide.call(this);
        menu.classList.remove('custom-menu-animation');
      }, 200);
    };
  });
}

docs

component-management.md

data-display-components.md

feedback-components.md

form-components.md

index.md

layout-components.md

navigation-components.md

visual-effects.md

tile.json