npm package: @ng-shangjc/dropdown-menu
Description: Dropdown menu component source package for ng-shangjc angular components
Author: jc-develop
Latest version: 1.0.3-beta

<p align="center">
<a href="https://juliancallejas.github.io/ng-shangjc-docs">
  <img src="https://res.cloudinary.com/dphleqb5t/image/upload/v1772819746/shangjc/shangjc-64-bg-dark_kwzs81.svg" width="307" alt="Shangjc"  /> 
</a>
</p>

# Dropdown Menu Component

A versatile dropdown menu component that displays context menus and action lists with modern Angular signals for reactive UI development. Features smart positioning, comprehensive keyboard navigation, and full accessibility support.

## Official Documentation

<a href="https://juliancallejas.github.io/ng-shangjc-docs/?path=/docs/components-dropdown-menu">
    ng-Shangjc docs
</a>

## Features

- 🚀 **Signal-Based State**: Modern Angular signals for reactive state management
- 🎨 **Smart Positioning**: Automatic positioning with portal-based rendering
- ⌨️ **Keyboard Navigation**: Full support for arrow keys, Enter, Space, and Escape
- ♿ **Accessibility**: Complete ARIA support with proper roles and attributes
- 🎯 **Type-Safe**: Full TypeScript support with comprehensive interfaces
- 🎨 **Customizable**: Flexible styling with CSS classes and icons
- ⚡ **Performance**: Optimized with OnPush change detection
- 🔧 **Controlled Mode**: Support for both controlled and uncontrolled usage

## Installation

### Step 1: Install the CLI

First, install the ng-shangjc CLI globally or use npx:

```bash
# Install globally
npm install -g @ng-shangjc/cli

# or with yarn
yarn global add @ng-shangjc/cli

# or with pnpm
pnpm install -g @ng-shangjc/cli

# Or use npx without installation
npx @ng-shangjc/cli <command>
```

### Step 2: Initialize your project

If you haven't already, initialize your Angular project for ng-shangjc components:

```bash
ng-shangjc init
```

This will create a `shangjc.config.json` file and set up your project for component installations.

### Step 3: Install the component

Install the dropdown-menu component into your project:

```bash
ng-shangjc install dropdown-menu
```

The component will be installed in the configured path (default: `src/ui/shangjc`). Adjust the import path in your components based on your project structure and the configured installation path.

## Import

After installation, import the components from your configured installation path. The examples below use the default path `src/ui/shangjc` - adjust the path according to your project structure and configuration.

### Standalone Components (Recommended)

Import and use the individual components directly in your standalone components:

```typescript
import { Component } from '@angular/core';
import { CommonModule } from '@angular/common';
import { 
  DropdownMenuComponent,
  DropdownMenuTriggerComponent,
  DropdownMenuContentComponent,
  DropdownMenuItemComponent,
  DropdownMenuSeparatorComponent,
  DropdownMenuLabelComponent
} from './ui/shangjc/dropdown-menu';

@Component({
  selector: 'app-example',
  standalone: true,
  imports: [
    CommonModule,
    DropdownMenuComponent,
    DropdownMenuTriggerComponent,
    DropdownMenuContentComponent,
    DropdownMenuItemComponent,
    DropdownMenuSeparatorComponent,
    DropdownMenuLabelComponent
  ],
  template: `
    <ng-shangjc-dropdown-menu>
      <ng-shangjc-dropdown-menu-trigger>
        <button class="btn">Open Menu</button>
      </ng-shangjc-dropdown-menu-trigger>
      <ng-shangjc-dropdown-menu-content>
        <ng-shangjc-dropdown-menu-item label="Profile" (itemSelected)="openProfile()"></ng-shangjc-dropdown-menu-item>
        <ng-shangjc-dropdown-menu-item label="Settings" (itemSelected)="openSettings()"></ng-shangjc-dropdown-menu-item>
        <ng-shangjc-dropdown-menu-separator></ng-shangjc-dropdown-menu-separator>
        <ng-shangjc-dropdown-menu-item label="Logout" (itemSelected)="logout()"></ng-shangjc-dropdown-menu-item>
      </ng-shangjc-dropdown-menu-content>
    </ng-shangjc-dropdown-menu>
  `
})
export class ExampleComponent {
  openProfile() {
    console.log('Opening profile...');
  }

  openSettings() {
    console.log('Opening settings...');
  }

  logout() {
    console.log('Logging out...');
  }
}
```

