Skip to content

Commit 1be5051

Browse files
Features/people picker (#46)
* PeoplePicker Added * Code refactor and Documentation * Updated readme, fixed typos. * refactor: code optimization-onsearch/retries * Tweaks * refactor : render - pre-selected members * Readme modified * refactor : duplicate code removed * refactor : personakey & presence type change * refactor: presence return type fix Co-authored-by: Scott Durow <[email protected]>
1 parent 38e8d97 commit 1be5051

28 files changed

+22458
-0
lines changed

PeoplePicker/.eslintrc.json

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
{
2+
"env": {
3+
"browser": true,
4+
"es2021": true
5+
},
6+
"extends": [
7+
"eslint:recommended",
8+
"plugin:react/recommended",
9+
"plugin:react-hooks/recommended",
10+
"plugin:@typescript-eslint/recommended",
11+
"plugin:prettier/recommended",
12+
"prettier"
13+
],
14+
"parser": "@typescript-eslint/parser",
15+
"parserOptions": {
16+
"ecmaFeatures": {
17+
"jsx": true
18+
},
19+
"ecmaVersion": 12,
20+
"sourceType": "module"
21+
},
22+
"plugins": [
23+
"react",
24+
"react-hooks",
25+
"@typescript-eslint",
26+
"prettier"
27+
],
28+
"settings": {
29+
"react": {
30+
"pragma": "React",
31+
"version": "detect"
32+
}
33+
},
34+
"ignorePatterns": ["**/generated/*.ts"],
35+
"rules": {
36+
"eqeqeq": [2, "smart"],
37+
"prettier/prettier": "error",
38+
"arrow-body-style": "off",
39+
"prefer-arrow-callback": "off",
40+
"linebreak-style": [
41+
"error",
42+
"windows"
43+
],
44+
"quotes": [
45+
"error",
46+
"single"
47+
],
48+
"semi": [
49+
"error",
50+
"always"
51+
]
52+
}
53+
}

PeoplePicker/.gitignore

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2+
3+
# dependencies
4+
/node_modules
5+
6+
# generated directory
7+
**/generated
8+
9+
# coverage directory
10+
/coverage
11+
12+
# output directory
13+
/out
14+
15+
# msbuild output directories
16+
/bin
17+
/obj

PeoplePicker/.prettierrc.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"semi": true,
3+
"trailingComma": "all",
4+
"singleQuote": true,
5+
"printWidth": 120,
6+
"tabWidth": 4,
7+
"endOfLine": "auto"
8+
}

