Update combobox and calendar with directives (#95)
Add directives for the ComboBox and Calendar to enable the Angular-style template localization pattern. Change wrapper to fix some timing bugs
This commit is contained in:
Родитель
87863d96b2
Коммит
8ccbaa5e3e
|
@ -142,6 +142,30 @@
|
|||
|
||||
<fab-calendar [strings]="strings" (onSelectDate)="onSelectDate($event)"></fab-calendar>
|
||||
|
||||
<fab-calendar (onSelectDate)="onSelectDate($event)">
|
||||
<fab-calendar-strings
|
||||
[months]="[
|
||||
'January',
|
||||
'Fabruary',
|
||||
'March',
|
||||
'April',
|
||||
'May',
|
||||
'June',
|
||||
'July',
|
||||
'August',
|
||||
'September',
|
||||
'October',
|
||||
'November',
|
||||
'December'
|
||||
]"
|
||||
[shortMonths]="['Jan', 'Fab', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']"
|
||||
[days]="['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday']"
|
||||
[shortDays]="['S', 'M', 'T', 'W', 'T', 'F', 'S']"
|
||||
goToToday="Go to tomorrow"
|
||||
weekNumberFormatString="Week number {0}"
|
||||
></fab-calendar-strings>
|
||||
</fab-calendar>
|
||||
|
||||
<fab-marquee-selection [isEnabled]="marqueeEnabled" [selection]="selection">
|
||||
<fab-details-list
|
||||
[selection]="selection"
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<h1>Microsoft Fabric [React] Components</h1>
|
||||
|
||||
<h2>Button</h2>
|
||||
<fab-default-button text="Toggle Disabled" (onClick)="toggle()"></fab-default-button>
|
||||
<fab-default-button text="Toggle Disabled" (onClick)="toggle()" contentStyle="margin-right: 10px;"></fab-default-button>
|
||||
<fab-default-button
|
||||
[disabled]="disabled"
|
||||
(onClick)="click()"
|
||||
|
@ -22,7 +22,46 @@
|
|||
>
|
||||
{{ sampleContent2 }} {{ sampleContent3 }}
|
||||
<fab-dialog-footer>
|
||||
<fab-default-button (onClick)="clickSave()" text="Save" contentStyle="margin-right: 10px;"></fab-default-button>
|
||||
<fab-default-button (onClick)="clickSave()" text="Save"></fab-default-button>
|
||||
<fab-default-button (onClick)="toggleDialog()" text="Cancel"></fab-default-button>
|
||||
</fab-dialog-footer>
|
||||
</fab-dialog>
|
||||
|
||||
<hr />
|
||||
|
||||
<h2>Combo box</h2>
|
||||
<fab-combo-box contentStyle="width: 300px;" (onChange)="comboChange($event)">
|
||||
<options>
|
||||
<fab-combo-box-option optionKey="A" text="See option A"></fab-combo-box-option>
|
||||
<fab-combo-box-option optionKey="B" text="See option B"></fab-combo-box-option>
|
||||
</options>
|
||||
</fab-combo-box>
|
||||
{{ selectedComboBoxKey }}: {{ selectedComboBoxValue }}
|
||||
|
||||
<hr />
|
||||
|
||||
<h2>Directive Calendar</h2>
|
||||
<fab-calendar (onSelectDate)="onSelectDate($event)">
|
||||
<fab-calendar-strings
|
||||
[months]="[
|
||||
'January',
|
||||
'Fabruary',
|
||||
'March',
|
||||
'April',
|
||||
'May',
|
||||
'June',
|
||||
'July',
|
||||
'August',
|
||||
'September',
|
||||
'October',
|
||||
'November',
|
||||
'December'
|
||||
]"
|
||||
[shortMonths]="['Jan', 'Fab', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']"
|
||||
[days]="['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday']"
|
||||
[shortDays]="['S', 'M', 'T', 'W', 'T', 'F', 'S']"
|
||||
goToToday="Go to tomorrow"
|
||||
weekNumberFormatString="Week number {0}"
|
||||
></fab-calendar-strings>
|
||||
</fab-calendar>
|
||||
{{ selectedDate }}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import { Component } from '@angular/core';
|
||||
import { IComboBoxOption, ICalendarStrings } from 'office-ui-fabric-react';
|
||||
|
||||
@Component({
|
||||
selector: 'app-fabric',
|
||||
|
@ -12,6 +13,23 @@ export class FabricComponent {
|
|||
secondsCounter = 0;
|
||||
sampleContent2 = '0 Seconds Passed';
|
||||
sampleContent3 = '';
|
||||
selectedComboBoxKey: string = "None";
|
||||
selectedComboBoxValue: string = "None";
|
||||
selectedDate: Date;
|
||||
|
||||
comboBoxOptions: IComboBoxOption[] = [
|
||||
{ key: 'A', text: 'See option A' },
|
||||
{ key: 'B', text: 'See option B' },
|
||||
];
|
||||
|
||||
onSelectDate(event) {
|
||||
this.selectedDate = event.date;
|
||||
}
|
||||
|
||||
comboChange(event) {
|
||||
this.selectedComboBoxKey = event.option.key;
|
||||
this.selectedComboBoxValue = event.option.text;
|
||||
}
|
||||
|
||||
get sampleContent() {
|
||||
return `Button clicked ${this.sampleContentCounter} times.`;
|
||||
|
|
|
@ -1,7 +1,17 @@
|
|||
import { NgModule } from '@angular/core';
|
||||
import { FabButtonModule, FabDialogModule } from '@angular-react/fabric';
|
||||
import {
|
||||
FabButtonModule,
|
||||
FabDialogModule,
|
||||
FabComboBoxModule,
|
||||
FabCalendarModule
|
||||
} from '@angular-react/fabric';
|
||||
|
||||
const componentModules = [FabButtonModule, FabDialogModule];
|
||||
const componentModules = [
|
||||
FabButtonModule,
|
||||
FabDialogModule,
|
||||
FabComboBoxModule,
|
||||
FabCalendarModule
|
||||
];
|
||||
@NgModule({
|
||||
imports: componentModules,
|
||||
exports: componentModules,
|
||||
|
|
|
@ -97,7 +97,7 @@ export abstract class ReactWrapperComponent<TProps extends {}> implements AfterV
|
|||
this._contentClass = value;
|
||||
if (isReactNode(this.reactNodeRef.nativeElement)) {
|
||||
this.reactNodeRef.nativeElement.setProperty('className', classnames(value));
|
||||
this.changeDetectorRef.detectChanges();
|
||||
this.markForCheck();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -118,7 +118,7 @@ export abstract class ReactWrapperComponent<TProps extends {}> implements AfterV
|
|||
if (isReactNode(this.reactNodeRef.nativeElement)) {
|
||||
const stringValue = typeof value === 'string' ? value : stylenames(value);
|
||||
this.reactNodeRef.nativeElement.setProperty('style', toStyle(stringValue));
|
||||
this.changeDetectorRef.detectChanges();
|
||||
this.markForCheck();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -142,9 +142,11 @@ export abstract class ReactWrapperComponent<TProps extends {}> implements AfterV
|
|||
this._shouldSetHostDisplay = setHostDisplay;
|
||||
}
|
||||
|
||||
ngAfterViewInit() {
|
||||
ngAfterContentInit() {
|
||||
this._passAttributesAsProps();
|
||||
}
|
||||
|
||||
ngAfterViewInit() {
|
||||
if (this._shouldSetHostDisplay) {
|
||||
this._setHostDisplay();
|
||||
}
|
||||
|
|
|
@ -129,6 +129,7 @@ export abstract class FabBaseButtonComponent extends ReactWrapperComponent<IButt
|
|||
|
||||
setItems(this.menuItemsDirectives.toArray());
|
||||
}
|
||||
super.ngAfterContentInit();
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
|
|
|
@ -12,8 +12,11 @@ import {
|
|||
Output,
|
||||
Renderer2,
|
||||
ViewChild,
|
||||
ContentChild,
|
||||
AfterContentInit,
|
||||
} from '@angular/core';
|
||||
import { ICalendarProps } from 'office-ui-fabric-react/lib/Calendar';
|
||||
import { CalendarStringsDirective } from './directives/calendar-strings-directive.component';
|
||||
|
||||
@Component({
|
||||
selector: 'fab-calendar',
|
||||
|
@ -57,7 +60,7 @@ import { ICalendarProps } from 'office-ui-fabric-react/lib/Calendar';
|
|||
styles: ['react-renderer'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class FabCalendarComponent extends ReactWrapperComponent<ICalendarProps> {
|
||||
export class FabCalendarComponent extends ReactWrapperComponent<ICalendarProps> implements AfterContentInit {
|
||||
@ViewChild('reactNode') protected reactNodeRef: ElementRef;
|
||||
|
||||
@Input() componentRef?: ICalendarProps['componentRef'];
|
||||
|
@ -91,14 +94,22 @@ export class FabCalendarComponent extends ReactWrapperComponent<ICalendarProps>
|
|||
@Output() readonly onSelectDate = new EventEmitter<{ date: Date; selectedDateRangeArray?: Date[] }>();
|
||||
@Output() readonly onDismiss = new EventEmitter<void>();
|
||||
|
||||
@ContentChild(CalendarStringsDirective) readonly calendarStringsDirective?: CalendarStringsDirective;
|
||||
|
||||
constructor(elementRef: ElementRef, changeDetectorRef: ChangeDetectorRef, renderer: Renderer2) {
|
||||
super(elementRef, changeDetectorRef, renderer);
|
||||
|
||||
// coming from React context - we need to bind to this so we can access the Angular Component properties
|
||||
this.onSelectDateHandler = this.onSelectDateHandler.bind(this);
|
||||
this.onDismissHandler = this.onDismissHandler.bind(this);
|
||||
}
|
||||
|
||||
ngAfterContentInit() {
|
||||
if (this.calendarStringsDirective) {
|
||||
this._initDirective(this.calendarStringsDirective);
|
||||
super.ngAfterContentInit();
|
||||
}
|
||||
}
|
||||
|
||||
onSelectDateHandler(date: Date, selectedDateRangeArray?: Date[]) {
|
||||
this.onSelectDate.emit({
|
||||
date,
|
||||
|
@ -109,4 +120,8 @@ export class FabCalendarComponent extends ReactWrapperComponent<ICalendarProps>
|
|||
onDismissHandler() {
|
||||
this.onDismiss.emit();
|
||||
}
|
||||
|
||||
private _initDirective(calendarStringsDirective: CalendarStringsDirective) {
|
||||
this.strings = calendarStringsDirective.strings;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,16 +8,17 @@ import * as CalendarCss from 'office-ui-fabric-react/lib-amd/components/Calendar
|
|||
import { Calendar } from 'office-ui-fabric-react';
|
||||
import { noop } from '../../utils/noop';
|
||||
import { FabCalendarComponent } from './calendar.component';
|
||||
import { CalendarStringsDirective } from './directives/calendar-strings-directive.component';
|
||||
|
||||
// Dummy action to force CalendarCss to load and not be tree-shaken away.
|
||||
noop(CalendarCss);
|
||||
|
||||
const components = [FabCalendarComponent];
|
||||
const declarations = [FabCalendarComponent, CalendarStringsDirective];
|
||||
|
||||
@NgModule({
|
||||
imports: [CommonModule],
|
||||
declarations: components,
|
||||
exports: components,
|
||||
declarations: declarations,
|
||||
exports: declarations,
|
||||
schemas: [NO_ERRORS_SCHEMA],
|
||||
})
|
||||
export class FabCalendarModule {
|
||||
|
|
|
@ -0,0 +1,31 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
import { Directive, Input } from '@angular/core';
|
||||
import { ICalendarStrings } from 'office-ui-fabric-react';
|
||||
|
||||
/**
|
||||
* Wrapper directive for calendar strings
|
||||
*/
|
||||
@Directive({ selector: 'fab-calendar > fab-calendar-strings' })
|
||||
export class CalendarStringsDirective {
|
||||
|
||||
@Input() months: ICalendarStrings['months'];
|
||||
@Input() shortMonths: ICalendarStrings['shortMonths'];
|
||||
@Input() days: ICalendarStrings['days'];
|
||||
@Input() shortDays: ICalendarStrings['shortDays'];
|
||||
@Input() goToToday: ICalendarStrings['goToToday'];
|
||||
@Input() weekNumberFormatString: ICalendarStrings['weekNumberFormatString'];
|
||||
|
||||
|
||||
get strings(): ICalendarStrings {
|
||||
return {
|
||||
months: this.months,
|
||||
shortMonths: this.shortMonths,
|
||||
days: this.days,
|
||||
shortDays: this.shortDays,
|
||||
goToToday: this.goToToday,
|
||||
weekNumberFormatString: this.weekNumberFormatString
|
||||
}
|
||||
}
|
||||
}
|
|
@ -2,10 +2,26 @@
|
|||
// Licensed under the MIT License.
|
||||
|
||||
import { InputRendererOptions, JsxRenderFunc, ReactWrapperComponent } from '@angular-react/core';
|
||||
import { ChangeDetectorRef, ElementRef, EventEmitter, Input, NgZone, OnInit, Output, Renderer2 } from '@angular/core';
|
||||
import {
|
||||
ChangeDetectorRef,
|
||||
ElementRef,
|
||||
EventEmitter,
|
||||
Input,
|
||||
NgZone,
|
||||
OnInit,
|
||||
Output,
|
||||
Renderer2,
|
||||
ContentChild,
|
||||
AfterContentInit,
|
||||
} from '@angular/core';
|
||||
import { IComboBox, IComboBoxOption, IComboBoxProps } from 'office-ui-fabric-react/lib/ComboBox';
|
||||
import { ComboBoxOptionDirective } from './directives/combo-box-option.directive';
|
||||
import { ComboBoxOptionsDirective } from './directives/combo-box-options.directive';
|
||||
|
||||
export abstract class FabBaseComboBoxComponent extends ReactWrapperComponent<IComboBoxProps>
|
||||
implements OnInit, AfterContentInit {
|
||||
@ContentChild(ComboBoxOptionDirective) readonly optionsDirective?: ComboBoxOptionDirective;
|
||||
|
||||
export abstract class FabBaseComboBoxComponent extends ReactWrapperComponent<IComboBoxProps> implements OnInit {
|
||||
@Input() componentRef?: IComboBoxProps['componentRef'];
|
||||
@Input() options: IComboBoxProps['options'];
|
||||
@Input() allowFreeform?: IComboBoxProps['allowFreeform'];
|
||||
|
@ -49,6 +65,8 @@ export abstract class FabBaseComboBoxComponent extends ReactWrapperComponent<ICo
|
|||
@Output() readonly onMenuDismissed = new EventEmitter<void>();
|
||||
@Output() readonly onScrollToItem = new EventEmitter<{ itemIndex: number }>();
|
||||
|
||||
@ContentChild(ComboBoxOptionsDirective) readonly comboBoxOptionsDirective?: ComboBoxOptionsDirective;
|
||||
|
||||
onRenderLowerContent: (props?: IComboBoxProps, defaultRender?: JsxRenderFunc<IComboBoxProps>) => JSX.Element;
|
||||
|
||||
constructor(elementRef: ElementRef, changeDetectorRef: ChangeDetectorRef, renderer: Renderer2, ngZone: NgZone) {
|
||||
|
@ -65,6 +83,13 @@ export abstract class FabBaseComboBoxComponent extends ReactWrapperComponent<ICo
|
|||
this.onRenderLowerContent = this.createRenderPropHandler(this.renderLowerContent);
|
||||
}
|
||||
|
||||
ngAfterContentInit() {
|
||||
if (this.comboBoxOptionsDirective) {
|
||||
this._initDirective(this.comboBoxOptionsDirective);
|
||||
}
|
||||
super.ngAfterContentInit();
|
||||
}
|
||||
|
||||
onItemClickHandler(event: React.FormEvent<IComboBox>, option?: IComboBoxOption, index?: number) {
|
||||
this.onItemClick.emit({
|
||||
event: event.nativeEvent,
|
||||
|
@ -95,4 +120,9 @@ export abstract class FabBaseComboBoxComponent extends ReactWrapperComponent<ICo
|
|||
itemIndex,
|
||||
});
|
||||
}
|
||||
|
||||
private _initDirective(directive: ComboBoxOptionsDirective) {
|
||||
this.options = directive.items;
|
||||
this.markForCheck();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,13 +7,20 @@ import { NgModule, NO_ERRORS_SCHEMA } from '@angular/core';
|
|||
import { ComboBox, VirtualizedComboBox } from 'office-ui-fabric-react';
|
||||
import { FabComboBoxComponent } from './combo-box.component';
|
||||
import { FabVirtualizedComboBoxComponent } from './virtualized-combo-box.component';
|
||||
import { ComboBoxOptionDirective } from './directives/combo-box-option.directive';
|
||||
import { ComboBoxOptionsDirective } from './directives/combo-box-options.directive';
|
||||
|
||||
const components = [FabComboBoxComponent, FabVirtualizedComboBoxComponent];
|
||||
const declarations = [
|
||||
FabComboBoxComponent,
|
||||
FabVirtualizedComboBoxComponent,
|
||||
ComboBoxOptionDirective,
|
||||
ComboBoxOptionsDirective
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
imports: [CommonModule],
|
||||
declarations: components,
|
||||
exports: components,
|
||||
declarations: declarations,
|
||||
exports: declarations,
|
||||
schemas: [NO_ERRORS_SCHEMA],
|
||||
})
|
||||
export class FabComboBoxModule {
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
import { Directive, Input } from '@angular/core';
|
||||
import { IComboBoxOption } from 'office-ui-fabric-react';
|
||||
|
||||
/**
|
||||
* Wrapper directive for creating a ComboBoxOption
|
||||
* @paramName optionKey Binds to React 'key' property.
|
||||
* Name change necessary because key is a reserved attribute in the wrapper component.
|
||||
*/
|
||||
@Directive({ selector: 'fab-combo-box-option' })
|
||||
export class ComboBoxOptionDirective {
|
||||
@Input() optionKey: IComboBoxOption['key'];
|
||||
@Input() text: IComboBoxOption['text'];
|
||||
@Input() title?: IComboBoxOption['title'];
|
||||
@Input() itemType: IComboBoxOption['itemType'];
|
||||
@Input() index?: IComboBoxOption['index'];
|
||||
@Input() ariaLabel?: IComboBoxOption['ariaLabel'];
|
||||
@Input() selected?: IComboBoxOption['selected'];
|
||||
@Input() disabled?: IComboBoxOption['disabled'];
|
||||
@Input() data?: IComboBoxOption['data'];
|
||||
@Input() styles?: IComboBoxOption['styles'];
|
||||
@Input() useAriaLabelAsText?: IComboBoxOption['useAriaLabelAsText'];
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
import { ContentChildren, Directive, QueryList } from '@angular/core';
|
||||
import { IComboBoxOption } from 'office-ui-fabric-react';
|
||||
|
||||
import { ComboBoxOptionDirective } from "./combo-box-option.directive";
|
||||
|
||||
/**
|
||||
* Wrapper directive for creating multiple ComboBoxOptions
|
||||
*/
|
||||
@Directive({ selector: 'fab-combo-box > options' })
|
||||
export class ComboBoxOptionsDirective {
|
||||
@ContentChildren(ComboBoxOptionDirective) readonly directiveItems: QueryList<ComboBoxOptionDirective>;
|
||||
|
||||
get items() {
|
||||
return this.directiveItems.map<IComboBoxOption>(directiveItem => {
|
||||
return {
|
||||
key: directiveItem.optionKey,
|
||||
text: directiveItem.text,
|
||||
title: directiveItem.title,
|
||||
itemType: directiveItem.itemType,
|
||||
index: directiveItem.index,
|
||||
ariaLabel: directiveItem.ariaLabel,
|
||||
selected: directiveItem.selected,
|
||||
disabled: directiveItem.disabled,
|
||||
data: directiveItem.data,
|
||||
styles: directiveItem.styles,
|
||||
useAriaLabelAsText: directiveItem.useAriaLabelAsText
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
|
@ -127,6 +127,7 @@ export class FabCommandBarComponent extends ReactWrapperComponent<ICommandBarPro
|
|||
if (this.itemsDirective) this._initDirective(this.itemsDirective, 'items');
|
||||
if (this.farItemsDirective) this._initDirective(this.farItemsDirective, 'farItems');
|
||||
if (this.overflowItemsDirective) this._initDirective(this.overflowItemsDirective, 'overflowItems');
|
||||
super.ngAfterContentInit();
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
|
|
|
@ -251,6 +251,7 @@ export class FabDetailsListComponent extends ReactWrapperComponent<IDetailsListP
|
|||
if (this.groupsDirective) {
|
||||
this._initDirective(this.groupsDirective, 'groups');
|
||||
}
|
||||
super.ngAfterContentInit();
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
|
|
Загрузка…
Ссылка в новой задаче