Menu
Genel Bakış
Bir menü, kullanıcılara eylemler veya seçenekler listesi sunar, tipik olarak bir buton tıklaması veya sağ tıklama yanıtı olarak görünür. Menüler ok tuşlarıyla klavye navigasyonunu, alt menüleri, onay kutularını, radyo butonlarını ve devre dışı öğeleri destekler.
app.ts
import {Component, viewChild} from '@angular/core';
import {Menu, MenuContent, MenuItem, MenuTrigger} from '@angular/aria/menu';
import {OverlayModule} from '@angular/cdk/overlay';
@Component({
selector: 'app-root',
templateUrl: 'app.html',
styleUrl: 'app.css',
imports: [Menu, MenuContent, MenuItem, MenuTrigger, OverlayModule],
})
export class App {
formatMenu = viewChild<Menu<string>>('formatMenu');
categorizeMenu = viewChild<Menu<string>>('categorizeMenu');
}
app.html
<button ngMenuTrigger #origin #trigger="ngMenuTrigger" [menu]="formatMenu()">Open Menu</button>
<ng-template
[cdkConnectedOverlayOpen]="trigger.expanded()"
[cdkConnectedOverlay]="{origin, usePopover: 'inline'}"
[cdkConnectedOverlayPositions]="[
{originX: 'start', originY: 'bottom', overlayX: 'start', overlayY: 'top', offsetY: 4},
]"
cdkAttachPopoverAsChild
>
<div ngMenu class="menu" #formatMenu="ngMenu">
<ng-template ngMenuContent>
<div ngMenuItem value="Mark as read">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>mark_email_read</span
>
<span class="label">Mark as read</span>
</div>
<div ngMenuItem value="Snooze">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true">snooze</span>
<span class="label">Snooze</span>
</div>
<div role="separator" aria-orientation="horizontal" class="separator"></div>
<div
ngMenuItem
class="menu-item"
value="Categorize"
#categorizeItem
[submenu]="categorizeMenu()"
>
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>category</span
>
<span class="label">Categorize</span>
<span class="icon material-symbols-outlined arrow" translate="no" aria-hidden="true"
>arrow_right</span
>
</div>
<ng-template
[cdkConnectedOverlayOpen]="formatMenu.visible()"
[cdkConnectedOverlay]="{origin: categorizeItem, usePopover: 'inline'}"
[cdkConnectedOverlayPositions]="[
{originX: 'end', originY: 'top', overlayY: 'top', overlayX: 'start', offsetX: 6},
]"
cdkAttachPopoverAsChild
>
<div ngMenu class="menu" #categorizeMenu="ngMenu">
<ng-template ngMenuContent>
<div ngMenuItem value="Mark as important">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>label_important</span
>
<span class="label">Mark as important</span>
</div>
<div ngMenuItem value="Star">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>star</span
>
<span class="label">Star</span>
</div>
<div ngMenuItem value="Label">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>label</span
>
<span class="label">Label</span>
</div>
</ng-template>
</div>
</ng-template>
<div role="separator" aria-orientation="horizontal" class="separator"></div>
<div ngMenuItem value="Archive">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>archive</span
>
<span class="label">Archive</span>
</div>
<div ngMenuItem value="Report spam">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true">report</span>
<span class="label">Report spam</span>
</div>
<div ngMenuItem value="Delete">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true">delete</span>
<span class="label">Delete</span>
</div>
</ng-template>
</div>
</ng-template>
app.css
@import url('https://fonts.googleapis.com/icon?family=Material+Symbols+Outlined');
:host {
display: flex;
justify-content: center;
font-family: var(--inter-font);
--border-color: color-mix(in srgb, var(--full-contrast) 20%, var(--page-background));
}
[ngMenuTrigger] {
display: flex;
cursor: pointer;
align-items: center;
padding: 0.6rem 2rem;
border-radius: 0.5rem;
color: var(--primary-contrast);
border: 1px solid var(--border-color);
background-color: var(--page-background);
}
[ngMenuTrigger] .icon {
font-size: 1.5rem;
opacity: 0.875;
}
[ngMenu] {
margin: 0;
width: 15rem;
padding: 0.25rem;
border-radius: 0.5rem;
border: 1px solid var(--border-color);
background-color: var(--page-background);
}
[ngMenu][data-visible='false'] {
display: none;
}
[ngMenuItem] {
outline: none;
display: flex;
cursor: pointer;
align-items: center;
gap: 0.5rem;
padding: 0.5rem;
font-size: 0.875rem;
border-radius: 0.25rem;
}
[ngMenuTrigger]:not([disabled]):hover,
[ngMenuTrigger][aria-expanded='true'],
[ngMenuItem]:not([aria-disabled='true']):hover,
[ngMenuItem]:not([aria-disabled='true']):focus,
[ngMenuItem][aria-expanded='true'] {
background: color-mix(in srgb, var(--border-color) 10%, transparent);
}
[ngMenuItem]:focus,
[ngMenuTrigger]:focus {
outline: 2px solid var(--vivid-pink);
}
[ngMenuItem] .icon {
opacity: 0.875;
font-size: 1.25rem;
}
[ngMenuItem] .label {
flex: 1;
opacity: 0.875;
font-size: 0.875rem;
}
[ngMenuItem]:not([aria-expanded='true']) .arrow {
opacity: 0.5;
}
[ngMenu] .separator {
border-top: 1px solid var(--border-color);
margin: 0.25rem 0;
opacity: 0.25;
}
app.ts
import {Component, viewChild} from '@angular/core';
import {Menu, MenuContent, MenuItem, MenuTrigger} from '@angular/aria/menu';
import {OverlayModule} from '@angular/cdk/overlay';
@Component({
selector: 'app-root',
templateUrl: 'app.html',
styleUrl: 'app.css',
imports: [Menu, MenuContent, MenuItem, MenuTrigger, OverlayModule],
})
export class App {
formatMenu = viewChild<Menu<string>>('formatMenu');
categorizeMenu = viewChild<Menu<string>>('categorizeMenu');
}
app.html
<button ngMenuTrigger #origin #trigger="ngMenuTrigger" [menu]="formatMenu()">Open Menu</button>
<ng-template
[cdkConnectedOverlayOpen]="trigger.expanded()"
[cdkConnectedOverlay]="{origin, usePopover: 'inline'}"
[cdkConnectedOverlayPositions]="[
{originX: 'start', originY: 'bottom', overlayX: 'start', overlayY: 'top', offsetY: 4},
]"
cdkAttachPopoverAsChild
>
<div ngMenu class="menu" #formatMenu="ngMenu">
<ng-template ngMenuContent>
<div class="group">
<div ngMenuItem value="Mark as read">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>mark_email_read</span
>
<span class="label">Mark as read</span>
</div>
<div ngMenuItem value="Snooze">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>snooze</span
>
<span class="label">Snooze</span>
</div>
<div ngMenuItem value="Delete">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>delete</span
>
<span class="label">Delete</span>
</div>
</div>
<div class="group">
<div
ngMenuItem
class="menu-item"
value="Categorize"
#categorizeItem
[submenu]="categorizeMenu()"
>
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>category</span
>
<span class="label">Categorize</span>
<span class="icon material-symbols-outlined arrow" translate="no" aria-hidden="true"
>arrow_right</span
>
</div>
</div>
<ng-template
[cdkConnectedOverlayOpen]="formatMenu.visible()"
[cdkConnectedOverlay]="{origin: categorizeItem, usePopover: 'inline'}"
[cdkConnectedOverlayPositions]="[
{originX: 'end', originY: 'top', overlayY: 'top', overlayX: 'start', offsetX: 6},
]"
cdkAttachPopoverAsChild
>
<div ngMenu class="menu" #categorizeMenu="ngMenu">
<ng-template ngMenuContent>
<div class="group">
<div ngMenuItem value="Mark as important">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>label_important</span
>
<span class="label">Mark as important</span>
</div>
<div ngMenuItem value="Star">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>star</span
>
<span class="label">Star</span>
</div>
<div ngMenuItem value="Label">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>label</span
>
<span class="label">Label</span>
</div>
</div>
</ng-template>
</div>
</ng-template>
</ng-template>
</div>
</ng-template>
app.css
@import url('https://fonts.googleapis.com/icon?family=Material+Symbols+Outlined');
:host {
display: flex;
justify-content: center;
font-family: var(--inter-font);
}
[ngMenuTrigger] {
display: flex;
cursor: pointer;
align-items: center;
padding: 0.6rem 2rem;
border-radius: 0.5rem;
border: 1px solid transparent;
background-color: color-mix(in srgb, var(--vivid-pink) 5%, transparent);
color: color-mix(in srgb, var(--vivid-pink) 70%, var(--primary-contrast));
}
[ngMenuTrigger] .icon {
font-size: 1.5rem;
opacity: 0.875;
}
[ngMenu] {
gap: 3px;
width: 15rem;
display: flex;
flex-direction: column;
}
[ngMenu] .group {
padding: 0.25rem;
border-radius: 0.25rem;
background-color: var(--page-background);
box-shadow: 0 1px 2px 1px color-mix(in srgb, var(--primary-contrast) 25%, transparent);
}
[ngMenu] .group:first-of-type {
border-top-left-radius: 1rem;
border-top-right-radius: 1rem;
}
[ngMenu] .group:last-of-type {
border-bottom-left-radius: 1rem;
border-bottom-right-radius: 1rem;
}
[ngMenu][data-visible='false'] {
display: none;
}
[ngMenuItem] {
outline: none;
display: flex;
cursor: pointer;
align-items: center;
gap: 0.5rem;
padding: 0.75rem;
font-size: 0.875rem;
border-radius: 0.75rem;
}
[ngMenuTrigger]:hover,
[ngMenuTrigger][aria-expanded='true'] {
background: color-mix(in srgb, var(--vivid-pink) 10%, transparent);
}
[ngMenuItem]:not([aria-disabled='true']):hover,
[ngMenuItem]:not([aria-disabled='true']):focus,
[ngMenuItem][aria-expanded='true'] {
color: color-mix(in srgb, var(--vivid-pink) 70%, var(--primary-contrast));
background: color-mix(in srgb, var(--vivid-pink) 5%, transparent);
}
[ngMenuItem]:focus,
[ngMenuTrigger]:focus {
outline: 2px solid var(--vivid-pink);
}
[ngMenuItem] .icon {
opacity: 0.875;
font-size: 1.25rem;
}
[ngMenuItem] .label {
flex: 1;
opacity: 0.875;
font-size: 0.875rem;
}
[ngMenuItem]:not([aria-expanded='true']) .arrow {
opacity: 0.5;
}
[ngMenu] .separator {
border-top: 1px solid var(--quaternary-contrast);
margin: 0.25rem 0;
opacity: 0.25;
}
app.ts
import {Component, viewChild} from '@angular/core';
import {Menu, MenuContent, MenuItem, MenuTrigger} from '@angular/aria/menu';
import {OverlayModule} from '@angular/cdk/overlay';
@Component({
selector: 'app-root',
templateUrl: 'app.html',
styleUrl: 'app.css',
imports: [Menu, MenuContent, MenuItem, MenuTrigger, OverlayModule],
})
export class App {
formatMenu = viewChild<Menu<string>>('formatMenu');
categorizeMenu = viewChild<Menu<string>>('categorizeMenu');
}
app.html
<button class="retro-trigger" ngMenuTrigger #trigger="ngMenuTrigger" #origin [menu]="formatMenu()">
Open Menu
</button>
<ng-template
[cdkConnectedOverlayOpen]="trigger.expanded()"
[cdkConnectedOverlay]="{origin, usePopover: 'inline'}"
[cdkConnectedOverlayPositions]="[
{originX: 'start', originY: 'bottom', overlayX: 'start', overlayY: 'top', offsetY: 4},
]"
cdkAttachPopoverAsChild
>
<div ngMenu class="menu" #formatMenu="ngMenu">
<ng-template ngMenuContent>
<div ngMenuItem value="Mark as read">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>mark_email_read</span
>
<span class="label">Mark as read</span>
</div>
<div ngMenuItem value="Snooze">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true">snooze</span>
<span class="label">Snooze</span>
</div>
<div role="separator" aria-orientation="horizontal" class="separator"></div>
<div
ngMenuItem
class="menu-item"
value="Categorize"
#categorizeItem
[submenu]="categorizeMenu()"
>
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>category</span
>
<span class="label">Categorize</span>
<span class="icon material-symbols-outlined arrow" translate="no" aria-hidden="true"
>arrow_right</span
>
</div>
<ng-template
[cdkConnectedOverlayOpen]="formatMenu.visible()"
[cdkConnectedOverlay]="{origin: categorizeItem, usePopover: 'inline'}"
[cdkConnectedOverlayPositions]="[
{originX: 'end', originY: 'top', overlayY: 'top', overlayX: 'start', offsetX: 6},
]"
cdkAttachPopoverAsChild
>
<div ngMenu class="menu" #categorizeMenu="ngMenu">
<ng-template ngMenuContent>
<div ngMenuItem value="Mark as important">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>label_important</span
>
<span class="label">Mark as important</span>
</div>
<div ngMenuItem value="Star">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>star</span
>
<span class="label">Star</span>
</div>
<div ngMenuItem value="Label">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>label</span
>
<span class="label">Label</span>
</div>
</ng-template>
</div>
</ng-template>
<div role="separator" aria-orientation="horizontal" class="separator"></div>
<div ngMenuItem value="Archive">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>archive</span
>
<span class="label">Archive</span>
</div>
<div ngMenuItem value="Report spam">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true">report</span>
<span class="label">Report spam</span>
</div>
<div ngMenuItem value="Delete">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true">delete</span>
<span class="label">Delete</span>
</div>
</ng-template>
</div>
</ng-template>
app.css
@import url('https://fonts.googleapis.com/css2?family=Press+Start+2P&display=swap');
@import url('https://fonts.googleapis.com/icon?family=Material+Symbols+Outlined');
:host {
display: flex;
justify-content: center;
font-size: 0.8rem;
--border-color: color-mix(in srgb, var(--full-contrast) 20%, var(--page-background));
font-family: 'Press Start 2P';
--retro-button-color: var(--vivid-pink);
--retro-shadow-light: color-mix(in srgb, #fff 20%, transparent);
--retro-shadow-dark: color-mix(in srgb, #000 20%, transparent);
--retro-elevated-shadow:
inset 4px 4px 0px 0px var(--retro-shadow-light),
inset -4px -4px 0px 0px var(--retro-shadow-dark), 4px 0px 0px 0px var(--tertiary-contrast),
0px 4px 0px 0px var(--tertiary-contrast), -4px 0px 0px 0px var(--tertiary-contrast),
0px -4px 0px 0px var(--tertiary-contrast);
--retro-flat-shadow:
4px 0px 0px 0px var(--tertiary-contrast), 0px 4px 0px 0px var(--tertiary-contrast),
-4px 0px 0px 0px var(--tertiary-contrast), 0px -4px 0px 0px var(--tertiary-contrast);
--retro-clickable-shadow:
inset 4px 4px 0px 0px var(--retro-shadow-light),
inset -4px -4px 0px 0px var(--retro-shadow-dark), 4px 0px 0px 0px var(--tertiary-contrast),
0px 4px 0px 0px var(--tertiary-contrast), -4px 0px 0px 0px var(--tertiary-contrast),
0px -4px 0px 0px var(--tertiary-contrast), 8px 8px 0px 0px var(--tertiary-contrast);
--retro-pressed-shadow:
inset 4px 4px 0px 0px var(--retro-shadow-dark),
inset -4px -4px 0px 0px var(--retro-shadow-light), 4px 0px 0px 0px var(--tertiary-contrast),
0px 4px 0px 0px var(--tertiary-contrast), -4px 0px 0px 0px var(--tertiary-contrast),
0px -4px 0px 0px var(--tertiary-contrast), 0px 0px 0px 0px var(--tertiary-contrast);
}
[ngMenuTrigger] {
display: flex;
cursor: pointer;
align-items: center;
padding: 0.6rem 2rem;
font-family: 'Press Start 2P';
color: #000;
background-color: var(--vivid-pink);
box-shadow: var(--retro-clickable-shadow);
transition:
transform 0.1s,
box-shadow 0.1s;
}
[ngMenuTrigger]:hover {
transform: translate(1px, 1px);
}
[ngMenuTrigger]:active {
background-color: color-mix(in srgb, var(--vivid-pink) 80%, #fff);
box-shadow: var(--retro-pressed-shadow);
transform: translate(4px, 4px);
}
[ngMenuTrigger] .icon {
font-size: 1.5rem;
opacity: 0.875;
}
[ngMenu] {
margin-top: 8px;
width: 15rem;
padding: 0.25rem;
background-color: var(--page-background);
box-shadow: var(--retro-flat-shadow);
}
[ngMenu][data-visible='false'] {
display: none;
}
[ngMenuItem] {
outline: none;
display: flex;
cursor: pointer;
align-items: center;
gap: 0.5rem;
padding: 0.5rem;
font-size: 0.875rem;
}
[ngMenuItem]:not([aria-disabled='true']):hover,
[ngMenuItem]:not([aria-disabled='true']):focus,
[ngMenuItem][aria-expanded='true'] {
background: color-mix(in srgb, var(--border-color) 10%, transparent);
}
[ngMenuTrigger]:focus {
outline: 4px dashed var(--vivid-pink);
outline-offset: 8px;
}
[ngMenuItem]:focus {
outline: 4px dashed var(--vivid-pink);
outline-offset: -4px;
}
[ngMenuItem] .icon {
opacity: 0.875;
font-size: 1.25rem;
}
[ngMenuItem] .label {
flex: 1;
opacity: 0.875;
font-size: 0.875rem;
}
[ngMenuItem]:not([aria-expanded='true']) .arrow {
opacity: 0.5;
}
[ngMenu] .separator {
border-top: 4px solid #000;
margin: 0.25rem 0;
opacity: 0.25;
}
Kullanım
Menüler, kullanıcıların seçebileceği eylem veya komut listeleri sunmak için iyi çalışır.
Menü kullanın:
- Uygulama komut menüleri oluştururken (Dosya, Düzenle, Görüntüle)
- Bağlam menüleri oluştururken (sağ tıklama eylemleri)
- Açılır eylem listeleri gösterirken
- Araç çubuğu açılır menüleri uygularken
- Ayarları veya seçenekleri düzenlerken
Menülerden kaçının:
- Site navigasyonu oluştururken (bunun yerine navigasyon yer işaretlerini kullanın)
- Form select'leri oluştururken (bunun yerine Select bileşenini kullanın)
- İçerik panelleri arasında geçiş yaparken (bunun yerine Tabs kullanın)
- Daraltılabilir içerik gösterirken (bunun yerine Accordion kullanın)
Özellikler
- Klavye navigasyonu - Ok tuşları, Home/End ve karakter arama ile verimli navigasyon
- Alt menüler - Otomatik konumlandırma ile iç içe menü desteği
- Menü türleri - Bağımsız menüler, tetikleyicili menüler ve menü çubukları
- Onay kutuları ve radyolar - Değiştirme ve seçim menü öğeleri
- Devre dışı öğeler - Odak yönetimiyle yumuşak veya sert devre dışı durumlar
- Otomatik kapanma davranışı - Seçimde yapılandırılabilir kapanma
- RTL desteği - Sağdan sola dil navigasyonu
Örnekler
Tetikleyicili menü
Bir tetikleyici butonla bir menüyü eşleştirerek açılır menü oluşturun. Tetikleyici menüyü açar ve kapatır.
app.ts
import {Component, viewChild} from '@angular/core';
import {Menu, MenuContent, MenuItem, MenuTrigger} from '@angular/aria/menu';
import {OverlayModule} from '@angular/cdk/overlay';
@Component({
selector: 'app-root',
templateUrl: 'app.html',
styleUrl: 'app.css',
imports: [Menu, MenuContent, MenuItem, MenuTrigger, OverlayModule],
})
export class App {
formatMenu = viewChild<Menu<string>>('formatMenu');
categorizeMenu = viewChild<Menu<string>>('categorizeMenu');
}
app.html
<button ngMenuTrigger #origin #trigger="ngMenuTrigger" [menu]="formatMenu()">Open Menu</button>
<ng-template
[cdkConnectedOverlayOpen]="trigger.expanded()"
[cdkConnectedOverlay]="{origin, usePopover: 'inline'}"
[cdkConnectedOverlayPositions]="[
{originX: 'start', originY: 'bottom', overlayX: 'start', overlayY: 'top', offsetY: 4},
]"
cdkAttachPopoverAsChild
>
<div ngMenu class="menu" #formatMenu="ngMenu">
<ng-template ngMenuContent>
<div ngMenuItem value="Mark as read">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>mark_email_read</span
>
<span class="label">Mark as read</span>
</div>
<div ngMenuItem value="Snooze">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true">snooze</span>
<span class="label">Snooze</span>
</div>
<div role="separator" aria-orientation="horizontal" class="separator"></div>
<div
ngMenuItem
class="menu-item"
value="Categorize"
#categorizeItem
[submenu]="categorizeMenu()"
>
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>category</span
>
<span class="label">Categorize</span>
<span class="icon material-symbols-outlined arrow" translate="no" aria-hidden="true"
>arrow_right</span
>
</div>
<ng-template
[cdkConnectedOverlayOpen]="formatMenu.visible()"
[cdkConnectedOverlay]="{origin: categorizeItem, usePopover: 'inline'}"
[cdkConnectedOverlayPositions]="[
{originX: 'end', originY: 'top', overlayY: 'top', overlayX: 'start', offsetX: 6},
]"
cdkAttachPopoverAsChild
>
<div ngMenu class="menu" #categorizeMenu="ngMenu">
<ng-template ngMenuContent>
<div ngMenuItem value="Mark as important">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>label_important</span
>
<span class="label">Mark as important</span>
</div>
<div ngMenuItem value="Star">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>star</span
>
<span class="label">Star</span>
</div>
<div ngMenuItem value="Label">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>label</span
>
<span class="label">Label</span>
</div>
</ng-template>
</div>
</ng-template>
<div role="separator" aria-orientation="horizontal" class="separator"></div>
<div ngMenuItem value="Archive">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>archive</span
>
<span class="label">Archive</span>
</div>
<div ngMenuItem value="Report spam">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true">report</span>
<span class="label">Report spam</span>
</div>
<div ngMenuItem value="Delete">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true">delete</span>
<span class="label">Delete</span>
</div>
</ng-template>
</div>
</ng-template>
app.css
@import url('https://fonts.googleapis.com/icon?family=Material+Symbols+Outlined');
:host {
display: flex;
justify-content: center;
font-family: var(--inter-font);
--border-color: color-mix(in srgb, var(--full-contrast) 20%, var(--page-background));
}
[ngMenuTrigger] {
display: flex;
cursor: pointer;
align-items: center;
padding: 0.6rem 2rem;
border-radius: 0.5rem;
color: var(--primary-contrast);
border: 1px solid var(--border-color);
background-color: var(--page-background);
}
[ngMenuTrigger] .icon {
font-size: 1.5rem;
opacity: 0.875;
}
[ngMenu] {
margin: 0;
width: 15rem;
padding: 0.25rem;
border-radius: 0.5rem;
border: 1px solid var(--border-color);
background-color: var(--page-background);
}
[ngMenu][data-visible='false'] {
display: none;
}
[ngMenuItem] {
outline: none;
display: flex;
cursor: pointer;
align-items: center;
gap: 0.5rem;
padding: 0.5rem;
font-size: 0.875rem;
border-radius: 0.25rem;
}
[ngMenuTrigger]:not([disabled]):hover,
[ngMenuTrigger][aria-expanded='true'],
[ngMenuItem]:not([aria-disabled='true']):hover,
[ngMenuItem]:not([aria-disabled='true']):focus,
[ngMenuItem][aria-expanded='true'] {
background: color-mix(in srgb, var(--border-color) 10%, transparent);
}
[ngMenuItem]:focus,
[ngMenuTrigger]:focus {
outline: 2px solid var(--vivid-pink);
}
[ngMenuItem] .icon {
opacity: 0.875;
font-size: 1.25rem;
}
[ngMenuItem] .label {
flex: 1;
opacity: 0.875;
font-size: 0.875rem;
}
[ngMenuItem]:not([aria-expanded='true']) .arrow {
opacity: 0.5;
}
[ngMenu] .separator {
border-top: 1px solid var(--border-color);
margin: 0.25rem 0;
opacity: 0.25;
}
app.ts
import {Component, viewChild} from '@angular/core';
import {Menu, MenuContent, MenuItem, MenuTrigger} from '@angular/aria/menu';
import {OverlayModule} from '@angular/cdk/overlay';
@Component({
selector: 'app-root',
templateUrl: 'app.html',
styleUrl: 'app.css',
imports: [Menu, MenuContent, MenuItem, MenuTrigger, OverlayModule],
})
export class App {
formatMenu = viewChild<Menu<string>>('formatMenu');
categorizeMenu = viewChild<Menu<string>>('categorizeMenu');
}
app.html
<button ngMenuTrigger #origin #trigger="ngMenuTrigger" [menu]="formatMenu()">Open Menu</button>
<ng-template
[cdkConnectedOverlayOpen]="trigger.expanded()"
[cdkConnectedOverlay]="{origin, usePopover: 'inline'}"
[cdkConnectedOverlayPositions]="[
{originX: 'start', originY: 'bottom', overlayX: 'start', overlayY: 'top', offsetY: 4},
]"
cdkAttachPopoverAsChild
>
<div ngMenu class="menu" #formatMenu="ngMenu">
<ng-template ngMenuContent>
<div class="group">
<div ngMenuItem value="Mark as read">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>mark_email_read</span
>
<span class="label">Mark as read</span>
</div>
<div ngMenuItem value="Snooze">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>snooze</span
>
<span class="label">Snooze</span>
</div>
<div ngMenuItem value="Delete">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>delete</span
>
<span class="label">Delete</span>
</div>
</div>
<div class="group">
<div
ngMenuItem
class="menu-item"
value="Categorize"
#categorizeItem
[submenu]="categorizeMenu()"
>
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>category</span
>
<span class="label">Categorize</span>
<span class="icon material-symbols-outlined arrow" translate="no" aria-hidden="true"
>arrow_right</span
>
</div>
</div>
<ng-template
[cdkConnectedOverlayOpen]="formatMenu.visible()"
[cdkConnectedOverlay]="{origin: categorizeItem, usePopover: 'inline'}"
[cdkConnectedOverlayPositions]="[
{originX: 'end', originY: 'top', overlayY: 'top', overlayX: 'start', offsetX: 6},
]"
cdkAttachPopoverAsChild
>
<div ngMenu class="menu" #categorizeMenu="ngMenu">
<ng-template ngMenuContent>
<div class="group">
<div ngMenuItem value="Mark as important">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>label_important</span
>
<span class="label">Mark as important</span>
</div>
<div ngMenuItem value="Star">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>star</span
>
<span class="label">Star</span>
</div>
<div ngMenuItem value="Label">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>label</span
>
<span class="label">Label</span>
</div>
</div>
</ng-template>
</div>
</ng-template>
</ng-template>
</div>
</ng-template>
app.css
@import url('https://fonts.googleapis.com/icon?family=Material+Symbols+Outlined');
:host {
display: flex;
justify-content: center;
font-family: var(--inter-font);
}
[ngMenuTrigger] {
display: flex;
cursor: pointer;
align-items: center;
padding: 0.6rem 2rem;
border-radius: 0.5rem;
border: 1px solid transparent;
background-color: color-mix(in srgb, var(--vivid-pink) 5%, transparent);
color: color-mix(in srgb, var(--vivid-pink) 70%, var(--primary-contrast));
}
[ngMenuTrigger] .icon {
font-size: 1.5rem;
opacity: 0.875;
}
[ngMenu] {
gap: 3px;
width: 15rem;
display: flex;
flex-direction: column;
}
[ngMenu] .group {
padding: 0.25rem;
border-radius: 0.25rem;
background-color: var(--page-background);
box-shadow: 0 1px 2px 1px color-mix(in srgb, var(--primary-contrast) 25%, transparent);
}
[ngMenu] .group:first-of-type {
border-top-left-radius: 1rem;
border-top-right-radius: 1rem;
}
[ngMenu] .group:last-of-type {
border-bottom-left-radius: 1rem;
border-bottom-right-radius: 1rem;
}
[ngMenu][data-visible='false'] {
display: none;
}
[ngMenuItem] {
outline: none;
display: flex;
cursor: pointer;
align-items: center;
gap: 0.5rem;
padding: 0.75rem;
font-size: 0.875rem;
border-radius: 0.75rem;
}
[ngMenuTrigger]:hover,
[ngMenuTrigger][aria-expanded='true'] {
background: color-mix(in srgb, var(--vivid-pink) 10%, transparent);
}
[ngMenuItem]:not([aria-disabled='true']):hover,
[ngMenuItem]:not([aria-disabled='true']):focus,
[ngMenuItem][aria-expanded='true'] {
color: color-mix(in srgb, var(--vivid-pink) 70%, var(--primary-contrast));
background: color-mix(in srgb, var(--vivid-pink) 5%, transparent);
}
[ngMenuItem]:focus,
[ngMenuTrigger]:focus {
outline: 2px solid var(--vivid-pink);
}
[ngMenuItem] .icon {
opacity: 0.875;
font-size: 1.25rem;
}
[ngMenuItem] .label {
flex: 1;
opacity: 0.875;
font-size: 0.875rem;
}
[ngMenuItem]:not([aria-expanded='true']) .arrow {
opacity: 0.5;
}
[ngMenu] .separator {
border-top: 1px solid var(--quaternary-contrast);
margin: 0.25rem 0;
opacity: 0.25;
}
app.ts
import {Component, viewChild} from '@angular/core';
import {Menu, MenuContent, MenuItem, MenuTrigger} from '@angular/aria/menu';
import {OverlayModule} from '@angular/cdk/overlay';
@Component({
selector: 'app-root',
templateUrl: 'app.html',
styleUrl: 'app.css',
imports: [Menu, MenuContent, MenuItem, MenuTrigger, OverlayModule],
})
export class App {
formatMenu = viewChild<Menu<string>>('formatMenu');
categorizeMenu = viewChild<Menu<string>>('categorizeMenu');
}
app.html
<button class="retro-trigger" ngMenuTrigger #trigger="ngMenuTrigger" #origin [menu]="formatMenu()">
Open Menu
</button>
<ng-template
[cdkConnectedOverlayOpen]="trigger.expanded()"
[cdkConnectedOverlay]="{origin, usePopover: 'inline'}"
[cdkConnectedOverlayPositions]="[
{originX: 'start', originY: 'bottom', overlayX: 'start', overlayY: 'top', offsetY: 4},
]"
cdkAttachPopoverAsChild
>
<div ngMenu class="menu" #formatMenu="ngMenu">
<ng-template ngMenuContent>
<div ngMenuItem value="Mark as read">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>mark_email_read</span
>
<span class="label">Mark as read</span>
</div>
<div ngMenuItem value="Snooze">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true">snooze</span>
<span class="label">Snooze</span>
</div>
<div role="separator" aria-orientation="horizontal" class="separator"></div>
<div
ngMenuItem
class="menu-item"
value="Categorize"
#categorizeItem
[submenu]="categorizeMenu()"
>
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>category</span
>
<span class="label">Categorize</span>
<span class="icon material-symbols-outlined arrow" translate="no" aria-hidden="true"
>arrow_right</span
>
</div>
<ng-template
[cdkConnectedOverlayOpen]="formatMenu.visible()"
[cdkConnectedOverlay]="{origin: categorizeItem, usePopover: 'inline'}"
[cdkConnectedOverlayPositions]="[
{originX: 'end', originY: 'top', overlayY: 'top', overlayX: 'start', offsetX: 6},
]"
cdkAttachPopoverAsChild
>
<div ngMenu class="menu" #categorizeMenu="ngMenu">
<ng-template ngMenuContent>
<div ngMenuItem value="Mark as important">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>label_important</span
>
<span class="label">Mark as important</span>
</div>
<div ngMenuItem value="Star">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>star</span
>
<span class="label">Star</span>
</div>
<div ngMenuItem value="Label">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>label</span
>
<span class="label">Label</span>
</div>
</ng-template>
</div>
</ng-template>
<div role="separator" aria-orientation="horizontal" class="separator"></div>
<div ngMenuItem value="Archive">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>archive</span
>
<span class="label">Archive</span>
</div>
<div ngMenuItem value="Report spam">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true">report</span>
<span class="label">Report spam</span>
</div>
<div ngMenuItem value="Delete">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true">delete</span>
<span class="label">Delete</span>
</div>
</ng-template>
</div>
</ng-template>
app.css
@import url('https://fonts.googleapis.com/css2?family=Press+Start+2P&display=swap');
@import url('https://fonts.googleapis.com/icon?family=Material+Symbols+Outlined');
:host {
display: flex;
justify-content: center;
font-size: 0.8rem;
--border-color: color-mix(in srgb, var(--full-contrast) 20%, var(--page-background));
font-family: 'Press Start 2P';
--retro-button-color: var(--vivid-pink);
--retro-shadow-light: color-mix(in srgb, #fff 20%, transparent);
--retro-shadow-dark: color-mix(in srgb, #000 20%, transparent);
--retro-elevated-shadow:
inset 4px 4px 0px 0px var(--retro-shadow-light),
inset -4px -4px 0px 0px var(--retro-shadow-dark), 4px 0px 0px 0px var(--tertiary-contrast),
0px 4px 0px 0px var(--tertiary-contrast), -4px 0px 0px 0px var(--tertiary-contrast),
0px -4px 0px 0px var(--tertiary-contrast);
--retro-flat-shadow:
4px 0px 0px 0px var(--tertiary-contrast), 0px 4px 0px 0px var(--tertiary-contrast),
-4px 0px 0px 0px var(--tertiary-contrast), 0px -4px 0px 0px var(--tertiary-contrast);
--retro-clickable-shadow:
inset 4px 4px 0px 0px var(--retro-shadow-light),
inset -4px -4px 0px 0px var(--retro-shadow-dark), 4px 0px 0px 0px var(--tertiary-contrast),
0px 4px 0px 0px var(--tertiary-contrast), -4px 0px 0px 0px var(--tertiary-contrast),
0px -4px 0px 0px var(--tertiary-contrast), 8px 8px 0px 0px var(--tertiary-contrast);
--retro-pressed-shadow:
inset 4px 4px 0px 0px var(--retro-shadow-dark),
inset -4px -4px 0px 0px var(--retro-shadow-light), 4px 0px 0px 0px var(--tertiary-contrast),
0px 4px 0px 0px var(--tertiary-contrast), -4px 0px 0px 0px var(--tertiary-contrast),
0px -4px 0px 0px var(--tertiary-contrast), 0px 0px 0px 0px var(--tertiary-contrast);
}
[ngMenuTrigger] {
display: flex;
cursor: pointer;
align-items: center;
padding: 0.6rem 2rem;
font-family: 'Press Start 2P';
color: #000;
background-color: var(--vivid-pink);
box-shadow: var(--retro-clickable-shadow);
transition:
transform 0.1s,
box-shadow 0.1s;
}
[ngMenuTrigger]:hover {
transform: translate(1px, 1px);
}
[ngMenuTrigger]:active {
background-color: color-mix(in srgb, var(--vivid-pink) 80%, #fff);
box-shadow: var(--retro-pressed-shadow);
transform: translate(4px, 4px);
}
[ngMenuTrigger] .icon {
font-size: 1.5rem;
opacity: 0.875;
}
[ngMenu] {
margin-top: 8px;
width: 15rem;
padding: 0.25rem;
background-color: var(--page-background);
box-shadow: var(--retro-flat-shadow);
}
[ngMenu][data-visible='false'] {
display: none;
}
[ngMenuItem] {
outline: none;
display: flex;
cursor: pointer;
align-items: center;
gap: 0.5rem;
padding: 0.5rem;
font-size: 0.875rem;
}
[ngMenuItem]:not([aria-disabled='true']):hover,
[ngMenuItem]:not([aria-disabled='true']):focus,
[ngMenuItem][aria-expanded='true'] {
background: color-mix(in srgb, var(--border-color) 10%, transparent);
}
[ngMenuTrigger]:focus {
outline: 4px dashed var(--vivid-pink);
outline-offset: 8px;
}
[ngMenuItem]:focus {
outline: 4px dashed var(--vivid-pink);
outline-offset: -4px;
}
[ngMenuItem] .icon {
opacity: 0.875;
font-size: 1.25rem;
}
[ngMenuItem] .label {
flex: 1;
opacity: 0.875;
font-size: 0.875rem;
}
[ngMenuItem]:not([aria-expanded='true']) .arrow {
opacity: 0.5;
}
[ngMenu] .separator {
border-top: 4px solid #000;
margin: 0.25rem 0;
opacity: 0.25;
}
Kullanıcı bir öğe seçtiğinde veya Escape'e bastığında menü otomatik olarak kapanır.
Bağlam menüsü
Bağlam menüleri, kullanıcılar bir elemana sağ tıkladığında imleç konumunda görünür.
app.ts
import {Component, viewChild, signal} from '@angular/core';
import {Menu, MenuContent, MenuItem, MenuTrigger} from '@angular/aria/menu';
import {OverlayModule} from '@angular/cdk/overlay';
@Component({
selector: 'app-root',
templateUrl: './app.html',
styleUrl: './app.css',
imports: [Menu, MenuContent, MenuItem, MenuTrigger, OverlayModule],
})
export class App {
trigger = viewChild.required<MenuTrigger<string>>('trigger');
contextMenu = viewChild<Menu<string>>('contextMenu');
// Position of the context menu
menuPosition = signal({x: 0, y: 0});
onContextMenu(event: MouseEvent) {
event.preventDefault();
// Update position coordinates
this.menuPosition.set({x: event.clientX, y: event.clientY});
// Open the menu via the trigger
this.trigger().open();
}
onItemSelected(value: string) {
console.log(`Action selected: ${value}`);
}
onOverlayKeydown(event: KeyboardEvent) {
if (event.key === 'Escape') {
this.closeContextMenu();
}
}
closeContextMenu() {
if (this.trigger().expanded()) {
this.trigger().close();
}
}
}
app.html
<!-- Target area for right-click -->
<div (contextmenu)="onContextMenu($event)" class="context-zone">
Right-click inside this area to open the context menu.
</div>
<!-- Hidden trigger positioned dynamically at cursor coordinates -->
<div
#triggerEl
ngMenuTrigger
#trigger="ngMenuTrigger"
[menu]="contextMenu()"
style="position: fixed; visibility: hidden"
[style.left.px]="menuPosition().x"
[style.top.px]="menuPosition().y"
></div>
<!-- Overlay container for the menu -->
<ng-template
[cdkConnectedOverlayOpen]="trigger.expanded()"
[cdkConnectedOverlay]="{
origin: triggerEl,
usePopover: 'inline',
hasBackdrop: true,
backdropClass: 'cdk-overlay-transparent-backdrop',
}"
(backdropClick)="closeContextMenu()"
(overlayKeydown)="onOverlayKeydown($event)"
cdkAttachPopoverAsChild
>
<div ngMenu #contextMenu="ngMenu" (itemSelected)="onItemSelected($event)">
<ng-template ngMenuContent>
<div ngMenuItem value="Edit">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true">edit</span>
<span class="label">Edit</span>
</div>
<div ngMenuItem value="Copy">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>content_copy</span
>
<span class="label">Copy</span>
</div>
<div role="separator" class="separator"></div>
<div ngMenuItem value="Delete" style="color: var(--vivid-pink)">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true">delete</span>
<span class="label">Delete</span>
</div>
</ng-template>
</div>
</ng-template>
Menüyü contextmenu olay koordinatlarını kullanarak konumlandırın.
Bağımsız menü
Bağımsız menü bir tetikleyici gerektirmez ve arayüzde görünür kalır.
app.ts
import {Component, signal, viewChild} from '@angular/core';
import {Menu, MenuContent, MenuItem} from '@angular/aria/menu';
import {OverlayModule} from '@angular/cdk/overlay';
@Component({
selector: 'app-root',
templateUrl: 'app.html',
styleUrl: 'app.css',
imports: [Menu, MenuContent, MenuItem, OverlayModule],
})
export class App {
hasInteracted = signal(false);
updateMenu = viewChild<Menu<string>>('updateMenu');
}
app.html
<div ngMenu (mouseover)="hasInteracted.set(true)" (focusin)="hasInteracted.set(true)">
<ng-template ngMenuContent>
<span id="security-label" class="heading">SECURITY</span>
<div role="group" aria-labelledby="security-label">
<div ngMenuItem value="Change password">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>lock_open</span
>
<span class="label">Change password</span>
</div>
<div ngMenuItem value="Two-factor authentication">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>security_key</span
>
<span class="label">Two-factor authentication</span>
</div>
<div ngMenuItem value="Reset" #resetItem [submenu]="updateMenu()">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>refresh</span
>
<span class="label">Reset</span>
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>arrow_right</span
>
</div>
<ng-template
[cdkConnectedOverlayOpen]="hasInteracted()"
[cdkConnectedOverlay]="{origin: resetItem, usePopover: 'inline'}"
[cdkConnectedOverlayPositions]="[
{originX: 'end', originY: 'top', overlayY: 'top', overlayX: 'start', offsetX: 6},
]"
cdkAttachPopoverAsChild
>
<div ngMenu #updateMenu="ngMenu">
<ng-template ngMenuContent>
<div ngMenuItem value="Email address">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>email</span
>
<span class="label">Email address</span>
</div>
<div ngMenuItem value="Phone number">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>phone</span
>
<span class="label">Phone number</span>
</div>
<div ngMenuItem value="Password">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>vpn_key</span
>
<span class="label">Password</span>
</div>
</ng-template>
</div>
</ng-template>
</div>
<div role="separator" class="separator"></div>
<span id="help-label" class="heading">HELP</span>
<div role="group" aria-labelledby="help-label">
<div ngMenuItem value="Support">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true">help</span>
<span class="label">Support</span>
</div>
<div ngMenuItem value="Feedback">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>feedback</span
>
<span class="label">Feedback</span>
</div>
</div>
<div role="separator" class="separator"></div>
<div ngMenuItem value="Logout">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true">logout</span>
<span class="label">Logout</span>
</div>
</ng-template>
</div>
app.css
@import url('https://fonts.googleapis.com/icon?family=Material+Symbols+Outlined');
:host {
display: flex;
justify-content: center;
font-family: var(--inter-font);
--border-color: color-mix(in srgb, var(--full-contrast) 20%, var(--page-background));
}
[ngMenu] {
margin: 0;
width: 30rem;
padding: 0.25rem;
border-radius: 0.5rem;
border: 1px solid var(--border-color);
background-color: var(--page-background);
}
[ngMenu] [ngMenu] {
width: 15rem;
}
[ngMenu][data-visible='false'] {
display: none;
}
[ngMenuItem] {
outline: none;
display: flex;
cursor: pointer;
align-items: center;
gap: 0.5rem;
padding: 0.5rem;
font-size: 0.875rem;
border-radius: 0.25rem;
}
[ngMenuItem]:not([aria-disabled='true']):hover,
[ngMenuItem]:not([aria-disabled='true']):focus,
[ngMenuItem][aria-expanded='true'] {
background: color-mix(in srgb, var(--border-color) 10%, transparent);
}
[ngMenuItem]:focus {
outline: 2px solid var(--vivid-pink);
}
[ngMenuItem] .icon {
opacity: 0.875;
font-size: 1.25rem;
}
[ngMenuItem] .label {
flex: 1;
opacity: 0.875;
font-size: 0.875rem;
}
[ngMenuItem]:not([aria-expanded='true']) .arrow {
opacity: 0.5;
}
[ngMenu] .separator {
border-top: 1px solid var(--border-color);
margin: 0.25rem 0;
opacity: 0.25;
}
[ngMenu] .heading {
display: block;
font-weight: bold;
opacity: 0.6;
font-size: 0.75rem;
padding: 0.75rem;
letter-spacing: 0.05rem;
}
app.ts
import {Component, signal, viewChild} from '@angular/core';
import {Menu, MenuContent, MenuItem} from '@angular/aria/menu';
import {OverlayModule} from '@angular/cdk/overlay';
@Component({
selector: 'app-root',
templateUrl: 'app.html',
styleUrl: 'app.css',
imports: [Menu, MenuContent, MenuItem, OverlayModule],
})
export class App {
hasInteracted = signal(false);
updateMenu = viewChild<Menu<string>>('updateMenu');
}
app.html
<div
ngMenu
class="material-menu"
(mouseover)="hasInteracted.set(true)"
(focusin)="hasInteracted.set(true)"
>
<ng-template ngMenuContent>
<span id="security-label" class="heading">SECURITY</span>
<div role="group" aria-labelledby="security-label">
<div ngMenuItem value="Change password">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>lock_open</span
>
<span class="label">Change password</span>
</div>
<div ngMenuItem value="Two-factor authentication">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>security_key</span
>
<span class="label">Two-factor authentication</span>
</div>
<div ngMenuItem value="Reset" #resetItem [submenu]="updateMenu()">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>refresh</span
>
<span class="label">Reset</span>
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>arrow_right</span
>
</div>
<ng-template
[cdkConnectedOverlayOpen]="hasInteracted()"
[cdkConnectedOverlay]="{origin: resetItem, usePopover: 'inline'}"
[cdkConnectedOverlayPositions]="[
{originX: 'end', originY: 'top', overlayY: 'top', overlayX: 'start', offsetX: 6},
]"
cdkAttachPopoverAsChild
>
<div ngMenu #updateMenu="ngMenu">
<ng-template ngMenuContent>
<div ngMenuItem value="Email address">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>email</span
>
<span class="label">Email address</span>
</div>
<div ngMenuItem value="Phone number">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>phone</span
>
<span class="label">Phone number</span>
</div>
<div ngMenuItem value="Password">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>vpn_key</span
>
<span class="label">Password</span>
</div>
</ng-template>
</div>
</ng-template>
</div>
<div role="separator" class="separator"></div>
<span id="help-label" class="heading">HELP</span>
<div role="group" aria-labelledby="help-label">
<div ngMenuItem value="Support">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true">help</span>
<span class="label">Support</span>
</div>
<div ngMenuItem value="Feedback">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>feedback</span
>
<span class="label">Feedback</span>
</div>
</div>
<div role="separator" class="separator"></div>
<div ngMenuItem value="Logout">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true">logout</span>
<span class="label">Logout</span>
</div>
</ng-template>
</div>
app.css
@import url('https://fonts.googleapis.com/icon?family=Material+Symbols+Outlined');
:host {
display: flex;
justify-content: center;
font-family: var(--inter-font);
}
[ngMenu] {
gap: 3px;
width: 30rem;
padding: 1rem;
border-radius: 1rem;
display: flex;
flex-direction: column;
background-color: color-mix(in srgb, var(--vivid-pink) 5%, var(--page-background));
border: 1px solid color-mix(in srgb, var(--full-contrast) 10%, var(--page-background));
}
[ngMenu] [ngMenu] {
width: 15rem;
}
[ngMenu] .group {
padding: 0.25rem;
border-radius: 0.25rem;
background-color: var(--page-background);
box-shadow: 0 1px 2px 1px color-mix(in srgb, var(--primary-contrast) 25%, transparent);
}
[ngMenu] .group:first-of-type {
border-top-left-radius: 1rem;
border-top-right-radius: 1rem;
}
[ngMenu] .group:last-of-type {
border-bottom-left-radius: 1rem;
border-bottom-right-radius: 1rem;
}
[ngMenu][data-visible='false'] {
display: none;
}
[ngMenuItem] {
outline: none;
display: flex;
cursor: pointer;
align-items: center;
gap: 0.5rem;
padding: 0.75rem;
font-size: 0.875rem;
border-radius: 0.75rem;
}
[ngMenuItem]:not([aria-disabled='true']):hover,
[ngMenuItem]:not([aria-disabled='true']):focus,
[ngMenuItem][aria-expanded='true'] {
color: color-mix(in srgb, var(--vivid-pink) 70%, var(--primary-contrast));
background: color-mix(in srgb, var(--vivid-pink) 5%, transparent);
}
[ngMenuItem]:focus {
outline: 2px solid var(--vivid-pink);
}
[ngMenuItem] .icon {
opacity: 0.875;
font-size: 1.25rem;
}
[ngMenuItem] .label {
flex: 1;
opacity: 0.875;
font-size: 0.875rem;
}
[ngMenuItem]:not([aria-expanded='true']) .arrow {
opacity: 0.5;
}
[ngMenu] .separator {
border-top: 1px solid var(--quaternary-contrast);
margin: 0.25rem 0;
opacity: 0.25;
}
[ngMenu] .heading {
display: block;
font-weight: bold;
opacity: 0.6;
font-size: 0.75rem;
padding: 0.75rem;
letter-spacing: 0.05rem;
}
app.ts
import {Component, signal, viewChild} from '@angular/core';
import {Menu, MenuContent, MenuItem} from '@angular/aria/menu';
import {OverlayModule} from '@angular/cdk/overlay';
@Component({
selector: 'app-root',
templateUrl: 'app.html',
styleUrl: 'app.css',
imports: [Menu, MenuContent, MenuItem, OverlayModule],
})
export class App {
hasInteracted = signal(false);
updateMenu = viewChild<Menu<string>>('updateMenu');
}
app.html
<div
ngMenu
class="retro-menu"
(mouseover)="hasInteracted.set(true)"
(focusin)="hasInteracted.set(true)"
>
<ng-template ngMenuContent>
<span id="security-label" class="heading">SECURITY</span>
<div role="group" aria-labelledby="security-label">
<div ngMenuItem value="Change password">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>lock_open</span
>
<span class="label">Change password</span>
</div>
<div ngMenuItem value="Two-factor authentication">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>security_key</span
>
<span class="label">Two-factor authentication</span>
</div>
<div ngMenuItem value="Reset" #resetItem [submenu]="updateMenu()">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>refresh</span
>
<span class="label">Reset</span>
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>arrow_right</span
>
</div>
<ng-template
[cdkConnectedOverlayOpen]="hasInteracted()"
[cdkConnectedOverlay]="{origin: resetItem, usePopover: 'inline'}"
[cdkConnectedOverlayPositions]="[
{originX: 'end', originY: 'top', overlayY: 'top', overlayX: 'start', offsetX: 6},
]"
cdkAttachPopoverAsChild
>
<div ngMenu #updateMenu="ngMenu">
<ng-template ngMenuContent>
<div ngMenuItem value="Email address">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>email</span
>
<span class="label">Email address</span>
</div>
<div ngMenuItem value="Phone number">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>phone</span
>
<span class="label">Phone number</span>
</div>
<div ngMenuItem value="Password">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>vpn_key</span
>
<span class="label">Password</span>
</div>
</ng-template>
</div>
</ng-template>
</div>
<div role="separator" class="separator"></div>
<span id="help-label" class="heading">HELP</span>
<div role="group" aria-labelledby="help-label">
<div ngMenuItem value="Support">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true">help</span>
<span class="label">Support</span>
</div>
<div ngMenuItem value="Feedback">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>feedback</span
>
<span class="label">Feedback</span>
</div>
</div>
<div role="separator" class="separator"></div>
<div ngMenuItem value="Logout">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true">logout</span>
<span class="label">Logout</span>
</div>
</ng-template>
</div>
app.css
@import url('https://fonts.googleapis.com/css2?family=Press+Start+2P&display=swap');
@import url('https://fonts.googleapis.com/icon?family=Material+Symbols+Outlined');
:host {
display: flex;
justify-content: center;
font-size: 0.8rem;
--border-color: color-mix(in srgb, var(--full-contrast) 20%, var(--page-background));
font-family: 'Press Start 2P';
--retro-button-color: var(--vivid-pink);
--retro-shadow-light: color-mix(in srgb, #fff 20%, transparent);
--retro-shadow-dark: color-mix(in srgb, #000 20%, transparent);
--retro-elevated-shadow:
inset 4px 4px 0px 0px var(--retro-shadow-light),
inset -4px -4px 0px 0px var(--retro-shadow-dark), 4px 0px 0px 0px var(--tertiary-contrast),
0px 4px 0px 0px var(--tertiary-contrast), -4px 0px 0px 0px var(--tertiary-contrast),
0px -4px 0px 0px var(--tertiary-contrast);
--retro-flat-shadow:
4px 0px 0px 0px var(--tertiary-contrast), 0px 4px 0px 0px var(--tertiary-contrast),
-4px 0px 0px 0px var(--tertiary-contrast), 0px -4px 0px 0px var(--tertiary-contrast);
--retro-clickable-shadow:
inset 4px 4px 0px 0px var(--retro-shadow-light),
inset -4px -4px 0px 0px var(--retro-shadow-dark), 4px 0px 0px 0px var(--tertiary-contrast),
0px 4px 0px 0px var(--tertiary-contrast), -4px 0px 0px 0px var(--tertiary-contrast),
0px -4px 0px 0px var(--tertiary-contrast), 8px 8px 0px 0px var(--tertiary-contrast);
--retro-pressed-shadow:
inset 4px 4px 0px 0px var(--retro-shadow-dark),
inset -4px -4px 0px 0px var(--retro-shadow-light), 4px 0px 0px 0px var(--tertiary-contrast),
0px 4px 0px 0px var(--tertiary-contrast), -4px 0px 0px 0px var(--tertiary-contrast),
0px -4px 0px 0px var(--tertiary-contrast), 0px 0px 0px 0px var(--tertiary-contrast);
}
[ngMenu] {
margin-top: 8px;
width: 30rem;
padding: 0.25rem;
background-color: var(--page-background);
box-shadow: var(--retro-flat-shadow);
}
[ngMenu] [ngMenu] {
width: 15rem;
}
[ngMenu][data-visible='false'] {
display: none;
}
[ngMenuItem] {
outline: none;
display: flex;
cursor: pointer;
align-items: center;
gap: 0.5rem;
padding: 0.5rem;
font-size: 0.875rem;
}
[ngMenuItem]:not([aria-disabled='true']):hover,
[ngMenuItem]:not([aria-disabled='true']):focus,
[ngMenuItem][aria-expanded='true'] {
background: color-mix(in srgb, var(--border-color) 10%, transparent);
}
[ngMenuItem]:focus {
outline: 4px dashed var(--vivid-pink);
outline-offset: -4px;
}
[ngMenuItem] .icon {
opacity: 0.875;
font-size: 1.25rem;
}
[ngMenuItem] .label {
flex: 1;
opacity: 0.875;
font-size: 0.875rem;
}
[ngMenuItem]:not([aria-expanded='true']) .arrow {
opacity: 0.5;
}
[ngMenu] .separator {
border-top: 4px solid #000;
margin: 0.25rem 0;
opacity: 0.25;
}
[ngMenu] .heading {
display: block;
font-weight: bold;
opacity: 0.6;
font-size: 0.75rem;
padding: 0.75rem;
letter-spacing: 0.05rem;
}
Bağımsız menüler her zaman görünen eylem listeleri veya navigasyon için iyi çalışır.
Devre dışı menü öğeleri
disabled girişini kullanarak belirli menü öğelerini devre dışı bırakın. softDisabled ile odak davranışını kontrol edin.
app.ts
import {Component, viewChild} from '@angular/core';
import {Menu, MenuContent, MenuItem, MenuTrigger} from '@angular/aria/menu';
import {OverlayModule} from '@angular/cdk/overlay';
@Component({
selector: 'app-root',
templateUrl: 'app.html',
styleUrl: 'app.css',
imports: [Menu, MenuContent, MenuItem, MenuTrigger, OverlayModule],
})
export class App {
formatMenu = viewChild<Menu<string>>('formatMenu');
categorizeMenu = viewChild<Menu<string>>('categorizeMenu');
}
app.html
<button ngMenuTrigger #origin #trigger="ngMenuTrigger" [menu]="formatMenu()">Open Menu</button>
<ng-template
[cdkConnectedOverlayOpen]="trigger.expanded()"
[cdkConnectedOverlay]="{origin, usePopover: 'inline'}"
[cdkConnectedOverlayPositions]="[
{originX: 'start', originY: 'bottom', overlayX: 'start', overlayY: 'top', offsetY: 4},
]"
cdkAttachPopoverAsChild
>
<div ngMenu class="menu" #formatMenu="ngMenu">
<ng-template ngMenuContent>
<div ngMenuItem value="Mark as read">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>mark_email_read</span
>
<span class="label">Mark as read</span>
</div>
<div ngMenuItem value="Snooze" [disabled]="true">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true">snooze</span>
<span class="label">Snooze</span>
</div>
<div role="separator" aria-orientation="horizontal" class="separator"></div>
<div
ngMenuItem
class="menu-item"
value="Categorize"
#categorizeItem
[submenu]="categorizeMenu()"
[disabled]="true"
>
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>category</span
>
<span class="label">Categorize</span>
<span class="icon material-symbols-outlined arrow" translate="no" aria-hidden="true"
>arrow_right</span
>
</div>
<ng-template
[cdkConnectedOverlayOpen]="formatMenu.visible()"
[cdkConnectedOverlay]="{origin: categorizeItem, usePopover: 'inline'}"
[cdkConnectedOverlayPositions]="[
{originX: 'end', originY: 'top', overlayY: 'top', overlayX: 'start', offsetX: 6},
]"
cdkAttachPopoverAsChild
>
<div ngMenu class="menu" #categorizeMenu="ngMenu">
<ng-template ngMenuContent>
<div ngMenuItem value="Mark as important">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>label_important</span
>
<span class="label">Mark as important</span>
</div>
<div ngMenuItem value="Star">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>star</span
>
<span class="label">Star</span>
</div>
<div ngMenuItem value="Label">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>label</span
>
<span class="label">Label</span>
</div>
</ng-template>
</div>
</ng-template>
<div role="separator" aria-orientation="horizontal" class="separator"></div>
<div ngMenuItem value="Archive">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>archive</span
>
<span class="label">Archive</span>
</div>
<div ngMenuItem value="Report spam">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true">report</span>
<span class="label">Report spam</span>
</div>
<div ngMenuItem value="Delete">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true">delete</span>
<span class="label">Delete</span>
</div>
</ng-template>
</div>
</ng-template>
app.css
@import url('https://fonts.googleapis.com/icon?family=Material+Symbols+Outlined');
:host {
display: flex;
justify-content: center;
font-family: var(--inter-font);
--border-color: color-mix(in srgb, var(--full-contrast) 20%, var(--page-background));
}
[ngMenuTrigger] {
display: flex;
cursor: pointer;
align-items: center;
padding: 0.6rem 2rem;
border-radius: 0.5rem;
color: var(--primary-contrast);
border: 1px solid var(--border-color);
background-color: var(--page-background);
}
[ngMenuTrigger] .icon {
font-size: 1.5rem;
opacity: 0.875;
}
[ngMenu] {
margin: 0;
width: 15rem;
padding: 0.25rem;
border-radius: 0.5rem;
border: 1px solid var(--border-color);
background-color: var(--page-background);
}
[ngMenu][data-visible='false'] {
display: none;
}
[ngMenuItem] {
outline: none;
display: flex;
cursor: pointer;
align-items: center;
gap: 0.5rem;
padding: 0.5rem;
font-size: 0.875rem;
border-radius: 0.25rem;
}
[ngMenuItem][aria-disabled='true'] {
opacity: 0.5;
cursor: default;
}
[ngMenuTrigger]:not([disabled]):hover,
[ngMenuTrigger][aria-expanded='true'],
[ngMenuItem]:not([aria-disabled='true']):hover,
[ngMenuItem]:not([aria-disabled='true']):focus,
[ngMenuItem][aria-expanded='true'] {
background: color-mix(in srgb, var(--border-color) 10%, transparent);
}
[ngMenuItem]:focus,
[ngMenuTrigger]:focus {
outline: 2px solid var(--vivid-pink);
}
[ngMenuItem] .icon {
opacity: 0.875;
font-size: 1.25rem;
}
[ngMenuItem] .label {
flex: 1;
opacity: 0.875;
font-size: 0.875rem;
}
[ngMenuItem]:not([aria-expanded='true']) .arrow {
opacity: 0.5;
}
[ngMenu] .separator {
border-top: 1px solid var(--border-color);
margin: 0.25rem 0;
opacity: 0.25;
}
app.ts
import {Component, viewChild} from '@angular/core';
import {Menu, MenuContent, MenuItem, MenuTrigger} from '@angular/aria/menu';
import {OverlayModule} from '@angular/cdk/overlay';
@Component({
selector: 'app-root',
templateUrl: 'app.html',
styleUrl: 'app.css',
imports: [Menu, MenuContent, MenuItem, MenuTrigger, OverlayModule],
})
export class App {
formatMenu = viewChild<Menu<string>>('formatMenu');
categorizeMenu = viewChild<Menu<string>>('categorizeMenu');
}
app.html
<button ngMenuTrigger #origin #trigger="ngMenuTrigger" [menu]="formatMenu()">Open Menu</button>
<ng-template
[cdkConnectedOverlayOpen]="trigger.expanded()"
[cdkConnectedOverlay]="{origin, usePopover: 'inline'}"
[cdkConnectedOverlayPositions]="[
{originX: 'start', originY: 'bottom', overlayX: 'start', overlayY: 'top', offsetY: 4},
]"
cdkAttachPopoverAsChild
>
<div ngMenu class="menu" #formatMenu="ngMenu">
<ng-template ngMenuContent>
<div class="group">
<div ngMenuItem value="Mark as read">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>mark_email_read</span
>
<span class="label">Mark as read</span>
</div>
<div ngMenuItem value="Snooze">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>snooze</span
>
<span class="label">Snooze</span>
</div>
<div ngMenuItem value="Delete" [disabled]="true">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>delete</span
>
<span class="label">Delete</span>
</div>
</div>
<div class="group">
<div
ngMenuItem
class="menu-item"
value="Categorize"
#categorizeItem
[submenu]="categorizeMenu()"
>
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>category</span
>
<span class="label">Categorize</span>
<span class="icon material-symbols-outlined arrow" translate="no" aria-hidden="true"
>arrow_right</span
>
</div>
</div>
<ng-template
[cdkConnectedOverlayOpen]="formatMenu.visible()"
[cdkConnectedOverlay]="{origin: categorizeItem, usePopover: 'inline'}"
[cdkConnectedOverlayPositions]="[
{originX: 'end', originY: 'top', overlayY: 'top', overlayX: 'start', offsetX: 6},
]"
cdkAttachPopoverAsChild
>
<div ngMenu class="menu" #categorizeMenu="ngMenu">
<ng-template ngMenuContent>
<div class="group">
<div ngMenuItem value="Mark as important">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>label_important</span
>
<span class="label">Mark as important</span>
</div>
<div ngMenuItem value="Star">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>star</span
>
<span class="label">Star</span>
</div>
<div ngMenuItem value="Label" [disabled]="true">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>label</span
>
<span class="label">Label</span>
</div>
</div>
</ng-template>
</div>
</ng-template>
</ng-template>
</div>
</ng-template>
app.css
@import url('https://fonts.googleapis.com/icon?family=Material+Symbols+Outlined');
:host {
display: flex;
justify-content: center;
font-family: var(--inter-font);
}
[ngMenuTrigger] {
display: flex;
cursor: pointer;
align-items: center;
padding: 0.6rem 2rem;
border-radius: 0.5rem;
border: 1px solid transparent;
background-color: color-mix(in srgb, var(--vivid-pink) 5%, transparent);
color: color-mix(in srgb, var(--vivid-pink) 70%, var(--primary-contrast));
}
[ngMenuTrigger] .icon {
font-size: 1.5rem;
opacity: 0.875;
}
[ngMenu] {
gap: 3px;
width: 15rem;
display: flex;
flex-direction: column;
}
[ngMenu] .group {
padding: 0.25rem;
border-radius: 0.25rem;
background-color: var(--page-background);
box-shadow: 0 1px 2px 1px color-mix(in srgb, var(--primary-contrast) 25%, transparent);
}
[ngMenu] .group:first-of-type {
border-top-left-radius: 1rem;
border-top-right-radius: 1rem;
}
[ngMenu] .group:last-of-type {
border-bottom-left-radius: 1rem;
border-bottom-right-radius: 1rem;
}
[ngMenu][data-visible='false'] {
display: none;
}
[ngMenuItem] {
outline: none;
display: flex;
cursor: pointer;
align-items: center;
gap: 0.5rem;
padding: 0.75rem;
font-size: 0.875rem;
border-radius: 0.75rem;
}
[ngMenuTrigger]:hover,
[ngMenuTrigger][aria-expanded='true'] {
background: color-mix(in srgb, var(--vivid-pink) 10%, transparent);
}
[ngMenuItem]:not([aria-disabled='true']):hover,
[ngMenuItem]:not([aria-disabled='true']):focus,
[ngMenuItem][aria-expanded='true'] {
color: color-mix(in srgb, var(--vivid-pink) 70%, var(--primary-contrast));
background: color-mix(in srgb, var(--vivid-pink) 5%, transparent);
}
[ngMenuItem]:focus,
[ngMenuTrigger]:focus {
outline: 2px solid var(--vivid-pink);
}
[ngMenuItem] .icon {
opacity: 0.875;
font-size: 1.25rem;
}
[ngMenuItem] .label {
flex: 1;
opacity: 0.875;
font-size: 0.875rem;
}
[ngMenuItem]:not([aria-expanded='true']) .arrow {
opacity: 0.5;
}
[ngMenu] .separator {
border-top: 1px solid var(--quaternary-contrast);
margin: 0.25rem 0;
opacity: 0.25;
}
[ngMenuItem][aria-disabled='true'] {
opacity: 0.5;
cursor: default;
}
app.ts
import {Component, viewChild} from '@angular/core';
import {Menu, MenuContent, MenuItem, MenuTrigger} from '@angular/aria/menu';
import {OverlayModule} from '@angular/cdk/overlay';
@Component({
selector: 'app-root',
templateUrl: 'app.html',
styleUrl: 'app.css',
imports: [Menu, MenuContent, MenuItem, MenuTrigger, OverlayModule],
})
export class App {
formatMenu = viewChild<Menu<string>>('formatMenu');
categorizeMenu = viewChild<Menu<string>>('categorizeMenu');
}
app.html
<button class="retro-trigger" ngMenuTrigger #trigger="ngMenuTrigger" #origin [menu]="formatMenu()">
Open Menu
</button>
<ng-template
[cdkConnectedOverlayOpen]="trigger.expanded()"
[cdkConnectedOverlay]="{origin, usePopover: 'inline'}"
[cdkConnectedOverlayPositions]="[
{originX: 'start', originY: 'bottom', overlayX: 'start', overlayY: 'top', offsetY: 4},
]"
cdkAttachPopoverAsChild
>
<div ngMenu class="menu" #formatMenu="ngMenu">
<ng-template ngMenuContent>
<div ngMenuItem value="Mark as read" [disabled]="true">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>mark_email_read</span
>
<span class="label">Mark as read</span>
</div>
<div ngMenuItem value="Snooze">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true">snooze</span>
<span class="label">Snooze</span>
</div>
<div role="separator" aria-orientation="horizontal" class="separator"></div>
<div
ngMenuItem
class="menu-item"
value="Categorize"
#categorizeItem
[submenu]="categorizeMenu()"
>
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>category</span
>
<span class="label">Categorize</span>
<span class="icon material-symbols-outlined arrow" translate="no" aria-hidden="true"
>arrow_right</span
>
</div>
<ng-template
[cdkConnectedOverlayOpen]="formatMenu.visible()"
[cdkConnectedOverlay]="{origin: categorizeItem, usePopover: 'inline'}"
[cdkConnectedOverlayPositions]="[
{originX: 'end', originY: 'top', overlayY: 'top', overlayX: 'start', offsetX: 6},
]"
cdkAttachPopoverAsChild
>
<div ngMenu class="menu" #categorizeMenu="ngMenu">
<ng-template ngMenuContent>
<div ngMenuItem value="Mark as important">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>label_important</span
>
<span class="label">Mark as important</span>
</div>
<div ngMenuItem value="Star">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>star</span
>
<span class="label">Star</span>
</div>
<div ngMenuItem value="Label">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>label</span
>
<span class="label">Label</span>
</div>
</ng-template>
</div>
</ng-template>
<div role="separator" aria-orientation="horizontal" class="separator"></div>
<div ngMenuItem value="Archive">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
>archive</span
>
<span class="label">Archive</span>
</div>
<div ngMenuItem value="Report spam">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true">report</span>
<span class="label">Report spam</span>
</div>
<div ngMenuItem value="Delete">
<span class="icon material-symbols-outlined" translate="no" aria-hidden="true">delete</span>
<span class="label">Delete</span>
</div>
</ng-template>
</div>
</ng-template>
app.css
@import url('https://fonts.googleapis.com/css2?family=Press+Start+2P&display=swap');
@import url('https://fonts.googleapis.com/icon?family=Material+Symbols+Outlined');
:host {
display: flex;
justify-content: center;
font-size: 0.8rem;
--border-color: color-mix(in srgb, var(--full-contrast) 20%, var(--page-background));
font-family: 'Press Start 2P';
--retro-button-color: var(--vivid-pink);
--retro-shadow-light: color-mix(in srgb, #fff 20%, transparent);
--retro-shadow-dark: color-mix(in srgb, #000 20%, transparent);
--retro-elevated-shadow:
inset 4px 4px 0px 0px var(--retro-shadow-light),
inset -4px -4px 0px 0px var(--retro-shadow-dark), 4px 0px 0px 0px var(--tertiary-contrast),
0px 4px 0px 0px var(--tertiary-contrast), -4px 0px 0px 0px var(--tertiary-contrast),
0px -4px 0px 0px var(--tertiary-contrast);
--retro-flat-shadow:
4px 0px 0px 0px var(--tertiary-contrast), 0px 4px 0px 0px var(--tertiary-contrast),
-4px 0px 0px 0px var(--tertiary-contrast), 0px -4px 0px 0px var(--tertiary-contrast);
--retro-clickable-shadow:
inset 4px 4px 0px 0px var(--retro-shadow-light),
inset -4px -4px 0px 0px var(--retro-shadow-dark), 4px 0px 0px 0px var(--tertiary-contrast),
0px 4px 0px 0px var(--tertiary-contrast), -4px 0px 0px 0px var(--tertiary-contrast),
0px -4px 0px 0px var(--tertiary-contrast), 8px 8px 0px 0px var(--tertiary-contrast);
--retro-pressed-shadow:
inset 4px 4px 0px 0px var(--retro-shadow-dark),
inset -4px -4px 0px 0px var(--retro-shadow-light), 4px 0px 0px 0px var(--tertiary-contrast),
0px 4px 0px 0px var(--tertiary-contrast), -4px 0px 0px 0px var(--tertiary-contrast),
0px -4px 0px 0px var(--tertiary-contrast), 0px 0px 0px 0px var(--tertiary-contrast);
}
[ngMenuTrigger] {
display: flex;
cursor: pointer;
align-items: center;
padding: 0.6rem 2rem;
font-family: 'Press Start 2P';
color: #000;
background-color: var(--vivid-pink);
box-shadow: var(--retro-clickable-shadow);
transition:
transform 0.1s,
box-shadow 0.1s;
}
[ngMenuTrigger]:hover {
transform: translate(1px, 1px);
}
[ngMenuTrigger]:active {
background-color: color-mix(in srgb, var(--vivid-pink) 80%, #fff);
box-shadow: var(--retro-pressed-shadow);
transform: translate(4px, 4px);
}
[ngMenuTrigger] .icon {
font-size: 1.5rem;
opacity: 0.875;
}
[ngMenu] {
margin-top: 8px;
width: 15rem;
padding: 0.25rem;
background-color: var(--page-background);
box-shadow: var(--retro-flat-shadow);
}
[ngMenu][data-visible='false'] {
display: none;
}
[ngMenuItem] {
outline: none;
display: flex;
cursor: pointer;
align-items: center;
gap: 0.5rem;
padding: 0.5rem;
font-size: 0.875rem;
}
[ngMenuItem]:not([aria-disabled='true']):hover,
[ngMenuItem]:not([aria-disabled='true']):focus,
[ngMenuItem][aria-expanded='true'] {
background: color-mix(in srgb, var(--border-color) 10%, transparent);
}
[ngMenuTrigger]:focus {
outline: 4px dashed var(--vivid-pink);
outline-offset: 8px;
}
[ngMenuItem]:focus {
outline: 4px dashed var(--vivid-pink);
outline-offset: -4px;
}
[ngMenuItem] .icon {
opacity: 0.875;
font-size: 1.25rem;
}
[ngMenuItem] .label {
flex: 1;
opacity: 0.875;
font-size: 0.875rem;
}
[ngMenuItem]:not([aria-expanded='true']) .arrow {
opacity: 0.5;
}
[ngMenu] .separator {
border-top: 4px solid #000;
margin: 0.25rem 0;
opacity: 0.25;
}
[ngMenuItem][aria-disabled='true'] {
opacity: 0.5;
cursor: default;
}
[softDisabled]="true" olduğunda, devre dışı öğeler odak alabilir ancak etkinleştirilemez. [softDisabled]="false" olduğunda, devre dışı öğeler klavye navigasyonu sırasında atlanır.
Test Etme
Angular Aria, menü bileşenlerini test etmek için bileşen harness'leri sağlar. Harness'lerin bir bileşen testinde nasıl kullanılacağına dair bir örnek:
import {ComponentFixture, TestBed} from '@angular/core/testing';
import {HarnessLoader} from '@angular/cdk/testing';
import {TestbedHarnessEnvironment} from '@angular/cdk/testing/testbed';
import {MenuHarness, MenuItemHarness} from '@angular/aria/menu/testing';
import {MyMenuComponent} from './my-menu'; // Bileşeniniz
describe('MyMenuComponent', () => {
let fixture: ComponentFixture<MyMenuComponent>;
let loader: HarnessLoader;
beforeEach(async () => {
TestBed.configureTestingModule({
imports: [MyMenuComponent],
});
fixture = TestBed.createComponent(MyMenuComponent);
await fixture.whenStable();
loader = TestbedHarnessEnvironment.loader(fixture);
});
it('should open menu and click item', async () => {
// Menü harness'ini tetikleyici metnine göre yükleyin
const menu = await loader.getHarness(MenuHarness.with({triggerText: 'Open Menu'}));
// Başlangıç durumunu doğrulayın
expect(await menu.isOpen()).toBe(false);
// Menüyü açın
await menu.open();
expect(await menu.isOpen()).toBe(true);
// Öğeleri alın
const items = await menu.getItems();
expect(items.length).toBe(3);
expect(await items[0].getText()).toBe('Item 1');
// İlk öğeye tıklayın
await items[0].click();
// Menü, seçimden sonra kapanmalıdır (uygulamanıza bağlı olarak)
expect(await menu.isOpen()).toBe(false);
});
it('should interact with submenus', async () => {
const menu = await loader.getHarness(MenuHarness.with({triggerText: 'Open Menu'}));
await menu.open();
// Bir alt menüyü tetikleyen öğeyi alın
const subItem = await loader.getHarness(MenuItemHarness.with({text: 'Submenu'}));
expect(await subItem.hasSubmenu()).toBe(true);
// Alt menüyü açın
await subItem.click();
const submenu = await subItem.getSubmenu();
expect(submenu).toBeTruthy();
expect(await submenu!.isOpen()).toBe(true);
// Alt menü öğeleriyle etkileşim kurun
const subItems = await submenu!.getItems();
expect(subItems.length).toBe(1);
});
});
API'ler
Menu
Menü öğeleri için kapsayıcı yönerge.
Girişler
| Property | Type | Default | Description |
|---|---|---|---|
disabled |
boolean |
false |
Menüdeki tüm öğeleri devre dışı bırakır |
wrap |
boolean |
true |
Klavye navigasyonunun kenarlarda sarılıp sarılmadığı |
softDisabled |
boolean |
true |
true olduğunda, devre dışı öğeler odaklanabilir ancak etkileşimli değildir |
Yöntemler
| Method | Parameters | Description |
|---|---|---|
close |
none | Menüyü kapatır |
MenuBar
Birden fazla menü için yatay kapsayıcı.
Girişler
| Property | Type | Default | Description |
|---|---|---|---|
disabled |
boolean |
false |
Tüm menü çubuğunu devre dışı bırakır |
wrap |
boolean |
true |
Klavye navigasyonunun kenarlarda sarılıp sarılmadığı |
softDisabled |
boolean |
true |
true olduğunda, devre dışı öğeler odaklanabilir ancak etkileşimli değildir |
MenuItem
Bir menü içindeki bireysel bir öğe.
Girişler
| Property | Type | Default | Description |
|---|---|---|---|
value |
any |
— | Zorunlu. Bu öğenin değeri |
role |
'menuitem' | 'menuitemcheckbox' | 'menuitemradio' |
'menuitem' |
Menü öğesi için ARIA rolü |
disabled |
boolean |
false |
Bu menü öğesini devre dışı bırakır |
submenu |
Menu |
— | Bir alt menüye referans |
searchTerm |
string |
'' |
Yazarak arama için arama terimi (iki yönlü bağlama destekler) |
Sinyaller
| Property | Type | Description |
|---|---|---|
active |
Signal<boolean> |
Öğenin şu anda odakta olup olmadığı |
expanded |
Signal<boolean> |
Alt menünün genişletilmiş olup olmadığı |
hasPopup |
Signal<boolean> |
Öğenin ilişkili bir alt menüsü olup olmadığı |
NOTE: MenuItem genel yöntemler sunmaz. Alt menüleri menü öğeleriyle ilişkilendirmek için submenu girişini kullanın.
MenuTrigger
Bir menüyü açan buton veya eleman.
Girişler
| Property | Type | Default | Description |
|---|---|---|---|
menu |
Menu |
— | Zorunlu. Tetiklenecek menü |
disabled |
boolean |
false |
Tetikleyiciyi devre dışı bırakır |
softDisabled |
boolean |
true |
true olduğunda, devre dışı tetikleyici odaklanabilir |
Sinyaller
| Property | Type | Description |
|---|---|---|
expanded |
Signal<boolean> |
Menünün şu anda açık olup olmadığı |
hasPopup |
Signal<boolean> |
Tetikleyicinin ilişkili bir menüsü olup olmadığı |
Yöntemler
| Method | Parameters | Description |
|---|---|---|
open |
none | Menüyü açar |
close |
none | Menüyü kapatır |
toggle |
none | Menüyü aç/kapa durumunu değiştirir |