From 03cb979d11de76c437924c2fe8d176c2490feda5 Mon Sep 17 00:00:00 2001 From: Amy Nguyen Date: Wed, 14 Mar 2018 13:26:19 -0700 Subject: [PATCH 1/7] create new suggestions, suggestionsControl, and suggestionsStore in experiments --- .../ExtendedPeoplePicker.Basic.Example.tsx | 69 ++- .../FloatingPicker/BaseFloatingPicker.tsx | 153 ++---- .../BaseFloatingPicker.types.ts | 30 +- .../FloatingPeoplePicker.Basic.Example.tsx | 33 +- .../Suggestions/Suggestions.scss | 120 +++++ .../Suggestions/Suggestions.tsx | 188 +++++++ .../Suggestions/Suggestions.types.ts | 123 +++++ .../Suggestions/SuggestionsControl.tsx | 466 ++++++++++++++++++ .../Suggestions/SuggestionsStore.ts | 59 +++ .../src/components/FloatingPicker/index.ts | 6 +- .../pickers/Suggestions/Suggestions.tsx | 9 +- .../pickers/Suggestions/Suggestions.types.ts | 1 + 12 files changed, 1116 insertions(+), 141 deletions(-) create mode 100644 packages/experiments/src/components/FloatingPicker/Suggestions/Suggestions.scss create mode 100644 packages/experiments/src/components/FloatingPicker/Suggestions/Suggestions.tsx create mode 100644 packages/experiments/src/components/FloatingPicker/Suggestions/Suggestions.types.ts create mode 100644 packages/experiments/src/components/FloatingPicker/Suggestions/SuggestionsControl.tsx create mode 100644 packages/experiments/src/components/FloatingPicker/Suggestions/SuggestionsStore.ts diff --git a/packages/experiments/src/components/ExtendedPicker/examples/ExtendedPeoplePicker.Basic.Example.tsx b/packages/experiments/src/components/ExtendedPicker/examples/ExtendedPeoplePicker.Basic.Example.tsx index cd13f19e5e7a26..7a7c2fe15708ed 100644 --- a/packages/experiments/src/components/ExtendedPicker/examples/ExtendedPeoplePicker.Basic.Example.tsx +++ b/packages/experiments/src/components/ExtendedPicker/examples/ExtendedPeoplePicker.Basic.Example.tsx @@ -7,19 +7,19 @@ import { autobind } from 'office-ui-fabric-react/lib/Utilities'; import { IPersonaProps } from 'office-ui-fabric-react/lib/Persona'; -import { IBasePickerSuggestionsProps, SuggestionsController } from 'office-ui-fabric-react/lib/Pickers'; import { ExtendedPeoplePicker } from '../PeoplePicker/ExtendedPeoplePicker'; import { PrimaryButton } from 'office-ui-fabric-react/lib/Button'; import { IPersonaWithMenu } from 'office-ui-fabric-react/lib/components/pickers/PeoplePicker/PeoplePickerItems/PeoplePickerItem.types'; import { people, mru, groupOne, groupTwo } from './PeopleExampleData'; import './ExtendedPeoplePicker.Basic.Example.scss'; -import { FloatingPeoplePicker, IBaseFloatingPickerProps } from '../../FloatingPicker'; +import { SuggestionsStore, FloatingPeoplePicker, IBaseFloatingPickerProps, IBaseFloatingPickerSuggestionProps } from '../../FloatingPicker'; import { IBaseSelectedItemsListProps, ISelectedPeopleProps, SelectedPeopleList, IExtendedPersonaProps } from '../../SelectedItemsList'; export interface IPeoplePickerExampleState { peopleList: IPersonaProps[]; mostRecentlyUsed: IPersonaProps[]; + searchMoreAvailable: boolean; } // tslint:disable-next-line:no-any @@ -27,7 +27,7 @@ export class ExtendedPeoplePickerTypesExample extends BaseComponent<{}, IPeopleP private _picker: ExtendedPeoplePicker; private _floatingPickerProps: IBaseFloatingPickerProps; private _selectedItemsListProps: ISelectedPeopleProps; - private _suggestionProps: IBasePickerSuggestionsProps; + private _suggestionProps: IBaseFloatingPickerSuggestionProps; constructor(props: {}) { super(props); @@ -42,22 +42,59 @@ export class ExtendedPeoplePickerTypesExample extends BaseComponent<{}, IPeopleP this.state = { peopleList: peopleList, mostRecentlyUsed: mru, + searchMoreAvailable: true, }; this._suggestionProps = { - suggestionsHeaderText: 'Suggested People', - mostRecentlyUsedHeaderText: 'Suggested Contacts', - noResultsFoundText: 'No results found', - loadingText: 'Loading', - showRemoveButtons: true, - suggestionsAvailableAlertText: 'People Picker Suggestions available', - suggestionsContainerAriaLabel: 'Suggested contacts', - searchForMoreText: 'Search more', - forceResolveText: 'Use this name', + headerItemsProps: [{ + renderItem: () => { + return ( +
Use this address: { this._picker + && this._picker.inputElement + && this._picker.inputElement ? this._picker.inputElement.value : '' }
+ ); + }, + shouldShow: () => { + return this._picker !== undefined + && this._picker.inputElement !== null + && this._picker.inputElement.value.indexOf('@') > -1; + }, + onExecute: () => { this._picker.floatingPicker.forceResolveSuggestion(); } + }, + { + renderItem: () => { + return ( +
Suggested Contacts
+ ); + }, + shouldShow: () => { + return this._picker !== undefined + && this._picker.inputElement !== null + && this._picker.inputElement.value === ''; + } + } + ], + footerItemsProps: [{ + renderItem: () => { + return ( +
No results
+ ); + }, + shouldShow: () => { + return this._picker !== undefined + && this._picker.floatingPicker !== undefined + && this._picker.floatingPicker.suggestions.length === 0; + } + }, + { + renderItem: () => { return (
Search for more
); }, + onExecute: () => { this.setState({ searchMoreAvailable: false }); }, + shouldShow: () => { return this.state.searchMoreAvailable; } + }], }; this._floatingPickerProps = { - suggestionsController: new SuggestionsController(), + suggestionsStore: new SuggestionsStore(), onResolveSuggestions: this._onFilterChanged, getTextFromItem: this._getTextFromItem, pickerSuggestionsProps: this._suggestionProps, @@ -66,6 +103,7 @@ export class ExtendedPeoplePickerTypesExample extends BaseComponent<{}, IPeopleP onValidateInput: this._validateInput, onZeroQuerySuggestion: this._returnMostRecentlyUsed, showForceResolve: this._shouldShowForceResolve, + onInputChanged: this._onInputChanged, }; this._selectedItemsListProps = { @@ -221,6 +259,11 @@ export class ExtendedPeoplePickerTypesExample extends BaseComponent<{}, IPeopleP return personas.filter((persona: IPersonaProps) => !this._listContainsPersona(persona, possibleDupes)); } + @autobind + private _onInputChanged(): void { + this.setState({ searchMoreAvailable: true }); + } + private _getTextFromItem(persona: IPersonaProps): string { return persona.primaryText as string; } diff --git a/packages/experiments/src/components/FloatingPicker/BaseFloatingPicker.tsx b/packages/experiments/src/components/FloatingPicker/BaseFloatingPicker.tsx index 5767a8170ce641..db3fcef8f2183d 100644 --- a/packages/experiments/src/components/FloatingPicker/BaseFloatingPicker.tsx +++ b/packages/experiments/src/components/FloatingPicker/BaseFloatingPicker.tsx @@ -9,9 +9,12 @@ import { RefObject } from '../../Utilities'; import { Callout, DirectionalHint } from 'office-ui-fabric-react/lib/Callout'; -import { Suggestions, ISuggestionsProps, SuggestionsController, IBasePickerSuggestionsProps, ISuggestionModel } +import { ISuggestionModel } from 'office-ui-fabric-react/lib/Pickers'; -import { IBaseFloatingPicker, IBaseFloatingPickerProps } from './BaseFloatingPicker.types'; +import { IBaseFloatingPicker, IBaseFloatingPickerProps, IBaseFloatingPickerSuggestionProps } from './BaseFloatingPicker.types'; +import { ISuggestionsControlProps } from './Suggestions/Suggestions.types'; +import { SuggestionsControl } from './Suggestions/SuggestionsControl'; +import { SuggestionsStore } from './Suggestions/SuggestionsStore'; import * as stylesImport from './BaseFloatingPicker.scss'; // tslint:disable-next-line:no-any const styles: any = stylesImport; @@ -33,11 +36,10 @@ export class BaseFloatingPicker> extend protected selection: Selection; protected root: RefObject = createRef(); - protected suggestionElement: RefObject> = createRef>(); - - protected suggestionStore: SuggestionsController; - protected SuggestionOfProperType: new (props: ISuggestionsProps) => Suggestions = - Suggestions as new (props: ISuggestionsProps) => Suggestions; + protected suggestionStore: SuggestionsStore; + protected suggestionsControl: SuggestionsControl; + protected SuggestionsControlOfProperType: new (props: ISuggestionsControlProps) => SuggestionsControl = + SuggestionsControl as new (props: ISuggestionsControlProps) => SuggestionsControl; protected loadingTimer: number | undefined; // tslint:disable-next-line:no-any protected currentPromise: PromiseLike; @@ -45,7 +47,7 @@ export class BaseFloatingPicker> extend constructor(basePickerProps: P) { super(basePickerProps); - this.suggestionStore = basePickerProps.suggestionsController; + this.suggestionStore = basePickerProps.suggestionsStore; this.state = { queryString: '', suggestedDisplayValue: '', @@ -66,7 +68,7 @@ export class BaseFloatingPicker> extend } public forceResolveSuggestion(): void { - if (this.suggestionStore.hasSelectedSuggestion()) { + if (this.suggestionsControl.hasSuggestionSelected()) { this.completeSuggestion(); } else { this._onValidateInput(); @@ -133,22 +135,10 @@ export class BaseFloatingPicker> extend this.setState({ suggestionsVisible: false }); } - public completeSuggestion(): void { - if (this.suggestionStore.hasSelectedSuggestion()) { - this.onChange(this.suggestionStore.currentSuggestion!.item); - } - } - @autobind - public refocusSuggestions(keyCode: KeyCodes): void { - if (this.suggestionStore.suggestions && this.suggestionStore.suggestions.length > 0) { - if (keyCode === KeyCodes.up) { - this.suggestionStore.setSelectedSuggestion( - this.suggestionStore.suggestions.length - 1 - ); - } else if (keyCode === KeyCodes.down) { - this.suggestionStore.setSelectedSuggestion(0); - } + public completeSuggestion(): void { + if (this.suggestionsControl.hasSuggestionSelected()) { + this.onChange(this.suggestionsControl.currentSuggestion!.item); } } @@ -165,7 +155,7 @@ export class BaseFloatingPicker> extend } protected renderSuggestions(): JSX.Element | null { - let TypedSuggestion = this.SuggestionOfProperType; + let TypedSuggestionsControl = this.SuggestionsControlOfProperType; return this.state.suggestionsVisible ? ( > extend } calloutWidth={ this.props.calloutWidth ? this.props.calloutWidth : 0 } > - ) : null; } protected onSuggestionSelect(): void { - if (this.suggestionStore.currentSuggestion) { + if (this.suggestionsControl.currentSuggestion) { let currentValue: string = this.state.queryString; let itemValue: string = this._getTextFromItem( - this.suggestionStore.currentSuggestion.item, + this.suggestionsControl.currentSuggestion.item, currentValue ); this.setState({ suggestedDisplayValue: itemValue }); @@ -219,11 +204,10 @@ export class BaseFloatingPicker> extend } protected updateSuggestions(suggestions: T[]): void { - this.suggestionStore.updateSuggestions(suggestions, 0); + this.suggestionStore.updateSuggestions(suggestions); } protected updateValue(updatedValue: string): void { - // Call onInputChanged if (this.props.onInputChanged) { (this.props.onInputChanged as (filter: string) => void)(updatedValue); } @@ -256,7 +240,7 @@ export class BaseFloatingPicker> extend if (updatedValue !== undefined) { this.resolveNewValue(updatedValue, suggestionsArray); } else { - this.suggestionStore.updateSuggestions(suggestionsArray, 0); + this.suggestionStore.updateSuggestions(suggestionsArray); } } else if (suggestionsPromiseLike && suggestionsPromiseLike.then) { this.setState({ @@ -298,12 +282,12 @@ export class BaseFloatingPicker> extend } protected resolveNewValue(updatedValue: string, suggestions: T[]): void { - this.suggestionStore.updateSuggestions(suggestions, 0); + this.suggestionStore.updateSuggestions(suggestions); let itemValue: string | undefined = undefined; - if (this.suggestionStore.currentSuggestion) { + if (this.suggestionsControl.currentSuggestion) { itemValue = this._getTextFromItem( - this.suggestionStore.currentSuggestion.item, + this.suggestionsControl.currentSuggestion.item, updatedValue ); } @@ -326,7 +310,7 @@ export class BaseFloatingPicker> extend ev: React.MouseEvent, item: T, index: number - ): void { + ): void { this.onChange(item); } @@ -335,11 +319,11 @@ export class BaseFloatingPicker> extend ev: React.MouseEvent, item: T, index: number - ): void { + ): void { if (this.props.onRemoveSuggestion) { (this.props.onRemoveSuggestion as ((item: T) => void))(item); } - this.suggestionStore.removeSuggestion(index); + this.suggestionsControl.removeSuggestion(index); } @autobind @@ -350,7 +334,6 @@ export class BaseFloatingPicker> extend return; } let keyCode = ev.which; - let { value: suggestionElement } = this.suggestionElement; switch (keyCode) { case KeyCodes.escape: this.setState({ suggestionsVisible: false }); @@ -360,12 +343,10 @@ export class BaseFloatingPicker> extend case KeyCodes.tab: case KeyCodes.enter: - if (suggestionElement && suggestionElement.hasSuggestedActionSelected()) { - suggestionElement.executeSelectedAction(); - } else if (!ev.shiftKey && + if (!ev.shiftKey && !ev.ctrlKey && - this.suggestionStore.hasSelectedSuggestion()) { - this.completeSuggestion(); + this.suggestionsControl && + this.suggestionsControl.handleKeyDown(keyCode)) { ev.preventDefault(); ev.stopPropagation(); } else { @@ -374,66 +355,30 @@ export class BaseFloatingPicker> extend break; case KeyCodes.del: - if (this.suggestionStore.currentIndex !== -1) { - if (this.props.onRemoveSuggestion) { - (this.props.onRemoveSuggestion as ((item: T) => void))( - this.suggestionStore.currentSuggestion!.item - ); - } - this.suggestionStore.removeSuggestion( - this.suggestionStore.currentIndex + if (this.props.onRemoveSuggestion + && this.suggestionsControl.hasSuggestionSelected + && this.suggestionsControl.currentSuggestion) { + (this.props.onRemoveSuggestion as ((item: T) => void))( + this.suggestionsControl.currentSuggestion!.item ); + + this.suggestionsControl.removeSuggestion(); this.forceUpdate(); } ev.stopPropagation(); break; case KeyCodes.up: - if (suggestionElement && suggestionElement.tryHandleKeyDown(keyCode, this.suggestionStore.currentIndex)) { + if (this.suggestionsControl && this.suggestionsControl.handleKeyDown(keyCode)) { ev.preventDefault(); ev.stopPropagation(); - } else { - if (suggestionElement && suggestionElement.hasSuggestedAction() && - this.suggestionStore.currentIndex === 0 - ) { - ev.preventDefault(); - ev.stopPropagation(); - suggestionElement.focusAboveSuggestions(); - this.suggestionStore.deselectAllSuggestions(); - this.forceUpdate(); - } else { - if (this.suggestionStore.previousSuggestion()) { - ev.preventDefault(); - ev.stopPropagation(); - this.onSuggestionSelect(); - } - } } break; case KeyCodes.down: - if (suggestionElement && suggestionElement.tryHandleKeyDown(keyCode, this.suggestionStore.currentIndex)) { + if (this.suggestionsControl && this.suggestionsControl.handleKeyDown(keyCode)) { ev.preventDefault(); ev.stopPropagation(); - } else { - if ( - suggestionElement && - suggestionElement.hasSuggestedAction() && - this.suggestionStore.currentIndex + 1 === - this.suggestionStore.suggestions.length - ) { - ev.preventDefault(); - ev.stopPropagation(); - suggestionElement.focusBelowSuggestions(); - this.suggestionStore.deselectAllSuggestions(); - this.forceUpdate(); - } else { - if (this.suggestionStore.nextSuggestion()) { - ev.preventDefault(); - ev.stopPropagation(); - this.onSuggestionSelect(); - } - } } break; } @@ -494,21 +439,17 @@ export class BaseFloatingPicker> extend this.props.onValidateInput && this.props.createGenericItem ) { - let itemToConvert = (this.props.createGenericItem as (( + let itemToConvert: ISuggestionModel = (this.props.createGenericItem as (( input: string, isValid: boolean ) => ISuggestionModel))( this.state.queryString, (this.props.onValidateInput as ((input: string) => boolean))(this.state.queryString) - ); - this.suggestionStore.createGenericSuggestion(itemToConvert); - this.completeSuggestion(); - } - } + ); - @autobind - private _showForceResolve(): boolean { - return this.props.showForceResolve ? this.props.showForceResolve() : false; + let convertedItems = this.suggestionStore.convertSuggestionsToSuggestionItems([itemToConvert]); + this.onChange(convertedItems[0].item); + } } private _getTextFromItem(item: T, currentValue?: string): string { diff --git a/packages/experiments/src/components/FloatingPicker/BaseFloatingPicker.types.ts b/packages/experiments/src/components/FloatingPicker/BaseFloatingPicker.types.ts index 3d960290cb61c5..88fae134db4ea5 100644 --- a/packages/experiments/src/components/FloatingPicker/BaseFloatingPicker.types.ts +++ b/packages/experiments/src/components/FloatingPicker/BaseFloatingPicker.types.ts @@ -1,6 +1,8 @@ import * as React from 'react'; -import { ISuggestionModel, IBasePickerSuggestionsProps, SuggestionsController } from 'office-ui-fabric-react/lib/Pickers'; +import { ISuggestionModel } from 'office-ui-fabric-react/lib/Pickers'; import { IPersonaProps } from 'office-ui-fabric-react/lib/Persona'; +import { ISuggestionsHeaderFooterProps } from './Suggestions/Suggestions.types'; +import { SuggestionsStore } from './Suggestions/SuggestionsStore'; export interface IBaseFloatingPicker { /** Whether the suggestions are shown */ @@ -14,6 +16,13 @@ export interface IBaseFloatingPicker { /** Shows the picker */ showPicker: () => void; + + /** Gets the suggestions */ + // tslint:disable-next-line:no-any + suggestions: any[]; + + /** Gets the input text */ + inputText: string; } // Type T is the type of the item that is displayed @@ -23,8 +32,10 @@ export interface IBaseFloatingPicker { export interface IBaseFloatingPickerProps extends React.Props { componentRef?: (component?: IBaseFloatingPicker | null) => void; - /** The suggestions controller */ - suggestionsController: SuggestionsController; + /** + * The suggestions store + */ + suggestionsStore: SuggestionsStore; /** * The suggestions to show on zero query @@ -76,7 +87,7 @@ export interface IBaseFloatingPickerProps extends React.Props { /** * The properties that will get passed to the Suggestions component. */ - pickerSuggestionsProps?: IBasePickerSuggestionsProps; + pickerSuggestionsProps?: IBaseFloatingPickerSuggestionProps; /** * A callback for when a persona is removed from the suggestion list */ @@ -118,4 +129,15 @@ export interface IBaseFloatingPickerProps extends React.Props { * Width for the suggestions callout */ calloutWidth?: number; +} + +export interface IBaseFloatingPickerSuggestionProps { + /** + * The header items props + */ + headerItemsProps?: ISuggestionsHeaderFooterProps[]; + /** + * The footer items props + */ + footerItemsProps?: ISuggestionsHeaderFooterProps[]; } \ No newline at end of file diff --git a/packages/experiments/src/components/FloatingPicker/PeoplePicker/examples/FloatingPeoplePicker.Basic.Example.tsx b/packages/experiments/src/components/FloatingPicker/PeoplePicker/examples/FloatingPeoplePicker.Basic.Example.tsx index ef37a1aef79397..07f5749b4bc9da 100644 --- a/packages/experiments/src/components/FloatingPicker/PeoplePicker/examples/FloatingPeoplePicker.Basic.Example.tsx +++ b/packages/experiments/src/components/FloatingPicker/PeoplePicker/examples/FloatingPeoplePicker.Basic.Example.tsx @@ -7,8 +7,8 @@ import { autobind } from 'office-ui-fabric-react/lib/Utilities'; import { IPersonaProps } from 'office-ui-fabric-react/lib/Persona'; -import { IBasePickerSuggestionsProps, SuggestionsController } from 'office-ui-fabric-react/lib/Pickers'; -import { IBaseFloatingPicker } from '../../BaseFloatingPicker.types'; +import { SuggestionsStore } from '../../Suggestions/SuggestionsStore'; +import { IBaseFloatingPicker, IBaseFloatingPickerSuggestionProps } from '../../BaseFloatingPicker.types'; import { FloatingPeoplePicker } from '../FloatingPeoplePicker'; import { IPersonaWithMenu } from 'office-ui-fabric-react/lib/components/pickers/PeoplePicker/PeoplePickerItems/PeoplePickerItem.types'; import { people, mru } from '../../../ExtendedPicker'; @@ -23,16 +23,6 @@ export interface IPeoplePickerExampleState { searchValue: string; } -const suggestionProps: IBasePickerSuggestionsProps = { - suggestionsHeaderText: 'Suggested People', - mostRecentlyUsedHeaderText: 'Suggested Contacts', - noResultsFoundText: 'No results found', - loadingText: 'Loading', - showRemoveButtons: true, - suggestionsAvailableAlertText: 'People Picker Suggestions available', - suggestionsContainerAriaLabel: 'Suggested contacts' -}; - export class FloatingPeoplePickerTypesExample extends BaseComponent<{}, IPeoplePickerExampleState> { private _picker: IBaseFloatingPicker; private _inputElement: HTMLDivElement; @@ -63,6 +53,7 @@ export class FloatingPeoplePickerTypesExample extends BaseComponent<{}, IPeopleP placeholder={ 'Search a person' } onChange={ this._onSearchChange } value={ this.state.searchValue } + onFocus={ this._onFocus } /> { this._renderFloatingPicker() } @@ -70,10 +61,26 @@ export class FloatingPeoplePickerTypesExample extends BaseComponent<{}, IPeopleP ); } + @autobind + private _onFocus(): void { + if (this._picker) { + this._picker.showPicker(); + } + } + private _renderFloatingPicker(): JSX.Element { + let suggestionProps: IBaseFloatingPickerSuggestionProps = { + footerItemsProps: [{ + renderItem: () => { return (
Showing { this._picker.suggestions.length } results
); }, + shouldShow: () => { + return this._picker.suggestions.length > 0; + } + }], + }; + return ( () } + suggestionsStore={ new SuggestionsStore() } onResolveSuggestions={ this._onFilterChanged } getTextFromItem={ this._getTextFromItem } pickerSuggestionsProps={ suggestionProps } diff --git a/packages/experiments/src/components/FloatingPicker/Suggestions/Suggestions.scss b/packages/experiments/src/components/FloatingPicker/Suggestions/Suggestions.scss new file mode 100644 index 00000000000000..430ce029aaa69d --- /dev/null +++ b/packages/experiments/src/components/FloatingPicker/Suggestions/Suggestions.scss @@ -0,0 +1,120 @@ +@import '~office-ui-fabric-react/src/common/common'; + +.root { + min-width: 260px; +} + +.actionButton { + background: none; + border: 0; + cursor: pointer; + margin: 0; + @include ms-padding-left(8px); + position: relative; + border: 1px 0px solid $ms-color-neutralLight; + height: 40px; + @include ms-text-align(left); + width: 100%; + font-size: 12px; + &:hover { + background-color: $ms-color-neutralLighter; + cursor: pointer; + } + // TODO: Works in Chrome, but not working in IE + &:focus, + &:active { + background-color: $ms-color-themeLight; + } + + :global(.ms-Button-icon) { + font-size: 16px; + width: 25px; + } + + :global(.ms-Button-label) { + @include margin(0, 4px, 0, 9px); + } +} + +.buttonSelected { + background-color: $ms-color-themeLighter; + + &:hover { + background-color: $ms-color-themeLight; + cursor: pointer; + } +} + +.suggestionsTitle { + padding: 0 12px; + font-size: $ms-font-size-s; + line-height: 40px; + border-bottom: 1px 0px solid $ms-color-neutralLight; +} + +.suggestionsContainer { + overflow-y: auto; + overflow-x: hidden; + max-height: 300px; + border-bottom: 1px solid $ms-color-neutralLight; + + :global(.ms-Suggestion-item) { + &:hover { + background-color: $ms-color-neutralLighter; + cursor: pointer; + } + } + + :global(.is-suggested) { + background-color: $ms-color-themeLighter; + + &:hover { + background-color: $ms-color-themeLight; + cursor: pointer; + } + } +} + +.suggestionsSpinner { + margin: 5px 0; + @include padding-left(14px); + @include text-align(left); + white-space: nowrap; + line-height: 20px; + font-size: 12px; + + :global(.ms-Spinner-circle) { + display: inline-block; + vertical-align: middle; + } + :global(.ms-Spinner-label) { + display: inline-block; + @include margin(0px, 10px, 0, 16px); + vertical-align: middle; + } +} + +.itemButton { + height: 100%; + width: 100%; + padding: 7px 12px; + + @include high-contrast { + color: WindowText; + } +} + +.closeButton { + padding: 0 4px; + height: auto; + width: 32px; + + @include high-contrast { + color: WindowText; + } + + &:hover { + background: $ms-color-neutralTertiaryAlt; + color: $ms-color-neutralDark; + } +} \ No newline at end of file diff --git a/packages/experiments/src/components/FloatingPicker/Suggestions/Suggestions.tsx b/packages/experiments/src/components/FloatingPicker/Suggestions/Suggestions.tsx new file mode 100644 index 00000000000000..1eabdcf4a433de --- /dev/null +++ b/packages/experiments/src/components/FloatingPicker/Suggestions/Suggestions.tsx @@ -0,0 +1,188 @@ +import * as React from 'react'; +import { + BaseComponent, + css, + autobind +} from '../../../Utilities'; +import { ISuggestionItemProps, SuggestionsItem, ISuggestionModel } from 'office-ui-fabric-react/lib/Pickers'; +import { ISuggestionsProps } from './Suggestions.types'; +import * as stylesImport from './Suggestions.scss'; +// tslint:disable-next-line:no-any +const styles: any = stylesImport; + +/** + * Class when used with SuggestionsStore, renders a basic suggestions control + */ +export class Suggestions extends BaseComponent, {}> { + public currentIndex: number; + public currentSuggestion: ISuggestionModel | undefined; + protected _selectedElement: HTMLDivElement; + private SuggestionsItemOfProperType: new (props: ISuggestionItemProps) => SuggestionsItem = + SuggestionsItem as new (props: ISuggestionItemProps) => SuggestionsItem; + + constructor(suggestionsProps: ISuggestionsProps) { + super(suggestionsProps); + this.currentIndex = -1; + } + + /** + * Increments the selected suggestion index + */ + public nextSuggestion(): boolean { + let { suggestions } = this.props; + + if (suggestions && suggestions.length > 0) { + if (this.currentIndex === -1) { + this.setSelectedSuggestion(0); + return true; + } else if (this.currentIndex < (suggestions.length - 1)) { + this.setSelectedSuggestion(this.currentIndex + 1); + return true; + } else if (this.props.shouldLoopSelection && this.currentIndex === (suggestions.length - 1)) { + this.setSelectedSuggestion(0); + return true; + } + } + + return false; + } + + /** + * Decrements the selected suggestion index + */ + public previousSuggestion(): boolean { + let { suggestions } = this.props; + + if (suggestions && suggestions.length > 0) { + if (this.currentIndex === -1) { + this.setSelectedSuggestion(suggestions.length - 1); + return true; + } else if (this.currentIndex > 0) { + this.setSelectedSuggestion(this.currentIndex - 1); + return true; + } else if (this.props.shouldLoopSelection && this.currentIndex === 0) { + this.setSelectedSuggestion(suggestions.length - 1); + return true; + } + } + + return false; + } + + public getCurrentItem(): ISuggestionModel { + return this.props.suggestions[this.currentIndex]; + } + + public getSuggestionAtIndex(index: number): ISuggestionModel { + return this.props.suggestions[index]; + } + + public hasSuggestionSelected(): boolean { + return this.currentIndex !== -1 && this.currentIndex < this.props.suggestions.length; + } + + public removeSuggestion(index: number): void { + this.props.suggestions.splice(index, 1); + } + + public deselectAllSuggestions(): void { + if (this.currentIndex > -1 && this.props.suggestions[this.currentIndex]) { + this.props.suggestions[this.currentIndex].selected = false; + this.currentIndex = -1; + this.forceUpdate(); + } + } + + public setSelectedSuggestion(index: number): void { + let { suggestions } = this.props; + + if (index > suggestions.length - 1 || index < 0) { + this.currentIndex = 0; + this.currentSuggestion!.selected = false; + this.currentSuggestion = suggestions[0]; + this.currentSuggestion.selected = true; + } else { + if (this.currentIndex > -1) { + suggestions[this.currentIndex].selected = false; + } + suggestions[index].selected = true; + this.currentIndex = index; + this.currentSuggestion = suggestions[index]; + } + + this.forceUpdate(); + } + + public componentDidUpdate(): void { + this.scrollSelected(); + } + + public render(): JSX.Element { + const { + onRenderSuggestion, + suggestionsItemClassName, + resultsMaximumNumber, + showRemoveButtons, + suggestionsContainerAriaLabel } = this.props; + const TypedSuggestionsItem = this.SuggestionsItemOfProperType; + let { suggestions } = this.props; + + if (resultsMaximumNumber) { + suggestions = suggestions.slice(0, resultsMaximumNumber); + } + + return ( +
+ { suggestions.map((suggestion: ISuggestionModel, index: number) => +
+ +
) } +
); + } + + // TODO get the element to scroll into view properly regardless of direction. + public scrollSelected(): void { + if (this._selectedElement) { + this._selectedElement.scrollIntoView(false); + } + } + + @autobind + private _onClickTypedSuggestionsItem(item: T, index: number): (ev: React.MouseEvent) => void { + return (ev: React.MouseEvent): void => { + this.props.onSuggestionClick(ev, item, index); + }; + } + + @autobind + private _onRemoveTypedSuggestionsItem(item: T, index: number): (ev: React.MouseEvent) => void { + return (ev: React.MouseEvent): void => { + const onSuggestionRemove = this.props.onSuggestionRemove!; + onSuggestionRemove(ev, item, index); + ev.stopPropagation(); + }; + } +} \ No newline at end of file diff --git a/packages/experiments/src/components/FloatingPicker/Suggestions/Suggestions.types.ts b/packages/experiments/src/components/FloatingPicker/Suggestions/Suggestions.types.ts new file mode 100644 index 00000000000000..d5a1c8f210fb50 --- /dev/null +++ b/packages/experiments/src/components/FloatingPicker/Suggestions/Suggestions.types.ts @@ -0,0 +1,123 @@ +import * as React from 'react'; +import { ISuggestionModel } from 'office-ui-fabric-react/lib/Pickers'; +import { IRenderFunction } from '../../../Utilities'; +import { IPersonaProps } from 'office-ui-fabric-react/lib/Persona'; + +// tslint:disable-next-line:no-any +export interface ISuggestionsProps extends React.Props { + /** + * Gets the component ref. + */ + componentRef?: () => void; + /** + * How the suggestion should look in the suggestion list. + */ + onRenderSuggestion?: (props: T, suggestionItemProps: T) => JSX.Element; + /** + * What should occur when a suggestion is clicked + */ + // tslint:disable-next-line:no-any + onSuggestionClick: (ev?: React.MouseEvent, item?: any, index?: number) => void; + /** + * The list of Suggestions that will be displayed + */ + suggestions: ISuggestionModel[]; + /** + * Function to fire when one of the optional remove buttons on a suggestion is clicked. + */ + onSuggestionRemove?: (ev?: React.MouseEvent, item?: IPersonaProps, index?: number) => void; + /** + * Screen reader message to read when there are suggestions available. + */ + suggestionsAvailableAlertText?: string; + /** + * An ARIA label for the container that is the parent of the suggestions. + */ + suggestionsContainerAriaLabel?: string; + /** + * the classname of the suggestionitem. + */ + suggestionsItemClassName?: string; + /** + * Maximum number of suggestions to show in the full suggestion list. + */ + resultsMaximumNumber?: number; + /** + * Indicates whether to show a button with each suggestion to remove that suggestion. + */ + showRemoveButtons?: boolean; + /** + * Indicates whether to loop around to the top or bottom of the the suggestions + * on calling nextSuggestion and previousSuggestion, respectively + */ + shouldLoopSelection: boolean; +} + +// tslint:disable-next-line:no-any +export interface ISuggestionsControlProps extends React.Props, ISuggestionsProps { + /** + * An ARIA label for the container that is the parent of the suggestions header items. + */ + suggestionsHeaderContainerAriaLabel?: string; + /** + * An ARIA label for the container that is the parent of the suggestions footer items. + */ + suggestionsFooterContainerAriaLabel?: string; + /** + * The header items props + */ + headerItemsProps?: ISuggestionsHeaderFooterProps[]; + /** + * The footer items props + */ + footerItemsProps?: ISuggestionsHeaderFooterProps[]; + /** + * How the "no result found" should look in the suggestion list. + */ + onRenderNoResultFound?: IRenderFunction; + /** + * The callback that should be called when the user attempts to use the input text as as item + */ + createGenericItem?: () => void; + /** + * The CSS classname of the suggestions list. + */ + className?: string; + /** + * Used to indicate whether or not the component is searching for more results. + */ + isSearching?: boolean; + /** + * The text to display while searching for more results in a limited sugesstions list. + */ + searchingText?: string; + /** + * A renderer that adds an element at the end of the suggestions list it has more items than resultsMaximumNumber. + */ + resultsFooterFull?: (props: ISuggestionsProps) => JSX.Element; + /** + * A renderer that adds an element at the end of the suggestions list it has fewer items than resultsMaximumNumber. + */ + resultsFooter?: (props: ISuggestionsProps) => JSX.Element; + /** + * Completes the suggestion + */ + completeSuggestion: () => void; +} + +export interface ISuggestionsHeaderFooterProps { + renderItem: () => JSX.Element; + onExecute?: () => void; + className?: string; + ariaLabel?: string; + shouldShow: () => boolean; +} + +export interface ISuggestionsHeaderFooterItemProps { + componentRef?: () => void; + renderItem: () => JSX.Element; + onExecute?: () => void; + isSelected: boolean; + id: string; + className: string | undefined; +} \ No newline at end of file diff --git a/packages/experiments/src/components/FloatingPicker/Suggestions/SuggestionsControl.tsx b/packages/experiments/src/components/FloatingPicker/Suggestions/SuggestionsControl.tsx new file mode 100644 index 00000000000000..04f12e384667a5 --- /dev/null +++ b/packages/experiments/src/components/FloatingPicker/Suggestions/SuggestionsControl.tsx @@ -0,0 +1,466 @@ +import * as React from 'react'; +import { + BaseComponent, + css, + KeyCodes +} from '../../../Utilities'; +import { CommandButton, IButton } from 'office-ui-fabric-react/lib/Button'; +import { Spinner } from 'office-ui-fabric-react/lib/Spinner'; +import { ISuggestionModel } from 'office-ui-fabric-react/lib/Pickers'; +import { + ISuggestionsHeaderFooterItemProps, + ISuggestionsControlProps, + ISuggestionsProps, + ISuggestionsHeaderFooterProps +} from './Suggestions.types'; +import { Suggestions } from './Suggestions'; +import * as stylesImport from './Suggestions.scss'; + +// tslint:disable-next-line:no-any +const styles: any = stylesImport; + +export enum SuggestionItemType { + header, + suggestion, + footer, +} + +export interface ISuggestionsState { + selectedHeaderIndex: number; + selectedFooterIndex: number; +} + +export class SuggestionsHeaderFooterItem extends BaseComponent { + public render(): JSX.Element { + const { + renderItem, + onExecute, + isSelected, + id, + + } = this.props; + return ( + onExecute ? + ( + + { renderItem() } + + ) : + ( +
+ { renderItem() } +
+ ) + ); + } +} + +/** + * Class when used with SuggestionsStore, renders a suggestions control with customizable headers and footers + */ +export class SuggestionsControl extends BaseComponent, ISuggestionsState> { + + protected _forceResolveButton: IButton; + protected _searchForMoreButton: IButton; + protected _selectedElement: HTMLDivElement; + protected _suggestions: Suggestions; + private SuggestionsOfProperType: new (props: ISuggestionsProps) => Suggestions = + Suggestions as new (props: ISuggestionsProps) => Suggestions; + + constructor(suggestionsProps: ISuggestionsControlProps) { + super(suggestionsProps); + + this.state = { + selectedHeaderIndex: -1, + selectedFooterIndex: -1, + }; + } + + public componentDidMount(): void { + this.resetSelectedItem(); + } + + public componentDidUpdate(): void { + this.scrollSelected(); + } + + public componentWillReceiveProps(): void { + this.resetSelectedItem(); + } + + public componentWillUnmount(): void { + this._suggestions.deselectAllSuggestions(); + } + + public render(): JSX.Element { + const { + className, + isSearching, + searchingText, + headerItemsProps, + footerItemsProps + } = this.props; + + return ( +
+ { headerItemsProps && this.renderHeaderItems() } + { this._renderSuggestions() } + { footerItemsProps && this.renderFooterItems() } + { isSearching ? + () : (null) + } +
+ ); + } + + public get currentSuggestion(): ISuggestionModel { + return this._suggestions.getCurrentItem(); + } + + public hasSuggestionSelected(): boolean { + return this._suggestions.hasSuggestionSelected(); + } + + public hasSelection(): boolean { + let { selectedHeaderIndex, selectedFooterIndex } = this.state; + return selectedHeaderIndex !== -1 || this.hasSuggestionSelected() || selectedFooterIndex !== -1; + } + + public executeSelectedAction(): void { + let { headerItemsProps, footerItemsProps } = this.props; + let { selectedHeaderIndex, selectedFooterIndex } = this.state; + + if (headerItemsProps && selectedHeaderIndex !== -1 && selectedHeaderIndex < headerItemsProps.length) { + let selectedHeaderItem = headerItemsProps[selectedHeaderIndex]; + if (selectedHeaderItem.onExecute) { + selectedHeaderItem.onExecute(); + } + } else if (this._suggestions.hasSuggestionSelected()) { + this.props.completeSuggestion(); + } else if (footerItemsProps && selectedFooterIndex !== -1 && selectedFooterIndex < footerItemsProps.length) { + let selectedFooterItem = footerItemsProps[selectedFooterIndex]; + if (selectedFooterItem.onExecute) { + selectedFooterItem.onExecute(); + } + } + } + + public removeSuggestion(index?: number): void { + this._suggestions.removeSuggestion(index ? index : this._suggestions.currentIndex); + } + + /** + * Handles the key down, returns true, if the event was handled, false otherwise + * @param keyCode The keyCode to handle + */ + public handleKeyDown(keyCode: number): boolean { + let { selectedHeaderIndex, selectedFooterIndex } = this.state; + let isKeyDownHandled = false; + if (keyCode === KeyCodes.down) { + if (selectedHeaderIndex !== -1) { + this.selectNextItem(SuggestionItemType.header); + isKeyDownHandled = true; + } else if (this._suggestions.hasSuggestionSelected()) { + this.selectNextItem(SuggestionItemType.suggestion); + isKeyDownHandled = true; + } else if (selectedFooterIndex !== -1) { + this.selectNextItem(SuggestionItemType.footer); + isKeyDownHandled = true; + } + } else if (keyCode === KeyCodes.up) { + if (selectedHeaderIndex !== -1) { + this.selectPreviousItem(SuggestionItemType.header); + isKeyDownHandled = true; + } else if (this._suggestions.hasSuggestionSelected()) { + this.selectPreviousItem(SuggestionItemType.suggestion); + isKeyDownHandled = true; + } else if (selectedFooterIndex !== -1) { + this.selectPreviousItem(SuggestionItemType.footer); + isKeyDownHandled = true; + } + } else if (keyCode === KeyCodes.enter) { + if (this.hasSelection()) { + this.executeSelectedAction(); + isKeyDownHandled = true; + } + } + + return isKeyDownHandled; + } + + // TODO get the element to scroll into view properly regardless of direction. + public scrollSelected(): void { + if (this._selectedElement) { + this._selectedElement.scrollIntoView(false); + } + } + + protected renderHeaderItems(): JSX.Element | null { + const { headerItemsProps, suggestionsHeaderContainerAriaLabel } = this.props; + const { selectedHeaderIndex } = this.state; + + return headerItemsProps ? ( +
+ { headerItemsProps.map((headerItemProps: ISuggestionsHeaderFooterProps, index: number) => { + let isSelected = selectedHeaderIndex !== -1 && selectedHeaderIndex === index; + return ( + headerItemProps.shouldShow() ?
+ +
: null + ); + }) } +
) : null; + } + + protected renderFooterItems(): JSX.Element | null { + const { footerItemsProps, suggestionsFooterContainerAriaLabel } = this.props; + let { selectedFooterIndex } = this.state; + return footerItemsProps ? ( +
+ { footerItemsProps.map((footerItemProps: ISuggestionsHeaderFooterProps, index: number) => { + let isSelected = selectedFooterIndex !== -1 && selectedFooterIndex === index; + return ( + footerItemProps.shouldShow() ?
+ +
: null + ); + }) } +
) : null; + } + + protected _renderSuggestions(): JSX.Element { + const TypedSuggestions = this.SuggestionsOfProperType; + + return ( + } + />); + } + + /** + * Selects the next selectable item + */ + protected selectNextItem(itemType: SuggestionItemType, originalItemType?: SuggestionItemType): void { + // If the recursive calling has not found a selectable item in the other suggestion item type groups + // And the method is being called again with the original item type, + // Select the first selectable item of this suggestion item type group (could be itself) + if (itemType === originalItemType) { + this._selectNextItemOfItemType(itemType); + return; + } + + let startedItemType = originalItemType !== undefined ? originalItemType : itemType; + + // Try to set the selection to the next selectable item, of the same suggestion item type group + // If this is the original item type, use the current index + let selectionChanged = this._selectNextItemOfItemType( + itemType, + startedItemType === itemType ? this._getCurrentIndexForType(itemType) : undefined); + + // If the selection did not change, try to select from the next suggestion type group + if (!selectionChanged) { + + this.selectNextItem(this._getNextItemSectionType(itemType), startedItemType); + } + } + + /** + * Selects the previous selectable item + */ + protected selectPreviousItem(itemType: SuggestionItemType, originalItemType?: SuggestionItemType): void { + // If the recursive calling has not found a selectable item in the other suggestion item type groups + // And the method is being called again with the original item type, + // Select the last selectable item of this suggestion item type group (could be itself) + if (itemType === originalItemType) { + this._selectPreviousItemOfItemType(itemType); + return; + } + + let startedItemType = originalItemType !== undefined ? originalItemType : itemType; + + // Try to set the selection to the previous selectable item, of the same suggestion item type group + let selectionChanged = this._selectPreviousItemOfItemType( + itemType, + startedItemType === itemType ? this._getCurrentIndexForType(itemType) : undefined); + + // If the selection did not change, try to select from the previous suggestion type group + if (!selectionChanged) { + + this.selectPreviousItem(this._getPreviousItemSectionType(itemType), startedItemType); + } + } + + /** + * Selects the first selectable item + */ + protected resetSelectedItem(): void { + if (this._selectNextItemOfItemType(SuggestionItemType.header)) { + return; + } + + this._suggestions.deselectAllSuggestions(); + if (this._selectNextItemOfItemType(SuggestionItemType.suggestion)) { + return; + } + + this._selectNextItemOfItemType(SuggestionItemType.footer); + } + + /** + * Selects the next item in the suggestion item type group, given the current index + * If none is able to be selected, returns false, otherwise returns true + * @param itemType The suggestion item type + * @param currentIndex The current index, default is -1 + */ + private _selectNextItemOfItemType(itemType: SuggestionItemType, currentIndex: number = -1): boolean { + if (itemType === SuggestionItemType.suggestion) { + if (this.props.suggestions.length > currentIndex + 1) { + this._suggestions.setSelectedSuggestion(currentIndex + 1); + this.setState({ selectedHeaderIndex: -1, selectedFooterIndex: -1 }); + return true; + } + } else { + let isHeader = itemType === SuggestionItemType.header; + let itemProps = isHeader ? this.props.headerItemsProps : this.props.footerItemsProps; + + if (itemProps && itemProps.length > currentIndex + 1) { + for (let i = currentIndex + 1; i < itemProps.length; i++) { + let item = itemProps[i]; + if (item.onExecute && item.shouldShow()) { + this.setState({ selectedHeaderIndex: isHeader ? i : -1 }); + this.setState({ selectedFooterIndex: isHeader ? -1 : i }); + this._suggestions.deselectAllSuggestions(); + return true; + } + } + } + } + + return false; + } + + /** + * Selects the previous item in the suggestion item type group, given the current index + * If none is able to be selected, returns false, otherwise returns true + * @param itemType The suggestion item type + * @param currentIndex The current index. If none is provided, the default is the items length of specified type + */ + private _selectPreviousItemOfItemType(itemType: SuggestionItemType, currentIndex?: number): boolean { + if (itemType === SuggestionItemType.suggestion) { + let index = currentIndex !== undefined ? currentIndex : this.props.suggestions.length; + if (index > 0) { + this._suggestions.setSelectedSuggestion(index - 1); + this.setState({ selectedHeaderIndex: -1, selectedFooterIndex: -1 }); + return true; + } + } else { + let isHeader = itemType === SuggestionItemType.header; + let itemProps = isHeader ? this.props.headerItemsProps : this.props.footerItemsProps; + if (itemProps) { + let index = currentIndex !== undefined ? currentIndex : itemProps.length; + if (index > 0) { + for (let i = index - 1; i >= 0; i--) { + let item = itemProps[i]; + if (item.onExecute && item.shouldShow()) { + this.setState({ selectedHeaderIndex: isHeader ? i : -1 }); + this.setState({ selectedFooterIndex: isHeader ? -1 : i }); + this._suggestions.deselectAllSuggestions(); + return true; + } + } + } + } + } + + return false; + } + + private _getCurrentIndexForType(itemType: SuggestionItemType): number { + switch (itemType) { + case SuggestionItemType.header: + return this.state.selectedHeaderIndex; + case SuggestionItemType.suggestion: + return this._suggestions.currentIndex; + case SuggestionItemType.footer: + return this.state.selectedFooterIndex; + } + } + + private _getNextItemSectionType(itemType: SuggestionItemType): SuggestionItemType { + switch (itemType) { + case SuggestionItemType.header: + return SuggestionItemType.suggestion; + case SuggestionItemType.suggestion: + return SuggestionItemType.footer; + case SuggestionItemType.footer: + return SuggestionItemType.header; + } + } + + private _getPreviousItemSectionType(itemType: SuggestionItemType): SuggestionItemType { + switch (itemType) { + case SuggestionItemType.header: + return SuggestionItemType.footer; + case SuggestionItemType.suggestion: + return SuggestionItemType.header; + case SuggestionItemType.footer: + return SuggestionItemType.suggestion; + } + } +} \ No newline at end of file diff --git a/packages/experiments/src/components/FloatingPicker/Suggestions/SuggestionsStore.ts b/packages/experiments/src/components/FloatingPicker/Suggestions/SuggestionsStore.ts new file mode 100644 index 00000000000000..c6a8c26c276919 --- /dev/null +++ b/packages/experiments/src/components/FloatingPicker/Suggestions/SuggestionsStore.ts @@ -0,0 +1,59 @@ +import { autobind } from '../../../Utilities'; +import { ISuggestionModel } from 'office-ui-fabric-react/lib/Pickers'; + +export class SuggestionsStore { + public suggestions: ISuggestionModel[]; + + constructor() { + this.suggestions = []; + } + + public updateSuggestions(newSuggestions: T[]): void { + if (newSuggestions && newSuggestions.length > 0) { + this.suggestions = this.convertSuggestionsToSuggestionItems(newSuggestions); + } else { + this.suggestions = []; + } + } + + public getSuggestions(): ISuggestionModel[] { + return this.suggestions; + } + + public getSuggestionAtIndex(index: number): ISuggestionModel { + return this.suggestions[index]; + } + + public removeSuggestion(index: number): void { + this.suggestions.splice(index, 1); + } + + public convertSuggestionsToSuggestionItems(suggestions: Array | T>): ISuggestionModel[] { + return Array.isArray(suggestions) + ? suggestions.map(this._ensureSuggestionModel) + : []; + } + + @autobind + private _isSuggestionModel( + value: ISuggestionModel | T + ): value is ISuggestionModel { + return (>value).item !== undefined; + } + + @autobind + private _ensureSuggestionModel( + suggestion: ISuggestionModel | T + ): ISuggestionModel { + if (this._isSuggestionModel(suggestion)) { + return suggestion as ISuggestionModel; + } else { + return { + item: suggestion, + selected: false, + // tslint:disable-next-line:no-any + ariaLabel: (suggestion).name || (suggestion).primaryText + } as ISuggestionModel; + } + } +} diff --git a/packages/experiments/src/components/FloatingPicker/index.ts b/packages/experiments/src/components/FloatingPicker/index.ts index ccce1d6c37a90a..75c02252736147 100644 --- a/packages/experiments/src/components/FloatingPicker/index.ts +++ b/packages/experiments/src/components/FloatingPicker/index.ts @@ -1,3 +1,7 @@ export * from './BaseFloatingPicker'; export * from './BaseFloatingPicker.types'; -export * from './PeoplePicker/FloatingPeoplePicker'; \ No newline at end of file +export * from './PeoplePicker/FloatingPeoplePicker'; +export * from './Suggestions/SuggestionsStore'; +export * from './Suggestions/SuggestionsControl'; +export * from './Suggestions/Suggestions'; +export * from './Suggestions/Suggestions.types'; \ No newline at end of file diff --git a/packages/office-ui-fabric-react/src/components/pickers/Suggestions/Suggestions.tsx b/packages/office-ui-fabric-react/src/components/pickers/Suggestions/Suggestions.tsx index b24ac5dcb575e1..a1f8b1451d71e5 100644 --- a/packages/office-ui-fabric-react/src/components/pickers/Suggestions/Suggestions.tsx +++ b/packages/office-ui-fabric-react/src/components/pickers/Suggestions/Suggestions.tsx @@ -28,7 +28,8 @@ export class SuggestionsItem extends BaseComponent, { RenderSuggestion, onClick, className, - onRemoveItem + onRemoveItem, + isSelectedOverride } = this.props; return (
extends BaseComponent, { 'ms-Suggestions-item', styles.suggestionsItem, { - ['is-suggested ' + styles.suggestionsItemIsSuggested]: suggestionModel.selected + ['is-suggested ' + styles.suggestionsItemIsSuggested]: suggestionModel.selected || isSelectedOverride }, className ) } @@ -135,7 +136,7 @@ export class Suggestions extends BaseComponent, ISuggest styles.actionButton, { ['is-selected ' + styles.buttonSelected]: - this.state.selectedActionType === SuggestionActionType.forceResolve + this.state.selectedActionType === SuggestionActionType.forceResolve }) } onClick={ this._forceResolve } > @@ -158,7 +159,7 @@ export class Suggestions extends BaseComponent, ISuggest styles.actionButton, { ['is-selected ' + styles.buttonSelected]: - this.state.selectedActionType === SuggestionActionType.searchMore + this.state.selectedActionType === SuggestionActionType.searchMore }) } iconProps={ { iconName: 'Search' } } onClick={ this._getMoreResults } diff --git a/packages/office-ui-fabric-react/src/components/pickers/Suggestions/Suggestions.types.ts b/packages/office-ui-fabric-react/src/components/pickers/Suggestions/Suggestions.types.ts index 10f711966da78f..d13a5fc71ddccd 100644 --- a/packages/office-ui-fabric-react/src/components/pickers/Suggestions/Suggestions.types.ts +++ b/packages/office-ui-fabric-react/src/components/pickers/Suggestions/Suggestions.types.ts @@ -141,4 +141,5 @@ export interface ISuggestionItemProps { className?: string; id?: string; showRemoveButton?: boolean; + isSelectedOverride?: boolean; } \ No newline at end of file From 675a067bfecaca6b33e9d6a0b1ae0af0d7400901 Mon Sep 17 00:00:00 2001 From: Amy Nguyen Date: Wed, 14 Mar 2018 13:28:22 -0700 Subject: [PATCH 2/7] add change files --- ...gu-floatingPickerSuggestions_2018-03-14-20-28.json | 11 +++++++++++ ...gu-floatingPickerSuggestions_2018-03-14-20-28.json | 11 +++++++++++ 2 files changed, 22 insertions(+) create mode 100644 common/changes/@uifabric/experiments/amyngu-floatingPickerSuggestions_2018-03-14-20-28.json create mode 100644 common/changes/office-ui-fabric-react/amyngu-floatingPickerSuggestions_2018-03-14-20-28.json diff --git a/common/changes/@uifabric/experiments/amyngu-floatingPickerSuggestions_2018-03-14-20-28.json b/common/changes/@uifabric/experiments/amyngu-floatingPickerSuggestions_2018-03-14-20-28.json new file mode 100644 index 00000000000000..0c570512875c7e --- /dev/null +++ b/common/changes/@uifabric/experiments/amyngu-floatingPickerSuggestions_2018-03-14-20-28.json @@ -0,0 +1,11 @@ +{ + "changes": [ + { + "packageName": "@uifabric/experiments", + "comment": "Create new pattern for suggestions for BaseFloatingPicker", + "type": "minor" + } + ], + "packageName": "@uifabric/experiments", + "email": "amyngu@microsoft.com" +} \ No newline at end of file diff --git a/common/changes/office-ui-fabric-react/amyngu-floatingPickerSuggestions_2018-03-14-20-28.json b/common/changes/office-ui-fabric-react/amyngu-floatingPickerSuggestions_2018-03-14-20-28.json new file mode 100644 index 00000000000000..3ae1534f0326ab --- /dev/null +++ b/common/changes/office-ui-fabric-react/amyngu-floatingPickerSuggestions_2018-03-14-20-28.json @@ -0,0 +1,11 @@ +{ + "changes": [ + { + "packageName": "office-ui-fabric-react", + "comment": "add isSelectedOverride to suggestionItemProps", + "type": "minor" + } + ], + "packageName": "office-ui-fabric-react", + "email": "amyngu@microsoft.com" +} \ No newline at end of file From 1d92b0c96584774d21e95cdfd057a166350a5e56 Mon Sep 17 00:00:00 2001 From: Amy Nguyen Date: Wed, 14 Mar 2018 14:31:52 -0700 Subject: [PATCH 3/7] fix picker tests and separate scss file into two files --- .../BaseExtendedPicker.tests.tsx | 5 +- .../BaseFloatingPicker.test.tsx | 8 +- .../Suggestions/Suggestions.scss | 96 ------------------- .../Suggestions/SuggestionsControl.scss | 85 ++++++++++++++++ .../Suggestions/SuggestionsControl.tsx | 13 +-- 5 files changed, 98 insertions(+), 109 deletions(-) create mode 100644 packages/experiments/src/components/FloatingPicker/Suggestions/SuggestionsControl.scss diff --git a/packages/experiments/src/components/ExtendedPicker/BaseExtendedPicker.tests.tsx b/packages/experiments/src/components/ExtendedPicker/BaseExtendedPicker.tests.tsx index 2e87311114a5af..36730a33c6a57e 100644 --- a/packages/experiments/src/components/ExtendedPicker/BaseExtendedPicker.tests.tsx +++ b/packages/experiments/src/components/ExtendedPicker/BaseExtendedPicker.tests.tsx @@ -7,8 +7,7 @@ import * as renderer from 'react-test-renderer'; import { IBaseExtendedPickerProps } from './BaseExtendedPicker.types'; import { BaseExtendedPicker } from './BaseExtendedPicker'; -import { SuggestionsController } from 'office-ui-fabric-react/lib/Pickers'; -import { IBaseFloatingPickerProps, BaseFloatingPicker } from '../FloatingPicker'; +import { IBaseFloatingPickerProps, BaseFloatingPicker, SuggestionsStore } from '../FloatingPicker'; import { IBaseSelectedItemsListProps, ISelectedItemProps, BaseSelectedItemsList } from '../SelectedItemsList'; import { KeyCodes } from '../../Utilities'; @@ -57,7 +56,7 @@ const basicRenderSelectedItemsList = (props: IBaseSelectedItemsListProps() + suggestionsStore: new SuggestionsStore() }; const selectedItemsListProps: IBaseSelectedItemsListProps = { diff --git a/packages/experiments/src/components/FloatingPicker/BaseFloatingPicker.test.tsx b/packages/experiments/src/components/FloatingPicker/BaseFloatingPicker.test.tsx index fa1dad84dfc0d0..252a302b2cd10c 100644 --- a/packages/experiments/src/components/FloatingPicker/BaseFloatingPicker.test.tsx +++ b/packages/experiments/src/components/FloatingPicker/BaseFloatingPicker.test.tsx @@ -6,7 +6,7 @@ import * as renderer from 'react-test-renderer'; import { IBaseFloatingPickerProps } from './BaseFloatingPicker.types'; import { BaseFloatingPicker } from './BaseFloatingPicker'; -import { SuggestionsController } from 'office-ui-fabric-react/lib/Pickers'; +import { SuggestionsStore } from './Suggestions/SuggestionsStore'; function onResolveSuggestions(text: string): ISimple[] { return [ @@ -57,7 +57,7 @@ describe('Pickers', () => { () } /> ); let tree = component.toJSON(); @@ -74,7 +74,7 @@ describe('Pickers', () => { () } onZeroQuerySuggestion={ onZeroQuerySuggestion } inputElement={ input } />, @@ -101,7 +101,7 @@ describe('Pickers', () => { () } inputElement={ input } />, root diff --git a/packages/experiments/src/components/FloatingPicker/Suggestions/Suggestions.scss b/packages/experiments/src/components/FloatingPicker/Suggestions/Suggestions.scss index 430ce029aaa69d..f39c939cab6f99 100644 --- a/packages/experiments/src/components/FloatingPicker/Suggestions/Suggestions.scss +++ b/packages/experiments/src/components/FloatingPicker/Suggestions/Suggestions.scss @@ -1,57 +1,5 @@ @import '~office-ui-fabric-react/src/common/common'; -.root { - min-width: 260px; -} - -.actionButton { - background: none; - border: 0; - cursor: pointer; - margin: 0; - @include ms-padding-left(8px); - position: relative; - border: 1px 0px solid $ms-color-neutralLight; - height: 40px; - @include ms-text-align(left); - width: 100%; - font-size: 12px; - &:hover { - background-color: $ms-color-neutralLighter; - cursor: pointer; - } - // TODO: Works in Chrome, but not working in IE - &:focus, - &:active { - background-color: $ms-color-themeLight; - } - - :global(.ms-Button-icon) { - font-size: 16px; - width: 25px; - } - - :global(.ms-Button-label) { - @include margin(0, 4px, 0, 9px); - } -} - -.buttonSelected { - background-color: $ms-color-themeLighter; - - &:hover { - background-color: $ms-color-themeLight; - cursor: pointer; - } -} - -.suggestionsTitle { - padding: 0 12px; - font-size: $ms-font-size-s; - line-height: 40px; - border-bottom: 1px 0px solid $ms-color-neutralLight; -} - .suggestionsContainer { overflow-y: auto; overflow-x: hidden; @@ -73,48 +21,4 @@ cursor: pointer; } } -} - -.suggestionsSpinner { - margin: 5px 0; - @include padding-left(14px); - @include text-align(left); - white-space: nowrap; - line-height: 20px; - font-size: 12px; - - :global(.ms-Spinner-circle) { - display: inline-block; - vertical-align: middle; - } - :global(.ms-Spinner-label) { - display: inline-block; - @include margin(0px, 10px, 0, 16px); - vertical-align: middle; - } -} - -.itemButton { - height: 100%; - width: 100%; - padding: 7px 12px; - - @include high-contrast { - color: WindowText; - } -} - -.closeButton { - padding: 0 4px; - height: auto; - width: 32px; - - @include high-contrast { - color: WindowText; - } - - &:hover { - background: $ms-color-neutralTertiaryAlt; - color: $ms-color-neutralDark; - } } \ No newline at end of file diff --git a/packages/experiments/src/components/FloatingPicker/Suggestions/SuggestionsControl.scss b/packages/experiments/src/components/FloatingPicker/Suggestions/SuggestionsControl.scss new file mode 100644 index 00000000000000..a4ed60153a79b7 --- /dev/null +++ b/packages/experiments/src/components/FloatingPicker/Suggestions/SuggestionsControl.scss @@ -0,0 +1,85 @@ + +@import '~office-ui-fabric-react/src/common/common'; + +.root { + min-width: 260px; +} + +.actionButton { + background: none; + border: 0; + cursor: pointer; + margin: 0; + @include ms-padding-left(8px); + position: relative; + border-top: 1px solid $ms-color-neutralLight; + border-bottom: 1px solid $ms-color-neutralLight; + height: 40px; + @include ms-text-align(left); + width: 100%; + font-size: 12px; + &:hover { + background-color: $ms-color-neutralLighter; + cursor: pointer; + } + // TODO: Works in Chrome, but not working in IE + &:focus, + &:active { + background-color: $ms-color-themeLight; + } + + :global(.ms-Button-icon) { + font-size: 16px; + width: 25px; + } + + :global(.ms-Button-label) { + @include margin(0, 4px, 0, 9px); + } +} + +.buttonSelected { + background-color: $ms-color-themeLighter; + + &:hover { + background-color: $ms-color-themeLight; + cursor: pointer; + } +} + +.suggestionsTitle { + padding: 0 12px; + font-size: $ms-font-size-s; + line-height: 40px; + border-top: 1px solid $ms-color-neutralLight; + border-bottom: 1px solid $ms-color-neutralLight; +} + +.suggestionsSpinner { + margin: 5px 0; + @include padding-left(14px); + @include text-align(left); + white-space: nowrap; + line-height: 20px; + font-size: 12px; + + :global(.ms-Spinner-circle) { + display: inline-block; + vertical-align: middle; + } + :global(.ms-Spinner-label) { + display: inline-block; + @include margin(0px, 10px, 0, 16px); + vertical-align: middle; + } +} + +.itemButton { + height: 100%; + width: 100%; + padding: 7px 12px; + + @include high-contrast { + color: WindowText; + } +} \ No newline at end of file diff --git a/packages/experiments/src/components/FloatingPicker/Suggestions/SuggestionsControl.tsx b/packages/experiments/src/components/FloatingPicker/Suggestions/SuggestionsControl.tsx index 04f12e384667a5..734e381241bba0 100644 --- a/packages/experiments/src/components/FloatingPicker/Suggestions/SuggestionsControl.tsx +++ b/packages/experiments/src/components/FloatingPicker/Suggestions/SuggestionsControl.tsx @@ -14,7 +14,7 @@ import { ISuggestionsHeaderFooterProps } from './Suggestions.types'; import { Suggestions } from './Suggestions'; -import * as stylesImport from './Suggestions.scss'; +import * as stylesImport from './SuggestionsControl.scss'; // tslint:disable-next-line:no-any const styles: any = stylesImport; @@ -201,7 +201,8 @@ export class SuggestionsControl extends BaseComponent extends BaseComponent @@ -299,7 +300,7 @@ export class SuggestionsControl extends BaseComponent extends BaseComponent Date: Mon, 19 Mar 2018 10:46:40 -0700 Subject: [PATCH 4/7] Update BaseFloatingPicker.tsx --- .../src/components/FloatingPicker/BaseFloatingPicker.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/experiments/src/components/FloatingPicker/BaseFloatingPicker.tsx b/packages/experiments/src/components/FloatingPicker/BaseFloatingPicker.tsx index 8c2afd2d1df167..0e6577828e1f93 100644 --- a/packages/experiments/src/components/FloatingPicker/BaseFloatingPicker.tsx +++ b/packages/experiments/src/components/FloatingPicker/BaseFloatingPicker.tsx @@ -133,7 +133,7 @@ export class BaseFloatingPicker> extend } @autobind - public completeSuggestion = (): void =>{ + public completeSuggestion = (): void => { if (this.suggestionsControl.hasSuggestionSelected()) { this.onChange(this.suggestionsControl.currentSuggestion!.item); } @@ -464,4 +464,4 @@ export class BaseFloatingPicker> extend this.setState({ didBind: false }); } } -} \ No newline at end of file +} From b086419b3b19149994e8542c138593b3b9ec7864 Mon Sep 17 00:00:00 2001 From: Amy Nguyen Date: Mon, 19 Mar 2018 12:13:24 -0700 Subject: [PATCH 5/7] remove autobind reference from bad merge --- .../src/components/FloatingPicker/BaseFloatingPicker.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/experiments/src/components/FloatingPicker/BaseFloatingPicker.tsx b/packages/experiments/src/components/FloatingPicker/BaseFloatingPicker.tsx index 0e6577828e1f93..167f1304eaa1b5 100644 --- a/packages/experiments/src/components/FloatingPicker/BaseFloatingPicker.tsx +++ b/packages/experiments/src/components/FloatingPicker/BaseFloatingPicker.tsx @@ -132,7 +132,6 @@ export class BaseFloatingPicker> extend this.setState({ suggestionsVisible: false }); } - @autobind public completeSuggestion = (): void => { if (this.suggestionsControl.hasSuggestionSelected()) { this.onChange(this.suggestionsControl.currentSuggestion!.item); From c3f63adda238ecab84d440fd172e24d524fc37b1 Mon Sep 17 00:00:00 2001 From: Amy Nguyen Date: Fri, 23 Mar 2018 15:33:35 -0700 Subject: [PATCH 6/7] address PR comments --- .../ExtendedPeoplePicker.Basic.Example.tsx | 16 +++++--- .../FloatingPicker/BaseFloatingPicker.tsx | 2 +- .../BaseFloatingPicker.types.ts | 5 +++ .../Suggestions/Suggestions.types.ts | 4 ++ .../Suggestions/SuggestionsControl.tsx | 39 +++++++++++++++++-- 5 files changed, 55 insertions(+), 11 deletions(-) diff --git a/packages/experiments/src/components/ExtendedPicker/examples/ExtendedPeoplePicker.Basic.Example.tsx b/packages/experiments/src/components/ExtendedPicker/examples/ExtendedPeoplePicker.Basic.Example.tsx index 4e913a4557a1a6..121fd52b0023e3 100644 --- a/packages/experiments/src/components/ExtendedPicker/examples/ExtendedPeoplePicker.Basic.Example.tsx +++ b/packages/experiments/src/components/ExtendedPicker/examples/ExtendedPeoplePicker.Basic.Example.tsx @@ -67,11 +67,7 @@ export class ExtendedPeoplePickerTypesExample extends BaseComponent<{}, IPeopleP
Suggested Contacts
); }, - shouldShow: () => { - return this._picker !== undefined - && this._picker.inputElement !== null - && this._picker.inputElement.value === ''; - } + shouldShow: this._shouldShowSuggestedContacts, } ], footerItemsProps: [{ @@ -89,8 +85,9 @@ export class ExtendedPeoplePickerTypesExample extends BaseComponent<{}, IPeopleP { renderItem: () => { return (
Search for more
); }, onExecute: () => { this.setState({ searchMoreAvailable: false }); }, - shouldShow: () => { return this.state.searchMoreAvailable; } + shouldShow: () => { return this.state.searchMoreAvailable && !this._shouldShowSuggestedContacts(); } }], + shouldSelectFirstItem: () => { return !this._shouldShowSuggestedContacts(); }, }; this._floatingPickerProps = { @@ -243,6 +240,13 @@ export class ExtendedPeoplePickerTypesExample extends BaseComponent<{}, IPeopleP ); } + @autobind + private _shouldShowSuggestedContacts(): boolean { + return this._picker !== undefined + && this._picker.inputElement !== null + && this._picker.inputElement.value === ''; + } + private _listContainsPersona(persona: IPersonaProps, personas: IPersonaProps[]): boolean { if (!personas || !personas.length || personas.length === 0) { return false; diff --git a/packages/experiments/src/components/FloatingPicker/BaseFloatingPicker.tsx b/packages/experiments/src/components/FloatingPicker/BaseFloatingPicker.tsx index 167f1304eaa1b5..9562f520020ccc 100644 --- a/packages/experiments/src/components/FloatingPicker/BaseFloatingPicker.tsx +++ b/packages/experiments/src/components/FloatingPicker/BaseFloatingPicker.tsx @@ -185,7 +185,7 @@ export class BaseFloatingPicker> extend } protected onSuggestionSelect(): void { - if (this.suggestionsControl.currentSuggestion) { + if (this.suggestionsControl && this.suggestionsControl.currentSuggestion) { let currentValue: string = this.state.queryString; let itemValue: string = this._getTextFromItem( this.suggestionsControl.currentSuggestion.item, diff --git a/packages/experiments/src/components/FloatingPicker/BaseFloatingPicker.types.ts b/packages/experiments/src/components/FloatingPicker/BaseFloatingPicker.types.ts index 88fae134db4ea5..d4533e468070e0 100644 --- a/packages/experiments/src/components/FloatingPicker/BaseFloatingPicker.types.ts +++ b/packages/experiments/src/components/FloatingPicker/BaseFloatingPicker.types.ts @@ -132,6 +132,11 @@ export interface IBaseFloatingPickerProps extends React.Props { } export interface IBaseFloatingPickerSuggestionProps { + /** + * Whether or not the first selectable item in the suggestions list should be selected + */ + shouldSelectFirstItem?: () => boolean; + /** * The header items props */ diff --git a/packages/experiments/src/components/FloatingPicker/Suggestions/Suggestions.types.ts b/packages/experiments/src/components/FloatingPicker/Suggestions/Suggestions.types.ts index d5a1c8f210fb50..6601e8147b9b06 100644 --- a/packages/experiments/src/components/FloatingPicker/Suggestions/Suggestions.types.ts +++ b/packages/experiments/src/components/FloatingPicker/Suggestions/Suggestions.types.ts @@ -71,6 +71,10 @@ export interface ISuggestionsControlProps extends React.Props, ISuggesti * The footer items props */ footerItemsProps?: ISuggestionsHeaderFooterProps[]; + /** + * Whether or not the first selectable item in the suggestions list should be selected + */ + shouldSelectFirstItem?: () => boolean; /** * How the "no result found" should look in the suggestion list. */ diff --git a/packages/experiments/src/components/FloatingPicker/Suggestions/SuggestionsControl.tsx b/packages/experiments/src/components/FloatingPicker/Suggestions/SuggestionsControl.tsx index 734e381241bba0..47be729c3652d6 100644 --- a/packages/experiments/src/components/FloatingPicker/Suggestions/SuggestionsControl.tsx +++ b/packages/experiments/src/components/FloatingPicker/Suggestions/SuggestionsControl.tsx @@ -180,7 +180,9 @@ export class SuggestionsControl extends BaseComponent extends BaseComponent extends BaseComponent extends BaseComponent Date: Mon, 26 Mar 2018 15:34:22 -0700 Subject: [PATCH 7/7] address PR comments, fix selection for shouldSelectFirstItem --- .../ExtendedPeoplePicker.Basic.Example.tsx | 15 +++++++++------ .../FloatingPeoplePicker.Basic.Example.tsx | 3 +-- .../Suggestions/SuggestionsControl.tsx | 6 +++--- 3 files changed, 13 insertions(+), 11 deletions(-) diff --git a/packages/experiments/src/components/ExtendedPicker/examples/ExtendedPeoplePicker.Basic.Example.tsx b/packages/experiments/src/components/ExtendedPicker/examples/ExtendedPeoplePicker.Basic.Example.tsx index e7c356eb658825..fd5c7b50792074 100644 --- a/packages/experiments/src/components/ExtendedPicker/examples/ExtendedPeoplePicker.Basic.Example.tsx +++ b/packages/experiments/src/components/ExtendedPicker/examples/ExtendedPeoplePicker.Basic.Example.tsx @@ -58,7 +58,11 @@ export class ExtendedPeoplePickerTypesExample extends BaseComponent<{}, IPeopleP && this._picker.inputElement !== null && this._picker.inputElement.value.indexOf('@') > -1; }, - onExecute: () => { this._picker.floatingPicker.forceResolveSuggestion(); } + onExecute: () => { + if (this._picker.floatingPicker.value !== null) { + this._picker.floatingPicker.value.forceResolveSuggestion(); + } + } }, { renderItem: () => { @@ -78,7 +82,8 @@ export class ExtendedPeoplePickerTypesExample extends BaseComponent<{}, IPeopleP shouldShow: () => { return this._picker !== undefined && this._picker.floatingPicker !== undefined - && this._picker.floatingPicker.suggestions.length === 0; + && this._picker.floatingPicker.value !== null + && this._picker.floatingPicker.value.suggestions.length === 0; } }, { @@ -235,8 +240,7 @@ export class ExtendedPeoplePickerTypesExample extends BaseComponent<{}, IPeopleP ); } - @autobind - private _shouldShowSuggestedContacts(): boolean { + private _shouldShowSuggestedContacts = (): boolean => { return this._picker !== undefined && this._picker.inputElement !== null && this._picker.inputElement.value === ''; @@ -261,8 +265,7 @@ export class ExtendedPeoplePickerTypesExample extends BaseComponent<{}, IPeopleP return personas.filter((persona: IPersonaProps) => !this._listContainsPersona(persona, possibleDupes)); } - @autobind - private _onInputChanged(): void { + private _onInputChanged = (): void => { this.setState({ searchMoreAvailable: true }); } diff --git a/packages/experiments/src/components/FloatingPicker/PeoplePicker/examples/FloatingPeoplePicker.Basic.Example.tsx b/packages/experiments/src/components/FloatingPicker/PeoplePicker/examples/FloatingPeoplePicker.Basic.Example.tsx index 8d88590957ab0e..9c4ad608c8d101 100644 --- a/packages/experiments/src/components/FloatingPicker/PeoplePicker/examples/FloatingPeoplePicker.Basic.Example.tsx +++ b/packages/experiments/src/components/FloatingPicker/PeoplePicker/examples/FloatingPeoplePicker.Basic.Example.tsx @@ -60,8 +60,7 @@ export class FloatingPeoplePickerTypesExample extends BaseComponent<{}, IPeopleP ); } - @autobind - private _onFocus(): void { + private _onFocus = (): void => { if (this._picker) { this._picker.showPicker(); } diff --git a/packages/experiments/src/components/FloatingPicker/Suggestions/SuggestionsControl.tsx b/packages/experiments/src/components/FloatingPicker/Suggestions/SuggestionsControl.tsx index 47be729c3652d6..c3924f40e27dbe 100644 --- a/packages/experiments/src/components/FloatingPicker/Suggestions/SuggestionsControl.tsx +++ b/packages/experiments/src/components/FloatingPicker/Suggestions/SuggestionsControl.tsx @@ -359,7 +359,7 @@ export class SuggestionsControl extends BaseComponent extends BaseComponent extends BaseComponent