### Using NgModule (Legacy)

If you're using NgModules, import the `DropdownMenuModule`:

```typescript
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { DropdownMenuModule } from './ui/shangjc/dropdown-menu';

@NgModule({
  declarations: [YourComponent],
  imports: [
    CommonModule,
    DropdownMenuModule
  ]
})
export class YourModule { }
```

Then use the components in your templates:

```html
<ng-shangjc-dropdown-menu>
  <!-- Component content -->
</ng-shangjc-dropdown-menu>
```

## Basic Usage

### Default Example

```typescript
import { Component } from '@angular/core';
import { 
  DropdownMenuComponent,
  DropdownMenuTriggerComponent,
  DropdownMenuContentComponent,
  DropdownMenuItemComponent,
  DropdownMenuSeparatorComponent
} from './ui/shangjc/dropdown-menu';

@Component({
  selector: 'app-basic-dropdown',
  standalone: true,
  imports: [
    DropdownMenuComponent,
    DropdownMenuTriggerComponent,
    DropdownMenuContentComponent,
    DropdownMenuItemComponent,
    DropdownMenuSeparatorComponent
  ],
  template: `
    <ng-shangjc-dropdown-menu>
      <ng-shangjc-dropdown-menu-trigger>
        <button class="inline-flex items-center justify-center rounded-md text-sm font-medium bg-primary text-primary-foreground hover:bg-primary/90 h-10 px-4 py-2">
          Menu
          <svg class="ml-2 h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
            <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"/>
          </svg>
        </button>
      </ng-shangjc-dropdown-menu-trigger>
      <ng-shangjc-dropdown-menu-content>
        <ng-shangjc-dropdown-menu-item label="New File" shortcut="⌘N" (itemSelected)="newFile()"></ng-shangjc-dropdown-menu-item>
        <ng-shangjc-dropdown-menu-item label="Open File" shortcut="⌘O" (itemSelected)="openFile()"></ng-shangjc-dropdown-menu-item>
        <ng-shangjc-dropdown-menu-separator></ng-shangjc-dropdown-menu-separator>
        <ng-shangjc-dropdown-menu-item label="Save" shortcut="⌘S" (itemSelected)="save()"></ng-shangjc-dropdown-menu-item>
        <ng-shangjc-dropdown-menu-item label="Exit" (itemSelected)="exit()"></ng-shangjc-dropdown-menu-item>
      </ng-shangjc-dropdown-menu-content>
    </ng-shangjc-dropdown-menu>
  `
})
export class BasicDropdownComponent {
  newFile() { console.log('New file'); }
  openFile() { console.log('Open file'); }
  save() { console.log('Save'); }
  exit() { console.log('Exit'); }
}
```

### With Icons and Labels

```html
<ng-shangjc-dropdown-menu>
  <ng-shangjc-dropdown-menu-trigger>
    <button class="btn">User Menu</button>
  </ng-shangjc-dropdown-menu-trigger>
  <ng-shangjc-dropdown-menu-content>
    <ng-shangjc-dropdown-menu-label>My Account</ng-shangjc-dropdown-menu-label>
    <ng-shangjc-dropdown-menu-separator></ng-shangjc-dropdown-menu-separator>
    <ng-shangjc-dropdown-menu-item 
      label="Profile" 
      icon="<svg class='h-4 w-4' fill='none' stroke='currentColor' viewBox='0 0 24 24'><path stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z'/></svg>" 
      shortcut="⌘P"
      (itemSelected)="openProfile()">
    </ng-shangjc-dropdown-menu-item>
    <ng-shangjc-dropdown-menu-item 
      label="Settings" 
      icon="<svg class='h-4 w-4' fill='none' stroke='currentColor' viewBox='0 0 24 24'><path stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z'/><path stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M15 12a3 3 0 11-6 0 3 3 0 016 0z'/></svg>" 
      shortcut="⌘S"
      (itemSelected)="openSettings()">
    </ng-shangjc-dropdown-menu-item>
  </ng-shangjc-dropdown-menu-content>
</ng-shangjc-dropdown-menu>
```

