Skip to content

Commit 48b94bc

Browse files
author
Ben Grynhaus
committed
CommandBar - declarative syntax. WIP 2
1 parent 65dd906 commit 48b94bc

File tree

11 files changed

+224
-41
lines changed

11 files changed

+224
-41
lines changed

apps/demo/src/app/app.component.html

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,14 +19,20 @@ <h2>Getting up and running...</h2>
1919
<fab-command-bar>
2020
<items>
2121
<fab-command-bar-item key="run" text="Run" [iconProps]="{ iconName: 'CaretRight' }" [disabled]="runDisabled"></fab-command-bar-item>
22-
<fab-command-bar-item key="new" text="New" [iconProps]="{ iconName: 'Add' }" (onClick)="onNewClicked()"></fab-command-bar-item>
23-
<fab-command-bar-item key="save" text="Save" [iconProps]="{ iconName: 'Save' }" [subMenuProps]="saveSubMenuProps"></fab-command-bar-item>
24-
<fab-command-bar-item key="copy" text="Copy" [iconProps]="{ iconName: 'Copy' }" (onClick)="onCopyClicked()"></fab-command-bar-item>
22+
<fab-command-bar-item key="new" text="New" [iconProps]="{ iconName: 'Add' }" (click)="onNewClicked()"></fab-command-bar-item>
23+
<fab-command-bar-item key="save" text="Save" [iconProps]="{ iconName: 'Save' }" [subMenuProps]="saveSubMenuProps">
24+
<contextual-menu-item key="save" text="Save"></contextual-menu-item>
25+
<contextual-menu-item key="save-as" text="Save As" (click)="onSaveAsClicked()">
26+
<contextual-menu-item key="save-as-1" text="Save As 1" (click)="onSaveAsFirstClicked()"></contextual-menu-item>
27+
<contextual-menu-item key="save-as-2" text="Save As 2" (click)="onSaveAsSecondClicked()"></contextual-menu-item>
28+
</contextual-menu-item>
29+
</fab-command-bar-item>
30+
<fab-command-bar-item key="copy" text="Copy" [iconProps]="{ iconName: 'Copy' }" (click)="onCopyClicked()"></fab-command-bar-item>
2531
</items>
2632

2733
<far-items>
2834
<fab-command-bar-item key="help" text="Help" [iconProps]="{ iconName: 'Help' }"></fab-command-bar-item>
29-
<fab-command-bar-item key="full-screen" [iconOnly]="true" [iconProps]="{ iconName: fullScreenIcon }" (onClick)="toggleFullScreen()"></fab-command-bar-item>
35+
<fab-command-bar-item key="full-screen" [iconOnly]="true" [iconProps]="{ iconName: fullScreenIcon }" (click)="toggleFullScreen()"></fab-command-bar-item>
3036
</far-items>
3137
</fab-command-bar>
3238

apps/demo/src/app/app.component.ts

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import {
2626
Button,
2727
MessageBarType,
2828
ShimmerElementType,
29+
IContextualMenuProps,
2930
} from 'office-ui-fabric-react';
3031
import { IExpandingCardOptions } from '@angular-react/fabric/src/components/hover-card';
3132
import { ICommandBarItemOptions, FabCommandBarComponent } from '@angular-react/fabric/src/components/command-bar';
@@ -50,9 +51,22 @@ export class AppComponent {
5051
console.log('Copy clicked');
5152
}
5253

