Skip to content

Commit bb5307b

Browse files
Kateryna ProkopenkoDevtools-frontend LUCI CQ
authored andcommitted
[DevToolsUIKit] Add context menu guidelines
Bug: 414331596 Change-Id: Ie9cc230a0d3d42e49c10ad1ce640e18e0c2fe129 Reviewed-on: https://chromium-review.googlesource.com/c/devtools/devtools-frontend/+/6551299 Auto-Submit: Kateryna Prokopenko <[email protected]> Commit-Queue: Kim-Anh Tran <[email protected]> Reviewed-by: Kim-Anh Tran <[email protected]>
1 parent 868bcc4 commit bb5307b

File tree

2 files changed

+186
-1
lines changed

2 files changed

+186
-1
lines changed

docs/styleguide/ux/components.md

Lines changed: 186 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -268,7 +268,7 @@ slider.addEventListener('change', event => onChange(event))
268268

269269
### Usage
270270

271-
#### Designer guidelines
271+
#### Design guidelines
272272

273273
Find an exhaustive collection of icons currently used in DevTools [here](https://drive.google.com/corp/drive/folders/1EpmxLRjvdHn5Ia8iT3aicgIEPEvhXY_D?resourcekey=0-QseNsNRsF4w8F5EKz7ncnA)
274274

@@ -299,3 +299,188 @@ Usage with the imperative API:
299299
```js
300300
const someIcon = IconButton.Icon.create('some-icon-name', 'some-class');
301301
```
302+
303+
## Context menus
304+
305+
![Context menu with various simple menu items](images/context-menu-example.png)
306+
307+
Context Menu allows us to display contextual actions to the user, typically triggered by a right-click or via a dedicated menu button.
308+
309+
### General information
310+
311+
#### ContextMenu Class
312+
313+
The primary class for creating and managing context menus.
314+
It can render as a "soft" menu (custom HTML-based implementation, offering extra functionality like keeping the menu open) or a native menu. The preferred and default way is the native menu, soft menu should ONLY be used in exceptional cases where native menu lacks features.
315+
Typically instantiated in response to a user event (e.g. contextmenu mouse event).
316+
317+
#### Item Class
318+
319+
Represents an individual entry within a context menu.
320+
Types:
321+
* 'item': A standard clickable menu item.
322+
* 'checkbox': An item that can be toggled on or off.
323+
* 'separator': A visual line to group related items.
324+
Items can have labels, handlers, be enabled/disabled. Optionally can also display shortcuts or tooltips (both are exceptions and not recommended).
325+
326+
#### Section Class
327+
328+
Groups related Items within a ContextMenu or SubMenu.
329+
Helps organize the menu structure and often introduces visual separation.
330+
Predefined section names (e.g. 'header', 'clipboard', 'default', 'footer') are available on ContextMenu and SubMenu instances.
331+
332+
#### SubMenu Class
333+
334+
A special type of Item that, when clicked or hovered, opens another nested menu.
335+
SubMenus contain their own Sections and Items, allowing for hierarchical menu structures.
336+
337+
#### MenuButton Component
338+
339+
A custom HTML element (`<devtools-menu-button>`) that renders as a button (currently an icon button only, though we plan to extend it to text buttons too).
340+
When clicked, it instantiates and displays a ContextMenu.
341+
Configured via HTML attributes (e.g. `icon-name`, `title`) and a `populateMenuCall` property to define the menu's content.
342+
343+
344+
#### Providers
345+
346+
A mechanism to dynamically add context-specific menu items.
347+
Providers are registered with `ContextMenu.registerProvider()` and are associated with specific object types.
348+
When a context menu is shown for a particular target object, relevant providers are invoked to append their items.
349+
350+
351+
#### Registered Items
352+
353+
A way to add menu items (linked to actions from the `ActionRegistry`) to predefined locations within the DevTools UI (e.g. the main menu, navigator context menus).
354+
Items are registered using `ContextMenu.registerItem()` with a specific `ItemLocation`.
355+
356+
### Usage
357+
358+
#### Design Guidelines
359+
360+
Generally, we do not recommend using shortcuts in context menus since this goes against Mac platform guidelines and we do not want to have them on some platforms, but not the others. Try to find an alternative solution, however if none is possible and showing shortcuts is critical for a smooth user flow, reach out to kimanh@ or kprokopenko@.
361+
362+
#### Developer guidelines
363+
364+
##### Dos and Don'ts
365+
366+
###### Do
367+
368+
* Use `new ContextMenu(event, options?)` to create and show menus in response to user interactions (e.g. contextmenu event on an element).
369+
* Use `<devtools-menu-button>` to create buttons that should trigger a context menu.
370+
371+
###### Don't
372+
373+
* Forget to call `contextMenu.show()` after populating the menu; otherwise, it won't open.
374+
375+
##### Developer examples
376+
377+
Usage with lit-html (left-click on a `<devtools-menu-button>` opens a menu):
378+
379+
```js
380+
html`
381+
<devtools-menu-button
382+
icon-name="some-icon-name"
383+
.populateMenuCall=${(menu: UI.ContextMenu.ContextMenu) => {
384+
menu.defaultSection().appendItem('Item', () => {
385+
console.log('Item clicked');
386+
}, {jslogContext: 'item'});
387+
}}
388+
jslogContext="my-menu-button"
389+
></devtools-menu-button>
390+
`
391+
```
392+
393+
Usage with the imperative API (menu shows on a right-click)
394+
395+
Various simple menu items:
396+
397+
```js
398+
const element = this.shadowRoot.querySelector('.element-to-add-menu-to');
399+
element.addEventListener('contextmenu', (event: MouseEvent) => {
400+
const contextMenu = new UI.ContextMenu.ContextMenu(event);
401+
402+
// Regular item
403+
contextMenu.defaultSection().appendItem('Regular item', () => {
404+
console.log('Regular item clicked ');
405+
}, { jslogContext: 'regular-item' });
406+
407+
// Disabled item
408+
contextMenu.defaultSection().appendItem('Disabled item', () => {
409+
console.log('Will not be printed');
410+
}, { jslogContext: 'disbaled-item',
411+
disabled: true });
412+
413+
// Experimental item
414+
const item = contextMenu.defaultSection().appendItem('Experimental item', () => {
415+
console.log('Experimental item clicked');
416+
}, { jslogContext: 'experimental-item',
417+
isPreviewFeature: true });
418+
419+
// Separator
420+
contextMenu.defaultSection().appendSeparator();
421+
422+
// Checkbox item
423+
contextMenu.defaultSection().appendCheckboxItem('Checkbox item', () => {
424+
console.log('Checkbox item clicked');
425+
}, { checked: true, jslogContext: 'checkbox-item' });
426+
427+
void contextMenu.show();
428+
});
429+
```
430+
431+
Custom section:
432+
433+
```js
434+
const customSection = contextMenu.section('Custom section');
435+
customSection.appendItem('Section inner item 1', () => { /* ... */ }, {jslogContext: 'my-inner-item-1'});
436+
437+
customSection.appendItem('Section inner item 2', () => { /* ... */ }, {jslogContext: 'my-inner-item-2'});
438+
```
439+
440+
Sub menu:
441+
442+
```js
443+
const subMenu = contextMenu.defaultSection().appendSubMenuItem('Item to open sub menu', /* disabled */ false, 'my-sub-menu');
444+
subMenu.defaultSection().appendItem('Sub menu inner item 1', () => { /* ... */ }, {jslogContext: 'my-inner-item-1'});
445+
subMenu.defaultSection().appendItem('Sub menu inner item 2', () => { /* ... */ }, {jslogContext: 'my-inner-item-2'});
446+
```
447+
448+
Context menu provider registration (adds items dynamically based on the context menu’s target):
449+
450+
```js
451+
// Define provider
452+
class MyCustomNodeProvider implements UI.ContextMenu.Provider<SomeTarget|SomeOtherTarget> {
453+
appendApplicableItems(event: Event, contextMenu: UI.ContextMenu.ContextMenu, target: SomeTarget|SomeOtherTarget) {
454+
if (target instanceof SomeTarget) {
455+
contextMenu.defaultSection().appendItem('Item 1', () => {
456+
console.log('Item 1 clicked');}, {jsLogContext: 'my-item-1'});
457+
} else {
458+
contextMenu.defaultSection().appendItem('Item 2', () => {
459+
console.log('Item 2 clicked');}, {jsLogContext: 'my-item-2'});
460+
}
461+
}
462+
}
463+
```
464+
465+
```js
466+
// Register provider
467+
UI.ContextMenu.registerProvider<SDK.DOMModel.DOMNode>({
468+
contextTypes: () => [SomeTarget, SomeOtherTarget],
469+
loadProvider: async () => {
470+
const myModule = await loadMyModule();
471+
return new myModule.MyCustomNodeProvider();
472+
},
473+
experiment: undefined, // or name of experiment this is behind
474+
});
475+
```
476+
477+
Static menu item registration via ItemLocation (adds an action to a predefined menu location):
478+
479+
```js
480+
UI.ContextMenu.registerItem({
481+
location: UI.ContextMenu.ItemLocation.NAVIGATOR_MENU_DEFAULT,
482+
actionId: 'quick-open.show',
483+
order: undefined,
484+
});
485+
```
486+
This will automatically add the "Open file" action to the context menu that appears when clicking the Elements panel's 3-dot button.
19.9 KB
Loading

0 commit comments

Comments
 (0)