## Advanced Usage

### Controlled Component

```typescript
import { Component, signal } from '@angular/core';
import { 
  DropdownMenuComponent,
  DropdownMenuTriggerComponent,
  DropdownMenuContentComponent,
  DropdownMenuItemComponent
} from './ui/shangjc/dropdown-menu';

@Component({
  selector: 'app-controlled-dropdown',
  standalone: true,
  imports: [
    DropdownMenuComponent,
    DropdownMenuTriggerComponent,
    DropdownMenuContentComponent,
    DropdownMenuItemComponent
  ],
  template: `
    <div class="space-y-4">
      <button (click)="toggleMenu()" class="btn">
        {{ isMenuOpen() ? 'Close' : 'Open' }} Menu (Controlled)
      </button>
      
      <ng-shangjc-dropdown-menu [open]="isMenuOpen()" (openChange)="onMenuOpenChange($event)">
        <ng-shangjc-dropdown-menu-trigger>
          <button class="btn">Trigger Button</button>
        </ng-shangjc-dropdown-menu-trigger>
        <ng-shangjc-dropdown-menu-content>
          <ng-shangjc-dropdown-menu-item label="Item 1" (itemSelected)="selectItem('item1')"></ng-shangjc-dropdown-menu-item>
          <ng-shangjc-dropdown-menu-item label="Item 2" (itemSelected)="selectItem('item2')"></ng-shangjc-dropdown-menu-item>
        </ng-shangjc-dropdown-menu-content>
      </ng-shangjc-dropdown-menu>
      
      <p class="text-sm text-muted-foreground">
        Menu state: {{ isMenuOpen() ? 'Open' : 'Closed' }}
      </p>
    </div>
  `
})
export class ControlledDropdownComponent {
  isMenuOpen = signal(false);

  toggleMenu() {
    this.isMenuOpen.set(!this.isMenuOpen());
  }

  onMenuOpenChange(open: boolean) {
    this.isMenuOpen.set(open);
    console.log('Menu state changed:', open);
  }

  selectItem(item: string) {
    console.log('Selected:', item);
    this.isMenuOpen.set(false);
  }
}
```

### With Disabled Items

```html
<ng-shangjc-dropdown-menu>
  <ng-shangjc-dropdown-menu-trigger>
    <button class="btn">File Menu</button>
  </ng-shangjc-dropdown-menu-trigger>
  <ng-shangjc-dropdown-menu-content>
    <ng-shangjc-dropdown-menu-item label="New File" shortcut="⌘N" (itemSelected)="newFile()"></ng-shangjc-dropdown-menu-item>
    <ng-shangjc-dropdown-menu-item label="Open File" shortcut="⌘O" (itemSelected)="openFile()"></ng-shangjc-dropdown-menu-item>
    <ng-shangjc-dropdown-menu-separator></ng-shangjc-dropdown-menu-separator>
    <ng-shangjc-dropdown-menu-item label="Save" shortcut="⌘S" [disabled]="!hasUnsavedChanges" (itemSelected)="save()"></ng-shangjc-dropdown-menu-item>
    <ng-shangjc-dropdown-menu-item label="Save As..." shortcut="⌘⇧S" (itemSelected)="saveAs()"></ng-shangjc-dropdown-menu-item>
    <ng-shangjc-dropdown-menu-separator></ng-shangjc-dropdown-menu-separator>
    <ng-shangjc-dropdown-menu-item label="Print" shortcut="⌘P" [disabled]="!canPrint" (itemSelected)="print()"></ng-shangjc-dropdown-menu-item>
  </ng-shangjc-dropdown-menu-content>
</ng-shangjc-dropdown-menu>
```