54+
onSaveAsClicked() {
55+
console.log('Save as clicked');
56+
}
57+
58+
onSaveAsFirstClicked() {
59+
console.log('Save as 1 clicked');
60+
}
61+
62+
onSaveAsSecondClicked() {
63+
console.log('Save as 2 clicked');
64+
}
65+
5366
// FIXME: Allow declarative syntax too
54-
saveSubMenuProps = {
55-
items: [
67+
saveSubMenuProps: Partial<IContextualMenuProps> = {
68+
gapSpace: 10,
69+
/* items: [
5670
{
5771
key: 'save',
5872
text: 'Save',
@@ -78,7 +92,7 @@ export class AppComponent {
7892
],
7993
},
8094
},
81-
],
95+
], */
8296
};
8397

8498
/* commandBarItems: ReadonlyArray<ICommandBarItemOptions> = [

libs/fabric/src/components/command-bar/command-bar.component.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ import { CommandBarItemsDirectiveBase } from './directives/command-bar-items-bas
2323
import { TypedChanges, OnChanges } from '../../types/angular/typed-changes';
2424
import {
2525
CommandBarItemDirective,
26-
CommandBarItemPropertiesChangedPayload,
26+
CommandBarItemChangedPayload,
2727
} from '@angular-react/fabric/src/components/command-bar/directives/command-bar-item.directive';
2828
import { Subscription } from 'rxjs';
2929

@@ -58,7 +58,8 @@ import { Subscription } from 'rxjs';
5858
export class FabCommandBarComponent extends ReactWrapperComponent<ICommandBarProps>
5959
implements OnChanges<FabCommandBarComponent>, AfterContentInit, OnDestroy {
6060
@ContentChild(CommandBarItemsDirective) readonly itemsDirective: CommandBarItemsDirective;
61-
@ContentChild(CommandBarFarItemsDirective) readonly farItemsDirective: CommandBarItemsDirective;
61+
@ContentChild(CommandBarFarItemsDirective) readonly farItemsDirective: CommandBarFarItemsDirective;
62+
@ContentChild(CommandBarOverflowItemsDirective) readonly overflowItemsDirective: CommandBarOverflowItemsDirective;
6263

6364
@ViewChild('reactNode') protected reactNodeRef: ElementRef;
6465

@@ -121,6 +122,7 @@ export class FabCommandBarComponent extends ReactWrapperComponent<ICommandBarPro
121122
ngAfterContentInit() {
122123
if (this.itemsDirective) this._initDirective(this.itemsDirective, 'items');
123124
if (this.farItemsDirective) this._initDirective(this.farItemsDirective, 'farItems');
125+
if (this.overflowItemsDirective) this._initDirective(this.overflowItemsDirective, 'overflowItems');
124126
}
125127

126128
ngOnDestroy() {
@@ -165,7 +167,7 @@ export class FabCommandBarComponent extends ReactWrapperComponent<ICommandBarPro
165167

166168
// Subscribe for existing items changes
167169
this._subscriptions.push(
168-
directive.onItemChanged.subscribe(({ key, changes }: CommandBarItemPropertiesChangedPayload) => {
170+
directive.onItemChanged.subscribe(({ key, changes }: CommandBarItemChangedPayload) => {
169171
setItems(items => items.map(item => (item.key === key ? mergeItemChanges(item, changes) : item)));
170172
this.detectChanges();
171173
})

libs/fabric/src/components/command-bar/command-bar.module.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { CommandBarItemDirective } from './directives/command-bar-item.directive
88
import { CommandBarItemsDirective } from './directives/command-bar-items.directive';
99
import { CommandBarFarItemsDirective } from './directives/command-bar-far-items.directive';
1010
import { CommandBarOverflowItemsDirective } from './directives/command-bar-overflow-items.directive';
11+
import { FabContextualMenuModule } from '../contextual-menu/contextual-menu.module';
1112

1213
const components = [
1314
FabCommandBarComponent,
@@ -18,9 +19,9 @@ const components = [
1819
];
1920

2021
@NgModule({
21-
imports: [CommonModule],
22+
imports: [CommonModule, FabContextualMenuModule],
2223
declarations: components,
23-
exports: components,
24+
exports: [...components, FabContextualMenuModule],
2425
schemas: [NO_ERRORS_SCHEMA],
2526
})
2627
export class FabCommandBarModule {
Lines changed: 73 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,42 +1,95 @@
1-
import { Directive, Input, TemplateRef, ContentChild, Output, EventEmitter } from '@angular/core';
1+
import {
2+
Directive,
3+
Input,
4+
TemplateRef,
5+
Output,
6+
EventEmitter,
7+
ContentChildren,
8+
QueryList,
9+
AfterContentInit,
10+
} from '@angular/core';
211

312
import { OnChanges, TypedChanges } from '../../../types/angular/typed-changes';
413
import { ICommandBarItemOptions } from '../command-bar.component';
5-
import { IContextualMenuItem } from 'office-ui-fabric-react';
14+
import { IContextualMenuItem, ICommandBarItemProps } from 'office-ui-fabric-react';
15+
import { ContextualMenuItemDirective } from '../../contextual-menu';
16+
import { ItemChangedPayload } from '../../core/declarative/item-changed.payload';
617

7-
export interface CommandBarItemPropertiesChangedPayload {
8-
readonly key: ICommandBarItemOptions['key'];
9-
readonly changes: TypedChanges<ICommandBarItemOptions>;
10-
}
18+
export type CommandBarItemChangedPayload = ItemChangedPayload<ICommandBarItemOptions['key'], ICommandBarItemOptions>;
1119

1220
@Directive({ selector: 'fab-command-bar-item' })
13-
export class CommandBarItemDirective implements ICommandBarItemOptions, OnChanges<CommandBarItemDirective> {
14-
@ContentChild(TemplateRef) template: TemplateRef<any>;
21+
export class CommandBarItemDirective
22+
implements ICommandBarItemOptions, OnChanges<CommandBarItemDirective>, AfterContentInit {
23+
@ContentChildren(ContextualMenuItemDirective) readonly menuItemsDirectives: QueryList<ContextualMenuItemDirective>;
1524

25+
// ICommandBarItemOptions implementation
1626
@Input() key: ICommandBarItemOptions['key'];
1727
@Input() text?: ICommandBarItemOptions['text'];
1828
@Input() iconProps?: ICommandBarItemOptions['iconProps'];
1929
@Input() disabled?: ICommandBarItemOptions['disabled'];
2030
@Input() iconOnly?: ICommandBarItemOptions['iconOnly'];
2131
@Input() subMenuProps?: ICommandBarItemOptions['subMenuProps'];
32+
@Input() buttonStyles?: ICommandBarItemOptions['buttonStyles'];
33+
@Input() cacheKey?: ICommandBarItemOptions['cacheKey'];
34+
@Input() renderedInOverflow?: ICommandBarItemOptions['renderedInOverflow'];
35+
@Input() componentRef?: ICommandBarItemOptions['componentRef'];
36+
@Input() secondaryText?: ICommandBarItemOptions['secondaryText'];
37+
@Input() submenuIconProps?: ICommandBarItemOptions['submenuIconProps'];
38+
@Input() primaryDisabled?: ICommandBarItemOptions['primaryDisabled'];
39+
@Input() shortCut?: ICommandBarItemOptions['shortCut'];
40+
@Input() canCheck?: ICommandBarItemOptions['canCheck'];
41+
@Input() checked?: ICommandBarItemOptions['checked'];
42+
@Input() split?: ICommandBarItemOptions['split'];
43+
@Input() href?: ICommandBarItemOptions['href'];
44+
@Input() target?: ICommandBarItemOptions['target'];
45+
@Input() rel?: ICommandBarItemOptions['rel'];
46+
@Input() getItemClassNames?: ICommandBarItemOptions['getItemClassNames'];
47+
@Input() getSplitButtonVerticalDividerClassNames?: ICommandBarItemOptions['getSplitButtonVerticalDividerClassNames'];
48+
@Input() sectionProps?: ICommandBarItemOptions['sectionProps'];
49+
@Input() className?: ICommandBarItemOptions['className'];
50+
@Input() style?: ICommandBarItemOptions['style'];
51+
@Input() ariaLabel?: ICommandBarItemOptions['ariaLabel'];
52+
@Input() title?: ICommandBarItemOptions['title'];
53+
@Input() onMouseDown?: ICommandBarItemOptions['onMouseDown'];
54+
@Input() role?: ICommandBarItemOptions['role'];
55+
@Input() customOnRenderListLength?: ICommandBarItemOptions['customOnRenderListLength'];
56+
@Input() keytipProps?: ICommandBarItemOptions['keytipProps'];
57+
@Input() inactive?: ICommandBarItemOptions['inactive'];
58+
@Input() data: ICommandBarItemOptions['data'];
59+
@Input() render: ICommandBarItemOptions['render'];
60+
@Input() renderIcon: ICommandBarItemOptions['renderIcon'];
2261

23-
@Output() readonly onClick = new EventEmitter<{ ev?: MouseEvent | KeyboardEvent; item?: IContextualMenuItem }>();
62+
@Output() readonly click = new EventEmitter<{ ev?: MouseEvent | KeyboardEvent; item?: IContextualMenuItem }>();
2463

25-
@Output() readonly onItemChanged = new EventEmitter<CommandBarItemPropertiesChangedPayload>();
64+
// Directive properties
65+
@Output() readonly onItemChanged = new EventEmitter<CommandBarItemChangedPayload>();
2666

2767
ngOnChanges(changes: TypedChanges<this>) {
2868
this.onItemChanged.emit({
2969
key: this.key,
30-
changes: Object.assign<{}, TypedChanges<this>, Pick<TypedChanges<this>, 'onClick'>>(
31-
{},
32-
changes,
33-
changes.onClick && {
34-
onClick: {
35-
...changes.onClick,
36-
isFirstChange: changes.onClick.isFirstChange,
37-
},
38-
}
39-
),
70+
changes,
4071
});
4172
}
73+
74+
ngAfterContentInit() {
75+
if (this.menuItemsDirectives.length === 0) {
76+
return;
77+
}
78+
79+
const items = this.menuItemsDirectives.map<IContextualMenuItem>(directiveItem => ({
80+
...directiveItem,
81+
onClick: (ev, item) => {
82+
directiveItem.click.emit({
83+
ev: ev && ev.nativeEvent,
84+
item: item,
85+
});
86+
},
87+
}));
88+
89+
if (!this.subMenuProps) {
90+
this.subMenuProps = { items: items };
91+
} else {
92+
this.subMenuProps.items = items;
93+
}
94+
}
4295
}

libs/fabric/src/components/command-bar/directives/command-bar-items-base.directive.ts

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import {
77
Output,
88
EventEmitter,
99
} from '@angular/core';
10-
import { CommandBarItemDirective, CommandBarItemPropertiesChangedPayload } from './command-bar-item.directive';
10+
import { CommandBarItemDirective, CommandBarItemChangedPayload } from './command-bar-item.directive';
1111
import { ICommandBarItemOptions } from '../command-bar.component';
1212
import { Subscription } from 'rxjs';
1313

@@ -16,19 +16,15 @@ export abstract class CommandBarItemsDirectiveBase implements AfterContentInit,
1616

1717
abstract readonly directiveItems: QueryList<CommandBarItemDirective>;
1818

19-
@Output() readonly onItemChanged = new EventEmitter<CommandBarItemPropertiesChangedPayload>();
19+
@Output() readonly onItemChanged = new EventEmitter<CommandBarItemChangedPayload>();
2020

2121
get items() {
2222
return (
2323
this.directiveItems &&
2424
this.directiveItems.map<ICommandBarItemOptions>(directiveItem => ({
25-
key: directiveItem.key,
26-
text: directiveItem.text,
27-
iconProps: directiveItem.iconProps,
28-
iconOnly: directiveItem.iconOnly,
29-
disabled: directiveItem.disabled,
25+
...directiveItem,
3026
onClick: (ev, item) => {
31-
directiveItem.onClick.emit({
27+
directiveItem.click.emit({
3228
ev: ev && ev.nativeEvent,
3329
item: item,
3430
});
@@ -40,6 +36,7 @@ export abstract class CommandBarItemsDirectiveBase implements AfterContentInit,
4036
ngAfterContentInit() {
4137
this._subscriptions.push(
4238
...this.directiveItems.map(directiveItem =>
39+
// Propagate the change to the parent CommandBarComponent
4340
directiveItem.onItemChanged.subscribe(changes => this.onItemChangedHandler(changes))
4441
)
4542
);
@@ -49,7 +46,7 @@ export abstract class CommandBarItemsDirectiveBase implements AfterContentInit,
4946
this._subscriptions.forEach(subscription => subscription.unsubscribe());
5047
}
5148

52-
private onItemChangedHandler(payload: CommandBarItemPropertiesChangedPayload) {
49+
private onItemChangedHandler(payload: CommandBarItemChangedPayload) {
5350
this.onItemChanged.emit(payload);
5451
}
5552
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import { registerElement } from '@angular-react/core';
2+
import { CommonModule } from '@angular/common';
3+
import { NgModule, NO_ERRORS_SCHEMA } from '@angular/core';
4+
import { CommandBar } from 'office-ui-fabric-react/lib/CommandBar';
5+
6+
import { ContextualMenuItemDirective } from './directives/contextual-menu-item.directive';
7+
8+
const components = [ContextualMenuItemDirective];
9+
10+
@NgModule({
11+
imports: [CommonModule],
12+
declarations: components,
13+
exports: components,
14+
schemas: [NO_ERRORS_SCHEMA],
15+
})
16+
export class FabContextualMenuModule {
17+
constructor() {}
18+
}
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
import { Directive, Input, EventEmitter, Output, QueryList, ContentChildren, AfterContentInit } from '@angular/core';
2+
import { IContextualMenuItem } from 'office-ui-fabric-react';
3+
import { ItemChangedPayload } from '../../core/declarative/item-changed.payload';
4+
import { OnChanges, TypedChanges } from '../../../types/angular/typed-changes';
5+
6+
export type ContextualMenuItemChangedPayload = ItemChangedPayload<IContextualMenuItem['key'], IContextualMenuItem>;
7+
8+
@Directive({ selector: 'contextual-menu-item' })
9+
export class ContextualMenuItemDirective
10+
implements IContextualMenuItem, OnChanges<ContextualMenuItemDirective>, AfterContentInit {
11+
@ContentChildren(ContextualMenuItemDirective) readonly menuItemsDirectives: QueryList<ContextualMenuItemDirective>;
12+
13+
@Input() key: IContextualMenuItem['key'];
14+
@Input() componentRef?: IContextualMenuItem['componentRef'];
15+
@Input() text?: IContextualMenuItem['text'];
16+
@Input() secondaryText?: IContextualMenuItem['secondaryText'];
17+
@Input() itemType?: IContextualMenuItem['itemType'];
18+
@Input() iconProps?: IContextualMenuItem['iconProps'];
19+
@Input() onRenderIcon?: IContextualMenuItem['onRenderIcon'];
20+
@Input() submenuIconProps?: IContextualMenuItem['submenuIconProps'];
21+
@Input() disabled?: IContextualMenuItem['disabled'];
22+
@Input() primaryDisabled?: IContextualMenuItem['primaryDisabled'];
23+
@Input() shortCut?: IContextualMenuItem['shortCut'];
24+
@Input() canCheck?: IContextualMenuItem['canCheck'];
25+
@Input() checked?: IContextualMenuItem['checked'];
26+
@Input() split?: IContextualMenuItem['split'];
27+
@Input() data?: IContextualMenuItem['data'];
28+
@Input() href?: IContextualMenuItem['href'];
29+
@Input() target?: IContextualMenuItem['target'];
30+
@Input() rel?: IContextualMenuItem['rel'];
31+
@Input() subMenuProps?: IContextualMenuItem['subMenuProps'];
32+
@Input() getItemClassNames?: IContextualMenuItem['getItemClassNames'];
33+
@Input() getSplitButtonVerticalDividerClassNames?: IContextualMenuItem['getSplitButtonVerticalDividerClassNames'];
34+
@Input() sectionProps?: IContextualMenuItem['sectionProps'];
35+
@Input() className?: IContextualMenuItem['className'];
36+
@Input() style?: IContextualMenuItem['style'];
37+
@Input() ariaLabel?: IContextualMenuItem['ariaLabel'];
38+
@Input() title?: IContextualMenuItem['title'];
39+
@Input() onRender?: IContextualMenuItem['onRender'];
40+
@Input() onMouseDown?: IContextualMenuItem['onMouseDown'];
41+
@Input() role?: IContextualMenuItem['role'];
42+
@Input() customOnRenderListLength?: IContextualMenuItem['customOnRenderListLength'];
43+
@Input() keytipProps?: IContextualMenuItem['keytipProps'];
44+
@Input() inactive?: IContextualMenuItem['inactive'];
45+
@Input() name?: IContextualMenuItem['name'];
46+
47+
@Output() readonly click = new EventEmitter<{ ev?: MouseEvent | KeyboardEvent; item?: IContextualMenuItem }>();
48+
49+
// Directive properties
50+
@Output() readonly onItemChanged = new EventEmitter<ContextualMenuItemChangedPayload>();
51+
52+
ngOnChanges(changes: TypedChanges<this>) {
53+
this.onItemChanged.emit({
54+
key: this.key,
55+
changes,
56+
});
57+
}
58+
59+
ngAfterContentInit() {
60+
// Currently @ContentChildren selects host component as well.
61+
// Relevant Github issue: https://github.com/angular/angular/issues/10098
62+
const nonSelfMenuItemsDirectives = this.menuItemsDirectives.filter(directive => directive !== this);
63+
if (nonSelfMenuItemsDirectives.length === 0) {
64+
return;
65+
}
66+
67+
const items = nonSelfMenuItemsDirectives.map<IContextualMenuItem>(directiveItem => ({
68+
...directiveItem,
69+
onClick: (ev, item) => {
70+
directiveItem.click.emit({
71+
ev: ev && ev.nativeEvent,
72+
item: item,
73+
});
74+
},
75+
}));
76+
77+
if (!this.subMenuProps) {
78+
this.subMenuProps = { items: items };
79+
} else {
80+
this.subMenuProps.items = items;
81+
}
82+
}
83+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from './public-api';
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export * from './contextual-menu.module';
2+
export * from './directives/contextual-menu-item.directive';

0 commit comments

Comments
 (0)