Skip to content

Commit 25c39eb

Browse files
committed
allow fab-*-buttons to render menu items in a declarative syntax, similar to how command bar items allow it
1 parent 2f54281 commit 25c39eb

File tree

1 file changed

+101
-2
lines changed

1 file changed

+101
-2
lines changed

libs/fabric/src/lib/components/button/base-button.component.ts

+101-2
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,31 @@
22
// Licensed under the MIT License.
33

44
import { InputRendererOptions, JsxRenderFunc, ReactWrapperComponent } from '@angular-react/core';
5-
import { ChangeDetectorRef, ElementRef, EventEmitter, Input, NgZone, OnInit, Output, Renderer2 } from '@angular/core';
5+
import {
6+
ChangeDetectorRef,
7+
ElementRef,
8+
EventEmitter,
9+
Input,
10+
NgZone,
11+
OnInit,
12+
Output,
13+
Renderer2,
14+
ContentChildren,
15+
QueryList,
16+
AfterContentInit,
17+
OnDestroy,
18+
} from '@angular/core';
619
import { IButtonProps } from 'office-ui-fabric-react/lib/Button';
20+
import { ContextualMenuItemDirective, IContextualMenuItemOptions } from '../contextual-menu/public-api';
21+
import { ChangeableItemsHelper } from '../core/shared/changeable-helper';
22+
import { IContextualMenuItem } from 'office-ui-fabric-react';
23+
import { Subscription } from 'rxjs';
24+
import { CommandBarItemChangedPayload } from '../command-bar/directives/command-bar-item.directives';
25+
import { mergeItemChanges } from '../core/declarative/item-changed';
26+
import { omit } from '../../utils/omit';
727

