Skip to content

Commit 6babde8

Browse files
authored
people-picker new states + defaultSelectedUserIds prop (#424)
* fixing default behavior to show people upon focus interaction with component * adding defaultSelectedUsers functionality * updates behavior for no default people and loading states * clear focus bug
1 parent b5e0472 commit 6babde8

File tree

2 files changed

+135
-62
lines changed

2 files changed

+135
-62
lines changed

src/components/mgt-people-picker/mgt-people-picker.scss

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -247,6 +247,7 @@ mgt-people-picker .loading-text {
247247
color: $font-color;
248248
margin-left: 50px;
249249
margin-right: 50px;
250+
color: #0078d4;
250251
}
251252

252253
:host .SearchErrorIcon,
@@ -258,11 +259,31 @@ mgt-people-picker .SearchErrorIcon {
258259

259260
:host .message-parent,
260261
mgt-people-picker .message-parent {
261-
margin: 20px;
262+
padding: 2px;
263+
margin-top: 30px;
264+
margin-bottom: 30px;
262265
display: flex;
266+
flex-direction: column;
263267
align-items: center;
264268
justify-content: center;
265269
vertical-align: middle;
270+
.spinner {
271+
border: 2px solid #c7e0f4; /* Light grey */
272+
border-top: 2px solid #0078d4; /* Blue */
273+
border-radius: 50%;
274+
width: 20px;
275+
height: 20px;
276+
animation: spin 2s linear infinite;
277+
}
278+
}
279+
280+
@keyframes spin {
281+
0% {
282+
transform: rotate(0deg);
283+
}
284+
100% {
285+
transform: rotate(360deg);
286+
}
266287
}
267288

268289
:host .highlight-search-text,

src/components/mgt-people-picker/mgt-people-picker.ts

Lines changed: 113 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,8 @@ import { customElement, html, internalProperty, property, TemplateResult } from
1010
import { classMap } from 'lit-html/directives/class-map';
1111
import { repeat } from 'lit-html/directives/repeat';
1212
import { findGroups, GroupType } from '../../graph/graph.groups';
13-
import { findPeople, getPeopleFromGroup, PersonType } from '../../graph/graph.people';
14-
import { findUsers, getUser } from '../../graph/graph.user';
13+
import { findPeople, getPeople, getPeopleFromGroup, PersonType } from '../../graph/graph.people';
14+
import { findUsers, getUser, getUsersForUserIds } from '../../graph/graph.user';
1515
import { IDynamicPerson } from '../../graph/types';
1616
import { Providers } from '../../Providers';
1717
import { ProviderState } from '../../providers/IProvider';
@@ -87,36 +87,6 @@ export class MgtPeoplePicker extends MgtTemplatedComponent {
8787
return this.renderRoot.querySelector('.flyout');
8888
}
8989

90-
/**
91-
* containing object of IDynamicPerson.
92-
* @type {IDynamicPerson[]}
93-
*/
94-
@property({
95-
attribute: 'people',
96-
type: Object
97-
})
98-
public people: IDynamicPerson[];
99-
100-
/**
101-
* determining how many people to show in list.
102-
* @type {number}
103-
*/
104-
@property({
105-
attribute: 'show-max',
106-
type: Number
107-
})
108-
public showMax: number;
109-
110-
/**
111-
* array of user picked people.
112-
* @type {IDynamicPerson[]}
113-
*/
114-
@property({
115-
attribute: 'selected-people',
116-
type: Array
117-
})
118-
public selectedPeople: IDynamicPerson[];
119-
12090
/**
12191
* value determining if search is filtered to a group.
12292
* @type {string}
@@ -206,6 +176,51 @@ export class MgtPeoplePicker extends MgtTemplatedComponent {
206176
this.requestStateUpdate(true);
207177
}
208178

179+
/**
180+
* containing object of IDynamicPerson.
181+
* @type {IDynamicPerson[]}
182+
*/
183+
@property({
184+
attribute: 'people',
185+
type: Object
186+
})
187+
public people: IDynamicPerson[];
188+
189+
/**
190+
* determining how many people to show in list.
191+
* @type {number}
192+
*/
193+
@property({
194+
attribute: 'show-max',
195+
type: Number
196+
})
197+
public showMax: number;
198+
199+
/**
200+
* array of user picked people.
201+
* @type {IDynamicPerson[]}
202+
*/
203+
@property({
204+
attribute: 'selected-people',
205+
type: Array
206+
})
207+
public selectedPeople: IDynamicPerson[];
208+
209+
/**
210+
* array of people to be selected upon intialization
211+
*
212+
* @type {Array}
213+
* @memberof MgtPeoplePicker
214+
*/
215+
@property({
216+
attribute: 'default-selected-user-ids',
217+
converter: value => {
218+
return value.split(',').map(v => v.trim());
219+
},
220+
type: String
221+
})
222+
public defaultSelectedUserIds: '';
223+
209224
/**
210225
* Placeholder text.
211226
*
@@ -246,6 +261,8 @@ export class MgtPeoplePicker extends MgtTemplatedComponent {
246261
private _type: PersonType = PersonType.Person;
247262
private _groupType: GroupType = GroupType.Any;
248263

264+
private defaultPeople: IDynamicPerson[];
265+
249266
// tracking of user arrow key input for selection
250267
private _arrowSelectionCount: number = 0;
251268
// List of people requested if group property is provided
@@ -291,14 +308,19 @@ export class MgtPeoplePicker extends MgtTemplatedComponent {
291308
peopleInput.focus(options);
292309
peopleInput.select();
293310

294-
window.requestAnimationFrame(() => {
295-
// Mouse is focused on input
296-
if (!peopleInput.value) {
297-
this.hideFlyout();
298-
} else {
311+
// handles hiding control if default people have no more selections available
312+
const peopleLeft = this.filterPeople(this.defaultPeople);
313+
let shouldShow = true;
314+
if (peopleLeft && peopleLeft.length === 0) {
315+
shouldShow = false;
316+
}
317+
318+
if (shouldShow) {
319+
window.requestAnimationFrame(() => {
320+
// Mouse is focused on input
299321
this.showFlyout();
300-
}
301-
});
322+
});
323+
}
302324
}
303325

304326
/**
@@ -479,6 +501,11 @@ export class MgtPeoplePicker extends MgtTemplatedComponent {
479501
return this.renderNoData();
480502
}
481503

504+
// clears focus
505+
for (const person of people) {
506+
(person as IFocusable).isFocused = false;
507+
}
508+
482509
people = people.slice(0, this.showMax);
483510
(people[0] as IFocusable).isFocused = true;
484511

@@ -497,8 +524,9 @@ export class MgtPeoplePicker extends MgtTemplatedComponent {
497524
this.renderTemplate('loading', null) ||
498525
html`
499526
<div class="message-parent">
500-
<div label="search-error-text" aria-label="loading" class="loading-text">
501-
......
527+
<div class="spinner"></div>
528+
<div label="loading-text" aria-label="loading" class="loading-text">
529+
Loading...
502530
</div>
503531
</div>
504532
`
@@ -617,10 +645,30 @@ export class MgtPeoplePicker extends MgtTemplatedComponent {
617645

618646
const provider = Providers.globalProvider;
619647
if (!people && provider && provider.state === ProviderState.SignedIn) {
620-
if (this.groupId) {
648+
const graph = provider.graph.forComponent(this);
649+
650+
// default common people
651+
if (!input.length && this._isFocused) {
652+
if (this.defaultPeople) {
653+
people = this.defaultPeople;
654+
} else {
655+
people = await getPeople(graph);
656+
this.defaultPeople = people;
657+
}
658+
this._showLoading = false;
659+
}
660+
661+
if (this.defaultSelectedUserIds && !this.selectedPeople.length) {
662+
const defaultSelectedUsers = await getUsersForUserIds(graph, this.defaultSelectedUserIds);
663+
664+
this.selectedPeople = [...defaultSelectedUsers];
665+
this.requestUpdate();
666+
this.fireCustomEvent('selectionChanged', this.selectedPeople);
667+
}
668+
669+
if (this.groupId && input) {
621670
if (this._groupPeople === null) {
622671
try {
623-
const graph = provider.graph.forComponent(this);
624672
this._groupPeople = await getPeopleFromGroup(graph, this.groupId);
625673
} catch (_) {
626674
this._groupPeople = [];
@@ -629,9 +677,7 @@ export class MgtPeoplePicker extends MgtTemplatedComponent {
629677

630678
people = this._groupPeople || [];
631679
} else if (input) {
632-
const graph = provider.graph.forComponent(this);
633680
people = [];
634-
635681
if (this.type === PersonType.Person || this.type === PersonType.Any) {
636682
try {
637683
people = (await findPeople(graph, input, this.showMax)) || [];
@@ -716,6 +762,7 @@ export class MgtPeoplePicker extends MgtTemplatedComponent {
716762
return p.id !== person.id;
717763
});
718764
this.selectedPeople = filteredPersonArr;
765+
this.loadState();
719766
this.fireCustomEvent('selectionChanged', this.selectedPeople);
720767
}
721768

@@ -738,6 +785,7 @@ export class MgtPeoplePicker extends MgtTemplatedComponent {
738785
this.selectedPeople = [...this.selectedPeople, person];
739786
this.fireCustomEvent('selectionChanged', this.selectedPeople);
740787

788+
this.loadState();
741789
this._foundPeople = [];
742790
}
743791
}
@@ -749,6 +797,8 @@ export class MgtPeoplePicker extends MgtTemplatedComponent {
749797
if (input) {
750798
input.focus();
751799
}
800+
this._showLoading = true;
801+
this.loadState();
752802
}
753803

754804
private lostFocus() {
@@ -815,6 +865,9 @@ export class MgtPeoplePicker extends MgtTemplatedComponent {
815865
this.userInput = '';
816866
// remove last person in selected list
817867
this.selectedPeople = this.selectedPeople.splice(0, this.selectedPeople.length - 1);
868+
// reset flyout position
869+
this.hideFlyout();
870+
this.showFlyout();
818871
// fire selected people changed event
819872
this.fireCustomEvent('selectionChanged', this.selectedPeople);
820873
return;
@@ -837,24 +890,23 @@ export class MgtPeoplePicker extends MgtTemplatedComponent {
837890
* @param input - input text
838891
*/
839892
private handleUserSearch(input: HTMLInputElement) {
893+
this._showLoading = true;
840894
if (!this._debouncedSearch) {
841895
this._debouncedSearch = debounce(async () => {
842-
if (!this.userInput.length) {
843-
this._foundPeople = [];
844-
this.hideFlyout();
845-
this._showLoading = true;
846-
} else {
847-
// Wait a few milliseconds before showing the flyout.
848-
// This helps prevent loading state flickering while the user is actively changing the query.
849-
const loadingTimeout = setTimeout(() => {
850-
this._showLoading = true;
851-
}, 400);
852-
853-
await this.loadState();
854-
clearTimeout(loadingTimeout);
855-
this._showLoading = false;
856-
this.showFlyout();
857-
}
896+
// Wait a few milliseconds before showing the flyout.
897+
// This helps prevent loading state flickering while the user is actively changing the query.
898+
899+
const loadingTimeout = setTimeout(() => {
900+
if (!this.userInput.length) {
901+
this._foundPeople = [];
902+
this.hideFlyout();
903+
}
904+
}, 400);
905+
906+
await this.loadState();
907+
clearTimeout(loadingTimeout);
908+
this._showLoading = false;
909+
this.showFlyout();
858910

859911
this._arrowSelectionCount = 0;
860912
}, 400);

0 commit comments

Comments
 (0)