feat: Implement SharePoint People Picker for selecting starting person and update version to 1.0.3

This commit is contained in:
Torsten Brendgen
2026-08-24 14:28:01 +02:00
parent 4e5dafabfc
commit a686f8c055
13 changed files with 299 additions and 48 deletions
@@ -4,6 +4,10 @@
color: "[theme:bodyText, default:#323130]";
font-family: "Segoe UI", "Segoe UI Web (West European)", -apple-system, BlinkMacSystemFont, Roboto, "Helvetica Neue", sans-serif;
}
.pickerPanel { background: "[theme:neutralLighterAlt, default:#faf9f8]"; border: 1px solid "[theme:neutralLight, default:#edebe9]"; margin-bottom: 16px; padding: 12px; }
.pickerLabel { color: "[theme:bodyText, default:#323130]"; display: block; font-size: 14px; font-weight: 600; margin-bottom: 6px; }
.pickerDescription { color: "[theme:neutralSecondary, default:#605e5c]"; font-size: 12px; line-height: 17px; margin-top: 6px; }
.pickerError { color: "[theme:errorText, default:#a80000]"; font-size: 12px; line-height: 17px; margin-top: 6px; }
.header { align-items: baseline; display: flex; justify-content: space-between; margin-bottom: 12px; }
.heading { font-size: 24px; font-weight: 300; line-height: 32px; margin: 0; }
.count { color: "[theme:neutralSecondary, default:#605e5c]"; font-size: 12px; }
@@ -0,0 +1,127 @@
import * as React from 'react';
import {
NormalPeoplePicker,
ValidationState
} from 'office-ui-fabric-react/lib/Pickers';
import { IPersonaProps } from 'office-ui-fabric-react/lib/Persona';
import { IOrganizationChartPersonSuggestion } from '../OrganizationChartTypes';
import styles from './OrganizationChart.module.scss';
export interface IOrganizationChartPickerItem extends IPersonaProps {
accountName: string;
key: string;
loginName: string;
}
export interface IOrganizationChartPeoplePickerProps {
description: string;
label: string;
loadingText: string;
noResultsText: string;
onChange: (loginName: string, accountName: string) => void;
placeholder: string;
removeButtonAriaLabel: string;
searchPeople: (query: string) => Promise<IOrganizationChartPersonSuggestion[]>;
selectedDisplayName: string;
selectedAccountName: string;
selectedLoginName: string;
suggestionsHeaderText: string;
validationErrorText: string;
validatePerson: (loginName: string, accountName: string) => Promise<boolean>;
}
export interface IOrganizationChartPeoplePickerState {
validationError: string;
}
export class OrganizationChartPeoplePicker
extends React.Component<IOrganizationChartPeoplePickerProps, IOrganizationChartPeoplePickerState> {
public constructor(props: IOrganizationChartPeoplePickerProps) {
super(props);
this.resolveSuggestions = this.resolveSuggestions.bind(this);
this.onSelectionChanged = this.onSelectionChanged.bind(this);
this.onItemSelected = this.onItemSelected.bind(this);
this.getTextFromItem = this.getTextFromItem.bind(this);
this.state = { validationError: '' };
}
public render(): React.ReactElement<HTMLDivElement> {
const selectedItems: IOrganizationChartPickerItem[] = this.props.selectedLoginName ? [{
key: this.props.selectedLoginName,
accountName: this.props.selectedAccountName,
loginName: this.props.selectedLoginName,
primaryText: this.props.selectedDisplayName || this.props.selectedLoginName,
secondaryText: this.props.selectedLoginName
}] : [];
return (
<div className={styles.pickerPanel}>
<label className={styles.pickerLabel}>{this.props.label}</label>
<NormalPeoplePicker
selectedItems={selectedItems}
onResolveSuggestions={this.resolveSuggestions}
onChange={this.onSelectionChanged}
onItemSelected={this.onItemSelected}
getTextFromItem={this.getTextFromItem}
onValidateInput={(): ValidationState => ValidationState.invalid}
itemLimit={1}
inputProps={{
'aria-label': this.props.label,
placeholder: this.props.placeholder
}}
pickerSuggestionsProps={{
loadingText: this.props.loadingText,
noResultsFoundText: this.props.noResultsText,
suggestionsHeaderText: this.props.suggestionsHeaderText
}}
removeButtonAriaLabel={this.props.removeButtonAriaLabel}
/>
{this.state.validationError ?
<div className={styles.pickerError} role='alert'>{this.state.validationError}</div> : null}
<div className={styles.pickerDescription}>{this.props.description}</div>
</div>
);
}
private resolveSuggestions(filter: string): Promise<IOrganizationChartPickerItem[]> {
return this.props.searchPeople(filter).then(
(suggestions: IOrganizationChartPersonSuggestion[]): IOrganizationChartPickerItem[] =>
suggestions.map((suggestion: IOrganizationChartPersonSuggestion): IOrganizationChartPickerItem => ({
key: suggestion.loginName,
accountName: suggestion.accountName,
loginName: suggestion.loginName,
primaryText: suggestion.displayName,
secondaryText: suggestion.email || suggestion.accountName || suggestion.loginName
})),
(): IOrganizationChartPickerItem[] => []
);
}
private onSelectionChanged(items?: IOrganizationChartPickerItem[]): void {
this.setState({ validationError: '' });
this.props.onChange(
items && items.length ? items[0].loginName : '',
items && items.length ? items[0].accountName : ''
);
}
private onItemSelected(item?: IOrganizationChartPickerItem): Promise<IOrganizationChartPickerItem> {
if (!item) { return Promise.resolve(undefined as IOrganizationChartPickerItem); }
this.setState({ validationError: '' });
return this.props.validatePerson(item.loginName, item.accountName).then(
(valid: boolean): IOrganizationChartPickerItem => {
if (valid) { return item; }
this.setState({ validationError: this.props.validationErrorText });
return undefined as IOrganizationChartPickerItem;
},
(): IOrganizationChartPickerItem => {
this.setState({ validationError: this.props.validationErrorText });
return undefined as IOrganizationChartPickerItem;
}
);
}
private getTextFromItem(item: IOrganizationChartPickerItem): string {
return item.primaryText || item.loginName;
}
}