Skip to content

Commit

Permalink
Picker header (#3737)
Browse files Browse the repository at this point in the history
* add header element, fix css styling for items and input

* add change file

* fix tslint errors
  • Loading branch information
amyngu authored Jan 24, 2018
1 parent 3b5b375 commit 194ce4e
Show file tree
Hide file tree
Showing 8 changed files with 95 additions and 77 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"changes": [
{
"packageName": "@uifabric/experiments",
"comment": "add optional title element to extendedPicker, css changes to have selected items flow on the same row as input",
"type": "minor"
}
],
"packageName": "@uifabric/experiments",
"email": "amyngu@microsoft.com"
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import * as stylesImport from './BaseExtendedPicker.scss';
import { IBaseExtendedPickerProps, IBaseExtendedPicker } from './BaseExtendedPicker.types';
import { IBaseFloatingPickerProps, BaseFloatingPicker } from '../../FloatingPicker';
import { BaseSelectedItemsList, IBaseSelectedItemsListProps } from '../../SelectedItemsList';
import { Selection, SelectionMode, SelectionZone } from 'office-ui-fabric-react/lib/Selection';
// tslint:disable-next-line:no-any
const styles: any = stylesImport;

Expand All @@ -38,6 +39,7 @@ export class BaseExtendedPicker<T, P extends IBaseExtendedPickerProps<T>> extend
protected root: HTMLElement;
protected input: BaseAutoFill;
protected focusZone: FocusZone;
protected selection: Selection;
protected floatingPickerProps: IBaseFloatingPickerProps<T>;
protected selectedItemsListProps: IBaseSelectedItemsListProps<T>;

Expand All @@ -46,6 +48,9 @@ export class BaseExtendedPicker<T, P extends IBaseExtendedPickerProps<T>> extend

let items: T[] = basePickerProps.selectedItems || basePickerProps.defaultSelectedItems || [];

this.selection = new Selection({ onSelectionChanged: () => this.onSelectionChange() });
this.selection.setItems(items);

this.state = {
items: items ? items : [],
suggestedDisplayValue: '',
Expand Down Expand Up @@ -91,32 +96,40 @@ export class BaseExtendedPicker<T, P extends IBaseExtendedPickerProps<T>> extend
onKeyDown={ this.onBackspace }
onCopy={ this.onCopy }
>
<div className={ css('ms-BasePicker-text', styles.pickerText) } role={ 'list' }>
{ this.renderSelectedItemsList() }
{ this.canAddItems() && (<BaseAutoFill
{ ...inputProps as IInputProps }
className={ css('ms-BasePicker-input', styles.pickerInput) }
ref={ this._resolveRef('input') }
onFocus={ this.onInputFocus }
onInputValueChange={ this.onInputChange }
suggestedDisplayValue={ suggestedDisplayValue }
aria-activedescendant={ 'sug-' + this.state.items.length }
aria-owns='suggestion-list'
aria-expanded='true'
aria-haspopup='true'
autoCapitalize='off'
autoComplete='off'
role='combobox'
disabled={ disabled }
aria-controls='selected-suggestion-alert'
/>) }
</div>

<SelectionZone selection={ this.selection } selectionMode={ SelectionMode.multiple }>
<div className={ css('ms-BasePicker-text', styles.pickerText) } role={ 'list' }>
{ this.props.headerComponent }
{ this.renderSelectedItemsList() }
{ this.canAddItems() && (<BaseAutoFill
{ ...inputProps as IInputProps }
className={ css('ms-BasePicker-input', styles.pickerInput) }
ref={ this._resolveRef('input') }
onFocus={ this.onInputFocus }
onInputValueChange={ this.onInputChange }
suggestedDisplayValue={ suggestedDisplayValue }
aria-activedescendant={ 'sug-' + this.state.items.length }
aria-owns='suggestion-list'
aria-expanded='true'
aria-haspopup='true'
autoCapitalize='off'
autoComplete='off'
role='combobox'
disabled={ disabled }
aria-controls='selected-suggestion-alert'
/>) }
</div>
</SelectionZone>
</FocusZone>
{ this.renderSuggestions() }
</div>
);
}