## API Reference

### DropdownMenuComponent

| Property | Type | Default | Description |
|----------|------|---------|-------------|
| `class` | `string` | `''` | Additional CSS classes to apply to the dropdown menu container |
| `open` | `boolean` | `false` | Whether the dropdown is open (controlled mode) |
| `id` | `string` | `ddm-<random>` | Unique identifier for the dropdown menu |

#### Events

| Event | Type | Description |
|-------|------|-------------|
| `openChange` | `EventEmitter<boolean>` | Emitted when the dropdown open state changes |

### DropdownMenuTriggerComponent

| Property | Type | Default | Description |
|----------|------|---------|-------------|
| `isOpen` | `Signal<boolean>` | `false` | Whether the dropdown is open (internal signal) |
| `id` | `Signal<string>` | `''` | Dropdown identifier (internal signal) |

#### Events

| Event | Type | Description |
|-------|------|-------------|
| `openChange` | `EventEmitter<boolean>` | Emitted when the trigger is activated |

### DropdownMenuContentComponent

| Property | Type | Default | Description |
|----------|------|---------|-------------|
| `class` | `string` | `''` | Additional CSS classes |
| `positionX` | `number` | `0` | X-coordinate for manual positioning |
| `positionY` | `number` | `0` | Y-coordinate for manual positioning |
| `minWidth` | `number` | `220` | Minimum width of the dropdown content |

### DropdownMenuItemComponent

| Property | Type | Default | Description |
|----------|------|---------|-------------|
| `label` | `string` | `''` | The text label for the menu item |
| `icon` | `string` | `''` | Icon HTML or text to display |
| `disabled` | `boolean` | `false` | Whether the menu item is disabled |
| `shortcut` | `string` | `''` | Keyboard shortcut text to display |
| `class` | `string` | `''` | Additional CSS classes |

#### Events

| Event | Type | Description |
|-------|------|-------------|
| `itemSelected` | `EventEmitter<void>` | Emitted when the menu item is selected |

### DropdownMenuLabelComponent

| Property | Type | Default | Description |
|----------|------|---------|-------------|
| `class` | `string` | `''` | Additional CSS classes |

### DropdownMenuSeparatorComponent

| Property | Type | Default | Description |
|----------|------|---------|-------------|
| `class` | `string` | `''` | Additional CSS classes |

## Accessibility

- **Keyboard Navigation**: Full support for Tab, Shift+Tab, Enter, Space, Escape, and Arrow keys
- **ARIA Attributes**: Proper ARIA labels, roles, and states
- **Focus Management**: Visible focus indicators and proper focus handling
- **Screen Reader**: Compatible with screen readers
- **Semantic Structure**: Uses proper HTML semantic elements

### ARIA Features

- `role="menu"` on dropdown content
- `role="menuitem"` on menu items
- `role="separator"` on separators
- `aria-haspopup="true"` indicates trigger has popup menu
- `aria-expanded` indicates whether menu is expanded
- `aria-disabled` indicates disabled menu items
- `aria-orientation="vertical"` for menu orientation

### Keyboard Navigation

- **Tab**: Move focus to trigger
- **Enter/Space**: Open/close dropdown and activate menu items
- **Escape**: Close dropdown and return focus to trigger
- **Arrow Keys**: Navigate between menu items (when open)
- **Home/End**: Jump to first/last menu item

## Browser Support

- Chrome 90+
- Firefox 88+
- Safari 14+
- Edge 90+

## Contributing

See the main project [CONTRIBUTING.md](../../CONTRIBUTING.md) for guidelines.

---

**Part of ng-shangjc component library** • [Documentation](https://juliancallejas.github.io/ng-shangjc-docs/)

<p align="center">
<a href="https://github.com/JulianCallejas">
  <img src="https://res.cloudinary.com/dphleqb5t/image/upload/v1740784502/github-jc-develop/JC-LOGO-Horizontal-170-50-thin-github_uu3b5n.svg" width="170" alt="{ jc - develop }"  /> 
</a>
</p>