Skip to content

Commit 5c16dc3

Browse files
committed
docs(cdk-experimental/menu): add initial documentation for cdk menu directives
1 parent fe14d88 commit 5c16dc3

File tree

2 files changed

+240
-0
lines changed

2 files changed

+240
-0
lines changed

src/cdk-experimental/menu/menu.md

Lines changed: 240 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,240 @@
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 following the ARIA
5+
best practices including consideration for left-to-right and right-to-left layouts, and accessible
6+
interaction. Further, all directives apply their associated aria roles to the component they are
7+
added to.
8+
9+
### Supported Aria Roles
10+
11+
The directives provided by `@angular/cdk-experimental/menu` automatically provide the related roles
12+
for each component they are placed on.
13+
14+
| Directive | ARIA Role |
15+
| ------------------- | ---------------- |
16+
| CdkMenuBar | menubar |
17+
| CdkMenu | menu |
18+
| CdkMenuGroup | group |
19+
| CdkMenuItem | menuitem |
20+
| CdkMenuItemRadio | menuitemradio |
21+
| CdkMenuItemCheckbox | menuitemcheckbox |
22+
23+
### Getting started
24+
25+
Start by importing the `CdkMenuModule` into the `NgModule` where you want to create menus. You can
26+
now add the various directives in order to build out your menu. A basic standalone menu consists of
27+
the following directives:
28+
29+
- `cdkMenuItem` - added to the button itself
30+
- `cdkMenuTriggerFor` - links a trigger button to a menu you intend to open
31+
- `cdkMenuPanel` - wraps the menu and provides a link between the `cdkMenuTriggerFor` and the
32+
`cdkMenu`
33+
- `cdkMenu` - the actual menu you want to open
34+
35+
<!-- TODO basic standalone menu example (like mat-menu has) -->
36+
37+
Most menu interactions consist of two parts: a trigger and a menu panel.
38+
39+
#### Triggers
40+
41+
A triggering component must have the `cdkMenuItem` and `cdkMenuTriggerFor` directives like so,
42+
43+
```html
44+
<button cdkMenuItem [cdkMenuTriggerFor]="menu">Click me!</button>
45+
```
46+
47+
Adding `cdkMenuItem` allows for keyboard navigation and focus management. Associating a trigger with
48+
a menu is done through the `cdkMenuTriggerFor` directive and you must provide a template reference
49+
variable to it. Once both of these directives are set, you can toggle the associated menu
50+
programmatically, using a mouse or using a keyboard.
51+
52+
#### Menu panels
53+
54+
Non-inline menus must be wrapped in an `ng-template` which must have a directive of `cdkMenuPanel`
55+
and a reference variable which must be of type `cdkMenuPanel`. Further, the `cdkMenu` must also
56+
reference the `cdkMenuPanel`.
57+
58+
```html
59+
<ng-template cdkMenuPanel #panel="cdkMenuPanel">
60+
<div cdkMenu [cdkMenuPanel]="panel">
61+
<!-- some content -->
62+
</div>
63+
</ng-template>
64+
```
65+
66+
Finally, note that this is part of the cdk and there is no styling - it is up to you to provide
67+
styles. It is also important that your style matches the `orientation` attribute on both the
68+
`cdkMenu` and `cdkMenuBar` directives.
69+
70+
### Standalone Menus
71+
72+
A standalone menu is triggered from a single standalone button and follows the [ARIA menu][menubar]
73+
spec. Standalone menu triggers cannot be grouped together meaning each single trigger is a tabstop.
74+
Standalone triggers can be opened from the keyboard and the opened menu behaves just like it does in
75+
the other implementations.
76+
77+
<!-- TODO standalone trigger example (use from above?) -->
78+
79+
### Menu Bars
80+
81+
The `CdkMenuBar` directive follows the [ARIA menubar][menubar] spec and operates similar to a
82+
desktop app menubar. It consists of at least one `CdkMenuItem` which triggers a submenu. A menubar
83+
can be layed out horizontally or vertically (defaulting to horizontal). If the style is changed, the
84+
`orientation` attribute must be set to match in order for the keyboard navigation to work properly
85+
and for menus to open up in the correct location.
86+
87+
The primary difference between a menubar and a standalone menu is the menubar groups together the
88+
triggering components. Therefore, menus in a menubar can be opened/closed when hovering between
89+
their triggers and keyboard navigation listens to left and right arrow buttons which toggles between
90+
the menu items in the menubar.
91+
92+
<!-- TODO basic menubar example (google docs?) -->
93+
94+
### Context Menus
95+
96+
A context menu is a menu which opens on a right click which occurs within some context component. A
97+
context is an area of your application which contains some content where you want to open up a menu.
98+
When a user right-clicks within the context the associated menu opens up next to their cursor - as
99+
you would expect with a typical right click menu.
100+
101+
<!-- TODO basic context menu example -->
102+
103+
Context menu triggers may be nested within parent contexts and when a user right clicks, the deepest
104+
context menu in which the user is hovering in is opened up.
105+
106+
```html
107+
<div [cdkContextMenuTriggerFor]="outer">
108+
My outer context
109+
<div [cdkContextMenuTriggerFor]="inner">
110+
My inner context
111+
</div>
112+
</div>
113+
```
114+
115+
In the example above, right clicking on "My inner context" will open up the "inner" menu and right
116+
clicking inside "My outer context" will open up the "outer" menu.
117+
118+
### Inline Menus
119+
120+
Inline menus are groups of menu items which typically do not trigger their own menus and the menu
121+
items implement some type of on-click behavior. The benefit of using an inline menu over a group of
122+
standalone menu items is that inline menus provide keyboard navigation and focus management. Menu
123+
items within an inline menus are logically grouped together.
124+
125+
<!-- TODO inline menu example (gmail buttons?) -->
126+
127+
### Menu Items
128+
129+
Components which interact with or are placed inside of a menu or menubar should typically have the
130+
`cdkMenuItem` directive provided. This directive allows the items to be navigated to via keyboard
131+
interaction.
132+
133+
A menuitem by itself can provide some user defined action by hooking into the `cdkMenuItemTriggered`
134+
output. An example may be a close button which performs some closing logic.
135+
136+
```html
137+
<ng-template cdkMenuPanel #panel="cdkMenuPanel">
138+
<div cdkMenu [cdkMenuPanel]="panel">
139+
<button cdkMenuItem [cdkMenuItemTriggered]="closeApp()">Close</button>
140+
</div>
141+
</ng-template>
142+
```
143+
144+
`cdkMenuItem` should also be added to a component which triggers a menu or submenu.
145+
146+
```html
147+
<ng-template cdkMenuPanel #panel="cdkMenuPanel">
148+
<div cdkMenu [cdkMenuPanel]="panel">
149+
<button cdkMenuItem [cdkMenuTriggerFor]="submenu">Open Submenu</button>
150+
</div>
151+
</ng-template>
152+
```
153+
154+
A menuitem also has two sub-types, neither of which should trigger a menu: CdkMenuItemCheckbox and
155+
CdkMenuItemRadio
156+
157+
#### Menu Item Checkboxes
158+
159+
A component with the `cdkMenuItemCheckbox` directive behaves just as you would expect a typical
160+
checkbox to behave. A use case may be toggling an independent setting since it contains a checked
161+
state. A component with the `cdkMenuItemCheckbox` directive does not need the additional
162+
`cdkMenuItem` directive.
163+
164+
#### Menu Item Radios
165+
166+
A `cdkMenuItemRadio` behaves as you would expect a radio button to behave. A use case may be
167+
toggling through more than one state since radio buttons can be grouped together. A component with
168+
the `cdkMenuItemRadio` directive does not need the additional `cdkMenuItem` directive.
169+
170+
#### Groups
171+
172+
By default `cdkMenu` acts as a group for `cdkMenuItemRadio` elements. That is, components marked
173+
with `cdkMenuItemRadio` added as children of a `cdkMenu` will be logically grouped and only a single
174+
component can have the checked state.
175+
176+
If you would like to have unrelated groups of radio buttons within a single menu you should use the
177+
`cdkMenuGroup` directive.
178+
179+
```html
180+
<ng-template cdkMenuPanel #panel="cdkMenuPanel">
181+
<div cdkMenu [cdkMenuPanel]="panel">
182+
<!-- Font size -->
183+
<div cdkMenuGroup>
184+
<button cdkMenuItemRadio>Small</button>
185+
<button cdkMenuItemRadio>Medium</button>
186+
<button cdkMenuItemRadio>Large</button>
187+
</div>
188+
<hr />
189+
<!-- Paragraph alignment -->
190+
<div cdkMenuGroup>
191+
<button cdkMenuItemRadio>Left</button>
192+
<button cdkMenuItemRadio>Center</button>
193+
<button cdkMenuItemRadio>Right</button>
194+
</div>
195+
</div>
196+
</ng-template>
197+
```
198+
199+
Note however that when the menu is closed and reopened any state is lost. You must subscribe to the
200+
groups `change` output, or to `cdkMenuItemToggled` on each radio item and track changes your self.
201+
202+
<!-- TODO standalone menu example with checkboxes and grouped radios -->
203+
204+
### Smart Menu Aim
205+
206+
`@angular/cdk-experimental/menu` can also intelligently predict if a user is attempting to navigate
207+
to an open submenu and prevent premature closeouts. This functionality prevents users from having to
208+
hunt through the open menus in a maze-like fashion in order to get to their destination.
209+
210+
![menu aim diagram][diagram]
211+
212+
As demonstrated in the diagram above we first track the users mouse movements within a menu. Next,
213+
when a user mouses into a sibling menu item (e.g. Share button) the sibling item asks the Menu Aim
214+
service if it can perform its close actions. In order to determine if the current submenu can be
215+
closed out, the Menu Aim service calculates the slope between a selected target coordinate in the
216+
submenu and the previous mouse point, and the slope between the target and the current mouse point.
217+
If the slope of the current mouse point is greater than the slope of the previous that means the
218+
user is moving towards the submenu and we shouldn't close out. Finally, as the user mouses into
219+
other sibling triggers, as long as the slope of the current mouse movement is steeper than the
220+
previous, it assumes that the user is moving towards the submenu. Users however may sometimes stop
221+
short in a sibling item after moving towards the submenu therefore the service is intelligent enough
222+
the detect this and will trigger the next menu.
223+
224+
### Accessibility
225+
226+
The set of directives defined in `@angular/cdk-experimental/menu` follow accessability best
227+
practices as defined in the [ARIA spec][menubar]. Specifically, the menus are aware of left-to-right
228+
and right-to-left layouts and opened appropriately however it is up to you to handle any related
229+
styling. Further, screen readers are supported but you should add `aria-label` or `aria-labelledby`
230+
if menu items do not have text. Finally, keyboard interaction is supported as defined in the [ARIA
231+
menubar keyboard interaction spec][keyboard].
232+
233+
<!-- links -->
234+
235+
[aria]: https://www.w3.org/TR/wai-aria-1.1/ 'Aria Spec'
236+
[menubar]: https://www.w3.org/TR/wai-aria-practices-1.1/#menu 'Aria Menubar Pattern'
237+
[keyboard]:
238+
https://www.w3.org/TR/wai-aria-practices-1.1/#keyboard-interaction-12
239+
'Aria Menubar Keyboard Interaction'
240+
[diagram]: menuaim.png 'Menu Aim Diagram'

src/cdk-experimental/menu/menuaim.png

62.6 KB
Loading

0 commit comments

Comments
 (0)