protected onSelectionChange(): void {
this.forceUpdate();
}

protected canAddItems(): boolean {
const { items } = this.state;
const { itemLimit } = this.props;
Expand Down Expand Up @@ -217,6 +230,7 @@ export class BaseExtendedPicker<T, P extends IBaseExtendedPickerProps<T>> extend
protected _onSuggestionSelected(item: T): void {
this.selectedItemsList.addItems([item]);
this.input.clear();

this.floatingPicker.hidePicker();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,11 @@ export interface IBaseExtendedPicker<T> {
export interface IBaseExtendedPickerProps<T> {
componentRef?: (component?: IBaseExtendedPicker<T>) => void;

/**
* Header/title element for the picker
*/
headerComponent?: JSX.Element;

/**
* Initial items that have already been selected and should appear in the people picker.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import './ExtendedPeoplePicker.Basic.Example.scss';
import { FloatingPeoplePicker, IBaseFloatingPickerProps } from '../../FloatingPicker';
import { IBaseSelectedItemsListProps, IExtendedPersonaProps, ISelectedPeopleProps, SelectedPeopleList }
from '../../SelectedItemsList';
import { Selection } from 'office-ui-fabric-react/lib/Selection';

export interface IPeoplePickerExampleState {
peopleList: IPersonaProps[];
Expand All @@ -38,6 +39,7 @@ export class ExtendedPeoplePickerTypesExample extends BaseComponent<{}, IPeopleP
private _picker: ExtendedPeoplePicker;
private floatingPickerProps: IBaseFloatingPickerProps<IExtendedPersonaProps>;
private selectedItemsListProps: ISelectedPeopleProps;
private selection: Selection;

constructor(props: {}) {
super(props);
Expand All @@ -49,6 +51,8 @@ export class ExtendedPeoplePickerTypesExample extends BaseComponent<{}, IPeopleP
peopleList.push(target);
});

this.selection = new Selection({ onSelectionChanged: () => this._onSelectionChange() });

this.state = {
peopleList: peopleList,
mostRecentlyUsed: mru,
Expand All @@ -70,6 +74,7 @@ export class ExtendedPeoplePickerTypesExample extends BaseComponent<{}, IPeopleP
onExpandGroup: this._onExpandItem,
removeMenuItemText: 'Remove',
copyMenuItemText: 'Copy name',
selection: this.selection
};
}

Expand Down Expand Up @@ -100,10 +105,15 @@ export class ExtendedPeoplePickerTypesExample extends BaseComponent<{}, IPeopleP
'aria-label': 'People Picker'
} }
componentRef={ this._setComponentRef }
headerComponent={ this._renderHeader() }
/>
);
}

private _renderHeader(): JSX.Element {
return <div>TO:</div>;
}

private _onRenderFloatingPicker(props: IBaseFloatingPickerProps<IExtendedPersonaProps>): JSX.Element {
return (<FloatingPeoplePicker {...props} />);
}
Expand All @@ -112,6 +122,10 @@ export class ExtendedPeoplePickerTypesExample extends BaseComponent<{}, IPeopleP
return (<SelectedPeopleList {...props} />);
}

private _onSelectionChange(): void {
this.forceUpdate();
}

@autobind
private _setComponentRef(component: ExtendedPeoplePicker): void {
this._picker = component;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,8 @@ import {
BaseComponent,
KeyCodes,
autobind,
css
} from '../../Utilities';
import { FocusZone, FocusZoneDirection } from 'office-ui-fabric-react/lib/FocusZone';
import { Selection, SelectionZone, SelectionMode } from 'office-ui-fabric-react/lib/Selection';

import { IBaseSelectedItemsList, IBaseSelectedItemsListProps, ISelectedItemProps } from './BaseSelectedItemsList.types';

export interface IBaseSelectedItemsListState {
Expand All @@ -24,18 +22,12 @@ export interface IBaseSelectedItemsListState {
export class BaseSelectedItemsList<T, P extends IBaseSelectedItemsListProps<T>>
extends BaseComponent<P, IBaseSelectedItemsListState> implements IBaseSelectedItemsList<T> {

protected selection: Selection;

protected root: HTMLElement;
protected focusZone: FocusZone;

constructor(basePickerProps: P) {
super(basePickerProps);

let items: T[] = basePickerProps.selectedItems || basePickerProps.defaultSelectedItems || [];

this.selection = new Selection({ onSelectionChanged: () => this.onSelectionChange() });
this.selection.setItems(items);
this.state = {
items: items,
};
Expand All @@ -56,11 +48,11 @@ export class BaseSelectedItemsList<T, P extends IBaseSelectedItemsListProps<T>>
if (processedItemPromiseLikes && processedItemPromiseLikes.then) {
processedItemPromiseLikes.then((resolvedProcessedItems: T[]) => {
let newItems: T[] = this.state.items.concat(resolvedProcessedItems);
this.updateSelectedItems(newItems);
this.updateItems(newItems);
});
} else {
let newItems: T[] = this.state.items.concat(processedItemObjects);
this.updateSelectedItems(newItems);
this.updateItems(newItems);
}
this.setState({ suggestedDisplayValue: '' });
}
Expand All @@ -71,30 +63,30 @@ export class BaseSelectedItemsList<T, P extends IBaseSelectedItemsListProps<T>>
// tslint:disable-next-line:no-any
if (index > -1) {
let newItems = items.slice(0, index).concat(items.slice(index + 1));
this.updateSelectedItems(newItems);
this.updateItems(newItems);
}
}

@autobind
public onCopy(ev: React.ClipboardEvent<HTMLElement>): void {
if (this.props.onCopyItems && this.selection.getSelectedCount() > 0) {
let selectedItems: T[] = this.selection.getSelection() as T[];
if (this.props.onCopyItems && this.props.selection.getSelectedCount() > 0) {
let selectedItems: T[] = this.props.selection.getSelection() as T[];
this.copyItems(selectedItems);
}
}

public unselectAll(): void {
this.selection.setAllSelected(false);
this.props.selection.setAllSelected(false);
}

public componentWillUpdate(newProps: P, newState: IBaseSelectedItemsListState): void {
if (newState.items && newState.items !== this.state.items) {
this.selection.setItems(newState.items);
this.props.selection.setItems(newState.items);
}
}

public componentDidMount(): void {
this.selection.setItems(this.state.items);
this.props.selection.setItems(this.state.items);
}

public componentWillReceiveProps(newProps: P): void {
Expand All @@ -105,31 +97,8 @@ export class BaseSelectedItemsList<T, P extends IBaseSelectedItemsListProps<T>>
}
}

public render(): JSX.Element {
let { className } = this.props;

return (
<div
ref={ this._resolveRef('root') }
className={ css(
'ms-BasePicker',
className ? className : '') }
onKeyDown={ this.onKeyDown }
onCopy={ this.onCopy }
>
<FocusZone
ref={ this._resolveRef('focusZone') }
direction={ FocusZoneDirection.bidirectional }
isInnerZoneKeystroke={ this._isFocusZoneInnerKeystroke }
>
<SelectionZone selection={ this.selection } selectionMode={ SelectionMode.multiple } >
<div role={ 'list' }>
{ this.renderItems() }
</div>
</SelectionZone>
</FocusZone>
</div>
);
public render(): JSX.Element[] {
return this.renderItems();
}

@autobind
Expand All @@ -143,18 +112,14 @@ export class BaseSelectedItemsList<T, P extends IBaseSelectedItemsListProps<T>>
item,
index,
key: item.key ? item.key : index,
selected: this.selection.isIndexSelected(index),
selected: this.props.selection.isIndexSelected(index),
onRemoveItem: () => this.removeItem(item),
onItemChange: this.onItemChange,
removeButtonAriaLabel: removeButtonAriaLabel,
onCopyItem: (itemToCopy: T) => this.copyItems([itemToCopy]),
}));
}

protected onSelectionChange(): void {
this.forceUpdate();
}

protected onChange(items?: T[]): void {
if (this.props.onChange) {
(this.props.onChange as (items?: T[]) => void)(items);
Expand Down Expand Up @@ -182,7 +147,7 @@ export class BaseSelectedItemsList<T, P extends IBaseSelectedItemsListProps<T>>
let newItems: T[] = items;
newItems[index] = changedItem;

this.updateSelectedItems(newItems);
this.updateItems(newItems);
}
}

Expand All @@ -193,7 +158,7 @@ export class BaseSelectedItemsList<T, P extends IBaseSelectedItemsListProps<T>>

if (index >= 0) {
let newItems: T[] = items.slice(0, index).concat(items.slice(index + 1));
this.updateSelectedItems(newItems);
this.updateItems(newItems);
}
}

Expand All @@ -206,15 +171,15 @@ export class BaseSelectedItemsList<T, P extends IBaseSelectedItemsListProps<T>>
let firstItemToRemove = itemsToRemove[0];
let index: number = items.indexOf(firstItemToRemove);

this.updateSelectedItems(newItems, index);
this.updateItems(newItems, index);
}

// This is protected because we may expect the backspace key to work differently in a different kind of picker.
// This lets the subclass override it and provide it's own onBackspace. For an example see the BasePickerListBelow
protected onBackspace(ev: React.KeyboardEvent<HTMLElement>): void {
if (this.state.items.length) {
if (this.selection.getSelectedCount() > 0) {
this.removeItems(this.selection.getSelection());
if (this.props.selection.getSelectedCount() > 0) {
this.removeItems(this.props.selection.getSelection());
} else {
this.removeItem(this.state.items[this.state.items.length - 1]);
}
Expand All @@ -225,7 +190,7 @@ export class BaseSelectedItemsList<T, P extends IBaseSelectedItemsListProps<T>>
* Controls what happens whenever there is an action that impacts the selected items.
* If selectedItems is provided as a property then this will act as a controlled component and it will not update it's own state.
*/
protected updateSelectedItems(items: T[], focusIndex?: number): void {
protected updateItems(items: T[], focusIndex?: number): void {
if (this.props.selectedItems) {
// If the component is a controlled component then the controlling component will need
this.onChange(items);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import * as React from 'react';
import { IPickerItemProps, ISuggestionModel, ValidationState } from 'office-ui-fabric-react/lib/Pickers';
import { Selection } from 'office-ui-fabric-react/lib/Selection';

export interface IBaseSelectedItemsList<T> {
/** Gets the current value of the input. */
Expand All @@ -17,6 +18,8 @@ export interface ISelectedItemProps<T> extends IPickerItemProps<T> {
// tslint:disable-next-line:no-any
export interface IBaseSelectedItemsListProps<T> extends React.Props<any> {
componentRef?: (component?: IBaseSelectedItemsList<T>) => void;

selection: Selection;
/**
* A callback for when items are copied
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ export class SelectedPeopleList extends BasePeopleSelectedItemsList {
let filteredExpandedItems = expandedItems.filter((item: any) => items.indexOf(item) === -1);
if (index > -1) {
let newItems = items.slice(0, index).concat(filteredExpandedItems).concat(items.slice(index + 1));
this.updateSelectedItems(newItems);
this.updateItems(newItems);
}
}

Expand All @@ -59,7 +59,7 @@ export class SelectedPeopleList extends BasePeopleSelectedItemsList {
item,
index,
key: item.key ? item.key : index,
selected: this.selection.isIndexSelected(index),
selected: this.props.selection.isIndexSelected(index),
onRemoveItem: () => this.removeItem(item),
onItemChange: this.onItemChange,
removeButtonAriaLabel: removeButtonAriaLabel,
Expand Down
Loading

0 comments on commit 194ce4e

Please sign in to comment.