PeoplePicker/PeoplePicker.pcfproj

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<Project ToolsVersion="15.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
3+
<PropertyGroup>
4+
<PowerAppsTargetsPath>$(MSBuildExtensionsPath)\Microsoft\VisualStudio\v$(VisualStudioVersion)\PowerApps</PowerAppsTargetsPath>
5+
</PropertyGroup>
6+
7+
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" />
8+
<Import Project="$(PowerAppsTargetsPath)\Microsoft.PowerApps.VisualStudio.Pcf.props" Condition="Exists('$(PowerAppsTargetsPath)\Microsoft.PowerApps.VisualStudio.Pcf.props')" />
9+
10+
<PropertyGroup>
11+
<Name>PeoplePicker</Name>
12+
<ProjectGuid>4f1851cb-52ae-4803-bce0-817b2091888f</ProjectGuid>
13+
<OutputPath>$(MSBuildThisFileDirectory)out\controls</OutputPath>
14+
<PcfBuildMode>production</PcfBuildMode>
15+
</PropertyGroup>
16+
17+
<PropertyGroup>
18+
<TargetFrameworkVersion>v4.6.2</TargetFrameworkVersion>
19+
<!--Remove TargetFramework when this is available in 16.1-->
20+
<TargetFramework>net462</TargetFramework>
21+
<RestoreProjectStyle>PackageReference</RestoreProjectStyle>
22+
</PropertyGroup>
23+
24+
<ItemGroup>
25+
<PackageReference Include="Microsoft.PowerApps.MSBuild.Pcf" Version="1.*" />
26+
<PackageReference Include="Microsoft.NETFramework.ReferenceAssemblies" Version="1.0.0" PrivateAssets="All" />
27+
</ItemGroup>
28+
29+
<ItemGroup>
30+
<ExcludeDirectories Include="$(MSBuildThisFileDirectory)\.gitignore" />
31+
<ExcludeDirectories Include="$(MSBuildThisFileDirectory)\bin\**" />
32+
<ExcludeDirectories Include="$(MSBuildThisFileDirectory)\obj\**" />
33+
<ExcludeDirectories Include="$(OutputPath)\**" />
34+
<ExcludeDirectories Include="$(MSBuildThisFileDirectory)\*.pcfproj" />
35+
<ExcludeDirectories Include="$(MSBuildThisFileDirectory)\*.pcfproj.user" />
36+
<ExcludeDirectories Include="$(MSBuildThisFileDirectory)\*.sln" />
37+
<ExcludeDirectories Include="$(MSBuildThisFileDirectory)\node_modules\**" />
38+
</ItemGroup>
39+
40+
<ItemGroup>
41+
<None Include="$(MSBuildThisFileDirectory)\**" Exclude="@(ExcludeDirectories)" />
42+
</ItemGroup>
43+
44+
<Import Project="$(MSBuildToolsPath)\Microsoft.Common.targets" />
45+
<Import Project="$(PowerAppsTargetsPath)\Microsoft.PowerApps.VisualStudio.Pcf.targets" Condition="Exists('$(PowerAppsTargetsPath)\Microsoft.PowerApps.VisualStudio.Pcf.targets')" />
46+
47+
</Project>
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
// This is undocumented - but needed since canvas apps sets non-zero tabindexes
2+
// so we must use the tabindex provided by the context for accessibility purposes
3+
export interface ContextEx {
4+
accessibility: {
5+
assignedTabIndex: number;
6+
assignedTooltip?: string;
7+
};
8+
}
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<manifest>
3+
<control namespace="PowerCAT" constructor="PeoplePicker" version="0.0.1" display-name-key="PeoplePicker" description-key="PeoplePicker description" control-type="virtual">
4+
<property name="Theme" display-name-key="Theme" of-type="Multiple" usage="input" required="false" />
5+
<property name="AccessibilityLabel" display-name-key="AccessibilityLabel" of-type="SingleLine.Text" usage="input" required="false" default-value="People Picker" />
6+
<property name="ShowSecondaryText" display-name-key="ShowSecondaryText_Desc" of-type="TwoOptions" usage="input" default-value="false" required="false" />
7+
<property name="Error" display-name-key="Error" of-type="TwoOptions" usage="input" default-value="false" required="false" />
8+
<property name="MinimumSearchTermLength" description-key="MinimumSearchTermLength_Desc" display-name-key="MinimumSearchTermLength" required="true" usage="input" of-type="Whole.None" default-value="3" />
9+
<property name="SearchTermToShortMessage" description-key="SearchTermToShortMessage_Desc" display-name-key="SearchTermToShortMessage" required="true" usage="input" of-type="SingleLine.Text" default-value="Continue Typing..." />
10+
<property name="NoResultFoundMesage" description-key="NoResultFoundMesage_Desc" display-name-key="NoResultFoundMesage" required="true" usage="input" of-type="SingleLine.Text" default-value="No results found" />
11+
<property name="SuggestionsHeaderText" description-key="SuggestionsHeaderText_Desc" display-name-key="SuggestionsHeaderText" required="true" usage="input" of-type="SingleLine.Text" default-value="Suggested People" />
12+
<property name="HintText" description-key="HintText_Desc" display-name-key="HintText" required="true" usage="input" of-type="SingleLine.Text" default-value="Search" />
13+
<property name="MaxPeople" description-key="MaxPeople_Desc" display-name-key="MaxPeople" required="true" usage="input" of-type="Whole.None" default-value="10" />
14+
<!-- People Picker Type -->
15+
<property name="PeoplePickerType" display-name-key="PeoplePickerType" description-key="PeoplePickerType" usage="input" of-type="Enum" required="true">
16+
<value name="NormalPeoplePicker" display-name-key="NormalPeoplePicker" description-key="Normal" default="true">Normal People Picker</value>
17+
<value name="CompactPeoplePicker" display-name-key="CompactPeoplePicker" description-key="Compact">Compact People Picker</value>
18+
<value name="ListPeoplePicker" display-name-key="ListPeoplePicker" description-key="List">List People Picker</value>
19+
</property>
20+
<!-- Output Properties -->
21+
<property name="SelectedPeople" display-name-key="SelectedPeople_Desc" description-key="SelectedPeopleDisplayName" of-type="Object" usage="output" default-value="" required="true" />
22+
<property name="SearchText" description-key="SearchText_Desc" display-name-key="SearchText" usage="output" of-type="SingleLine.Text" />
23+
<property name="AutoHeight" description-key="AutoHeight_Desc" display-name-key="AutoHeight" usage="output" of-type="Whole.None" />
24+
<!-- InputEvent : "SetFocus" -->
25+
<property name="InputEvent" display-name-key="InputEvent" description-key="InputEvent_Desc" of-type="SingleLine.Text" usage="input" />
26+
<!-- Custom Event -->
27+
<event name="OnSearch" display-name-key="OnSearch" description-key="OnSearch_Desc" />
28+
<event name="OnBlur" display-name-key="OnBlur" description-key="OnBlur_Desc" />
29+
<event name="OnFocus" display-name-key="OnFocus" description-key="OnFocus_Desc" />
30+
<!-- Dataset Property -->
31+
<data-set name="Personas" display-name-key="Personas">
32+
<property-set name="PersonaKey" display-name-key="PersonaKey" description-key="PersonaKey_Desc" of-type="SingleLine.Text" usage="bound" required="true" />
33+
<property-set name="PersonaName" display-name-key="PersonaName" of-type="SingleLine.Text" usage="bound" required="true" />
34+
<property-set name="PersonaImgUrl" display-name-key="PersonaImgUrl" of-type="SingleLine.Text" usage="bound" required="false" />
35+
<property-set name="PersonaImageAlt" display-name-key="PersonaImageAlt" of-type="SingleLine.Text" usage="bound" required="false" />
36+
<property-set name="PersonaPresence" display-name-key="PersonaPresence" of-type="SingleLine.Text" usage="bound" required="false" />
37+
<property-set name="PersonaOOF" display-name-key="PersonaOOF" of-type="TwoOptions" usage="bound" required="false" />
38+
<property-set name="PersonaRole" display-name-key="PersonaRole" of-type="SingleLine.Text" usage="bound" required="false" />
39+
</data-set>
40+
<data-set name="Suggestions" display-name-key="Suggestions">
41+
<property-set name="SuggestionKey" display-name-key="SuggestionKey" description-key="SuggestionsKey_Desc" of-type="SingleLine.Text" usage="bound" required="true" />
42+
<property-set name="SuggestionName" display-name-key="SuggestionName" of-type="SingleLine.Text" usage="bound" required="true" />
43+
<property-set name="SuggestionImgUrl" display-name-key="SuggestionImgUrl" of-type="SingleLine.Text" usage="bound" required="false" />
44+
<property-set name="SuggestionImageAlt" display-name-key="SuggestionImageAlt" of-type="SingleLine.Text" usage="bound" required="false" />
45+
<property-set name="SuggestionPresence" display-name-key="SuggestionPresence" of-type="SingleLine.Text" usage="bound" required="false" />
46+
<property-set name="SuggestionOOF" display-name-key="SuggestiosOOF" of-type="TwoOptions" usage="bound" required="false" />
47+
<property-set name="SuggestionRole" display-name-key="SuggestionRole" of-type="SingleLine.Text" usage="bound" required="false" />
48+
</data-set>
49+
<property-dependencies>
50+
<property-dependency input="PeoplePickerType" output="SelectedPeople" required-for="schema" />
51+
<property-dependency input="PeoplePickerType" output="SearchText" required-for="schema" />
52+
<property-dependency input="PeoplePickerType" output="AutoHeight" required-for="schema" />
53+
</property-dependencies>
54+
<resources>
55+
<code path="index.ts" order="1" />
56+
<resx path="strings/PeoplePicker.1033.resx" version="1.0.0" />
57+
<css path="css/PeoplePicker.css" order="1" />
58+
<platform-library name="React" version="16.8.6" />
59+
<platform-library name="Fluent" version="8.29.0" />
60+
</resources>
61+
</control>
62+
</manifest>
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
export const enum ManifestPropertyNames {
2+
dataset = 'dataset',
3+
Suggestions_dataset = 'Suggestions_dataset',
4+
SearchText = 'SearchText',
5+
}
6+
7+
export const enum PersonaColumns {
8+
PersonaKey = 'PersonaKey',
9+
PersonaName = 'PersonaName',
10+
PersonaSize = 'PersonaSize',
11+
PersonaImgUrl = 'PersonaImgUrl',
12+
PersonaImageAlt = 'PersonaImageAlt',
13+
PersonaPresence = 'PersonaPresence',
14+
PersonaOOF = 'PersonaOOF',
15+
PersonaRole = 'PersonaRole',
16+
}
17+
18+
export const enum SuggestionColumns {
19+
SuggestionKey = 'SuggestionKey',
20+
SuggestionName = 'SuggestionName',
21+
SuggestionSize = 'SuggestionSize',
22+
SuggestionImgUrl = 'SuggestionImgUrl',
23+
SuggestionImageAlt = 'SuggestionImageAlt',
24+
SuggestionPresence = 'SuggestionPresence',
25+
SuggestionOOF = 'SuggestionOOF',
26+
SuggestionRole = 'SuggestionRole',
27+
}
28+
29+
export const enum InputEvents {
30+
SetFocus = 'SetFocus',
31+
}
32+
33+
export const enum InputProperties {
34+
InputEvent = 'InputEvent',
35+
SelectedKey = 'SelectedKey',
36+
}
37+
38+
export const enum OutputEvents {
39+
Add = 'Add',
40+
Remove = 'Remove',
41+
Search = 'Search',
42+
}
Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
/* istanbul ignore file */
2+
export const MockResources = {
3+
getResource: jest.fn(),
4+
getString: jest.fn().mockImplementation((k) => {
5+
switch (k) {
6+
case 'Aria_TagRemove':
7+
return 'Remove';
8+
case '...':
9+
return '...';
10+
default:
11+
return 'Resource_String_' + k;
12+
}
13+
}),
14+
};
15+
export class MockContext<T> implements ComponentFramework.Context<T> {
16+
constructor(parameters: T) {
17+
this.parameters = parameters;
18+
this.mode = {
19+
allocatedHeight: -1,
20+
allocatedWidth: -1,
21+
isControlDisabled: false,
22+
isVisible: true,
23+
label: '',
24+
setControlState: jest.fn(),
25+
setFullScreen: jest.fn(),
26+
trackContainerResize: jest.fn(),
27+
};
28+
this.client = {
29+
disableScroll: false,
30+
getClient: jest.fn(),
31+
getFormFactor: jest.fn(),
32+
isOffline: jest.fn(),
33+
};
34+
35+
// Canvas apps currently assigns a positive tab-index
36+
// so we must use this property to assign a positive tab-index also
37+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
38+
(this as any).accessibility = { assignedTabIndex: 0 };
39+
}
40+
client: ComponentFramework.Client;
41+
device: ComponentFramework.Device;
42+
factory: ComponentFramework.Factory;
43+
formatting: ComponentFramework.Formatting;
44+
mode: ComponentFramework.Mode;
45+
navigation: ComponentFramework.Navigation;
46+
resources: ComponentFramework.Resources = MockResources;
47+
userSettings: ComponentFramework.UserSettings;
48+
utils: ComponentFramework.Utility;
49+
webAPI: ComponentFramework.WebApi;
50+
parameters: T;
51+
updatedProperties: string[] = [];
52+
events: IEventBag;
53+
}
54+
55+
export class MockState implements ComponentFramework.Dictionary {}
56+
57+
export class MockStringProperty implements ComponentFramework.PropertyTypes.StringProperty {
58+
constructor(raw?: string | null, formatted?: string | undefined) {
59+
this.raw = raw ?? null;
60+
this.formatted = formatted;
61+
}
62+
raw: string | null;
63+
attributes?: ComponentFramework.PropertyHelper.FieldPropertyMetadata.StringMetadata | undefined;
64+
error: boolean;
65+
errorMessage: string;
66+
formatted?: string | undefined;
67+
security?: ComponentFramework.PropertyHelper.SecurityValues | undefined;
68+
type: string;
69+
}
70+
71+
export class MockWholeNumberProperty implements ComponentFramework.PropertyTypes.WholeNumberProperty {
72+
constructor(raw?: number | null, formatted?: string | undefined) {
73+
this.raw = raw ?? null;
74+
this.formatted = formatted;
75+
}
76+
attributes?: ComponentFramework.PropertyHelper.FieldPropertyMetadata.WholeNumberMetadata | undefined;
77+
raw: number | null;
78+
error: boolean;
79+
errorMessage: string;
80+
formatted?: string | undefined;
81+
security?: ComponentFramework.PropertyHelper.SecurityValues | undefined;
82+
type: string;
83+
}
84+
85+
export class MockEnumProperty<T> implements ComponentFramework.PropertyTypes.EnumProperty<T> {
86+
constructor(raw?: T, type?: string) {
87+
if (raw) this.raw = raw;
88+
if (type) this.type = type;
89+
}
90+
type: string;
91+
raw: T;
92+
}
93+
94+
export class MockTwoOptionsProperty implements ComponentFramework.PropertyTypes.TwoOptionsProperty {
95+
constructor(raw?: boolean) {
96+
if (raw) this.raw = raw;
97+
}
98+
raw: boolean;
99+
attributes?: ComponentFramework.PropertyHelper.FieldPropertyMetadata.TwoOptionMetadata | undefined;
100+
error: boolean;
101+
errorMessage: string;
102+
formatted?: string | undefined;
103+
security?: ComponentFramework.PropertyHelper.SecurityValues | undefined;
104+
type: string;
105+
}
106+
107+
export class MockDecimalProperty implements ComponentFramework.PropertyTypes.DecimalNumberProperty {
108+
constructor(raw?: number) {
109+
if (raw) this.raw = raw;
110+
}
111+
raw: number;
112+
attributes?: ComponentFramework.PropertyHelper.FieldPropertyMetadata.DecimalNumberMetadata | undefined;
113+
error: boolean;
114+
errorMessage: string;
115+
formatted?: string | undefined;
116+
security?: ComponentFramework.PropertyHelper.SecurityValues | undefined;
117+
type: string;
118+
}
119+
120+
export declare type IEventBag = Record<string, () => void>;

0 commit comments

Comments
 (0)