From a51f82f12e316d7cbf52a7ceb9adc94733b8f380 Mon Sep 17 00:00:00 2001 From: Kristiyan Kostadinov Date: Thu, 27 Jul 2017 19:44:04 +0300 Subject: [PATCH] feat(menu): add indicator to menu items that trigger a sub-menu (#5995) Adds an arrow indicator to the `md-menu-item` instances that trigger a sub-menu. --- src/lib/menu/_menu-theme.scss | 9 ++++---- src/lib/menu/menu-item.ts | 4 ++++ src/lib/menu/menu-trigger.ts | 20 +++++++++++------- src/lib/menu/menu.scss | 39 ++++++++++++++++++++++++++++++++++- src/lib/menu/menu.spec.ts | 11 ++++++++++ 5 files changed, 70 insertions(+), 13 deletions(-) diff --git a/src/lib/menu/_menu-theme.scss b/src/lib/menu/_menu-theme.scss index 417604f9a24a..3439edb75751 100644 --- a/src/lib/menu/_menu-theme.scss +++ b/src/lib/menu/_menu-theme.scss @@ -18,12 +18,11 @@ &[disabled] { color: mat-color($foreground, 'disabled'); } + } - .mat-icon { - color: mat-color($foreground, 'icon'); - vertical-align: middle; - } - + .mat-menu-item .mat-icon, + .mat-menu-item-submenu-trigger::after { + color: mat-color($foreground, 'icon'); } .mat-menu-item:hover, diff --git a/src/lib/menu/menu-item.ts b/src/lib/menu/menu-item.ts index 6cb43f867103..f00b62cca332 100644 --- a/src/lib/menu/menu-item.ts +++ b/src/lib/menu/menu-item.ts @@ -28,6 +28,7 @@ export const _MdMenuItemMixinBase = mixinDisabled(MdMenuItemBase); 'role': 'menuitem', 'class': 'mat-menu-item', '[class.mat-menu-item-highlighted]': '_highlighted', + '[class.mat-menu-item-submenu-trigger]': '_triggersSubmenu', '[attr.tabindex]': '_getTabIndex()', '[attr.aria-disabled]': 'disabled.toString()', '[attr.disabled]': 'disabled || null', @@ -45,6 +46,9 @@ export class MdMenuItem extends _MdMenuItemMixinBase implements Focusable, CanDi /** Whether the menu item is highlighted. */ _highlighted: boolean = false; + /** Whether the menu item acts as a trigger for a sub-menu. */ + _triggersSubmenu: boolean = false; + constructor(private _elementRef: ElementRef) { super(); } diff --git a/src/lib/menu/menu-trigger.ts b/src/lib/menu/menu-trigger.ts index 84221d36fb7e..8433c9c8d38d 100644 --- a/src/lib/menu/menu-trigger.ts +++ b/src/lib/menu/menu-trigger.ts @@ -122,13 +122,19 @@ export class MdMenuTrigger implements AfterViewInit, OnDestroy { /** Event emitted when the associated menu is closed. */ @Output() onMenuClose = new EventEmitter(); - constructor(private _overlay: Overlay, - private _element: ElementRef, - private _viewContainerRef: ViewContainerRef, - @Inject(MD_MENU_SCROLL_STRATEGY) private _scrollStrategy, - @Optional() private _parentMenu: MdMenu, - @Optional() @Self() private _menuItemInstance: MdMenuItem, - @Optional() private _dir: Directionality) { } + constructor( + private _overlay: Overlay, + private _element: ElementRef, + private _viewContainerRef: ViewContainerRef, + @Inject(MD_MENU_SCROLL_STRATEGY) private _scrollStrategy, + @Optional() private _parentMenu: MdMenu, + @Optional() @Self() private _menuItemInstance: MdMenuItem, + @Optional() private _dir: Directionality) { + + if (_menuItemInstance) { + _menuItemInstance._triggersSubmenu = this.triggersSubmenu(); + } + } ngAfterViewInit() { this._checkMenu(); diff --git a/src/lib/menu/menu.scss b/src/lib/menu/menu.scss index f3e6d879843f..3ef88f1378c4 100644 --- a/src/lib/menu/menu.scss +++ b/src/lib/menu/menu.scss @@ -1,5 +1,4 @@ // TODO(kara): update vars for desktop when MD team responds -// TODO(kara): animation for menu opening @import '../core/style/button-common'; @import '../core/style/layout-common'; @@ -8,6 +7,7 @@ $mat-menu-vertical-padding: 8px !default; $mat-menu-border-radius: 2px !default; +$mat-menu-submenu-indicator-size: 10px !default; .mat-menu-panel { @include mat-menu-base(2); @@ -36,6 +36,43 @@ $mat-menu-border-radius: 2px !default; @include mat-button-reset(); @include mat-menu-item-base(); position: relative; + + .mat-icon { + vertical-align: middle; + } +} + +.mat-menu-item-submenu-trigger { + // Increase the side padding to prevent the indicator from overlapping the text. + padding-right: $mat-menu-side-padding * 2; + + // Renders a triangle to indicate that the menu item will open a sub-menu. + &::after { + $size: $mat-menu-submenu-indicator-size / 2; + + width: 0; + height: 0; + border-style: solid; + border-width: $size 0 $size $size; + border-color: transparent transparent transparent currentColor; + content: ''; + display: inline-block; + position: absolute; + top: 50%; + right: $mat-menu-side-padding; + transform: translateY(-50%); + } + + [dir='rtl'] & { + padding-right: $mat-menu-side-padding / 2; + padding-left: $mat-menu-side-padding * 2; + + &::after { + right: auto; + left: $mat-menu-side-padding; + transform: rotateY(180deg) translateY(-50%); + } + } } button.mat-menu-item { diff --git a/src/lib/menu/menu.spec.ts b/src/lib/menu/menu.spec.ts index 22914dae3630..f4a755dda820 100644 --- a/src/lib/menu/menu.spec.ts +++ b/src/lib/menu/menu.spec.ts @@ -872,6 +872,17 @@ describe('MdMenu', () => { expect(overlay.querySelectorAll('.mat-menu-panel').length).toBe(0, 'Expected no open menus'); }); + it('should set a class on the menu items that trigger a sub-menu', () => { + compileTestComponent(); + instance.rootTrigger.openMenu(); + fixture.detectChanges(); + + const menuItems = overlay.querySelectorAll('[md-menu-item]'); + + expect(menuItems[0].classList).toContain('mat-menu-item-submenu-trigger'); + expect(menuItems[1].classList).not.toContain('mat-menu-item-submenu-trigger'); + }); + }); });