8-
export abstract class FabBaseButtonComponent extends ReactWrapperComponent<IButtonProps> implements OnInit {
28+
export abstract class FabBaseButtonComponent extends ReactWrapperComponent<IButtonProps>
29+
implements OnInit, AfterContentInit, OnDestroy {
930
@Input() componentRef?: IButtonProps['componentRef'];
1031
@Input() href?: IButtonProps['href'];
1132
@Input() primary?: IButtonProps['primary'];
@@ -47,13 +68,18 @@ export abstract class FabBaseButtonComponent extends ReactWrapperComponent<IButt
4768
@Output() readonly onMenuClick = new EventEmitter<{ ev?: MouseEvent | KeyboardEvent; button?: IButtonProps }>();
4869
@Output() readonly onAfterMenuDismiss = new EventEmitter<void>();
4970

71+
@ContentChildren(ContextualMenuItemDirective) readonly menuItemsDirectives?: QueryList<ContextualMenuItemDirective>;
72+
5073
onRenderIcon: (props?: IButtonProps, defaultRender?: JsxRenderFunc<IButtonProps>) => JSX.Element;
5174
onRenderText: (props?: IButtonProps, defaultRender?: JsxRenderFunc<IButtonProps>) => JSX.Element;
5275
onRenderDescription: (props?: IButtonProps, defaultRender?: JsxRenderFunc<IButtonProps>) => JSX.Element;
5376
onRenderAriaDescription: (props?: IButtonProps, defaultRender?: JsxRenderFunc<IButtonProps>) => JSX.Element;
5477
onRenderChildren: (props?: IButtonProps, defaultRender?: JsxRenderFunc<IButtonProps>) => JSX.Element;
5578
onRenderMenuIcon: (props?: IButtonProps, defaultRender?: JsxRenderFunc<IButtonProps>) => JSX.Element;
5679

80+
private _changeableItemsHelper: ChangeableItemsHelper<IContextualMenuItem>;
81+
private _subscriptions: Subscription[] = [];
82+
5783
constructor(elementRef: ElementRef, changeDetectorRef: ChangeDetectorRef, renderer: Renderer2, ngZone: NgZone) {
5884
super(elementRef, changeDetectorRef, renderer, { ngZone, setHostDisplay: true });
5985

@@ -70,6 +96,50 @@ export abstract class FabBaseButtonComponent extends ReactWrapperComponent<IButt
7096
this.onRenderMenuIcon = this.createRenderPropHandler(this.renderMenuIcon);
7197
}
7298

99+
ngAfterContentInit() {
100+
if (this.menuItemsDirectives && this.menuItemsDirectives.length > 0) {
101+
const setItems = (directiveItems: ReadonlyArray<ContextualMenuItemDirective>) => {
102+
const items = directiveItems.map(directive =>
103+
this._transformContextualMenuItemOptionsToProps(this._directiveToContextualMenuItem(directive))
104+
);
105+
if (!this.menuProps) {
106+
this.menuProps = { items: items };
107+
} else {
108+
this.menuProps.items = items;
109+
}
110+
111+
this.markForCheck();
112+
};
113+
114+
this._changeableItemsHelper = new ChangeableItemsHelper(this.menuItemsDirectives);
115+
this._subscriptions.push(
116+
this._changeableItemsHelper.onItemsChanged.subscribe((newItems: QueryList<ContextualMenuItemDirective>) => {
117+
setItems(newItems.toArray());
118+
}),
119+
this._changeableItemsHelper.onChildItemChanged.subscribe(({ key, changes }: CommandBarItemChangedPayload) => {
120+
const newItems = this.menuItemsDirectives.map(item =>
121+
item.key === key ? mergeItemChanges(item, changes) : item
122+
);
123+
setItems(newItems);
124+
125+
this.markForCheck();
126+
})
127+
);
128+
129+
setItems(this.menuItemsDirectives.toArray());
130+
}
131+
}
132+
133+
ngOnDestroy() {
134+
if (this._changeableItemsHelper) {
135+
this._changeableItemsHelper.destroy();
136+
}
137+
138+
if (this._subscriptions) {
139+
this._subscriptions.forEach(subscription => subscription.unsubscribe());
140+
}
141+
}
142+
73143
onMenuClickHandler(ev?: React.MouseEvent<HTMLElement> | React.KeyboardEvent<HTMLElement>, button?: IButtonProps) {
74144
this.onMenuClick.emit({
75145
ev: ev && ev.nativeEvent,
@@ -80,4 +150,33 @@ export abstract class FabBaseButtonComponent extends ReactWrapperComponent<IButt
80150
onClickHandler(ev?: React.MouseEvent) {
81151
this.onClick.emit(ev.nativeEvent);
82152
}
153+
154+
private _directiveToContextualMenuItem(directive: ContextualMenuItemDirective): IContextualMenuItemOptions {
155+
return {
156+
...directive,
157+
onClick: (ev, item) => {
158+
directive.click.emit({ ev: ev && ev.nativeEvent, item: item });
159+
},
160+
};
161+
}
162+
163+
private _transformContextualMenuItemOptionsToProps(itemOptions: IContextualMenuItemOptions): IContextualMenuItem {
164+
const sharedProperties = omit(itemOptions, 'renderIcon', 'render');
165+
166+
// Legacy render mode is used for the icon because otherwise the icon is to the right of the text (instead of the usual left)
167+
const iconRenderer = this.createInputJsxRenderer(itemOptions.renderIcon, { legacyRenderMode: true });
168+
const renderer = this.createInputJsxRenderer(itemOptions.render);
169+
170+
return Object.assign(
171+
{},
172+
sharedProperties,
173+
iconRenderer && {
174+
onRenderIcon: (item: IContextualMenuItem) => iconRenderer({ contextualMenuItem: item }),
175+
},
176+
renderer &&
177+
({
178+
onRender: (item, dismissMenu) => renderer({ item, dismissMenu }),
179+
} as Pick<IContextualMenuItem, 'onRender'>)
180+
) as IContextualMenuItem;
181+
}
83182
}

0 commit comments

Comments
 (0)