Skip to content

Commit 4f1238f

Browse files
authored
docs(cdk-experimental/menu): add initial documentation for cdk menu directives (#20412)
1 parent de98466 commit 4f1238f

File tree

2 files changed

+216
-0
lines changed

2 files changed

+216
-0
lines changed

src/cdk-experimental/menu/menu.md

Lines changed: 216 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,216 @@
1+
The `@angular/cdk-experimental/menu` module provides directives to help create custom menu
2+
interactions based on the [WAI ARIA specification][aria].
3+
4+
By using `@angular/cdk-experimental/menu` you get all of the expected behaviors for an accessible
5+
experience, including bidi layout support, keyboard interaction, and focus management. All
6+
directives apply their associated ARIA roles to their host element.
7+
8+
### Supported ARIA Roles
9+
10+
The directives in `@angular/cdk-experimental/menu` set the appropriate roles on their host element.
11+
12+
| Directive | ARIA Role |
13+
| ------------------- | ---------------- |
14+
| CdkMenuBar | menubar |
15+
| CdkMenu | menu |
16+
| CdkMenuGroup | group |
17+
| CdkMenuItem | menuitem |
18+
| CdkMenuItemRadio | menuitemradio |
19+
| CdkMenuItemCheckbox | menuitemcheckbox |
20+
21+
### Getting started
22+
23+
Import the `CdkMenuModule` into the `NgModule` in which you want to create menus. You can then apply
24+
menu directives to build your custom menu. A typical menu consists of the following directives:
25+
26+
- `cdkMenuTriggerFor` - links a trigger button to a menu you intend to open
27+
- `cdkMenuPanel` - wraps the menu and provides a link between the `cdkMenuTriggerFor` and the
28+
`cdkMenu`
29+
- `cdkMenu` - the actual menu you want to open
30+
- `cdkMenuItem` - added to each button
31+
32+
<!-- TODO basic standalone menu example (like mat-menu has) -->
33+
34+
Most menu interactions consist of two parts: a trigger and a menu panel.
35+
36+
#### Triggers
37+
38+
You must add the `cdkMenuItem` and `cdkMenuTriggerFor` directives to triggers like so,
39+
40+
```html
41+
<button cdkMenuItem [cdkMenuTriggerFor]="menu">Click me!</button>
42+
```
43+
44+
Adding `cdkMenuItem` gives you keyboard navigation and focus management. Associating a trigger with
45+
a menu is done through the `cdkMenuTriggerFor` directive and you must provide a template reference
46+
variable to it. Once both of these directives are set, you can toggle the associated menu
47+
programmatically, using a mouse or using a keyboard.
48+
49+
#### Menu panels
50+
51+
You must wrap pop-up menus with an `ng-template` with the `cdkMenuPanel` directive and a reference
52+
variable which must be of type `cdkMenuPanel`. Further, the `cdkMenu` must also reference the
53+
`cdkMenuPanel`.
54+
55+
```html
56+
<ng-template cdkMenuPanel #panel="cdkMenuPanel">
57+
<div cdkMenu [cdkMenuPanel]="panel">
58+
<!-- some content -->
59+
</div>
60+
</ng-template>
61+
```
62+
63+
Note that Angular CDK provides no styles; you must add styles as part of building your custom menu.
64+
65+
### Menu Bars
66+
67+
The `CdkMenuBar` directive follows the [ARIA menubar][menubar] spec and behaves similar to a desktop
68+
app menubar. It consists of at least one `CdkMenuItem` which triggers a submenu. A menubar can be
69+
layed out horizontally or vertically (defaulting to horizontal). If the layout changes, you must set
70+
the `orientation` attribute to match in order for the keyboard navigation to work properly and for
71+
menus to open up in the correct location.
72+
73+
<!-- TODO basic menubar example (google docs?) -->
74+
75+
### Context Menus
76+
77+
A context menu opens when a user right-clicks within some container element. You can mark a
78+
container element with the `cdkContextMenuTriggerFor`, which behaves like `cdkMenuTriggerFor` except
79+
that it responds to the browser's native `contextmenu` event. Custom context menus appear next to
80+
the cursor, similarly to native context menus.
81+
82+
<!-- TODO basic context menu example -->
83+
84+
You can nest context menu container elements. Upon right-click, the menu associated with the closest
85+
container element will open.
86+
87+
```html
88+
<div [cdkContextMenuTriggerFor]="outer">
89+
My outer context
90+
<div [cdkContextMenuTriggerFor]="inner">My inner context</div>
91+
</div>
92+
```
93+
94+
In the example above, right clicking on "My inner context" will open up the "inner" menu and right
95+
clicking inside "My outer context" will open up the "outer" menu.
96+
97+
### Inline Menus
98+
99+
An _inline menu_ is a menu that lives directly on the page rather than a pop-up associated with a
100+
trigger. You can use an inline menu when you want a persistent menu interaction on a page. Menu
101+
items within an inline menus are logically grouped together and you can navigate through them using
102+
your keyboard.
103+
104+
<!-- TODO inline menu example (gmail buttons?) -->
105+
106+
### Menu Items
107+
108+
Both menu and menubar elements should exclusively contain menuitem elements. This directive allows
109+
the items to be navigated to via keyboard interaction.
110+
111+
A menuitem by itself can provide some user defined action by hooking into the `cdkMenuItemTriggered`
112+
output. An example may be a close button which performs some closing logic.
113+
114+
```html
115+
<ng-template cdkMenuPanel #panel="cdkMenuPanel">
116+
<div cdkMenu [cdkMenuPanel]="panel">
117+
<button cdkMenuItem (cdkMenuItemTriggered)="closeApp()">Close</button>
118+
</div>
119+
</ng-template>
120+
```
121+
122+
You can create nested menus by using a menuitem as the trigger for another menu.
123+
124+
```html
125+
<ng-template cdkMenuPanel #panel="cdkMenuPanel">
126+
<div cdkMenu [cdkMenuPanel]="panel">
127+
<button cdkMenuItem [cdkMenuTriggerFor]="submenu">Open Submenu</button>
128+
</div>
129+
</ng-template>
130+
```
131+
132+
A menuitem also has two sub-types, neither of which should trigger a menu: CdkMenuItemCheckbox and
133+
CdkMenuItemRadio
134+
135+
#### Menu Item Checkboxes
136+
137+
A `cdkMenuItemCheckbox` is a special type of menuitem that behaves as a checkbox. You can use this
138+
type of menuitem to toggle items on and off. An element with the `cdkMenuItemCheckbox` directive
139+
does not need the additional `cdkMenuItem` directive.
140+
141+
#### Menu Item Radios
142+
143+
A `cdkMenuItemRadio` is a special type of menuitem that behaves as a radio button. You can use this
144+
type of menuitem for menus with exclusively selectable items. An element with the `cdkMenuItemRadio`
145+
directive does not need the additional `cdkMenuItem` directive.
146+
147+
#### Groups
148+
149+
By default `cdkMenu` acts as a group for `cdkMenuItemRadio` elements. Elements with
150+
`cdkMenuItemRadio` added as children of a `cdkMenu` will be logically grouped and only a single item
151+
can have the checked state.
152+
153+
If you would like to have unrelated groups of radio buttons within a single menu you should use the
154+
`cdkMenuGroup` directive.
155+
156+
```html
157+
<ng-template cdkMenuPanel #panel="cdkMenuPanel">
158+
<div cdkMenu [cdkMenuPanel]="panel">
159+
<!-- Font size -->
160+
<div cdkMenuGroup>
161+
<button cdkMenuItemRadio>Small</button>
162+
<button cdkMenuItemRadio>Medium</button>
163+
<button cdkMenuItemRadio>Large</button>
164+
</div>
165+
<hr />
166+
<!-- Paragraph alignment -->
167+
<div cdkMenuGroup>
168+
<button cdkMenuItemRadio>Left</button>
169+
<button cdkMenuItemRadio>Center</button>
170+
<button cdkMenuItemRadio>Right</button>
171+
</div>
172+
</div>
173+
</ng-template>
174+
```
175+
176+
Note however that when the menu is closed and reopened any state is lost. You must subscribe to the
177+
groups `change` output, or to `cdkMenuItemToggled` on each radio item and track changes your self.
178+
Finally, you can provide state for each item using the `checked` attribute.
179+
180+
<!-- TODO standalone menu example with checkboxes and grouped radios -->
181+
182+
### Smart Menu Aim
183+
184+
`@angular/cdk-experimental/menu` intelligently predicts when a user intends to navigate to an open
185+
submenu and prevent premature closeouts. This functionality prevents users from having to hunt
186+
through the open menus in a maze-like fashion to reach their destination.
187+
188+
![menu aim diagram][diagram]
189+
190+
As demonstrated in the diagram above we first track the user's mouse movements within a menu. Next,
191+
when a user mouses into a sibling menu item (e.g. Share button) the sibling item asks the Menu Aim
192+
service if it can perform its close actions. In order to determine if the current submenu can be
193+
closed out, the Menu Aim service calculates the slope between a selected target coordinate in the
194+
submenu and the previous mouse point, and the slope between the target and the current mouse point.
195+
If the slope of the current mouse point is greater than the slope of the previous that means the
196+
user is moving towards the submenu and we shouldn't close out. Users however may sometimes stop
197+
short in a sibling item after moving towards the submenu. The service is intelligent enough the
198+
detect this intention and will trigger the next menu.
199+
200+
### Accessibility
201+
202+
The set of directives defined in `@angular/cdk-experimental/menu` follow accessibility best
203+
practices as defined in the [ARIA spec][menubar]. Specifically, the menus are aware of left-to-right
204+
and right-to-left layouts and opened appropriately. You should however add any necessary CSS styles.
205+
Menu items should always have meaningful labels, whether through text content, `aria-label`, or
206+
`aria-labelledby`. Finally, keyboard interaction is supported as defined in the [ARIA menubar
207+
keyboard interaction spec][keyboard].
208+
209+
<!-- links -->
210+
211+
[aria]: https://www.w3.org/TR/wai-aria-1.1/ 'ARIA Spec'
212+
[menubar]: https://www.w3.org/TR/wai-aria-practices-1.1/#menu 'ARIA Menubar Pattern'
213+
[keyboard]:
214+
https://www.w3.org/TR/wai-aria-practices-1.1/#keyboard-interaction-12
215+
'ARIA Menubar Keyboard Interaction'
216+
[diagram]: menuaim.png 'Menu Aim Diagram'

src/cdk-experimental/menu/menuaim.png

62.6 KB
Loading

0 commit comments

Comments
 (0)