Merge branch 'master' into UpdateBigCalendar

This commit is contained in:
Abderahman88 2020-10-21 21:52:55 +01:00
commit 94312db65f
97 changed files with 32793 additions and 9962 deletions

View File

@ -0,0 +1,25 @@
# EditorConfig helps developers define and maintain consistent
# coding styles between different editors and IDEs
# editorconfig.org
root = true
[*]
# change these settings to your own preference
indent_style = space
indent_size = 2
# we recommend you to keep these unchanged
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
[*.md]
trim_trailing_whitespace = false
[{package,bower}.json]
indent_style = space
indent_size = 2

32
samples/react-avatar/.gitignore vendored Normal file
View File

@ -0,0 +1,32 @@
# Logs
logs
*.log
npm-debug.log*
# Dependency directories
node_modules
# Build generated files
dist
lib
solution
temp
*.sppkg
# Coverage directory used by tools like istanbul
coverage
# OSX
.DS_Store
# Visual Studio files
.ntvs_analysis.dat
.vs
bin
obj
# Resx Generated Code
*.resx.ts
# Styles Generated Code
*.scss.ts

View File

@ -0,0 +1,12 @@
{
"@microsoft/generator-sharepoint": {
"isCreatingSolution": true,
"environment": "spo",
"version": "1.11.0",
"libraryName": "react-avatar",
"libraryId": "b96dfed7-ec48-4082-ba50-f6c7b09143c7",
"packageManager": "npm",
"isDomainIsolated": false,
"componentType": "webpart"
}
}

View File

@ -2,8 +2,7 @@
## Summary ## Summary
This is a sample web part that helps user create their avatar and save as profile picture. User can even download their avatar as PNG file. This webpart can be useful in Intranet to help user create their avatar and save it as profile picture. This is a sample web part that helps user create their avatar and save as profile picture. User can even download their avatar as PNG file. This web part can be useful in Intranet to help user create their avatar and save it as profile picture.
## ##
![directory](/samples/react-avatar/assets/reactAvatarOutcome.gif) ![directory](/samples/react-avatar/assets/reactAvatarOutcome.gif)
@ -16,7 +15,8 @@ This is a sample web part that helps user create their avatar and save as profil
## Used SharePoint Framework Version ## Used SharePoint Framework Version
![drop](https://img.shields.io/badge/version-1.10.0-green.svg)
![SPFx 1.11](https://img.shields.io/badge/version-1.11.0-green.svg)
## Applies to ## Applies to
@ -55,6 +55,7 @@ Web Part Title | Text| no|
## Solution ## Solution
The web part Use avataaars library for creating avatars and MS Graph with User.ReadWrite and User.ReadWriteAll for saving avatar as current users Profile Picture.FileSaver for downloading avatar image as png file. The web part Use avataaars library for creating avatars and MS Graph with User.ReadWrite and User.ReadWriteAll for saving avatar as current users Profile Picture.FileSaver for downloading avatar image as png file.
Solution|Author(s) Solution|Author(s)
@ -66,9 +67,11 @@ react Avatar|Kunj Sangani
Version|Date|Comments Version|Date|Comments
-------|----|-------- -------|----|--------
1.0.0|August 1, 2020|Initial release 1.0.0|August 1, 2020|Initial release
1.0.1|October 20, 2020|Update to SPFx 1.11.0
## Disclaimer ## Disclaimer
**THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT.** **THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT.**
--- ---
@ -81,9 +84,6 @@ Version|Date|Comments
- `gulp build` - `gulp build`
- `gulp bundle --ship` - `gulp bundle --ship`
- `gulp package-solution --ship` - `gulp package-solution --ship`
- `Add to AppCatalog and deploy` - Add to AppCatalog and deploy
<img src="https://telemetry.sharepointpnp.com/sp-dev-fx-webparts/samples/react-avatar" /> <img src="https://telemetry.sharepointpnp.com/sp-dev-fx-webparts/samples/react-avatar" />

View File

@ -3,7 +3,7 @@
"solution": { "solution": {
"name": "react-avatars-client-side-solution", "name": "react-avatars-client-side-solution",
"id": "b96dfed7-ec48-4082-ba50-f6c7b09143c7", "id": "b96dfed7-ec48-4082-ba50-f6c7b09143c7",
"version": "1.0.0.0", "version": "1.0.1.0",
"includeClientSideAssets": true, "includeClientSideAssets": true,
"isDomainIsolated": false, "isDomainIsolated": false,
"webApiPermissionRequests": [ "webApiPermissionRequests": [
@ -11,7 +11,14 @@
"resource": "Microsoft Graph", "resource": "Microsoft Graph",
"scope": "User.ReadWrite" "scope": "User.ReadWrite"
} }
] ],
"developer": {
"name": "Contoso",
"privacyUrl": "https://contoso.com/privacy",
"termsOfUseUrl": "https://contoso.com/terms-of-use",
"websiteUrl": "https://contoso.com/my-app",
"mpnId": "000000"
}
}, },
"paths": { "paths": {
"zippedPackage": "solution/react-avatars.sppkg" "zippedPackage": "solution/react-avatars.sppkg"

File diff suppressed because it is too large Load Diff

View File

@ -13,18 +13,14 @@
}, },
"dependencies": { "dependencies": {
"@material-ui/core": "^4.11.0", "@material-ui/core": "^4.11.0",
"@microsoft/sp-core-library": "1.10.0", "@microsoft/sp-core-library": "1.11.0",
"@microsoft/sp-lodash-subset": "1.10.0", "@microsoft/sp-lodash-subset": "1.11.0",
"@microsoft/sp-office-ui-fabric-core": "1.10.0", "@microsoft/sp-office-ui-fabric-core": "1.11.0",
"@microsoft/sp-property-pane": "1.10.0", "@microsoft/sp-property-pane": "1.11.0",
"@microsoft/sp-webpart-base": "1.10.0", "@microsoft/sp-webpart-base": "1.11.0",
"@types/es6-promise": "0.0.33",
"@types/react": "16.8.8",
"@types/react-dom": "16.8.3",
"@types/webpack-env": "1.13.1",
"avataaars": "^1.2.1", "avataaars": "^1.2.1",
"file-saver": "^2.0.2", "file-saver": "^2.0.2",
"office-ui-fabric-react": "6.189.2", "office-ui-fabric-react": "6.214.0",
"react": "16.8.5", "react": "16.8.5",
"react-dom": "16.8.5" "react-dom": "16.8.5"
}, },
@ -32,14 +28,14 @@
"@types/react": "16.8.8" "@types/react": "16.8.8"
}, },
"devDependencies": { "devDependencies": {
"@microsoft/sp-build-web": "1.10.0",
"@microsoft/sp-tslint-rules": "1.10.0",
"@microsoft/sp-module-interfaces": "1.10.0",
"@microsoft/sp-webpart-workbench": "1.10.0",
"@microsoft/rush-stack-compiler-3.3": "0.3.5", "@microsoft/rush-stack-compiler-3.3": "0.3.5",
"gulp": "~3.9.1", "@microsoft/sp-build-web": "1.11.0",
"@microsoft/sp-module-interfaces": "1.11.0",
"@microsoft/sp-tslint-rules": "1.11.0",
"@microsoft/sp-webpart-workbench": "1.11.0",
"@types/chai": "3.4.34", "@types/chai": "3.4.34",
"@types/mocha": "2.2.38", "@types/mocha": "2.2.38",
"ajv": "~5.2.2" "ajv": "~5.2.2",
"gulp": "~3.9.1"
} }
} }

View File

@ -29,7 +29,8 @@
] ]
}, },
"include": [ "include": [
"src/**/*.ts" "src/**/*.ts",
"src/**/*.tsx"
], ],
"exclude": [ "exclude": [
"node_modules", "node_modules",

View File

@ -104,13 +104,15 @@ Start Date | Date | yes | Event Date
End Date| Date| yes | Event Date End Date| Date| yes | Event Date
## Solution ## Solution
The Web Part Use PnPjs library, Office-ui-fabric-react components. react Big-Calendar Compoment
The Web Part Use PnPjs library, Office-ui-fabric-react components. react Big-Calendar Component
Solution|Author(s) Solution|Author(s)
--------|--------- --------|---------
Calendar Web Part|João Mendes Calendar Web Part|João Mendes
Calendar Web Part|Mohamed Derhalli Calendar Web Part|Mohamed Derhalli
Calendar Web Part (Upgrade)|Hugo Bernier ([@bernier](https://twitter.com/bernierh), [Tahoe Ninjas](https://tahoeninjas.blog/)) Calendar Web Part (Upgrade)|Hugo Bernier ([@bernier](https://twitter.com/bernierh), [Tahoe Ninjas](https://tahoeninjas.blog/))
Calendar Web Part|Nanddeep Nachan ([@NanddeepNachan](https://twitter.com/NanddeepNachan))
## Version history ## Version history
@ -120,6 +122,7 @@ Version|Date|Comments
1.0.1|June 10, 2019|update add recurrence events 1.0.1|June 10, 2019|update add recurrence events
1.0.2|April 25, 2020|Update styles according to the applied theme 1.0.2|April 25, 2020|Update styles according to the applied theme
1.0.3|June 06, 2020|Upgrade to SPFx 1.10.0 1.0.3|June 06, 2020|Upgrade to SPFx 1.10.0
1.0.4|October 18, 2020|Added support for all-day events
## Disclaimer ## Disclaimer

View File

@ -3,7 +3,7 @@
"solution": { "solution": {
"name": "react-calendar-client-side-solution", "name": "react-calendar-client-side-solution",
"id": "3a13208b-3874-4036-9262-4edd22e88187", "id": "3a13208b-3874-4036-9262-4edd22e88187",
"version": "1.0.0.1", "version": "1.0.4.0",
"includeClientSideAssets": true, "includeClientSideAssets": true,
"skipFeatureDeployment": true, "skipFeatureDeployment": true,
"isDomainIsolated": false "isDomainIsolated": false

View File

@ -1,7 +1,7 @@
{ {
"name": "react-calendar", "name": "react-calendar",
"main": "lib/index.js", "main": "lib/index.js",
"version": "1.0.2", "version": "1.0.4",
"private": true, "private": true,
"engines": { "engines": {
"node": ">=0.10.0" "node": ">=0.10.0"

View File

@ -1,33 +1,34 @@
import { IEventData } from '../../services/IEventData'; import { IEventData } from '../../services/IEventData';
import { IUserPermissions } from '../../services/IUserPermissions'; import { IUserPermissions } from '../../services/IUserPermissions';
import { DayOfWeek} from 'office-ui-fabric-react/lib/DatePicker'; import { DayOfWeek } from 'office-ui-fabric-react/lib/DatePicker';
import { IDropdownOption } from 'office-ui-fabric-react/'; import { IDropdownOption } from 'office-ui-fabric-react/';
export interface IEventState { export interface IEventState {
showPanel: boolean; showPanel: boolean;
eventData:IEventData; eventData: IEventData;
firstDayOfWeek?: DayOfWeek; firstDayOfWeek?: DayOfWeek;
startSelectedHour: IDropdownOption ; startSelectedHour: IDropdownOption;
startSelectedMin: IDropdownOption ; startSelectedMin: IDropdownOption;
endSelectedHour: IDropdownOption ; endSelectedHour: IDropdownOption;
endSelectedMin: IDropdownOption ; endSelectedMin: IDropdownOption;
startDate?: Date; startDate?: Date;
endDate?: Date; endDate?: Date;
editorState?: any; editorState?: any;
selectedUsers: string[]; selectedUsers: string[];
locationLatitude: number; locationLatitude: number;
locationLongitude: number; locationLongitude: number;
errorMessage?:string; errorMessage?: string;
hasError?:boolean; hasError?: boolean;
disableButton?: boolean; disableButton?: boolean;
isSaving?:boolean; isSaving?: boolean;
isDeleting?:boolean; isDeleting?: boolean;
displayDialog:boolean; displayDialog: boolean;
userPermissions?: IUserPermissions; userPermissions?: IUserPermissions;
isloading:boolean; isloading: boolean;
isAllDayEvent: boolean;
siteRegionalSettings: any; siteRegionalSettings: any;
recurrenceSeriesEdited?:boolean; recurrenceSeriesEdited?: boolean;
showRecurrenceSeriesInfo:boolean; showRecurrenceSeriesInfo: boolean;
newRecurrenceEvent:boolean; newRecurrenceEvent: boolean;
recurrenceAction:string; recurrenceAction: string;
recurrenceDescription?:string; recurrenceDescription?: string;
} }

View File

@ -79,7 +79,6 @@ export class Event extends React.Component<IEventProps, IEventState> {
navigator.geolocation.getCurrentPosition((position) => { navigator.geolocation.getCurrentPosition((position) => {
this.latitude = position.coords.latitude; this.latitude = position.coords.latitude;
this.longitude = position.coords.longitude; this.longitude = position.coords.longitude;
}); });
} else { } else {
/* geolocation IS NOT available */ /* geolocation IS NOT available */
@ -104,10 +103,11 @@ export class Event extends React.Component<IEventProps, IEventState> {
isSaving: false, isSaving: false,
displayDialog: false, displayDialog: false,
isloading: false, isloading: false,
isAllDayEvent: this.props.event && this.props.event.fAllDayEvent,
siteRegionalSettings: undefined, siteRegionalSettings: undefined,
recurrenceSeriesEdited: false, recurrenceSeriesEdited: false,
showRecurrenceSeriesInfo:false, showRecurrenceSeriesInfo: false,
newRecurrenceEvent:false, newRecurrenceEvent: false,
recurrenceAction: 'display', recurrenceAction: 'display',
userPermissions: { hasPermissionAdd: false, hasPermissionDelete: false, hasPermissionEdit: false, hasPermissionView: false }, userPermissions: { hasPermissionAdd: false, hasPermissionDelete: false, hasPermissionEdit: false, hasPermissionView: false },
}; };
@ -148,13 +148,15 @@ export class Event extends React.Component<IEventProps, IEventState> {
* @memberof Event * @memberof Event
*/ */
private async onSave() { private async onSave() {
let eventData: IEventData = this.state.eventData; let eventData: IEventData = this.state.eventData;
let panelMode = this.props.panelMode; let panelMode = this.props.panelMode;
let startDate: string = null; let startDate: string = null;
let endDate: string = null; let endDate: string = null;
eventData.fRecurrence = false; eventData.fRecurrence = false;
// set All day event
eventData.fAllDayEvent = this.state.isAllDayEvent;
// if there are new Event recurrence or Edited recurrence series // if there are new Event recurrence or Edited recurrence series
if (this.state.recurrenceSeriesEdited || this.state.newRecurrenceEvent) { if (this.state.recurrenceSeriesEdited || this.state.newRecurrenceEvent) {
eventData.RecurrenceData = this.returnedRecurrenceInfo.recurrenceData; eventData.RecurrenceData = this.returnedRecurrenceInfo.recurrenceData;
@ -163,16 +165,17 @@ export class Event extends React.Component<IEventProps, IEventState> {
if (eventData.EventType == "0" && this.state.newRecurrenceEvent) { if (eventData.EventType == "0" && this.state.newRecurrenceEvent) {
eventData.EventType = "1"; eventData.EventType = "1";
eventData.fRecurrence= true; eventData.fRecurrence = true;
eventData.UID = getGUID();
}
if (eventData.EventType == "1" && this.state.recurrenceSeriesEdited) {
eventData.fRecurrence= true;
eventData.UID = getGUID(); eventData.UID = getGUID();
} }
} else { if (eventData.EventType == "1" && this.state.recurrenceSeriesEdited) {
if (this.state.eventData.EventType == '1'){ // recurrence exception eventData.fRecurrence = true;
eventData.UID = getGUID();
}
}
else {
if (this.state.eventData.EventType == '1') { // recurrence exception
eventData.RecurrenceID = eventData.EventDate.toString(); eventData.RecurrenceID = eventData.EventDate.toString();
eventData.MasterSeriesItemID = eventData.ID.toString(); eventData.MasterSeriesItemID = eventData.ID.toString();
eventData.EventType = "4"; eventData.EventType = "4";
@ -185,20 +188,19 @@ export class Event extends React.Component<IEventProps, IEventState> {
endDate = `${moment(this.state.endDate).format('YYYY/MM/DD')}`; endDate = `${moment(this.state.endDate).format('YYYY/MM/DD')}`;
} }
// Start Date
const startTime = `${this.state.startSelectedHour.key}:${this.state.startSelectedMin.key}`; const startTime = `${this.state.startSelectedHour.key}:${this.state.startSelectedMin.key}`;
const startDateTime = `${startDate} ${startTime}`; const startDateTime = `${startDate} ${startTime}`;
const start = moment(startDateTime, 'YYYY/MM/DD HH:mm').toLocaleString(); const start = moment(startDateTime, 'YYYY/MM/DD HH:mm').toLocaleString();
eventData.EventDate = new Date(start); eventData.EventDate = new Date(start);
// End Date // End Date
const endTime = `${this.state.endSelectedHour.key}:${this.state.endSelectedMin.key}`; const endTime = `${this.state.endSelectedHour.key}:${this.state.endSelectedMin.key}`;
const endDateTime = `${endDate} ${endTime}`; const endDateTime = `${endDate} ${endTime}`;
const end = moment(endDateTime, 'YYYY/MM/DD HH:mm').toLocaleString(); const end = moment(endDateTime, 'YYYY/MM/DD HH:mm').toLocaleString();
eventData.EndDate = new Date(end); eventData.EndDate = new Date(end);
// get Geolocation // get Geolocation
eventData.geolocation = { Latitude: this.latitude, Longitude: this.longitude }; eventData.geolocation = { Latitude: this.latitude, Longitude: this.longitude };
const locationInfo = await this.spService.getGeoLactionName(this.latitude, this.longitude); const locationInfo = await this.spService.getGeoLactionName(this.latitude, this.longitude);
eventData.location = locationInfo ? locationInfo.display_name : 'N/A'; eventData.location = locationInfo ? locationInfo.display_name : 'N/A';
@ -213,7 +215,6 @@ export class Event extends React.Component<IEventProps, IEventState> {
try { try {
for (const user of this.attendees) { for (const user of this.attendees) {
const userInfo: any = await this.spService.getUserByLoginName(user.id, this.props.siteUrl); const userInfo: any = await this.spService.getUserByLoginName(user.id, this.props.siteUrl);
eventData.attendes.push(Number(userInfo.Id)); eventData.attendes.push(Number(userInfo.Id));
} }
@ -479,7 +480,7 @@ export class Event extends React.Component<IEventProps, IEventState> {
this.setState({ isDeleting: false }); this.setState({ isDeleting: false });
this.props.onDissmissPanel(true); this.props.onDissmissPanel(true);
} catch (error) { } catch (error) {
this.setState({ hasError: true, errorMessage: error.message, isDeleting: false, displayDialog:false }); this.setState({ hasError: true, errorMessage: error.message, isDeleting: false, displayDialog: false });
} }
} }
@ -616,7 +617,7 @@ export class Event extends React.Component<IEventProps, IEventState> {
} }
return format("{}{} {} {}", return format("{}{} {} {}",
frequency === 1 ? format(strings.everyFormat, frequency) : frequency === 2 ? format(strings.everySecondFormat, frequency): format(strings.everyNthFormat, frequency), frequency === 1 ? format(strings.everyFormat, frequency) : frequency === 2 ? format(strings.everySecondFormat, frequency) : format(strings.everyNthFormat, frequency),
strings.weekLabel, strings.weekLabel,
strings.onLabel, strings.onLabel,
days.join(", ")); days.join(", "));
@ -634,7 +635,7 @@ export class Event extends React.Component<IEventProps, IEventState> {
const day: number = parseInt(rule["day"]); const day: number = parseInt(rule["day"]);
return format("{}{} {}", return format("{}{} {}",
frequency === 1 ? format(strings.everyFormat, frequency) : frequency === 2 ? format(strings.everySecondFormat, frequency): format(strings.everyNthFormat, frequency), frequency === 1 ? format(strings.everyFormat, frequency) : frequency === 2 ? format(strings.everySecondFormat, frequency) : format(strings.everyNthFormat, frequency),
strings.monthLabel, strings.monthLabel,
format(strings.onTheDayFormat, day) format(strings.onTheDayFormat, day)
); );
@ -676,7 +677,7 @@ export class Event extends React.Component<IEventProps, IEventState> {
switch (key) { switch (key) {
case "monthFrequency": case "monthFrequency":
const frequency = parseInt(rule[key]); const frequency = parseInt(rule[key]);
switch(frequency) { switch (frequency) {
case 1: case 1:
frequencyFormat = format(strings.everyFormat, frequency); frequencyFormat = format(strings.everyFormat, frequency);
break; break;
@ -719,7 +720,7 @@ export class Event extends React.Component<IEventProps, IEventState> {
let month: string; let month: string;
let day: string; let day: string;
for (let key of keys) { for (let key of keys) {
switch(key) { switch (key) {
case "yearFrequency": case "yearFrequency":
const frequency = parseInt(rule[key]); const frequency = parseInt(rule[key]);
const frequencyFormat = frequency == 1 ? strings.everyFormat : frequency == 2 ? strings.everySecondFormat : strings.everyNthFormat; const frequencyFormat = frequency == 1 ? strings.everyFormat : frequency == 2 ? strings.everySecondFormat : strings.everyNthFormat;
@ -770,7 +771,7 @@ export class Event extends React.Component<IEventProps, IEventState> {
let order: string; let order: string;
let dayTypeString: string; let dayTypeString: string;
for (let key of keys) { for (let key of keys) {
switch(key) { switch (key) {
case "yearFrequency": case "yearFrequency":
const frequency = parseInt(rule[key]); const frequency = parseInt(rule[key]);
const frequencyFormat = frequency === 1 ? strings.everyFormat : frequency === 2 ? strings.everySecondFormat : strings.everyNthFormat; const frequencyFormat = frequency === 1 ? strings.everyFormat : frequency === 2 ? strings.everySecondFormat : strings.everyNthFormat;
@ -814,13 +815,12 @@ export class Event extends React.Component<IEventProps, IEventState> {
}); });
const recurrenceInfo: any = await promise; const recurrenceInfo: any = await promise;
if(recurrenceInfo != null) if (recurrenceInfo != null) {
{
let keys = Object.keys(recurrenceInfo.recurrence.rule[0].repeat[0]); let keys = Object.keys(recurrenceInfo.recurrence.rule[0].repeat[0]);
const recurrenceTypes = ["daily", "weekly", "monthly", "monthlyByDay", "yearly", "yearlyByDay"]; const recurrenceTypes = ["daily", "weekly", "monthly", "monthlyByDay", "yearly", "yearlyByDay"];
for (var key of keys) { for (var key of keys) {
const rule = recurrenceInfo.recurrence.rule[0].repeat[0][key][0]['$']; const rule = recurrenceInfo.recurrence.rule[0].repeat[0][key][0]['$'];
switch(recurrenceTypes.indexOf(key)) { switch (recurrenceTypes.indexOf(key)) {
case 0: case 0:
return this.parseDailyRule(rule); return this.parseDailyRule(rule);
break; break;
@ -899,15 +899,15 @@ export class Event extends React.Component<IEventProps, IEventState> {
{ {
(this.state.eventData && (this.state.eventData.EventType !== "0" && this.state.showRecurrenceSeriesInfo !== true)) ? (this.state.eventData && (this.state.eventData.EventType !== "0" && this.state.showRecurrenceSeriesInfo !== true)) ?
<div> <div>
<h2 style={{ display: 'inline-block', verticalAlign: 'top' }}>{ strings.recurrenceEventLabel }</h2> <h2 style={{ display: 'inline-block', verticalAlign: 'top' }}>{strings.recurrenceEventLabel}</h2>
{ this.state.recurrenceDescription ? <span style={{ display: 'block' }} >{ this.state.recurrenceDescription }</span> : null } {this.state.recurrenceDescription ? <span style={{ display: 'block' }} >{this.state.recurrenceDescription}</span> : null}
<DefaultButton <DefaultButton
style={{ display: 'inline-block', marginLeft: '330px', verticalAlign: 'top', width: 'auto' }} style={{ display: 'inline-block', marginLeft: '330px', verticalAlign: 'top', width: 'auto' }}
iconProps={{ iconName: 'RecurringEvent' }} iconProps={{ iconName: 'RecurringEvent' }}
allowDisabledFocus={true} allowDisabledFocus={true}
onClick={this.onEditRecurrence} onClick={this.onEditRecurrence}
> >
{ strings.editRecurrenceSeries } {strings.editRecurrenceSeries}
</DefaultButton> </DefaultButton>
</div> </div>
@ -948,6 +948,7 @@ export class Event extends React.Component<IEventProps, IEventState> {
hidden={this.state.showRecurrenceSeriesInfo} hidden={this.state.showRecurrenceSeriesInfo}
/> />
</div> </div>
{!this.state.isAllDayEvent &&
<div style={{ display: 'inline-block', verticalAlign: 'top', paddingRight: 10 }}> <div style={{ display: 'inline-block', verticalAlign: 'top', paddingRight: 10 }}>
<Dropdown <Dropdown
selectedKey={this.state.startSelectedHour.key} selectedKey={this.state.startSelectedHour.key}
@ -982,6 +983,8 @@ export class Event extends React.Component<IEventProps, IEventState> {
]} ]}
/> />
</div> </div>
}
{!this.state.isAllDayEvent &&
<div style={{ display: 'inline-block', verticalAlign: 'top', }}> <div style={{ display: 'inline-block', verticalAlign: 'top', }}>
<Dropdown <Dropdown
label={strings.StartMinLabel} label={strings.StartMinLabel}
@ -1004,6 +1007,7 @@ export class Event extends React.Component<IEventProps, IEventState> {
]} ]}
/> />
</div> </div>
}
<br /> <br />
<div style={{ display: 'inline-block', verticalAlign: 'top', paddingRight: 10 }}> <div style={{ display: 'inline-block', verticalAlign: 'top', paddingRight: 10 }}>
<DatePicker <DatePicker
@ -1020,6 +1024,7 @@ export class Event extends React.Component<IEventProps, IEventState> {
hidden={this.state.showRecurrenceSeriesInfo} hidden={this.state.showRecurrenceSeriesInfo}
/> />
</div> </div>
{!this.state.isAllDayEvent &&
<div style={{ display: 'inline-block', verticalAlign: 'top', paddingRight: 10 }}> <div style={{ display: 'inline-block', verticalAlign: 'top', paddingRight: 10 }}>
<Dropdown <Dropdown
selectedKey={this.state.endSelectedHour.key} selectedKey={this.state.endSelectedHour.key}
@ -1054,6 +1059,8 @@ export class Event extends React.Component<IEventProps, IEventState> {
]} ]}
/> />
</div> </div>
}
{!this.state.isAllDayEvent &&
<div style={{ display: 'inline-block', verticalAlign: 'top', }}> <div style={{ display: 'inline-block', verticalAlign: 'top', }}>
<Dropdown <Dropdown
label={strings.EndMinLabel} label={strings.EndMinLabel}
@ -1077,18 +1084,34 @@ export class Event extends React.Component<IEventProps, IEventState> {
]} ]}
/> />
</div> </div>
}
<Label>{this.state.siteRegionalSettings ? this.state.siteRegionalSettings.Description : ''}</Label> <Label>{this.state.siteRegionalSettings ? this.state.siteRegionalSettings.Description : ''}</Label>
<br /> <br />
{
<div style={{ display: 'inline-block', verticalAlign: 'top', width: '200px' }}>
<Toggle
defaultChecked={this.state.eventData && this.state.eventData.fAllDayEvent}
inlineLabel={true}
label={strings.allDayEventLabel}
onText={strings.onLabel}
offText={strings.offLabel}
onChange={(ev, checked: boolean) => {
ev.preventDefault();
this.setState({ isAllDayEvent: checked });
}}
/>
</div>
<br />
{
this.state.eventData && (this.state.eventData.EventType == "0") ? this.state.eventData && (this.state.eventData.EventType == "0") ?
<div style={{ display: 'inline-block', verticalAlign: 'top', width: '200px' }}> <div style={{ display: 'inline-block', verticalAlign: 'top', width: '200px' }}>
<Toggle <Toggle
defaultChecked={false} defaultChecked={false}
inlineLabel={true} inlineLabel={true}
label={ strings.ifRecurrenceLabel } label={strings.ifRecurrenceLabel}
onText={ strings.onLabel } onText={strings.onLabel}
offText={ strings.offLabel } offText={strings.offLabel}
onChange={(ev, checked: boolean) => { onChange={(ev, checked: boolean) => {
ev.preventDefault(); ev.preventDefault();
this.setState({ showRecurrenceSeriesInfo: checked, newRecurrenceEvent: checked }); this.setState({ showRecurrenceSeriesInfo: checked, newRecurrenceEvent: checked });
@ -1109,12 +1132,11 @@ export class Event extends React.Component<IEventProps, IEventState> {
siteUrl={this.props.siteUrl} siteUrl={this.props.siteUrl}
returnRecurrenceData={this.returnRecurrenceInfo} returnRecurrenceData={this.returnRecurrenceInfo}
> >
</EventRecurrenceInfo> </EventRecurrenceInfo>
) )
} }
< Label > {strings.eventDescriptionLabel }</Label> <Label>{strings.eventDescriptionLabel}</Label>
<div className={styles.description}> <div className={styles.description}>
<Editor <Editor
@ -1125,7 +1147,6 @@ export class Event extends React.Component<IEventProps, IEventState> {
</div> </div>
<div> <div>
<PeoplePicker <PeoplePicker
webAbsoluteUrl={this.props.siteUrl} webAbsoluteUrl={this.props.siteUrl}
context={this.props.context} context={this.props.context}
titleText={strings.AttendeesLabel} titleText={strings.AttendeesLabel}

View File

@ -1,25 +1,25 @@
export interface IEventData { export interface IEventData {
Id?:number; Id?: number;
ID?:number; ID?: number;
title: string; title: string;
Description?: any; Description?: any;
location?:string; location?: string;
EventDate: Date; EventDate: Date;
EndDate: Date; EndDate: Date;
color?:string; color?: string;
ownerInitial?: string; ownerInitial?: string;
ownerPhoto?:string; ownerPhoto?: string;
ownerEmail?:string; ownerEmail?: string;
ownerName?:string; ownerName?: string;
fAllDayEvent?: boolean; fAllDayEvent?: boolean;
attendes?: number[]; attendes?: number[];
geolocation?: {Longitude:number, Latitude: number}; geolocation?: { Longitude: number, Latitude: number };
Category?: string; Category?: string;
Duration?: number; Duration?: number;
RecurrenceData?:string; RecurrenceData?: string;
fRecurrence?:string | boolean; fRecurrence?: string | boolean;
EventType?:string; EventType?: string;
UID?:string; UID?: string;
RecurrenceID?: string; RecurrenceID?: string;
MasterSeriesItemID?: string; MasterSeriesItemID?: string;
} }

View File

@ -76,7 +76,6 @@ export default class spservices {
let results = null; let results = null;
try { try {
const web = new Web(siteUrl); const web = new Web(siteUrl);
const siteTimeZoneHours: number = await this.getSiteTimeZoneHours(siteUrl); const siteTimeZoneHours: number = await this.getSiteTimeZoneHours(siteUrl);
results = await web.lists.getById(listId).items.add({ results = await web.lists.getById(listId).items.add({
@ -87,7 +86,7 @@ export default class spservices {
EventDate: new Date(moment(newEvent.EventDate).add(siteTimeZoneHours, 'hours').toISOString()), EventDate: new Date(moment(newEvent.EventDate).add(siteTimeZoneHours, 'hours').toISOString()),
EndDate: new Date(moment(newEvent.EndDate).add(siteTimeZoneHours, 'hours').toISOString()), EndDate: new Date(moment(newEvent.EndDate).add(siteTimeZoneHours, 'hours').toISOString()),
Location: newEvent.location, Location: newEvent.location,
fAllDayEvent: false, fAllDayEvent: newEvent.fAllDayEvent,
fRecurrence: newEvent.fRecurrence, fRecurrence: newEvent.fRecurrence,
Category: newEvent.Category, Category: newEvent.Category,
EventType: newEvent.EventType, EventType: newEvent.EventType,
@ -96,7 +95,8 @@ export default class spservices {
MasterSeriesItemID: newEvent.MasterSeriesItemID, MasterSeriesItemID: newEvent.MasterSeriesItemID,
RecurrenceID: newEvent.RecurrenceID ? moment(newEvent.RecurrenceID).add(siteTimeZoneHours, 'hours').toISOString() : undefined, RecurrenceID: newEvent.RecurrenceID ? moment(newEvent.RecurrenceID).add(siteTimeZoneHours, 'hours').toISOString() : undefined,
}); });
} catch (error) { }
catch (error) {
return Promise.reject(error); return Promise.reject(error);
} }
return results; return results;
@ -117,13 +117,13 @@ export default class spservices {
try { try {
const siteTimeZoneHours: number = await this.getSiteTimeZoneHours(siteUrl); const siteTimeZoneHours: number = await this.getSiteTimeZoneHours(siteUrl);
const web = new Web(siteUrl); const web = new Web(siteUrl);
//"Title","fRecurrence", "fAllDayEvent","EventDate", "EndDate", "Description","ID", "Location","Geolocation","ParticipantsPickerId" //"Title","fRecurrence", "fAllDayEvent","EventDate", "EndDate", "Description","ID", "Location","Geolocation","ParticipantsPickerId"
const event = await web.lists.getById(listId).items.usingCaching().getById(eventId) const event = await web.lists.getById(listId).items.usingCaching().getById(eventId)
.select("RecurrenceID", "MasterSeriesItemID", "Id", "ID", "ParticipantsPickerId", "EventType", "Title", "Description", "EventDate", "EndDate", "Location", "Author/SipAddress", "Author/Title", "Geolocation", "fAllDayEvent", "fRecurrence", "RecurrenceData", "RecurrenceData", "Duration", "Category", "UID") .select("RecurrenceID", "MasterSeriesItemID", "Id", "ID", "ParticipantsPickerId", "EventType", "Title", "Description", "EventDate", "EndDate", "Location", "Author/SipAddress", "Author/Title", "Geolocation", "fAllDayEvent", "fRecurrence", "RecurrenceData", "RecurrenceData", "Duration", "Category", "UID")
.expand("Author") .expand("Author")
.get(); .get();
returnEvent = { returnEvent = {
Id: event.ID, Id: event.ID,
ID: event.ID, ID: event.ID,
@ -139,7 +139,7 @@ export default class spservices {
color: '', color: '',
ownerName: event.Author.Title, ownerName: event.Author.Title,
attendes: event.ParticipantsPickerId, attendes: event.ParticipantsPickerId,
fAllDayEvent: false, fAllDayEvent: event.fAllDayEvent,
geolocation: { Longitude: event.Geolocation ? event.Geolocation.Longitude : 0, Latitude: event.Geolocation ? event.Geolocation.Latitude : 0 }, geolocation: { Longitude: event.Geolocation ? event.Geolocation.Longitude : 0, Latitude: event.Geolocation ? event.Geolocation.Latitude : 0 },
Category: event.Category, Category: event.Category,
Duration: event.Duration, Duration: event.Duration,
@ -149,13 +149,13 @@ export default class spservices {
RecurrenceID: event.RecurrenceID, RecurrenceID: event.RecurrenceID,
MasterSeriesItemID: event.MasterSeriesItemID, MasterSeriesItemID: event.MasterSeriesItemID,
}; };
} catch (error) { }
catch (error) {
return Promise.reject(error); return Promise.reject(error);
} }
return returnEvent; return returnEvent;
} }
/** /**
* *
* @param {IEventData} newEvent * @param {IEventData} newEvent
@ -167,13 +167,12 @@ export default class spservices {
public async updateEvent(updateEvent: IEventData, siteUrl: string, listId: string) { public async updateEvent(updateEvent: IEventData, siteUrl: string, listId: string) {
let results = null; let results = null;
try { try {
// delete all recursive extentions before update recurrence event // delete all recursive extentions before update recurrence event
if (updateEvent.EventType.toString() == "1") await this.deleteRecurrenceExceptions(updateEvent, siteUrl, listId); if (updateEvent.EventType.toString() == "1") await this.deleteRecurrenceExceptions(updateEvent, siteUrl, listId);
const siteTimeZoneHours: number = await this.getSiteTimeZoneHours(siteUrl); const siteTimeZoneHours: number = await this.getSiteTimeZoneHours(siteUrl);
const web = new Web(siteUrl); const web = new Web(siteUrl);
//"Title","fRecurrence", "fAllDayEvent","EventDate", "EndDate", "Description","ID", "Location","Geolocation","ParticipantsPickerId" //"Title","fRecurrence", "fAllDayEvent","EventDate", "EndDate", "Description","ID", "Location","Geolocation","ParticipantsPickerId"
let newItem: any = { let newItem: any = {
Title: updateEvent.title, Title: updateEvent.title,
@ -183,12 +182,13 @@ export default class spservices {
EventDate: new Date(moment(updateEvent.EventDate).add(siteTimeZoneHours, 'hours').toISOString()), EventDate: new Date(moment(updateEvent.EventDate).add(siteTimeZoneHours, 'hours').toISOString()),
EndDate: new Date(moment(updateEvent.EndDate).add(siteTimeZoneHours, 'hours').toISOString()), EndDate: new Date(moment(updateEvent.EndDate).add(siteTimeZoneHours, 'hours').toISOString()),
Location: updateEvent.location, Location: updateEvent.location,
fAllDayEvent: false, fAllDayEvent: updateEvent.fAllDayEvent,
fRecurrence: updateEvent.fRecurrence, fRecurrence: updateEvent.fRecurrence,
Category: updateEvent.Category, Category: updateEvent.Category,
RecurrenceData: updateEvent.RecurrenceData ? await this.deCodeHtmlEntities(updateEvent.RecurrenceData) : "", RecurrenceData: updateEvent.RecurrenceData ? await this.deCodeHtmlEntities(updateEvent.RecurrenceData) : "",
EventType: updateEvent.EventType, EventType: updateEvent.EventType,
}; };
if (updateEvent.UID) { if (updateEvent.UID) {
newItem.UID = updateEvent.UID; newItem.UID = updateEvent.UID;
} }
@ -197,7 +197,8 @@ export default class spservices {
} }
results = await web.lists.getById(listId).items.getById(updateEvent.Id).update(newItem); results = await web.lists.getById(listId).items.getById(updateEvent.Id).update(newItem);
} catch (error) { }
catch (error) {
return Promise.reject(error); return Promise.reject(error);
} }
return results; return results;
@ -489,6 +490,7 @@ export default class spservices {
if (results && results.Row.length > 0) { if (results && results.Row.length > 0) {
let event: any = ''; let event: any = '';
for (event of results.Row) { for (event of results.Row) {
const initialsArray: string[] = event.Author[0].title.split(' '); const initialsArray: string[] = event.Author[0].title.split(' ');
const initials: string = initialsArray[0].charAt(0) + initialsArray[initialsArray.length - 1].charAt(0); const initials: string = initialsArray[0].charAt(0) + initialsArray[initialsArray.length - 1].charAt(0);
@ -501,22 +503,20 @@ export default class spservices {
const CategoryColorValue: any[] = categoryColor.filter((value) => { const CategoryColorValue: any[] = categoryColor.filter((value) => {
return value.category == event.Category; return value.category == event.Category;
}); });
const isAllDayEvent: boolean = event.fAllDayEvent === "Yes";
for (const attendee of event.ParticipantsPicker) { for (const attendee of event.ParticipantsPicker) {
attendees.push(parseInt(attendee.id)); attendees.push(parseInt(attendee.id));
} }
events.push({ events.push({
Id: event.ID, Id: event.ID,
ID: event.ID, ID: event.ID,
EventType: event.EventType, EventType: event.EventType,
title: await this.deCodeHtmlEntities(event.Title), title: await this.deCodeHtmlEntities(event.Title),
Description: event.Description, Description: event.Description,
EventDate: isAllDayEvent ? new Date(moment(event.EventDate).toISOString()) : new Date(moment(event.EventDate).subtract((siteTimeZoneHours), 'hour').toISOString()),
EventDate: new Date(moment(event.EventDate).subtract((siteTimeZoneHours), 'hour').toISOString()), EndDate: isAllDayEvent ? new Date(moment(event.EndDate).toISOString()) : new Date(moment(event.EndDate).subtract(siteTimeZoneHours, 'hour').toISOString()),
EndDate: new Date(moment(event.EndDate).subtract(siteTimeZoneHours, 'hour').toISOString()),
location: event.Location, location: event.Location,
ownerEmail: event.Author[0].email, ownerEmail: event.Author[0].email,
ownerPhoto: userPictureUrl ? ownerPhoto: userPictureUrl ?
@ -525,7 +525,7 @@ export default class spservices {
color: CategoryColorValue.length > 0 ? CategoryColorValue[0].color : '#1a75ff', // blue default color: CategoryColorValue.length > 0 ? CategoryColorValue[0].color : '#1a75ff', // blue default
ownerName: event.Author[0].title, ownerName: event.Author[0].title,
attendes: attendees, attendes: attendees,
fAllDayEvent: false, fAllDayEvent: isAllDayEvent,
geolocation: { Longitude: parseFloat(geolocation[0]), Latitude: parseFloat(geolocation[1]) }, geolocation: { Longitude: parseFloat(geolocation[0]), Latitude: parseFloat(geolocation[1]) },
Category: event.Category, Category: event.Category,
Duration: event.Duration, Duration: event.Duration,
@ -540,6 +540,7 @@ export default class spservices {
let parseEvt: parseRecurrentEvent = new parseRecurrentEvent(); let parseEvt: parseRecurrentEvent = new parseRecurrentEvent();
events = parseEvt.parseEvents(events, null, null); events = parseEvt.parseEvents(events, null, null);
} }
// Return Data // Return Data
return events; return events;
} catch (error) { } catch (error) {

View File

@ -115,6 +115,7 @@ define([], function () {
showMore: "more", showMore: "more",
recurrenceEventLabel: "Recurrence Event", recurrenceEventLabel: "Recurrence Event",
editRecurrenceSeries: "Edit Recurrence Series", editRecurrenceSeries: "Edit Recurrence Series",
allDayEventLabel: "All Day Event ?",
ifRecurrenceLabel: "Recurrence ?", ifRecurrenceLabel: "Recurrence ?",
onLabel: "On", onLabel: "On",
offLabel: "Off", offLabel: "Off",

View File

@ -115,6 +115,7 @@ declare interface ICalendarWebPartStrings {
showMore: string; showMore: string;
recurrenceEventLabel: string; recurrenceEventLabel: string;
editRecurrenceSeries: string; editRecurrenceSeries: string;
allDayEventLabel: string;
ifRecurrenceLabel: string; ifRecurrenceLabel: string;
onLabel: string; onLabel: string;
offLabel: string; offLabel: string;

View File

@ -114,6 +114,7 @@ define([], function () {
showMore: "mer", showMore: "mer",
recurrenceEventLabel: "Återkommande händelse", recurrenceEventLabel: "Återkommande händelse",
editRecurrenceSeries: "Redigera serie", editRecurrenceSeries: "Redigera serie",
allDayEventLabel: "All Day Event ?",
ifRecurrenceLabel: "Återkommande ?", ifRecurrenceLabel: "Återkommande ?",
onLabel: "På", onLabel: "På",
offLabel: "Av", offLabel: "Av",

View File

@ -44,6 +44,14 @@ Site Url of library | Text| yes|
Picture Library| Choice/Dropdown | yes| this is filled with all Picture Libraries (BaseTemplate : 109) Picture Library| Choice/Dropdown | yes| this is filled with all Picture Libraries (BaseTemplate : 109)
number images to load | number| yes | number between 1 and 200 number images to load | number| yes | number between 1 and 200
### react-slick Props
For all available props, go [here](https://react-slick.neostack.com/docs/api/).
### react-slick Methods
For all available methods, go [here](https://react-slick.neostack.com/docs/api#methods)
## Solution ## Solution
The web part Use PnPjs library, Microsoft Graph API, Office-ui-fabric-react components, react-slick Component The web part Use PnPjs library, Microsoft Graph API, Office-ui-fabric-react components, react-slick Component

View File

@ -1,19 +1,16 @@
@import '~@microsoft/sp-office-ui-fabric-core/dist/sass/SPFabricCore.scss'; @import '~@microsoft/sp-office-ui-fabric-core/dist/sass/SPFabricCore.scss';
.carousel { .carousel {
.container { .container {
max-width: 700px; max-width: 700px;
margin: 0px auto; margin: 0px auto;
box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.2), 0 25px 50px 0 rgba(0, 0, 0, 0.1); box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.2), 0 25px 50px 0 rgba(0, 0, 0, 0.1);
} }
.row { .row {
@include ms-Grid-row; @include ms-Grid-row;
@include ms-fontColor-white; @include ms-fontColor-white;
background-color: $ms-color-themeDark; background-color: $ms-color-themeDark;
padding: 20px; padding: 20px;
} }
.column { .column {
@include ms-Grid-col; @include ms-Grid-col;
@include ms-lg10; @include ms-lg10;
@ -21,37 +18,31 @@
@include ms-xlPush2; @include ms-xlPush2;
@include ms-lgPush1; @include ms-lgPush1;
} }
.title { .title {
@include ms-font-xl; @include ms-font-xl;
@include ms-fontColor-white; @include ms-fontColor-white;
} }
.subTitle { .subTitle {
@include ms-font-l; @include ms-font-l;
@include ms-fontColor-white; @include ms-fontColor-white;
} }
.description { .description {
@include ms-font-l; @include ms-font-l;
@include ms-fontColor-white; @include ms-fontColor-white;
} }
.button { .button {
// Our button // Our button
text-decoration: none; text-decoration: none;
height: 32px; height: 32px;
// Primary Button // Primary Button
min-width: 80px; min-width: 80px;
background-color: $ms-color-themePrimary; background-color: $ms-color-themePrimary;
border-color: $ms-color-themePrimary; border-color: $ms-color-themePrimary;
color: $ms-color-white; color: $ms-color-white;
// Basic Button // Basic Button
outline: transparent; outline: transparent;
position: relative; position: relative;
font-family: "Segoe UI WestEuropean","Segoe UI",-apple-system,BlinkMacSystemFont,Roboto,"Helvetica Neue",sans-serif; font-family: "Segoe UI WestEuropean", "Segoe UI", -apple-system, BlinkMacSystemFont, Roboto, "Helvetica Neue", sans-serif;
-webkit-font-smoothing: antialiased; -webkit-font-smoothing: antialiased;
font-size: $ms-font-size-m; font-size: $ms-font-size-m;
font-weight: $ms-font-weight-regular; font-weight: $ms-font-weight-regular;
@ -60,7 +51,6 @@
cursor: pointer; cursor: pointer;
display: inline-block; display: inline-block;
padding: 0 16px; padding: 0 16px;
.label { .label {
font-weight: $ms-font-weight-semibold; font-weight: $ms-font-weight-semibold;
font-size: $ms-font-size-m; font-size: $ms-font-size-m;
@ -72,3 +62,13 @@
} }
} }
} }
:global {
.slick-next,
.slick-prev {
background: $ms-color-themePrimary !important;
}
.slick-dots {
position: initial !important;
}
}

View File

@ -173,7 +173,7 @@ export default class Carousel extends React.Component<ICarouselProps, ICarouselS
} }
public render(): React.ReactElement<ICarouselProps> { public render(): React.ReactElement<ICarouselProps> {
const sliderSettings = { const sliderSettings = {
dots: false, dots: true,
infinite: true, infinite: true,
speed: 500, speed: 500,
slidesToShow: 1, slidesToShow: 1,
@ -181,7 +181,7 @@ export default class Carousel extends React.Component<ICarouselProps, ICarouselS
lazyLoad: 'progressive', lazyLoad: 'progressive',
autoplaySpeed: 3000, autoplaySpeed: 3000,
initialSlide: this.state.photoIndex, initialSlide: this.state.photoIndex,
arrows: false, arrows: true,
draggable: true, draggable: true,
adaptiveHeight: true, adaptiveHeight: true,
useCSS: true, useCSS: true,
@ -216,7 +216,7 @@ export default class Carousel extends React.Component<ICarouselProps, ICarouselS
<Label style={{ width: '250px', margin: 'auto', fontSize: FontSizes.size20 }}>No images in the library</Label> <Label style={{ width: '250px', margin: 'auto', fontSize: FontSizes.size20 }}>No images in the library</Label>
</div> </div>
: :
<div style={{ width: '100%', height: '100%', overflow: 'hidden' }}> <div style={{ width: '100%', height: '100%'}}>
<div style={{ width: '100%'}}> <div style={{ width: '100%'}}>
<Slider <Slider

View File

@ -33,7 +33,6 @@
## Used SharePoint Framework Version ## Used SharePoint Framework Version
![SPFx 1.11](https://img.shields.io/badge/version-1.11-green.svg) ![SPFx 1.11](https://img.shields.io/badge/version-1.11-green.svg)
## Applies to ## Applies to
@ -47,21 +46,18 @@
Property |Type|Required| comments Property |Type|Required| comments
--------------------|----|--------|---------- --------------------|----|--------|----------
Title | Text| no|WebPart Title Title | Text| No|WebPart Title
searchFirstName | boolean|no| Lastname or Firstname search query searchFirstName | boolean|No| Lastname or Firstname search query
Properties to search | text | no | By default **FirstName,LastName,WorkEmail,Department** are used for search. You can add custom properties separated by comma. Properties to search | text | No | By default **FirstName,LastName,WorkEmail,Department** are used for search. You can add custom properties separated by comma.
Results per page | number | Number of people result to be displayed per page. Max of **20** is allowed, default of **10** is set. Properties to sent as clear text | text | No | By default if the search key has empty spaces, its replaced with **+** before sending it to the search query. The search properties mentioned here will be sent without the empty space replacemnt.
Results per page | number | Yes | Number of people result to be displayed per page. Max of **20** is allowed, default of **10** is set.
## Solution ## Solution
The web part use PnPjs library, Office-ui-fabric-react components The web part use PnPjs library, Office-ui-fabric-react components
Solution|Author(s) Solution|Author(s)
--------|--------- --------|---------
Directory Web Part| João Mendes Directory Web Part|João Mendes
Directory Web Part| Peter Paul Kirschner ([@petkir_at](https://twitter.com/petkir_at)) Directory Web Part| Peter Paul Kirschner ([@petkir_at](https://twitter.com/petkir_at))
Directory Web Part| Sudharsan K ([@sudharsank](https://twitter.com/sudharsank)) Directory Web Part| Sudharsan K ([@sudharsank](https://twitter.com/sudharsank))
@ -71,7 +67,8 @@ Version|Date|Comments
-------|----|-------- -------|----|--------
1.0.0|July 29, 2019|Initial release 1.0.0|July 29, 2019|Initial release
1.0.1|July 19, 2020|Bugfix and mock-service for workbench (```LivePersonaCard``` not supported in workbench) 1.0.1|July 19, 2020|Bugfix and mock-service for workbench (```LivePersonaCard``` not supported in workbench)
2.0.0|Sep 18 2020|React hooks, paging, dynamic search props, result alignment using office ui fabric stack. 2.0.0.0|Sep 18 2020|React hooks, paging, dynamic search props, result alignment using office ui fabric stack.
3.0.0.0|Oct 17 2020|Minor fixes and add the additional web part property.
## Disclaimer ## Disclaimer
@ -89,8 +86,4 @@ Version|Date|Comments
- `gulp package-solution --ship` - `gulp package-solution --ship`
- `Add to AppCatalog and deploy` - `Add to AppCatalog and deploy`
<img src="https://telemetry.sharepointpnp.com/sp-dev-fx-webparts/samples/react-directory" /> <img src="https://telemetry.sharepointpnp.com/sp-dev-fx-webparts/samples/react-directory" />

View File

@ -10,7 +10,7 @@
}, },
"name": "Search Directory", "name": "Search Directory",
"id": "5b62bc16-3a71-461d-be2f-16bfcb011e8a", "id": "5b62bc16-3a71-461d-be2f-16bfcb011e8a",
"version": "2.0.0.0", "version": "3.0.0.0",
"includeClientSideAssets": true, "includeClientSideAssets": true,
"skipFeatureDeployment": true, "skipFeatureDeployment": true,
"isDomainIsolated": false "isDomainIsolated": false

View File

@ -19,6 +19,7 @@ export interface IDirectoryWebPartProps {
title: string; title: string;
searchFirstName: boolean; searchFirstName: boolean;
searchProps: string; searchProps: string;
clearTextSearchProps: string;
pageSize: number; pageSize: number;
} }
@ -47,6 +48,7 @@ export default class DirectoryWebPart extends BaseClientSideWebPart<
this.properties.title = value; this.properties.title = value;
}, },
searchProps: this.properties.searchProps, searchProps: this.properties.searchProps,
clearTextSearchProps: this.properties.clearTextSearchProps,
pageSize: this.properties.pageSize pageSize: this.properties.pageSize
} }
); );
@ -91,6 +93,13 @@ export default class DirectoryWebPart extends BaseClientSideWebPart<
multiline: false, multiline: false,
resizable: false resizable: false
}), }),
PropertyPaneTextField('clearTextSearchProps', {
label: strings.ClearTextSearchPropsLabel,
description: strings.ClearTextSearchPropsDesc,
value: this.properties.clearTextSearchProps,
multiline: false,
resizable: false
}),
PropertyPaneSlider('pageSize', { PropertyPaneSlider('pageSize', {
label: 'Results per page', label: 'Results per page',
showValue: true, showValue: true,

View File

@ -19,6 +19,7 @@ import { IDirectoryProps } from './IDirectoryProps';
import Paging from './Pagination/Paging'; import Paging from './Pagination/Paging';
const slice: any = require('lodash/slice'); const slice: any = require('lodash/slice');
const filter: any = require('lodash/filter');
const wrapStackTokens: IStackTokens = { childrenGap: 30 }; const wrapStackTokens: IStackTokens = { childrenGap: 30 };
const DirectoryHook: React.FC<IDirectoryProps> = (props) => { const DirectoryHook: React.FC<IDirectoryProps> = (props) => {
@ -131,11 +132,36 @@ const DirectoryHook: React.FC<IDirectoryProps> = (props) => {
props.searchProps.split(',') : ['FirstName', 'LastName', 'WorkEmail', 'Department']; props.searchProps.split(',') : ['FirstName', 'LastName', 'WorkEmail', 'Department'];
let qryText: string = ''; let qryText: string = '';
let finalSearchText: string = searchText ? searchText.replace(/ /g, '+') : searchText; let finalSearchText: string = searchText ? searchText.replace(/ /g, '+') : searchText;
if (props.clearTextSearchProps) {
let tmpCTProps: string[] = props.clearTextSearchProps.indexOf(',') >= 0 ? props.clearTextSearchProps.split(',') : [props.clearTextSearchProps];
if (tmpCTProps.length > 0) {
searchProps.map((srchprop, index) => {
let ctPresent: any[] = filter(tmpCTProps, (o) => { return o.toLowerCase() == srchprop.toLowerCase(); });
if (ctPresent.length > 0) {
if(index == searchProps.length - 1) {
qryText += `${srchprop}:${searchText}*`;
} else qryText += `${srchprop}:${searchText}* OR `;
} else {
if(index == searchProps.length - 1) {
qryText += `${srchprop}:${finalSearchText}*`;
} else qryText += `${srchprop}:${finalSearchText}* OR `;
}
});
} else {
searchProps.map((srchprop, index) => { searchProps.map((srchprop, index) => {
if (index == searchProps.length - 1) if (index == searchProps.length - 1)
qryText += `${srchprop}:${finalSearchText}*`; qryText += `${srchprop}:${finalSearchText}*`;
else qryText += `${srchprop}:${finalSearchText}* OR `; else qryText += `${srchprop}:${finalSearchText}* OR `;
}); });
}
} else {
searchProps.map((srchprop, index) => {
if (index == searchProps.length - 1)
qryText += `${srchprop}:${finalSearchText}*`;
else qryText += `${srchprop}:${finalSearchText}* OR `;
});
}
console.log(qryText);
const users = await _services.searchUsersNew('', qryText, false); const users = await _services.searchUsersNew('', qryText, false);
setstate({ setstate({
...state, ...state,
@ -252,12 +278,10 @@ const DirectoryHook: React.FC<IDirectoryProps> = (props) => {
}, [state.users, props.pageSize]); }, [state.users, props.pageSize]);
useEffect(() => { useEffect(() => {
console.log("Alpha change");
if (alphaKey.length > 0 && alphaKey != "0") _searchByAlphabets(false); if (alphaKey.length > 0 && alphaKey != "0") _searchByAlphabets(false);
}, [alphaKey]); }, [alphaKey]);
useEffect(() => { useEffect(() => {
console.log("yes");
_loadAlphabets(); _loadAlphabets();
_searchByAlphabets(true); _searchByAlphabets(true);
}, [props]); }, [props]);

View File

@ -7,5 +7,6 @@ export interface IDirectoryProps {
searchFirstName: boolean; searchFirstName: boolean;
updateProperty: (value: string) => void; updateProperty: (value: string) => void;
searchProps?: string; searchProps?: string;
clearTextSearchProps?: string;
pageSize?: number; pageSize?: number;
} }

View File

@ -10,6 +10,8 @@ define([], function() {
"LoadingText": "Searching for user. Please wait...", "LoadingText": "Searching for user. Please wait...",
"SearchPropsLabel": "Properties to search", "SearchPropsLabel": "Properties to search",
"SearchPropsDesc": "Enter the properties separated by comma to be used for search", "SearchPropsDesc": "Enter the properties separated by comma to be used for search",
"ClearTextSearchPropsLabel":"Properties whose values are not replaced",
"ClearTextSearchPropsDesc":"Enter the properties separated by comma to be sent as it is without replacing space with '+'",
"PagingLabel": "Results per page" "PagingLabel": "Results per page"
} }
}); });

View File

@ -9,6 +9,8 @@ declare interface IDirectoryWebPartStrings {
LoadingText: string; LoadingText: string;
SearchPropsLabel: string; SearchPropsLabel: string;
SearchPropsDesc: string; SearchPropsDesc: string;
ClearTextSearchPropsLabel: string;
ClearTextSearchPropsDesc: string;
PagingLabel: string; PagingLabel: string;
} }

View File

@ -15,7 +15,12 @@
{ {
"resource": "Microsoft Graph", "resource": "Microsoft Graph",
"scope": "Directory.AccessAsUser.All" "scope": "Directory.AccessAsUser.All"
},
{
"resource": "Microsoft Graph",
"scope": "User.Read"
} }
] ]
}, },
"paths": { "paths": {

View File

@ -59,6 +59,8 @@ react-outlook-add-todo-task|Luis Mañez (MVP, [ClearPeople](http://www.clearpeop
Version|Date|Comments Version|Date|Comments
-------|----|-------- -------|----|--------
1.0.0|Jun 3, 2020|Initial release 1.0.0|Jun 3, 2020|Initial release
1.1.0|Sep 3, 2020|To Do item body coming from email body
1.2.0|Oct 16, 2020|To Do item body included a "open in outlook" link
## Disclaimer ## Disclaimer

View File

@ -162,8 +162,8 @@ export default class CreateTask extends React.Component<
status: "notStarted", status: "notStarted",
title: taskTitle, title: taskTitle,
body: { body: {
content: this.props.context.item.body, content: this._composeBody(this.props.context.item.body),
contentType: "text", contentType: "html",
}, },
}; };
@ -180,4 +180,10 @@ export default class CreateTask extends React.Component<
console.log(e); console.log(e);
} }
} }
private _composeBody(emailBody: string): string {
const id: string = encodeURIComponent(this.props.context.item.id);
const link: string = `<a href='https://outlook.office365.com/owa/?ItemID=${id}&exvsurl=1&viewmodel=ReadMessageItem'>Open in Outlook</a>`;
return `${emailBody}<p style='font-size: large; padding-top: 10px;'>${link}</p>`;
}
} }

View File

@ -16,7 +16,7 @@
"environment": "spo", "environment": "spo",
"framework": "react", "framework": "react",
"isCreatingSolution": true, "isCreatingSolution": true,
"version": "1.10.0", "version": "1.11.0",
"libraryName": "react-personal-greeting", "libraryName": "react-personal-greeting",
"libraryId": "5e7ea24d-fccc-4d96-a56c-488564d9c61c", "libraryId": "5e7ea24d-fccc-4d96-a56c-488564d9c61c",
"packageManager": "npm", "packageManager": "npm",

View File

@ -8,7 +8,7 @@ The web part pulls in the current user's name and displays it on the page. The g
## Used SharePoint Framework Version ## Used SharePoint Framework Version
![1.10.0](https://img.shields.io/badge/version-1.10.0-green.svg) ![1.11.0](https://img.shields.io/badge/version-1.11.0-green.svg)
## Applies to ## Applies to
@ -30,6 +30,7 @@ react-personal-greeting|Zach Roberts - [SPODev](https://spodev.com)
Version|Date|Comments Version|Date|Comments
-------|----|-------- -------|----|--------
1.1|September 24, 2020| Updated SPFX version and added font-size
1.0|April 14, 2020|Initial release 1.0|April 14, 2020|Initial release
## Disclaimer ## Disclaimer
@ -46,8 +47,8 @@ Version|Date|Comments
* `gulp build` * `gulp build`
* `gulp bundle --ship` * `gulp bundle --ship`
* `gulp package-solution --ship` * `gulp package-solution --ship`
* add the webpart to your tenant app store * add the web part to your tenant app store
* add the app to a SharePoint site and then add the webpart to the page * add the app to a SharePoint site and then add the web part to the page
## Features ## Features
@ -55,8 +56,8 @@ Version|Date|Comments
This Web Part illustrates the following concepts on top of the SharePoint Framework: This Web Part illustrates the following concepts on top of the SharePoint Framework:
* Using the SPFx context to gather the current user's display name. * Using the SPFx context to gather the current user's display name.
* Adjusting the styles of the component in the webpart using the props adjusted through the property pane. * Adjusting the styles of the component in the web part using the props adjusted through the property pane.
* PnP SPFx Placeholder - This component allows you to have a placeholder visble under certain conditions if your web parts requires some setup. * PnP SPFx Placeholder - This component allows you to have a placeholder visible under certain conditions if your web parts requires some setup.
* PnP SPFx Color Picker - This component adds an awesome color picker to the property pane, great for adjusting colors in your webpart. * PnP SPFx Color Picker - This component adds an awesome color picker to the property pane, great for adjusting colors in your web part.
<img src="https://telemetry.sharepointpnp.com/sp-dev-fx-webparts/samples/react-personal-greeting" /> <img src="https://telemetry.sharepointpnp.com/sp-dev-fx-webparts/samples/react-personal-greeting" />

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 MiB

After

Width:  |  Height:  |  Size: 2.0 MiB

View File

@ -3,9 +3,16 @@
"solution": { "solution": {
"name": "react-personal-greeting-client-side-solution", "name": "react-personal-greeting-client-side-solution",
"id": "5e7ea24d-fccc-4d96-a56c-488564d9c61c", "id": "5e7ea24d-fccc-4d96-a56c-488564d9c61c",
"version": "1.0.0.0", "version": "1.1.0.0",
"includeClientSideAssets": true, "includeClientSideAssets": true,
"isDomainIsolated": false "isDomainIsolated": false,
"developer": {
"name": "",
"mpnId": "",
"privacyUrl": "",
"termsOfUseUrl": "",
"websiteUrl": ""
}
}, },
"paths": { "paths": {
"zippedPackage": "solution/react-personal-greeting.sppkg" "zippedPackage": "solution/react-personal-greeting.sppkg"

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
{ {
"name": "react-personal-greeting", "name": "react-personal-greeting",
"version": "0.0.1", "version": "1.1.0",
"private": true, "private": true,
"main": "lib/index.js", "main": "lib/index.js",
"engines": { "engines": {
@ -14,19 +14,15 @@
"postversion": "gulp dist" "postversion": "gulp dist"
}, },
"dependencies": { "dependencies": {
"@microsoft/sp-core-library": "1.10.0", "@microsoft/sp-core-library": "1.11.0",
"@microsoft/sp-lodash-subset": "1.10.0", "@microsoft/sp-lodash-subset": "1.11.0",
"@microsoft/sp-office-ui-fabric-core": "1.10.0", "@microsoft/sp-office-ui-fabric-core": "1.11.0",
"@microsoft/sp-property-pane": "1.10.0", "@microsoft/sp-property-pane": "1.11.0",
"@microsoft/sp-webpart-base": "1.10.0", "@microsoft/sp-webpart-base": "1.11.0",
"@pnp/pnpjs": "^2.0.3", "@pnp/pnpjs": "^2.0.3",
"@pnp/spfx-controls-react": "1.17.0", "@pnp/spfx-controls-react": "1.17.0",
"@pnp/spfx-property-controls": "1.17.0", "@pnp/spfx-property-controls": "1.17.0",
"@types/es6-promise": "0.0.33", "office-ui-fabric-react": "6.214.0",
"@types/react": "16.8.8",
"@types/react-dom": "16.8.3",
"@types/webpack-env": "1.13.1",
"office-ui-fabric-react": "6.189.2",
"react": "16.8.5", "react": "16.8.5",
"react-dom": "16.8.5" "react-dom": "16.8.5"
}, },
@ -36,10 +32,10 @@
"devDependencies": { "devDependencies": {
"@microsoft/microsoft-graph-types": "^1.12.0", "@microsoft/microsoft-graph-types": "^1.12.0",
"@microsoft/rush-stack-compiler-3.3": "0.3.5", "@microsoft/rush-stack-compiler-3.3": "0.3.5",
"@microsoft/sp-build-web": "1.10.0", "@microsoft/sp-build-web": "1.11.0",
"@microsoft/sp-module-interfaces": "1.10.0", "@microsoft/sp-module-interfaces": "1.11.0",
"@microsoft/sp-tslint-rules": "1.10.0", "@microsoft/sp-tslint-rules": "1.11.0",
"@microsoft/sp-webpart-workbench": "1.10.0", "@microsoft/sp-webpart-workbench": "1.11.0",
"@types/chai": "3.4.34", "@types/chai": "3.4.34",
"@types/mocha": "2.2.38", "@types/mocha": "2.2.38",
"ajv": "~5.2.2", "ajv": "~5.2.2",

View File

@ -19,7 +19,7 @@
"group": { "default": "Other" }, "group": { "default": "Other" },
"title": { "default": "Personal Greeting" }, "title": { "default": "Personal Greeting" },
"description": { "default": "Personal Greeting description" }, "description": { "default": "Personal Greeting description" },
"officeFabricIconFontName": "Page", "officeFabricIconFontName": "TextDocumentShared",
"properties": { "properties": {
"description": "Personal Greeting" "description": "Personal Greeting"
} }

View File

@ -4,11 +4,11 @@ import { Version } from '@microsoft/sp-core-library';
import { import {
IPropertyPaneConfiguration, IPropertyPaneConfiguration,
PropertyPaneTextField, PropertyPaneTextField,
PropertyPaneDropdown PropertyPaneDropdown,
IPropertyPaneDropdownOption
} from '@microsoft/sp-property-pane'; } from '@microsoft/sp-property-pane';
import { BaseClientSideWebPart, WebPartContext } from '@microsoft/sp-webpart-base'; import { BaseClientSideWebPart, WebPartContext } from '@microsoft/sp-webpart-base';
import * as strings from 'PersonalGreetingWebPartStrings';
import PersonalGreeting from './components/PersonalGreeting'; import PersonalGreeting from './components/PersonalGreeting';
import { IPersonalGreetingProps } from './components/IPersonalGreetingProps'; import { IPersonalGreetingProps } from './components/IPersonalGreetingProps';
import { PropertyFieldColorPicker, PropertyFieldColorPickerStyle } from '@pnp/spfx-property-controls/lib/PropertyFieldColorPicker'; import { PropertyFieldColorPicker, PropertyFieldColorPickerStyle } from '@pnp/spfx-property-controls/lib/PropertyFieldColorPicker';
@ -18,8 +18,56 @@ export interface IPersonalGreetingWebPartProps {
context: WebPartContext; context: WebPartContext;
position: string; position: string;
textColor: string; textColor: string;
fontSize: number;
} }
const fontSizeOptions: IPropertyPaneDropdownOption[] = [
{
key: 12,
text: '12'
},
{
key: 14,
text: '16'
},
{
key: 18,
text: '18'
},
{
key: 20,
text: '20'
},
{
key: 24,
text: '24'
},
{
key: 28,
text: '28'
},
{
key: 32,
text: '32'
},
{
key: 36,
text: '36'
},
{
key: 42,
text: '42'
},
{
key: 46,
text: '46'
},
{
key: 68,
text: '68'
},
];
export default class PersonalGreetingWebPart extends BaseClientSideWebPart <IPersonalGreetingWebPartProps> { export default class PersonalGreetingWebPart extends BaseClientSideWebPart <IPersonalGreetingWebPartProps> {
public render(): void { public render(): void {
@ -29,7 +77,8 @@ export default class PersonalGreetingWebPart extends BaseClientSideWebPart <IPer
greetingText: this.properties.greetingText, greetingText: this.properties.greetingText,
context: this.context, context: this.context,
position: this.properties.position, position: this.properties.position,
textColor: this.properties.textColor textColor: this.properties.textColor,
fontSize: this.properties.fontSize
} }
); );
@ -76,6 +125,11 @@ export default class PersonalGreetingWebPart extends BaseClientSideWebPart <IPer
} }
] ]
}), }),
PropertyPaneDropdown('fontSize', {
label: 'Font Size',
options: fontSizeOptions,
selectedKey: 20
}),
PropertyFieldColorPicker('textColor', { PropertyFieldColorPicker('textColor', {
label: 'Text Color', label: 'Text Color',
properties: this.properties, properties: this.properties,

View File

@ -6,4 +6,5 @@ export interface IPersonalGreetingProps {
context: WebPartContext; context: WebPartContext;
position: string; position: string;
textColor: string; textColor: string;
fontSize: number;
} }

View File

@ -7,7 +7,6 @@
box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.2), 0 25px 50px 0 rgba(0, 0, 0, 0.1); box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.2), 0 25px 50px 0 rgba(0, 0, 0, 0.1);
} }
.title { .title {
font-size: x-large;
font-weight: 375; font-weight: 375;
} }
} }

View File

@ -1,25 +1,22 @@
import * as React from 'react'; import * as React from 'react';
import styles from './PersonalGreeting.module.scss'; import styles from './PersonalGreeting.module.scss';
import { IPersonalGreetingProps } from './IPersonalGreetingProps'; import { IPersonalGreetingProps } from './IPersonalGreetingProps';
import { escape } from '@microsoft/sp-lodash-subset';
import { CommandBar, ICommandBarItemProps } from 'office-ui-fabric-react/lib/CommandBar';
import { Placeholder } from "@pnp/spfx-controls-react/lib/Placeholder"; import { Placeholder } from "@pnp/spfx-controls-react/lib/Placeholder";
export default class PersonalGreeting extends React.Component<IPersonalGreetingProps, {}> { export default class PersonalGreeting extends React.Component<IPersonalGreetingProps, {}> {
public render(): React.ReactElement<IPersonalGreetingProps> { public render(): React.ReactElement<IPersonalGreetingProps> {
const custstyles = { const custStyles = {
'text-align': this.props.position, 'text-align': this.props.position,
'color': this.props.textColor 'color': this.props.textColor,
'fontSize': this.props.fontSize
} as React.CSSProperties; } as React.CSSProperties;
return ( return (
<div className={ styles.personalGreeting }> <div className={ styles.personalGreeting }>
{this.props.greetingText == null ? {this.props.greetingText == null ?
<Placeholder iconName='Edit' iconText='Configure the web part' description='Please configure the web part' buttonLabel='Configure' onConfigure={this._onConfigure} /> <Placeholder iconName='Edit' iconText='Configure the web part' description='Please configure the web part' buttonLabel='Configure' onConfigure={this._onConfigure} />
: <div className={ styles.title } style={custstyles}>{this.props.greetingText} {this.props.context.pageContext.user.displayName}</div> : <div className={ styles.title } style={custStyles}>{this.props.greetingText} {this.props.context.pageContext.user.displayName}</div>
// : <h2>{this.props.greetingText} {this.props.context.pageContext.user.displayName} </h2>
} }
</div> </div>
); );

View File

@ -30,7 +30,8 @@
"esModuleInterop": true "esModuleInterop": true
}, },
"include": [ "include": [
"src/**/*.ts" "src/**/*.ts",
"src/**/*.tsx"
], ],
"exclude": [ "exclude": [
"node_modules", "node_modules",

View File

@ -2,7 +2,7 @@
"@microsoft/generator-sharepoint": { "@microsoft/generator-sharepoint": {
"isCreatingSolution": true, "isCreatingSolution": true,
"environment": "spo", "environment": "spo",
"version": "1.10.0", "version": "1.11.0",
"libraryName": "react-simple-poll", "libraryName": "react-simple-poll",
"libraryId": "890ced10-dacc-4d0d-b9df-355a289980b3", "libraryId": "890ced10-dacc-4d0d-b9df-355a289980b3",
"packageManager": "npm", "packageManager": "npm",

View File

@ -1,6 +1,7 @@
# React Quick Poll # React Quick Poll
## Summary ## Summary
> This component is developed for the users who really need to create a **_Poll_** within a minute and with less maintenance. **_'QuickPoll'_** list will be created automatically to store the user response. > This component is developed for the users who really need to create a **_Poll_** within a minute and with less maintenance. **_'QuickPoll'_** list will be created automatically to store the user response.
> Following are some of the features of this component. > Following are some of the features of this component.
* **_Easy_** to setup with most of the configurations are optional. * **_Easy_** to setup with most of the configurations are optional.
@ -22,7 +23,7 @@
* **_Start Date_** - Date when the end user can start seeing the poll question. * **_Start Date_** - Date when the end user can start seeing the poll question.
* **_End Date_** - Last day of the poll question visible to the end user. * **_End Date_** - Last day of the poll question visible to the end user.
3. **_Success Message_** - Message to be displayed to the user after successful submission. It is optional, if not provided the default message '**Thank you for your submission**' will be displayed. 3. **_Success Message_** - Message to be displayed to the user after a successful submission. It is optional, if not provided the default message '**Thank you for your submission**' will be displayed.
4. **_Response Message_** - Message to be displayed to the user with the user response, once the user has submitted. It is optional, if not provided the default message '**You voted for: ~User Response~**' will be displayed below the chart. 4. **_Response Message_** - Message to be displayed to the user with the user response, once the user has submitted. It is optional, if not provided the default message '**You voted for: ~User Response~**' will be displayed below the chart.
@ -36,33 +37,37 @@
* Make sure the **Multi Choice** option is chosen wisely, do not change once the user started to response to the poll. * Make sure the **Multi Choice** option is chosen wisely, do not change once the user started to response to the poll.
## Preview ## Preview
![Advanced-Comments-Box](./assets/react-quick-poll.gif)
![React-Quick-Poll](./assets/react-quick-poll.gif)
## Used SharePoint Framework Version ## Used SharePoint Framework Version
![drop](https://img.shields.io/badge/version-GA-green.svg)
![SPFx 1.11](https://img.shields.io/badge/version-1.11-green.svg)
## Applies to ## Applies to
* [SharePoint Framework](https://docs.microsoft.com/sharepoint/dev/spfx/sharepoint-framework-overview) * [SharePoint Framework](https:/dev.office.com/sharepoint)
* [Office 365 tenant](https://docs.microsoft.com/sharepoint/dev/spfx/set-up-your-development-environment) * [Office 365 tenant](https://dev.office.com/sharepoint/docs/spfx/set-up-your-development-environment)
## Prerequisites ## Prerequisites
> **@microsoft/generator-sharepoint - 1.10.0** > **@microsoft/generator-sharepoint - 1.11.0**
## Solution ## Solution
Solution|Author(s) Solution|Author(s)
--------|--------- --------|---------
react-quick-poll | Sudharsan K.([@sudharsank](https://twitter.com/sudharsank), [Know More](http://windowssharepointserver.blogspot.com/)) react-quick-poll | Sudharsan K.([@sudharsank](https://twitter.com/sudharsank), [Know More](https://spknowledge.com/))
## Version history ## Version history
Version|Date|Comments Version|Date|Comments
-------|----|-------- -------|----|--------
1.0.0.1|Feb 24 2020|Initial release 1.0.0.1|Feb 24 2020|Initial release
2.0.0.0|Oct 17 2020|Initial release
## Disclaimer ## Disclaimer
**THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT.** **THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT.**
## Minimal Path to Awesome ## Minimal Path to Awesome
@ -71,26 +76,27 @@ Version|Date|Comments
- in the command line run: - in the command line run:
- `npm install` - `npm install`
- `gulp bundle --ship && gulp package-solution --ship` - `gulp bundle --ship && gulp package-solution --ship`
- Add the .sppkg file to the app catalog and add the **'_Quick Poll_'** web part to the page. - Add the `.sppkg` file to the app catalog and add the **'_Quick Poll_'** web part to the page.
## Features ## Features
- Used [PnP Property Pane Controls](https://sharepoint.github.io/sp-dev-fx-property-controls/) to create the property pane controls - Used [PnP Property Pane Controls](https://sharepoint.github.io/sp-dev-fx-property-controls/) to create the property pane controls
* [PropertyFieldToggleWithCallout](https://sharepoint.github.io/sp-dev-fx-property-controls/controls/PropertyFieldToggleWithCallout/) * [PropertyFieldToggleWithCallout](https://sharepoint.github.io/sp-dev-fx-property-controls/controls/PropertyFieldToggleWithCallout/)
* [PropertyFieldCollectionData](https://sharepoint.github.io/sp-dev-fx-property-controls/controls/PropertyFieldCollectionData/) * [PropertyFieldCollectionData](https://sharepoint.github.io/sp-dev-fx-property-controls/controls/PropertyFieldCollectionData/)
* [PropertyFieldChoiceGroupWithCallout](https://sharepoint.github.io/sp-dev-fx-property-controls/controls/PropertyFieldChoiceGroupWithCallout/) * [PropertyFieldChoiceGroupWithCallout](https://sharepoint.github.io/sp-dev-fx-property-controls/controls/PropertyFieldChoiceGroupWithCallout/)
* PropertyPaneTextField (From base property controls) * PropertyPaneTextField (From base property controls)
- Used [PnP Reusable REact Controls](https://sharepoint.github.io/sp-dev-fx-controls-react/) - Used [PnP Reusable React Controls](https://sharepoint.github.io/sp-dev-fx-controls-react/)
* [Placeholder](https://sharepoint.github.io/sp-dev-fx-controls-react/controls/Placeholder/) * [Placeholder](https://sharepoint.github.io/sp-dev-fx-controls-react/controls/Placeholder/)
* [ChartControl](https://sharepoint.github.io/sp-dev-fx-controls-react/controls/ChartControl/) * [ChartControl](https://sharepoint.github.io/sp-dev-fx-controls-react/controls/ChartControl/)
- Used few styles and controls (Text, MessageBar, ProgressIndicator, PrimaryButton, ChoiceGroup, List, Checkbox) from [Office UI Fabric](https://developer.microsoft.com/en-us/fabric) - Used few styles and controls (Text, MessageBar, ProgressIndicator, PrimaryButton, ChoiceGroup, List, Checkbox) from [Office UI Fabric](https://developer.microsoft.com/en-us/fabric)
- Used [PnP](https://pnp.github.io/pnpjs/) for communication with SharePoint. - Used [PnP](https://pnp.github.io/pnpjs/) for communication with SharePoint.
- Used [Moment.js](https://momentjs.com/) for datetime formatting. - Used [Moment.js](https://momentjs.com/) for datetime formatting.
#### Local Mode ### Local Mode
This solution doesn't work on local mode. This solution doesn't work on local mode.
#### SharePoint Mode ### SharePoint Mode
If you want to try on a real environment, open: If you want to try on a real environment, open:
[O365 Workbench](https://your-domain.sharepoint.com/_layouts/15/workbench.aspx) [O365 Workbench](https://your-domain.sharepoint.com/_layouts/15/workbench.aspx)
<img src="https://telemetry.sharepointpnp.com/sp-dev-fx-webparts/samples/react-quick-poll" />

View File

@ -3,9 +3,16 @@
"solution": { "solution": {
"name": "React Quick Poll", "name": "React Quick Poll",
"id": "890ced10-dacc-4d0d-b9df-355a289980b3", "id": "890ced10-dacc-4d0d-b9df-355a289980b3",
"version": "1.0.0.1", "version": "2.0.0.0",
"includeClientSideAssets": true, "includeClientSideAssets": true,
"isDomainIsolated": false "isDomainIsolated": false,
"developer": {
"name": "Sudharsan K.",
"privacyUrl": "",
"termsOfUseUrl": "",
"websiteUrl": "https://spknowledge.com/",
"mpnId": "000000"
}
}, },
"paths": { "paths": {
"zippedPackage": "solution/react-quick-poll.sppkg" "zippedPackage": "solution/react-quick-poll.sppkg"

File diff suppressed because it is too large Load Diff

View File

@ -12,20 +12,17 @@
"test": "gulp test" "test": "gulp test"
}, },
"dependencies": { "dependencies": {
"@microsoft/sp-core-library": "1.10.0", "@microsoft/sp-core-library": "1.11.0",
"@microsoft/sp-lodash-subset": "1.10.0", "@microsoft/sp-lodash-subset": "1.11.0",
"@microsoft/sp-office-ui-fabric-core": "1.10.0", "@microsoft/sp-office-ui-fabric-core": "1.11.0",
"@microsoft/sp-property-pane": "1.10.0", "@microsoft/sp-property-pane": "1.11.0",
"@microsoft/sp-webpart-base": "1.10.0", "@microsoft/sp-webpart-base": "1.11.0",
"@pnp/polyfill-ie11": "^2.0.1-5",
"@pnp/sp": "^2.0.2", "@pnp/sp": "^2.0.2",
"@pnp/spfx-controls-react": "^1.16.0", "@pnp/spfx-controls-react": "^1.16.0",
"@pnp/spfx-property-controls": "^1.16.0", "@pnp/spfx-property-controls": "^1.16.0",
"@types/es6-promise": "0.0.33",
"@types/react": "16.8.8",
"@types/react-dom": "16.8.3",
"@types/webpack-env": "1.13.1",
"moment": "^2.24.0", "moment": "^2.24.0",
"office-ui-fabric-react": "6.189.2", "office-ui-fabric-react": "6.214.0",
"react": "16.8.5", "react": "16.8.5",
"react-dom": "16.8.5" "react-dom": "16.8.5"
}, },
@ -33,14 +30,14 @@
"@types/react": "16.8.8" "@types/react": "16.8.8"
}, },
"devDependencies": { "devDependencies": {
"@microsoft/sp-build-web": "1.10.0",
"@microsoft/sp-tslint-rules": "1.10.0",
"@microsoft/sp-module-interfaces": "1.10.0",
"@microsoft/sp-webpart-workbench": "1.10.0",
"@microsoft/rush-stack-compiler-3.3": "0.3.5", "@microsoft/rush-stack-compiler-3.3": "0.3.5",
"gulp": "~3.9.1", "@microsoft/sp-build-web": "1.11.0",
"@microsoft/sp-module-interfaces": "1.11.0",
"@microsoft/sp-tslint-rules": "1.11.0",
"@microsoft/sp-webpart-workbench": "1.11.0",
"@types/chai": "3.4.34", "@types/chai": "3.4.34",
"@types/mocha": "2.2.38", "@types/mocha": "2.2.38",
"ajv": "~5.2.2" "ajv": "~5.2.2",
"gulp": "~3.9.1"
} }
} }

View File

@ -11,6 +11,7 @@ import { PropertyFieldToggleWithCallout } from '@pnp/spfx-property-controls/lib/
import { PropertyFieldChoiceGroupWithCallout } from '@pnp/spfx-property-controls/lib/PropertyFieldChoiceGroupWithCallout'; import { PropertyFieldChoiceGroupWithCallout } from '@pnp/spfx-property-controls/lib/PropertyFieldChoiceGroupWithCallout';
import { PropertyFieldCollectionData, CustomCollectionFieldType } from '@pnp/spfx-property-controls/lib/PropertyFieldCollectionData'; import { PropertyFieldCollectionData, CustomCollectionFieldType } from '@pnp/spfx-property-controls/lib/PropertyFieldCollectionData';
import { DateTimePicker, DateConvention, TimeConvention } from '@pnp/spfx-controls-react/lib/DateTimePicker'; import { DateTimePicker, DateConvention, TimeConvention } from '@pnp/spfx-controls-react/lib/DateTimePicker';
import "@pnp/polyfill-ie11";
import { sp } from "@pnp/sp/presets/all"; import { sp } from "@pnp/sp/presets/all";
import * as strings from 'SimplePollWebPartStrings'; import * as strings from 'SimplePollWebPartStrings';
import SimplePoll from './components/SimplePoll'; import SimplePoll from './components/SimplePoll';
@ -35,7 +36,10 @@ export default class SimplePollWebPart extends BaseClientSideWebPart<ISimplePoll
private userinfo: IUserInfo = null; private userinfo: IUserInfo = null;
protected async onInit(): Promise<void> { protected async onInit(): Promise<void> {
await super.onInit(); await super.onInit();
sp.setup(this.context); sp.setup({
ie11: true,
spfxContext: this.context
});
this.helper = new SPHelper(); this.helper = new SPHelper();
this.userinfo = await this.helper.getCurrentUserInfo(); this.userinfo = await this.helper.getCurrentUserInfo();
} }
@ -113,7 +117,7 @@ export default class SimplePollWebPart extends BaseClientSideWebPart<ISimplePoll
React.createElement("div", null, React.createElement("div", null,
React.createElement("textarea", React.createElement("textarea",
{ {
style: { width: "250px", height: "70px" }, style: { width: "220px", height: "70px" },
placeholder: strings.Q_Title_Placeholder, placeholder: strings.Q_Title_Placeholder,
key: itemId, key: itemId,
value: value, value: value,
@ -135,7 +139,7 @@ export default class SimplePollWebPart extends BaseClientSideWebPart<ISimplePoll
React.createElement("div", null, React.createElement("div", null,
React.createElement("textarea", React.createElement("textarea",
{ {
style: { width: "250px", height: "70px" }, style: { width: "220px", height: "70px" },
placeholder: strings.Q_Options_Placeholder, placeholder: strings.Q_Options_Placeholder,
key: itemId, key: itemId,
value: value, value: value,

View File

@ -40,18 +40,23 @@ export default class OptionsContainer extends React.Component<IOptionsContainerP
private getOptions = (): string[] => { private getOptions = (): string[] => {
let tempChoices: string[] = []; let tempChoices: string[] = [];
if (this.props.options.indexOf(',') >= 0) { if (this.props.options.indexOf(',') >= 0) {
tempChoices = this.props.options.split(','); let tmpChoices = this.props.options.split(',');
tmpChoices.map(choice => {
if (choice && choice.trim().length > 0) tempChoices.push(choice);
});
} else tempChoices.push(this.props.options); } else tempChoices.push(this.props.options);
return tempChoices; return tempChoices;
} }
private _onRenderCell = (item: any, index: number | undefined): JSX.Element => { private _onRenderCell = (item: any, index: number | undefined): JSX.Element => {
if (item && item.length > 0) {
return ( return (
<div style={{ marginBottom: "15px" }}> <div style={{ marginBottom: "15px" }}>
<Checkbox label={item} onChange={this._makeChangeHandler(item)} /> <Checkbox label={item} onChange={this._makeChangeHandler(item)} />
</div> </div>
); );
} }
}
private onRenderChoiceOptions(): IChoiceGroupOption[] { private onRenderChoiceOptions(): IChoiceGroupOption[] {
let choices: IChoiceGroupOption[] = []; let choices: IChoiceGroupOption[] = [];

View File

@ -29,7 +29,8 @@
] ]
}, },
"include": [ "include": [
"src/**/*.ts" "src/**/*.ts",
"src/**/*.tsx"
], ],
"exclude": [ "exclude": [
"node_modules", "node_modules",

View File

@ -0,0 +1,344 @@
# Upgrade project react-simple-poll to v1.11.0
Date: 10/16/2020
## Findings
Following is the list of steps required to upgrade your project to SharePoint Framework version 1.11.0. [Summary](#Summary) of the modifications is included at the end of the report.
### FN001001 @microsoft/sp-core-library | Required
Upgrade SharePoint Framework dependency package @microsoft/sp-core-library
Execute the following command:
```sh
npm i -SE @microsoft/sp-core-library@1.11.0
```
File: [./package.json](./package.json)
### FN001002 @microsoft/sp-lodash-subset | Required
Upgrade SharePoint Framework dependency package @microsoft/sp-lodash-subset
Execute the following command:
```sh
npm i -SE @microsoft/sp-lodash-subset@1.11.0
```
File: [./package.json](./package.json)
### FN001003 @microsoft/sp-office-ui-fabric-core | Required
Upgrade SharePoint Framework dependency package @microsoft/sp-office-ui-fabric-core
Execute the following command:
```sh
npm i -SE @microsoft/sp-office-ui-fabric-core@1.11.0
```
File: [./package.json](./package.json)
### FN001004 @microsoft/sp-webpart-base | Required
Upgrade SharePoint Framework dependency package @microsoft/sp-webpart-base
Execute the following command:
```sh
npm i -SE @microsoft/sp-webpart-base@1.11.0
```
File: [./package.json](./package.json)
### FN001005 @types/react | Required
Remove SharePoint Framework dependency package @types/react
Execute the following command:
```sh
npm un -S @types/react
```
File: [./package.json](./package.json)
### FN001006 @types/react-dom | Required
Remove SharePoint Framework dependency package @types/react-dom
Execute the following command:
```sh
npm un -S @types/react-dom
```
File: [./package.json](./package.json)
### FN001007 @types/webpack-env | Required
Remove SharePoint Framework dependency package @types/webpack-env
Execute the following command:
```sh
npm un -S @types/webpack-env
```
File: [./package.json](./package.json)
### FN001010 @types/es6-promise | Required
Remove SharePoint Framework dependency package @types/es6-promise
Execute the following command:
```sh
npm un -S @types/es6-promise
```
File: [./package.json](./package.json)
### FN001021 @microsoft/sp-property-pane | Required
Upgrade SharePoint Framework dependency package @microsoft/sp-property-pane
Execute the following command:
```sh
npm i -SE @microsoft/sp-property-pane@1.11.0
```
File: [./package.json](./package.json)
### FN001022 office-ui-fabric-react | Required
Upgrade SharePoint Framework dependency package office-ui-fabric-react
Execute the following command:
```sh
npm i -SE office-ui-fabric-react@6.214.0
```
File: [./package.json](./package.json)
### FN002001 @microsoft/sp-build-web | Required
Upgrade SharePoint Framework dev dependency package @microsoft/sp-build-web
Execute the following command:
```sh
npm i -DE @microsoft/sp-build-web@1.11.0
```
File: [./package.json](./package.json)
### FN002002 @microsoft/sp-module-interfaces | Required
Upgrade SharePoint Framework dev dependency package @microsoft/sp-module-interfaces
Execute the following command:
```sh
npm i -DE @microsoft/sp-module-interfaces@1.11.0
```
File: [./package.json](./package.json)
### FN002003 @microsoft/sp-webpart-workbench | Required
Upgrade SharePoint Framework dev dependency package @microsoft/sp-webpart-workbench
Execute the following command:
```sh
npm i -DE @microsoft/sp-webpart-workbench@1.11.0
```
File: [./package.json](./package.json)
### FN002009 @microsoft/sp-tslint-rules | Required
Upgrade SharePoint Framework dev dependency package @microsoft/sp-tslint-rules
Execute the following command:
```sh
npm i -DE @microsoft/sp-tslint-rules@1.11.0
```
File: [./package.json](./package.json)
### FN002013 @types/webpack-env | Required
Install SharePoint Framework dev dependency package @types/webpack-env
Execute the following command:
```sh
npm i -DE @types/webpack-env@1.13.1
```
File: [./package.json](./package.json)
### FN002014 @types/es6-promise | Required
Install SharePoint Framework dev dependency package @types/es6-promise
Execute the following command:
```sh
npm i -DE @types/es6-promise@0.0.33
```
File: [./package.json](./package.json)
### FN002015 @types/react | Required
Install SharePoint Framework dev dependency package @types/react
Execute the following command:
```sh
npm i -DE @types/react@16.8.8
```
File: [./package.json](./package.json)
### FN002016 @types/react-dom | Required
Install SharePoint Framework dev dependency package @types/react-dom
Execute the following command:
```sh
npm i -DE @types/react-dom@16.8.3
```
File: [./package.json](./package.json)
### FN006004 package-solution.json developer | Optional
In package-solution.json add developer section
In file [./config/package-solution.json](./config/package-solution.json) update the code as follows:
```json
{
"solution": {
"developer": {
"name": "Contoso",
"privacyUrl": "https://contoso.com/privacy",
"termsOfUseUrl": "https://contoso.com/terms-of-use",
"websiteUrl": "https://contoso.com/my-app",
"mpnId": "000000"
}
}
}
```
File: [./config/package-solution.json](./config/package-solution.json)
### FN010001 .yo-rc.json version | Recommended
Update version in .yo-rc.json
In file [./.yo-rc.json](./.yo-rc.json) update the code as follows:
```json
{
"@microsoft/generator-sharepoint": {
"version": "1.11.0"
}
}
```
File: [./.yo-rc.json](./.yo-rc.json)
### FN012012 tsconfig.json include property | Required
Update tsconfig.json include property
In file [./tsconfig.json](./tsconfig.json) update the code as follows:
```json
{
"include": [
"src/**/*.tsx"
]
}
```
File: [./tsconfig.json](./tsconfig.json)
### FN017001 Run npm dedupe | Optional
If, after upgrading npm packages, when building the project you have errors similar to: "error TS2345: Argument of type 'SPHttpClientConfiguration' is not assignable to parameter of type 'SPHttpClientConfiguration'", try running 'npm dedupe' to cleanup npm packages.
Execute the following command:
```sh
npm dedupe
```
File: [./package.json](./package.json)
## Summary
### Execute script
```sh
npm i -SE @microsoft/sp-core-library@1.11.0 @microsoft/sp-lodash-subset@1.11.0 @microsoft/sp-office-ui-fabric-core@1.11.0 @microsoft/sp-webpart-base@1.11.0 @microsoft/sp-property-pane@1.11.0 office-ui-fabric-react@6.214.0
npm i -DE @microsoft/sp-build-web@1.11.0 @microsoft/sp-module-interfaces@1.11.0 @microsoft/sp-webpart-workbench@1.11.0 @microsoft/sp-tslint-rules@1.11.0 @types/webpack-env@1.13.1 @types/es6-promise@0.0.33 @types/react@16.8.8 @types/react-dom@16.8.3
npm un -S @types/react @types/react-dom @types/webpack-env @types/es6-promise
npm dedupe
```
### Modify files
#### [./config/package-solution.json](./config/package-solution.json)
In package-solution.json add developer section:
```json
{
"solution": {
"developer": {
"name": "Contoso",
"privacyUrl": "https://contoso.com/privacy",
"termsOfUseUrl": "https://contoso.com/terms-of-use",
"websiteUrl": "https://contoso.com/my-app",
"mpnId": "000000"
}
}
}
```
#### [./.yo-rc.json](./.yo-rc.json)
Update version in .yo-rc.json:
```json
{
"@microsoft/generator-sharepoint": {
"version": "1.11.0"
}
}
```
#### [./tsconfig.json](./tsconfig.json)
Update tsconfig.json include property:
```json
{
"include": [
"src/**/*.tsx"
]
}
```

View File

@ -0,0 +1,25 @@
# EditorConfig helps developers define and maintain consistent
# coding styles between different editors and IDEs
# editorconfig.org
root = true
[*]
# change these settings to your own preference
indent_style = space
indent_size = 2
# we recommend you to keep these unchanged
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
[*.md]
trim_trailing_whitespace = false
[{package,bower}.json]
indent_style = space
indent_size = 2

32
samples/react-tailwindcss/.gitignore vendored Normal file
View File

@ -0,0 +1,32 @@
# Logs
logs
*.log
npm-debug.log*
# Dependency directories
node_modules
# Build generated files
dist
lib
solution
temp
*.sppkg
# Coverage directory used by tools like istanbul
coverage
# OSX
.DS_Store
# Visual Studio files
.ntvs_analysis.dat
.vs
bin
obj
# Resx Generated Code
*.resx.ts
# Styles Generated Code
*.scss.ts

View File

@ -0,0 +1,12 @@
{
"@microsoft/generator-sharepoint": {
"version": "1.11.0",
"libraryName": "react-tailwindcss",
"libraryId": "62daa1f6-c9b4-4664-b101-37ccbddb47da",
"environment": "spo",
"packageManager": "npm",
"isCreatingSolution": true,
"isDomainIsolated": false,
"componentType": "webpart"
}
}

View File

@ -0,0 +1,81 @@
---
page_type: sample
products:
- office-sp
languages:
- javascript
- typescript
extensions:
contentType: samples
technologies:
- SharePoint Framework
- TailWind CSS
platforms:
- React
createdDate: 10/17/2020 12:00:00 AM
---
# react-tailwindcss
## Summary
This project shows how to integrate [Tailwind CSS](https://tailwindcss.com/) framework into a SPFx React project by:
- Create a custom Tailwind CSS config file
- Create a custom Gulp Task to process the Tailwind CSS generated file and optimize it
- Using CSS custom properties to manage in Tailwind CSS Classes the Theme Variant into sections
- Avoid writing SASS but only use Tailwind CSS classes to style components
![WebPart](./assets/react-tailwindcss-overview.gif)
## Used SharePoint Framework Version
![SPFx 1.11](https://img.shields.io/badge/version-1.11-green.svg)
## Applies to
- [SharePoint Framework](https://aka.ms/spfx)
- [Microsoft 365 tenant](https://docs.microsoft.com/en-us/sharepoint/dev/spfx/set-up-your-developer-tenant)
> Get your own free development tenant by subscribing to [Microsoft 365 developer program](http://aka.ms/o365devprogram)
## Prerequisites
- Basic knowledge of [Tailwind CSS](https://tailwindcss.com/)
- Basic knowledge of [GulpJs](https://gulpjs.com/)
## Solution
Solution|Author(s)
--------|---------
react-tailwindcss | Fabio Franzini, [@franzinifabio](https://twitter.com/franzinifabio)
## Version history
Version|Date|Comments
-------|----|--------
1.0|October 17, 2020|Initial release
## Disclaimer
**THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT.**
---
## Minimal Path to Awesome
- Clone this repository
- Ensure that you are at the solution folder
- in the command-line run:
- **npm install**
- **gulp serve**
## References
- [Getting started with Tailwind CSS](https://tailwindcss.com/)
- [Getting started with SharePoint Framework](https://docs.microsoft.com/en-us/sharepoint/dev/spfx/set-up-your-developer-tenant)
- [Building for Microsoft teams](https://docs.microsoft.com/en-us/sharepoint/dev/spfx/build-for-teams-overview)
- [Use Microsoft Graph in your solution](https://docs.microsoft.com/en-us/sharepoint/dev/spfx/web-parts/get-started/using-microsoft-graph-apis)
- [Publish SharePoint Framework applications to the Marketplace](https://docs.microsoft.com/en-us/sharepoint/dev/spfx/publish-to-marketplace-overview)
- [Microsoft 365 Patterns and Practices](https://aka.ms/m365pnp) - Guidance, tooling, samples and open-source controls for your Microsoft 365 development
<img src="https://telemetry.sharepointpnp.com/sp-dev-fx-webparts/samples/react-tailwindcss" />

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 MiB

View File

@ -0,0 +1,18 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/config.2.0.schema.json",
"version": "2.0",
"bundles": {
"hello-tailwind-web-part": {
"components": [
{
"entrypoint": "./lib/webparts/HelloTailwindCss/HelloTailwindCssWebPart.js",
"manifest": "./src/webparts/HelloTailwindCss/HelloTailwindCssWebPart.manifest.json"
}
]
}
},
"externals": {},
"localizedResources": {
"HelloTailwindCssWebPartStrings": "lib/webparts/HelloTailwindCss/loc/{locale}.js"
}
}

View File

@ -0,0 +1,4 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/copy-assets.schema.json",
"deployCdnPath": "temp/deploy"
}

View File

@ -0,0 +1,7 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/deploy-azure-storage.schema.json",
"workingDir": "./temp/deploy/",
"account": "<!-- STORAGE ACCOUNT NAME -->",
"container": "react-tailwindcss",
"accessKey": "<!-- ACCESS KEY -->"
}

View File

@ -0,0 +1,20 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/package-solution.schema.json",
"solution": {
"name": "react-tailwindcss-client-side-solution",
"id": "62daa1f6-c9b4-4664-b101-37ccbddb47da",
"version": "1.0.0.0",
"includeClientSideAssets": true,
"isDomainIsolated": false,
"developer": {
"name": "",
"websiteUrl": "",
"privacyUrl": "",
"termsOfUseUrl": "",
"mpnId": ""
}
},
"paths": {
"zippedPackage": "solution/react-tailwindcss.sppkg"
}
}

View File

@ -0,0 +1,10 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/core-build/serve.schema.json",
"port": 4321,
"https": true,
"initialPage": "https://localhost:5432/workbench",
"api": {
"port": 5432,
"entryPath": "node_modules/@microsoft/sp-webpart-workbench/lib/api/"
}
}

View File

@ -0,0 +1,4 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/write-manifests.schema.json",
"cdnBasePath": "<!-- PATH TO CDN -->"
}

24
samples/react-tailwindcss/gulpfile.js vendored Normal file
View File

@ -0,0 +1,24 @@
'use strict';
const build = require('@microsoft/sp-build-web');
build.addSuppression(`Warning - [sass] The local CSS class 'ms-Grid' is not camelCase and will not be type-safe.`);
// Step 1 + './src/tailwind.css' + './tailwind.config.js'
let tailwindBuild = build.subTask('build-tailwind', (gulp, buildOptions, done) => {
const postcss = require('gulp-postcss');
gulp.src(`${buildOptions.srcFolder}/tailwind.css`)
.pipe(postcss([
require('tailwindcss')('./tailwind.config.js'),
require('gulp-autoprefixer')
]))
.pipe(gulp.dest(buildOptions.libFolder));
done();
});
build.rig.addPostBuildTask(tailwindBuild);
// End Step 1
build.initialize(require('gulp'));

18661
samples/react-tailwindcss/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,44 @@
{
"name": "react-tailwindcss",
"version": "0.0.1",
"private": true,
"main": "lib/index.js",
"engines": {
"node": ">=0.10.0"
},
"scripts": {
"build": "npm run tailwind:css && gulp bundle",
"clean": "gulp clean",
"test": "gulp test",
"build:tailwind": "postcss tailwind.source.css -o src/tailwind.css"
},
"dependencies": {
"@microsoft/sp-core-library": "1.11.0",
"@microsoft/sp-lodash-subset": "1.11.0",
"@microsoft/sp-office-ui-fabric-core": "^1.11.0",
"@microsoft/sp-property-pane": "1.11.0",
"@microsoft/sp-webpart-base": "1.11.0",
"office-ui-fabric-react": "6.214.0",
"react": "16.8.5",
"react-dom": "16.8.5"
},
"devDependencies": {
"@microsoft/rush-stack-compiler-3.3": "0.3.5",
"@microsoft/sp-build-web": "1.11.0",
"@microsoft/sp-module-interfaces": "1.11.0",
"@microsoft/sp-tslint-rules": "1.11.0",
"@microsoft/sp-webpart-workbench": "1.11.0",
"@types/chai": "3.4.34",
"@types/es6-promise": "0.0.33",
"@types/mocha": "2.2.38",
"@types/react": "16.8.8",
"@types/react-dom": "16.8.3",
"@types/webpack-env": "1.13.1",
"ajv": "~5.2.2",
"gulp": "~3.9.1",
"gulp-autoprefixer": "^7.0.1",
"gulp-postcss": "^8.0.0",
"gulp-sass": "^4.1.0",
"tailwindcss": "^1.8.10"
}
}

View File

@ -0,0 +1 @@
// A file is required to be in the root of the /src directory by the TypeScript compiler

View File

@ -0,0 +1,136 @@
@import "tailwindcss/components";
@import "tailwindcss/utilities";
:root {
--tw-fui-themeDarker: #004578;
--tw-fui-themeDark: #005a9e;
--tw-fui-themeDarkAlt: #106ebe;
--tw-fui-themePrimary: #0078d4;
--tw-fui-themeSecondary: #2b88d8;
--tw-fui-themeTertiary: #71afe5;
--tw-fui-themeLight: #c7e0f4;
--tw-fui-themeLighter: #deecf9;
--tw-fui-themeLighterAlt: #eff6fc;
--tw-fui-black: #000000;
--tw-fui-blackTranslucent40: rgba(0, 0, 0, .4);
--tw-fui-neutralDark: #212121;
--tw-fui-neutralPrimary: #333333;
--tw-fui-neutralPrimaryAlt: #3c3c3c;
--tw-fui-neutralSecondary: #666666;
--tw-fui-neutralSecondaryAlt: #767676;
--tw-fui-neutralTertiary: #a6a6a6;
--tw-fui-neutralTertiaryAlt: #c8c8c8;
--tw-fui-neutralQuaternary: #d0d0d0;
--tw-fui-neutralQuaternaryAlt: #dadada;
--tw-fui-neutralLight: #eaeaea;
--tw-fui-neutralLighter: #f4f4f4;
--tw-fui-neutralLighterAlt: #f8f8f8;
--tw-fui-accent: #0078d4;
--tw-fui-white: #ffffff;
--tw-fui-whiteTranslucent40: rgba(255, 255, 255, .4);
--tw-fui-yellow: #ffb900;
--tw-fui-yellowLight: #fff100;
--tw-fui-orange: #d83b01;
--tw-fui-orangeLight: #ea4300;
--tw-fui-orangeLighter: #ff8c00;
--tw-fui-redDark: #a80000;
--tw-fui-red: #e81123;
--tw-fui-magentaDark: #5c005c;
--tw-fui-magenta: #b4009e;
--tw-fui-magentaLight: #e3008c;
--tw-fui-purpleDark: #32145a;
--tw-fui-purple: #5c2d91;
--tw-fui-purpleLight: #b4a0ff;
--tw-fui-blueDark: #002050;
--tw-fui-blueMid: #00188f;
--tw-fui-blue: #0078d4;
--tw-fui-blueLight: #00bcf2;
--tw-fui-tealDark: #004b50;
--tw-fui-teal: #008272;
--tw-fui-tealLight: #00b294;
--tw-fui-greenDark: #004b1c;
--tw-fui-green: #107c10;
--tw-fui-greenLight: #bad80a;
/* ***** */
--tw-fui-bodyBackground: #ffffff;
--tw-fui-bodyStandoutBackground: #f8f8f8;
--tw-fui-bodyFrameBackground: #ffffff;
--tw-fui-bodyFrameDivider: #eaeaea;
--tw-fui-bodyText: #333333;
--tw-fui-bodyTextChecked: #000000;
--tw-fui-bodySubtext: #666666;
--tw-fui-bodyDivider: #eaeaea;
--tw-fui-disabledBackground: #f4f4f4;
--tw-fui-disabledText: #a6a6a6;
--tw-fui-disabledSubtext: #d0d0d0;
--tw-fui-disabledBodyText: #a6a6a6;
--tw-fui-disabledBodySubtext: #c8c8c8;
--tw-fui-focusBorder: #666666;
--tw-fui-variantBorder: #eaeaea;
--tw-fui-variantBorderHovered: #a6a6a6;
--tw-fui-defaultStateBackground: #f8f8f8;
--tw-fui-errorText: #a80000;
--tw-fui-warningText: #333333;
--tw-fui-errorBackground: rgba(232, 17, 35, .2);
--tw-fui-blockingBackground: rgba(234, 67, 0, .2);
--tw-fui-warningBackground: rgba(255, 185, 0, .2);
--tw-fui-warningHighlight: #ffb900;
--tw-fui-successBackground: rgba(186, 216, 10, .2);
--tw-fui-inputBorder: #a6a6a6;
--tw-fui-inputBorderHovered: #333333;
--tw-fui-inputBackground: #ffffff;
--tw-fui-inputBackgroundChecked: #0078d4;
--tw-fui-inputBackgroundCheckedHovered: #106ebe;
--tw-fui-inputForegroundChecked: #ffffff;
--tw-fui-inputFocusBorderAlt: #0078d4;
--tw-fui-smallInputBorder: #666666;
--tw-fui-inputText: #333333;
--tw-fui-inputTextHovered: #212121;
--tw-fui-inputPlaceholderText: #666666;
--tw-fui-buttonBackground: #f4f4f4;
--tw-fui-buttonBackgroundChecked: #c8c8c8;
--tw-fui-buttonBackgroundHovered: #eaeaea;
--tw-fui-buttonBackgroundCheckedHovered: #eaeaea;
--tw-fui-buttonBackgroundPressed: #eaeaea;
--tw-fui-buttonBackgroundDisabled: #f4f4f4;
--tw-fui-buttonBorder: transparent;
--tw-fui-buttonText: #333333;
--tw-fui-buttonTextHovered: #212121;
--tw-fui-buttonTextChecked: #212121;
--tw-fui-buttonTextCheckedHovered: #000000;
--tw-fui-buttonTextPressed: #212121;
--tw-fui-buttonTextDisabled: #a6a6a6;
--tw-fui-buttonBorderDisabled: transparent;
--tw-fui-primaryButtonBackground: #0078d4;
--tw-fui-primaryButtonBackgroundHovered: #106ebe;
--tw-fui-primaryButtonBackgroundPressed: #005a9e;
--tw-fui-primaryButtonBackgroundDisabled: #f4f4f4;
--tw-fui-primaryButtonBorder: transparent;
--tw-fui-primaryButtonText: #ffffff;
--tw-fui-primaryButtonTextHovered: #ffffff;
--tw-fui-primaryButtonTextPressed: #ffffff;
--tw-fui-primaryButtonTextDisabled: #d0d0d0;
--tw-fui-accentButtonBackground: #0078d4;
--tw-fui-accentButtonText: #ffffff;
--tw-fui-menuBackground: #ffffff;
--tw-fui-menuDivider: #c8c8c8;
--tw-fui-menuIcon: #0078d4;
--tw-fui-menuHeader: #0078d4;
--tw-fui-menuItemBackgroundHovered: #f4f4f4;
--tw-fui-menuItemBackgroundPressed: #eaeaea;
--tw-fui-menuItemText: #333333;
--tw-fui-menuItemTextHovered: #212121;
--tw-fui-listBackground: #ffffff;
--tw-fui-listText: #333333;
--tw-fui-listItemBackgroundHovered: #f4f4f4;
--tw-fui-listItemBackgroundChecked: #eaeaea;
--tw-fui-listItemBackgroundCheckedHovered: #dadada;
--tw-fui-listHeaderBackgroundHovered: #f4f4f4;
--tw-fui-listHeaderBackgroundPressed: #eaeaea;
--tw-fui-actionLink: #333333;
--tw-fui-actionLinkHovered: #212121;
--tw-fui-link: #0078d4;
--tw-fui-linkHovered: #004578;
--tw-fui-listTextColor: #333333;
--tw-fui-menuItemBackgroundChecked: #eaeaea;
}

View File

@ -0,0 +1,27 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/spfx/client-side-web-part-manifest.schema.json",
"id": "acebbfec-5fc8-42b0-94e5-cfbc43222cae",
"alias": "HelloTailwindCssWebPart",
"componentType": "WebPart",
"supportsThemeVariants": true,
// The "*" signifies that the version should be taken from the package.json
"version": "*",
"manifestVersion": 2,
// If true, the component can only be installed on sites where Custom Script is allowed.
// Components that allow authors to embed arbitrary script code should set this to true.
// https://support.office.com/en-us/article/Turn-scripting-capabilities-on-or-off-1f2c515f-5d7e-448a-9fd7-835da935584f
"requiresCustomScript": false,
"supportedHosts": ["SharePointWebPart"],
"preconfiguredEntries": [{
"groupId": "5c03119e-3074-46fd-976b-c60198311f70", // Other
"group": { "default": "Other" },
"title": { "default": "Hello TailwindCss" },
"description": { "default": "Hello TailwindCss description" },
"officeFabricIconFontName": "Duststorm",
"properties": {
"description": "Hello TailwindCss"
}
}]
}

View File

@ -0,0 +1,223 @@
import * as React from 'react';
import * as ReactDom from 'react-dom';
import { Version } from '@microsoft/sp-core-library';
import {
IPropertyPaneConfiguration,
PropertyPaneTextField
} from '@microsoft/sp-property-pane';
import { BaseClientSideWebPart } from '@microsoft/sp-webpart-base';
import * as strings from 'HelloTailwindCssWebPartStrings';
import HelloTailwindCss from './components/HelloTailwindCss';
import { IHelloTailwindCssProps } from './components/IHelloTailwindCssProps';
import { ThemeProvider, ThemeChangedEventArgs, IReadonlyTheme } from '@microsoft/sp-component-base';
export interface IHelloTailwindCssWebPartProps {
description: string;
}
export default class HelloTailwindCssWebPart extends BaseClientSideWebPart<IHelloTailwindCssWebPartProps> {
private _themeProvider: ThemeProvider;
private _themeVariant: IReadonlyTheme | undefined;
protected onInit(): Promise<void> {
// Consume the new ThemeProvider service
this._themeProvider = this.context.serviceScope.consume(ThemeProvider.serviceKey);
// If it exists, get the theme variant
this._themeVariant = this._themeProvider.tryGetTheme();
// Register a handler to be notified if the theme variant changes
this._themeProvider.themeChangedEvent.add(this, this._handleThemeChangedEvent);
// Apply theme variant as CSS Variables at current DOM node
this._applyThemeVariant();
return super.onInit();
}
private _handleThemeChangedEvent(args: ThemeChangedEventArgs): void {
this._themeVariant = args.theme;
this._applyThemeVariant();
this.render();
}
private _applyThemeVariant() {
let style = this.domElement.style;
style.setProperty('--tw-fui-themeDarker', this._themeVariant.palette.themeDarker);
style.setProperty('--tw-fui-themeDark', this._themeVariant.palette.themeDark);
style.setProperty('--tw-fui-themeDarkAlt', this._themeVariant.palette.themeDarkAlt);
style.setProperty('--tw-fui-themePrimary', this._themeVariant.palette.themePrimary);
style.setProperty('--tw-fui-themeSecondary', this._themeVariant.palette.themeSecondary);
style.setProperty('--tw-fui-themeTertiary', this._themeVariant.palette.themeTertiary);
style.setProperty('--tw-fui-themeLight', this._themeVariant.palette.themeLight);
style.setProperty('--tw-fui-themeLighter', this._themeVariant.palette.themeLighter);
style.setProperty('--tw-fui-themeLighterAlt', this._themeVariant.palette.themeLighterAlt);
style.setProperty('--tw-fui-black', this._themeVariant.palette.black);
style.setProperty('--tw-fui-blackTranslucent40', this._themeVariant.palette.blackTranslucent40);
style.setProperty('--tw-fui-neutralDark', this._themeVariant.palette.neutralDark);
style.setProperty('--tw-fui-neutralPrimary', this._themeVariant.palette.neutralPrimary);
style.setProperty('--tw-fui-neutralPrimaryAlt', this._themeVariant.palette.neutralPrimaryAlt);
style.setProperty('--tw-fui-neutralSecondary', this._themeVariant.palette.neutralSecondary);
style.setProperty('--tw-fui-neutralSecondaryAlt', this._themeVariant.palette.neutralSecondaryAlt);
style.setProperty('--tw-fui-neutralTertiary', this._themeVariant.palette.neutralTertiary);
style.setProperty('--tw-fui-neutralTertiaryAlt', this._themeVariant.palette.neutralTertiaryAlt);
style.setProperty('--tw-fui-neutralQuaternary', this._themeVariant.palette.neutralQuaternary);
style.setProperty('--tw-fui-neutralQuaternaryAlt', this._themeVariant.palette.neutralQuaternaryAlt);
style.setProperty('--tw-fui-neutralLight', this._themeVariant.palette.neutralLight);
style.setProperty('--tw-fui-neutralLighter', this._themeVariant.palette.neutralLighter);
style.setProperty('--tw-fui-neutralLighterAlt', this._themeVariant.palette.neutralLighterAlt);
style.setProperty('--tw-fui-accent', this._themeVariant.palette.accent);
style.setProperty('--tw-fui-white', this._themeVariant.palette.white);
style.setProperty('--tw-fui-whiteTranslucent40', this._themeVariant.palette.whiteTranslucent40);
style.setProperty('--tw-fui-yellow', this._themeVariant.palette.yellow);
style.setProperty('--tw-fui-yellowLight', this._themeVariant.palette.yellowLight);
style.setProperty('--tw-fui-orange', this._themeVariant.palette.orange);
style.setProperty('--tw-fui-orangeLight', this._themeVariant.palette.orangeLight);
style.setProperty('--tw-fui-orangeLighter', this._themeVariant.palette.orangeLighter);
style.setProperty('--tw-fui-redDark', this._themeVariant.palette.redDark);
style.setProperty('--tw-fui-red', this._themeVariant.palette.red);
style.setProperty('--tw-fui-magentaDark', this._themeVariant.palette.magentaDark);
style.setProperty('--tw-fui-magenta', this._themeVariant.palette.magenta);
style.setProperty('--tw-fui-magentaLight', this._themeVariant.palette.magentaLight);
style.setProperty('--tw-fui-purpleDark', this._themeVariant.palette.purpleDark);
style.setProperty('--tw-fui-purple', this._themeVariant.palette.purple);
style.setProperty('--tw-fui-purpleLight', this._themeVariant.palette.purpleLight);
style.setProperty('--tw-fui-blueDark', this._themeVariant.palette.blueDark);
style.setProperty('--tw-fui-blueMid', this._themeVariant.palette.blueMid);
style.setProperty('--tw-fui-blue', this._themeVariant.palette.blue);
style.setProperty('--tw-fui-blueLight', this._themeVariant.palette.blueLight);
style.setProperty('--tw-fui-tealDark', this._themeVariant.palette.tealDark);
style.setProperty('--tw-fui-teal', this._themeVariant.palette.teal);
style.setProperty('--tw-fui-tealLight', this._themeVariant.palette.tealLight);
style.setProperty('--tw-fui-greenDark', this._themeVariant.palette.greenDark);
style.setProperty('--tw-fui-green', this._themeVariant.palette.green);
style.setProperty('--tw-fui-greenLight', this._themeVariant.palette.greenLight);
style.setProperty('--tw-fui-bodyBackground', this._themeVariant.semanticColors.bodyBackground);
style.setProperty('--tw-fui-bodyStandoutBackground', this._themeVariant.semanticColors.bodyStandoutBackground);
style.setProperty('--tw-fui-bodyFrameBackground', this._themeVariant.semanticColors.bodyFrameBackground);
style.setProperty('--tw-fui-bodyFrameDivider', this._themeVariant.semanticColors.bodyFrameDivider);
style.setProperty('--tw-fui-bodyText', this._themeVariant.semanticColors.bodyText);
style.setProperty('--tw-fui-bodyTextChecked', this._themeVariant.semanticColors.bodyTextChecked);
style.setProperty('--tw-fui-bodySubtext', this._themeVariant.semanticColors.bodySubtext);
style.setProperty('--tw-fui-bodyDivider', this._themeVariant.semanticColors.bodyDivider);
style.setProperty('--tw-fui-disabledBackground', this._themeVariant.semanticColors.disabledBackground);
style.setProperty('--tw-fui-disabledText', this._themeVariant.semanticColors.disabledText);
style.setProperty('--tw-fui-disabledSubtext', this._themeVariant.semanticColors.disabledSubtext);
style.setProperty('--tw-fui-disabledBodyText', this._themeVariant.semanticColors.disabledBodyText);
style.setProperty('--tw-fui-disabledBodySubtext', this._themeVariant.semanticColors.disabledBodySubtext);
style.setProperty('--tw-fui-focusBorder', this._themeVariant.semanticColors.focusBorder);
style.setProperty('--tw-fui-variantBorder', this._themeVariant.semanticColors.variantBorder);
style.setProperty('--tw-fui-variantBorderHovered', this._themeVariant.semanticColors.variantBorderHovered);
style.setProperty('--tw-fui-defaultStateBackground', this._themeVariant.semanticColors.defaultStateBackground);
style.setProperty('--tw-fui-errorText', this._themeVariant.semanticColors.errorText);
style.setProperty('--tw-fui-warningText', this._themeVariant.semanticColors.warningText);
style.setProperty('--tw-fui-errorBackground', this._themeVariant.semanticColors.errorBackground);
style.setProperty('--tw-fui-blockingBackground', this._themeVariant.semanticColors.blockingBackground);
style.setProperty('--tw-fui-warningBackground', this._themeVariant.semanticColors.warningBackground);
style.setProperty('--tw-fui-warningHighlight', this._themeVariant.semanticColors.warningHighlight);
style.setProperty('--tw-fui-successBackground', this._themeVariant.semanticColors.successBackground);
style.setProperty('--tw-fui-inputBorder', this._themeVariant.semanticColors.inputBorder);
style.setProperty('--tw-fui-inputBorderHovered', this._themeVariant.semanticColors.inputBorderHovered);
style.setProperty('--tw-fui-inputBackground', this._themeVariant.semanticColors.inputBackground);
style.setProperty('--tw-fui-inputBackgroundChecked', this._themeVariant.semanticColors.inputBackgroundChecked);
style.setProperty('--tw-fui-inputBackgroundCheckedHovered', this._themeVariant.semanticColors.inputBackgroundCheckedHovered);
style.setProperty('--tw-fui-inputForegroundChecked', this._themeVariant.semanticColors.inputForegroundChecked);
style.setProperty('--tw-fui-inputFocusBorderAlt', this._themeVariant.semanticColors.inputFocusBorderAlt);
style.setProperty('--tw-fui-smallInputBorder', this._themeVariant.semanticColors.smallInputBorder);
style.setProperty('--tw-fui-inputText', this._themeVariant.semanticColors.inputText);
style.setProperty('--tw-fui-inputTextHovered', this._themeVariant.semanticColors.inputTextHovered);
style.setProperty('--tw-fui-inputPlaceholderText', this._themeVariant.semanticColors.inputPlaceholderText);
style.setProperty('--tw-fui-buttonBackground', this._themeVariant.semanticColors.buttonBackground);
style.setProperty('--tw-fui-buttonBackgroundChecked', this._themeVariant.semanticColors.buttonBackgroundChecked);
style.setProperty('--tw-fui-buttonBackgroundHovered', this._themeVariant.semanticColors.buttonBackgroundHovered);
style.setProperty('--tw-fui-buttonBackgroundCheckedHovered', this._themeVariant.semanticColors.buttonBackgroundCheckedHovered);
style.setProperty('--tw-fui-buttonBackgroundPressed', this._themeVariant.semanticColors.buttonBackgroundPressed);
style.setProperty('--tw-fui-buttonBackgroundDisabled', this._themeVariant.semanticColors.buttonBackgroundDisabled);
style.setProperty('--tw-fui-buttonBorder', this._themeVariant.semanticColors.buttonBorder);
style.setProperty('--tw-fui-buttonText', this._themeVariant.semanticColors.buttonText);
style.setProperty('--tw-fui-buttonTextHovered', this._themeVariant.semanticColors.buttonTextHovered);
style.setProperty('--tw-fui-buttonTextChecked', this._themeVariant.semanticColors.buttonTextChecked);
style.setProperty('--tw-fui-buttonTextCheckedHovered', this._themeVariant.semanticColors.buttonTextCheckedHovered);
style.setProperty('--tw-fui-buttonTextPressed', this._themeVariant.semanticColors.buttonTextPressed);
style.setProperty('--tw-fui-buttonTextDisabled', this._themeVariant.semanticColors.buttonTextDisabled);
style.setProperty('--tw-fui-buttonBorderDisabled', this._themeVariant.semanticColors.buttonBorderDisabled);
style.setProperty('--tw-fui-primaryButtonBackground', this._themeVariant.semanticColors.primaryButtonBackground);
style.setProperty('--tw-fui-primaryButtonBackgroundHovered', this._themeVariant.semanticColors.primaryButtonBackgroundHovered);
style.setProperty('--tw-fui-primaryButtonBackgroundPressed', this._themeVariant.semanticColors.primaryButtonBackgroundPressed);
style.setProperty('--tw-fui-primaryButtonBackgroundDisabled', this._themeVariant.semanticColors.primaryButtonBackgroundDisabled);
style.setProperty('--tw-fui-primaryButtonBorder', this._themeVariant.semanticColors.primaryButtonBorder);
style.setProperty('--tw-fui-primaryButtonText', this._themeVariant.semanticColors.primaryButtonText);
style.setProperty('--tw-fui-primaryButtonTextHovered', this._themeVariant.semanticColors.primaryButtonTextHovered);
style.setProperty('--tw-fui-primaryButtonTextPressed', this._themeVariant.semanticColors.primaryButtonTextPressed);
style.setProperty('--tw-fui-primaryButtonTextDisabled', this._themeVariant.semanticColors.primaryButtonTextDisabled);
style.setProperty('--tw-fui-accentButtonBackground', this._themeVariant.semanticColors.accentButtonBackground);
style.setProperty('--tw-fui-accentButtonText', this._themeVariant.semanticColors.accentButtonText);
style.setProperty('--tw-fui-menuBackground', this._themeVariant.semanticColors.menuBackground);
style.setProperty('--tw-fui-menuDivider', this._themeVariant.semanticColors.menuDivider);
style.setProperty('--tw-fui-menuIcon', this._themeVariant.semanticColors.menuIcon);
style.setProperty('--tw-fui-menuHeader', this._themeVariant.semanticColors.menuHeader);
style.setProperty('--tw-fui-menuItemBackgroundHovered', this._themeVariant.semanticColors.menuItemBackgroundHovered);
style.setProperty('--tw-fui-menuItemBackgroundPressed', this._themeVariant.semanticColors.menuItemBackgroundPressed);
style.setProperty('--tw-fui-menuItemText', this._themeVariant.semanticColors.menuItemText);
style.setProperty('--tw-fui-menuItemTextHovered', this._themeVariant.semanticColors.menuItemTextHovered);
style.setProperty('--tw-fui-listBackground', this._themeVariant.semanticColors.listBackground);
style.setProperty('--tw-fui-listText', this._themeVariant.semanticColors.listText);
style.setProperty('--tw-fui-listItemBackgroundHovered', this._themeVariant.semanticColors.listItemBackgroundHovered);
style.setProperty('--tw-fui-listItemBackgroundChecked', this._themeVariant.semanticColors.listItemBackgroundChecked);
style.setProperty('--tw-fui-listItemBackgroundCheckedHovered', this._themeVariant.semanticColors.listItemBackgroundCheckedHovered);
style.setProperty('--tw-fui-listHeaderBackgroundHovered', this._themeVariant.semanticColors.listHeaderBackgroundHovered);
style.setProperty('--tw-fui-listHeaderBackgroundPressed', this._themeVariant.semanticColors.listHeaderBackgroundPressed);
style.setProperty('--tw-fui-actionLink', this._themeVariant.semanticColors.actionLink);
style.setProperty('--tw-fui-actionLinkHovered', this._themeVariant.semanticColors.actionLinkHovered);
style.setProperty('--tw-fui-link', this._themeVariant.semanticColors.link);
style.setProperty('--tw-fui-linkHovered', this._themeVariant.semanticColors.linkHovered);
style.setProperty('--tw-fui-listTextColor', this._themeVariant.semanticColors.listTextColor);
style.setProperty('--tw-fui-menuItemBackgroundChecked', this._themeVariant.semanticColors.menuItemBackgroundChecked);
}
public render(): void {
const element: React.ReactElement<IHelloTailwindCssProps> = React.createElement(
HelloTailwindCss,
{
description: this.properties.description
}
);
ReactDom.render(element, this.domElement);
}
protected onDispose(): void {
ReactDom.unmountComponentAtNode(this.domElement);
}
protected get dataVersion(): Version {
return Version.parse('1.0');
}
protected getPropertyPaneConfiguration(): IPropertyPaneConfiguration {
return {
pages: [
{
header: {
description: strings.PropertyPaneDescription
},
groups: [
{
groupName: strings.BasicGroupName,
groupFields: [
PropertyPaneTextField('description', {
label: strings.DescriptionFieldLabel
})
]
}
]
}
]
};
}
}

View File

@ -0,0 +1,26 @@
import * as React from 'react';
import { escape } from '@microsoft/sp-lodash-subset';
import './../../../tailwind.css';
import User, { IUserProps } from './User';
export interface IDocumentCardProps {
title: string;
documentImageUrl: string;
lastEditUserInfo: IUserProps;
}
export default class DocumentCard extends React.Component<IDocumentCardProps, {}> {
public render(): React.ReactElement<IDocumentCardProps> {
return (
<div className="max-w-sm overflow-hidden bg-white text-black border border-solid border-gray-400 hover:outline-none hover:shadow-lg hover:cursor-pointer">
<img className="w-full border-0 border-solid border-b border-gray-400" src={this.props.documentImageUrl} alt="Document Preview" />
<div className="px-4 py-3 pt-1">
<div className="text-lg mb-2">{this.props.title}</div>
<User profileImageUrl={this.props.lastEditUserInfo.profileImageUrl}
line1={this.props.lastEditUserInfo.line1}
line2={this.props.lastEditUserInfo.line1} />
</div>
</div>
);
}
}

View File

@ -0,0 +1,32 @@
import * as React from 'react';
import { IHelloTailwindCssProps } from './IHelloTailwindCssProps';
import { escape } from '@microsoft/sp-lodash-subset';
import './../../../tailwind.css';
import DocumentCard from './DocumentCard';
import HorizontalCard from './HorizontalCard';
import { Stack } from 'office-ui-fabric-react';
export default class HelloTailwindCss extends React.Component<IHelloTailwindCssProps, {}> {
public render(): React.ReactElement<IHelloTailwindCssProps> {
return (
<Stack gap="20">
<DocumentCard title="Revenue stream proposal fiscal year 2016 version02.pptx"
documentImageUrl="https://static2.sharepointonline.com/files/fabric/office-ui-fabric-react-assets/document-preview.png"
lastEditUserInfo={{
profileImageUrl: "https://static2.sharepointonline.com/files/fabric/office-ui-fabric-react-assets/persona-female.png",
line1: "Annie Lindqvist",
line2: "Created a few minutes ago"
}}
/>
<HorizontalCard title="Can coffee make you a better developer?"
description="Lorem ipsum dolor sit amet, consectetur adipisicing elit. Voluptatibus quia, nulla! Maiores et perferendis eaque, exercitationem praesentium nihil."
documentImageUrl="//tailwindcss.com/img/card-left.jpg"
lastEditUserInfo={{
profileImageUrl: "https://static2.sharepointonline.com/files/fabric/office-ui-fabric-react-assets/persona-female.png",
line1: "Annie Lindqvist",
line2: "Aug 18"
}}
/></Stack>
);
}
}

View File

@ -0,0 +1,33 @@
import * as React from 'react';
import { escape } from '@microsoft/sp-lodash-subset';
import './../../../tailwind.css';
import User, { IUserProps } from './User';
export interface IHorizontalCardProps {
title: string;
description: string;
documentImageUrl: string;
lastEditUserInfo: IUserProps;
}
const rootClassName = "sp-col-1/3:max-w-sm sp-col-1:max-w-full sp-col-1:flex overflow-hidden shadow hover:outline-none hover:shadow-lg hover:cursor-pointer";
export default class HorizontalCard extends React.Component<IHorizontalCardProps, {}> {
public render(): React.ReactElement<IHorizontalCardProps> {
return (
<div className={rootClassName}>
<div className="h-48 sp-col-1:h-auto sp-col-1:w-48 flex-none bg-cover text-center overflow-hidden" style={{ backgroundImage: "url('//tailwindcss.com/img/card-left.jpg')" }} title="Woman holding a mug">
</div>
<div className="border-r border-b border-l border-gray-400 sp-col-1:border-l-0 sp-col-1:border-t bg-buttonBackground text-buttonText p-4 flex flex-col justify-between leading-normal">
<div className="mb-8">
<div className="font-bold text-xl mb-2">{this.props.title}</div>
<p className="text-base text-buttonTextDisabled">{this.props.description}</p>
</div>
<User profileImageUrl={this.props.lastEditUserInfo.profileImageUrl}
line1={this.props.lastEditUserInfo.line1}
line2={this.props.lastEditUserInfo.line1} />
</div>
</div>
);
}
}

View File

@ -0,0 +1,3 @@
export interface IHelloTailwindCssProps {
description: string;
}

View File

@ -0,0 +1,23 @@
import * as React from 'react';
import { escape } from '@microsoft/sp-lodash-subset';
import './../../../tailwind.css';
export interface IUserProps {
profileImageUrl: string;
line1: string;
line2: string;
}
export default class User extends React.Component<IUserProps, {}> {
public render(): React.ReactElement<IUserProps> {
return (
<div className="flex mt-4">
<img className="h-8 w-8 rounded-full mr-2" src={this.props.profileImageUrl} />
<div className="text-left">
<div className="font-bold text-xs">{this.props.line1}</div>
<div className="text-xs text-buttonTextDisabled">{this.props.line2}</div>
</div>
</div>
);
}
}

View File

@ -0,0 +1,7 @@
define([], function() {
return {
"PropertyPaneDescription": "Description",
"BasicGroupName": "Group Name",
"DescriptionFieldLabel": "Description Field"
}
});

View File

@ -0,0 +1,10 @@
declare interface IHelloTailwindCssWebPartStrings {
PropertyPaneDescription: string;
BasicGroupName: string;
DescriptionFieldLabel: string;
}
declare module 'HelloTailwindCssWebPartStrings' {
const strings: IHelloTailwindCssWebPartStrings;
export = strings;
}

View File

@ -0,0 +1,289 @@
const plugin = require('tailwindcss/plugin');
const customSpSectionVariant = ['sp-col-1', 'sp-col-1/2', 'sp-col-1/3', 'sp-col-2/3'];
module.exports = {
purge: {
enabled: true,
content: ['./src/**/*.tsx'],
options: {
whitelistPatterns: [/^CanvasSection*/]
}
},
theme: {
extend: {
colors: {
themeDarker: 'var(--tw-fui-themeDarker)',
themeDark: 'var(--tw-fui-themeDark)',
themeDarkAlt: 'var(--tw-fui-themeDarkAlt)',
themePrimary: 'var(--tw-fui-themePrimary)',
themeSecondary: 'var(--tw-fui-themeSecondary)',
themeTertiary: 'var(--tw-fui-themeTertiary)',
themeLight: 'var(--tw-fui-themeLight)',
themeLighter: 'var(--tw-fui-themeLighter)',
themeLighterAlt: 'var(--tw-fui-themeLighterAlt)',
//black: 'var(--tw-fui-black)',
blackTranslucent40: 'var(--tw-fui-blackTranslucent40)',
neutralDark: 'var(--tw-fui-neutralDark)',
neutralPrimary: 'var(--tw-fui-neutralPrimary)',
neutralPrimaryAlt: 'var(--tw-fui-neutralPrimaryAlt)',
neutralSecondary: 'var(--tw-fui-neutralSecondary)',
neutralSecondaryAlt: 'var(--tw-fui-neutralSecondaryAlt)',
neutralTertiary: 'var(--tw-fui-neutralTertiary)',
neutralTertiaryAlt: 'var(--tw-fui-neutralTertiaryAlt)',
neutralQuaternary: 'var(--tw-fui-neutralQuaternary)',
neutralQuaternaryAlt: 'var(--tw-fui-neutralQuaternaryAlt)',
neutralLight: 'var(--tw-fui-neutralLight)',
neutralLighter: 'var(--tw-fui-neutralLighter)',
neutralLighterAlt: 'var(--tw-fui-neutralLighterAlt)',
accent: 'var(--tw-fui-accent)',
//white: 'var(--tw-fui-white)',
whiteTranslucent40: 'var(--tw-fui-whiteTranslucent40)',
yellow: 'var(--tw-fui-yellow)',
yellowLight: 'var(--tw-fui-yellowLight)',
orange: 'var(--tw-fui-orange)',
orangeLight: 'var(--tw-fui-orangeLight)',
orangeLighter: 'var(--tw-fui-orangeLighter)',
redDark: 'var(--tw-fui-redDark)',
red: 'var(--tw-fui-red)',
magentaDark: 'var(--tw-fui-magentaDark)',
magenta: 'var(--tw-fui-magenta)',
magentaLight: 'var(--tw-fui-magentaLight)',
purpleDark: 'var(--tw-fui-purpleDark)',
purple: 'var(--tw-fui-purple)',
purpleLight: 'var(--tw-fui-purpleLight)',
blueDark: 'var(--tw-fui-blueDark)',
blueMid: 'var(--tw-fui-blueMid)',
blue: 'var(--tw-fui-blue)',
blueLight: 'var(--tw-fui-blueLight)',
tealDark: 'var(--tw-fui-tealDark)',
teal: 'var(--tw-fui-teal)',
tealLight: 'var(--tw-fui-tealLight)',
greenDark: 'var(--tw-fui-greenDark)',
green: 'var(--tw-fui-green)',
greenLight: 'var(--tw-fui-greenLight)',
/* ***** */
bodyBackground: 'var(--tw-fui-bodyBackground)',
bodyStandoutBackground: 'var(--tw-fui-bodyStandoutBackground)',
bodyFrameBackground: 'var(--tw-fui-bodyFrameBackground)',
bodyFrameDivider: 'var(--tw-fui-bodyFrameDivider)',
bodyText: 'var(--tw-fui-bodyText)',
bodyTextChecked: 'var(--tw-fui-bodyTextChecked)',
bodySubtext: 'var(--tw-fui-bodySubtext)',
bodyDivider: 'var(--tw-fui-bodyDivider)',
disabledBackground: 'var(--tw-fui-disabledBackground)',
disabledText: 'var(--tw-fui-disabledText)',
disabledSubtext: 'var(--tw-fui-disabledSubtext)',
disabledBodyText: 'var(--tw-fui-disabledBodyText)',
disabledBodySubtext: 'var(--tw-fui-disabledBodySubtext)',
focusBorder: 'var(--tw-fui-focusBorder)',
variantBorder: 'var(--tw-fui-variantBorder)',
variantBorderHovered: 'var(--tw-fui-variantBorderHovered)',
defaultStateBackground: 'var(--tw-fui-defaultStateBackground)',
errorText: 'var(--tw-fui-errorText)',
warningText: 'var(--tw-fui-warningText)',
errorBackground: 'var(--tw-fui-errorBackground)',
blockingBackground: 'var(--tw-fui-blockingBackground)',
warningBackground: 'var(--tw-fui-warningBackground)',
warningHighlight: 'var(--tw-fui-warningHighlight)',
successBackground: 'var(--tw-fui-successBackground)',
inputBorder: 'var(--tw-fui-inputBorder)',
inputBorderHovered: 'var(--tw-fui-inputBorderHovered)',
inputBackground: 'var(--tw-fui-inputBackground)',
inputBackgroundChecked: 'var(--tw-fui-inputBackgroundChecked)',
inputBackgroundCheckedHovered: 'var(--tw-fui-inputBackgroundCheckedHovered)',
inputForegroundChecked: 'var(--tw-fui-inputForegroundChecked)',
inputFocusBorderAlt: 'var(--tw-fui-inputFocusBorderAlt)',
smallInputBorder: 'var(--tw-fui-smallInputBorder)',
inputText: 'var(--tw-fui-inputText)',
inputTextHovered: 'var(--tw-fui-inputTextHovered)',
inputPlaceholderText: 'var(--tw-fui-inputPlaceholderText)',
buttonBackground: 'var(--tw-fui-buttonBackground)',
buttonBackgroundChecked: 'var(--tw-fui-buttonBackgroundChecked)',
buttonBackgroundHovered: 'var(--tw-fui-buttonBackgroundHovered)',
buttonBackgroundCheckedHovered: 'var(--tw-fui-buttonBackgroundCheckedHovered)',
buttonBackgroundPressed: 'var(--tw-fui-buttonBackgroundPressed)',
buttonBackgroundDisabled: 'var(--tw-fui-buttonBackgroundDisabled)',
buttonBorder: 'var(--tw-fui-buttonBorder)',
buttonText: 'var(--tw-fui-buttonText)',
buttonTextHovered: 'var(--tw-fui-buttonTextHovered)',
buttonTextChecked: 'var(--tw-fui-buttonTextChecked)',
buttonTextCheckedHovered: 'var(--tw-fui-buttonTextCheckedHovered)',
buttonTextPressed: 'var(--tw-fui-buttonTextPressed)',
buttonTextDisabled: 'var(--tw-fui-buttonTextDisabled)',
buttonBorderDisabled: 'var(--tw-fui-buttonBorderDisabled)',
primaryButtonBackground: 'var(--tw-fui-primaryButtonBackground)',
primaryButtonBackgroundHovered: 'var(--tw-fui-primaryButtonBackgroundHovered)',
primaryButtonBackgroundPressed: 'var(--tw-fui-primaryButtonBackgroundPressed)',
primaryButtonBackgroundDisabled: 'var(--tw-fui-primaryButtonBackgroundDisabled)',
primaryButtonBorder: 'var(--tw-fui-primaryButtonBorder)',
primaryButtonText: 'var(--tw-fui-primaryButtonText)',
primaryButtonTextHovered: 'var(--tw-fui-primaryButtonTextHovered)',
primaryButtonTextPressed: 'var(--tw-fui-primaryButtonTextPressed)',
primaryButtonTextDisabled: 'var(--tw-fui-primaryButtonTextDisabled)',
accentButtonBackground: 'var(--tw-fui-accentButtonBackground)',
accentButtonText: 'var(--tw-fui-accentButtonText)',
menuBackground: 'var(--tw-fui-menuBackground)',
menuDivider: 'var(--tw-fui-menuDivider)',
menuIcon: 'var(--tw-fui-menuIcon)',
menuHeader: 'var(--tw-fui-menuHeader)',
menuItemBackgroundHovered: 'var(--tw-fui-menuItemBackgroundHovered)',
menuItemBackgroundPressed: 'var(--tw-fui-menuItemBackgroundPressed)',
menuItemText: 'var(--tw-fui-menuItemText)',
menuItemTextHovered: 'var(--tw-fui-menuItemTextHovered)',
listBackground: 'var(--tw-fui-listBackground)',
listText: 'var(--tw-fui-listText)',
listItemBackgroundHovered: 'var(--tw-fui-listItemBackgroundHovered)',
listItemBackgroundChecked: 'var(--tw-fui-listItemBackgroundChecked)',
listItemBackgroundCheckedHovered: 'var(--tw-fui-listItemBackgroundCheckedHovered)',
listHeaderBackgroundHovered: 'var(--tw-fui-listHeaderBackgroundHovered)',
listHeaderBackgroundPressed: 'var(--tw-fui-listHeaderBackgroundPressed)',
actionLink: 'var(--tw-fui-actionLink)',
actionLinkHovered: 'var(--tw-fui-actionLinkHovered)',
link: 'var(--tw-fui-link)',
linkHovered: 'var(--tw-fui-linkHovered)',
listTextColor: 'var(--tw-fui-listTextColor)',
menuItemBackgroundChecked: 'var(--tw-fui-menuItemBackgroundChecked)',
}
},
},
variants: {
accessibility: customSpSectionVariant.concat(['hover', 'focus']),
alignContent: customSpSectionVariant.concat(['hover', 'focus']),
alignItems: customSpSectionVariant.concat(['hover', 'focus']),
alignSelf: customSpSectionVariant.concat(['hover', 'focus']),
appearance: customSpSectionVariant.concat(['hover', 'focus']),
backgroundAttachment: customSpSectionVariant.concat(['hover', 'focus']),
backgroundClip: customSpSectionVariant.concat(['hover', 'focus']),
backgroundColor: customSpSectionVariant.concat(['hover', 'focus']),
backgroundImage: customSpSectionVariant.concat(['hover', 'focus']),
gradientColorStops: customSpSectionVariant.concat(['hover', 'focus']),
backgroundOpacity: customSpSectionVariant.concat(['hover', 'focus']),
backgroundPosition: customSpSectionVariant.concat(['hover', 'focus']),
backgroundRepeat: customSpSectionVariant.concat(['hover', 'focus']),
backgroundSize: customSpSectionVariant.concat(['hover', 'focus']),
borderCollapse: customSpSectionVariant.concat(['hover', 'focus']),
borderColor: customSpSectionVariant.concat(['hover', 'focus']),
borderOpacity: customSpSectionVariant.concat(['hover', 'focus']),
borderRadius: customSpSectionVariant.concat(['hover', 'focus']),
borderStyle: customSpSectionVariant.concat(['hover', 'focus']),
borderWidth: customSpSectionVariant.concat(['hover', 'focus']),
boxShadow: customSpSectionVariant.concat(['hover', 'focus']),
boxSizing: customSpSectionVariant.concat(['hover', 'focus']),
container: customSpSectionVariant.concat(['hover', 'focus']),
cursor: customSpSectionVariant.concat(['hover', 'focus']),
display: customSpSectionVariant.concat(['hover', 'focus']),
divideColor: customSpSectionVariant.concat(['hover', 'focus']),
divideOpacity: customSpSectionVariant.concat(['hover', 'focus']),
divideStyle: customSpSectionVariant.concat(['hover', 'focus']),
divideWidth: customSpSectionVariant.concat(['hover', 'focus']),
fill: customSpSectionVariant.concat(['hover', 'focus']),
flex: customSpSectionVariant.concat(['hover', 'focus']),
flexDirection: customSpSectionVariant.concat(['hover', 'focus']),
flexGrow: customSpSectionVariant.concat(['hover', 'focus']),
flexShrink: customSpSectionVariant.concat(['hover', 'focus']),
flexWrap: customSpSectionVariant.concat(['hover', 'focus']),
float: customSpSectionVariant.concat(['hover', 'focus']),
clear: customSpSectionVariant.concat(['hover', 'focus']),
fontFamily: customSpSectionVariant.concat(['hover', 'focus']),
fontSize: customSpSectionVariant.concat(['hover', 'focus']),
fontSmoothing: customSpSectionVariant.concat(['hover', 'focus']),
fontVariantNumeric: customSpSectionVariant.concat(['hover', 'focus']),
fontStyle: customSpSectionVariant.concat(['hover', 'focus']),
fontWeight: customSpSectionVariant.concat(['hover', 'focus']),
height: customSpSectionVariant.concat(['hover', 'focus']),
inset: customSpSectionVariant.concat(['hover', 'focus']),
justifyContent: customSpSectionVariant.concat(['hover', 'focus']),
justifyItems: customSpSectionVariant.concat(['hover', 'focus']),
justifySelf: customSpSectionVariant.concat(['hover', 'focus']),
letterSpacing: customSpSectionVariant.concat(['hover', 'focus']),
lineHeight: customSpSectionVariant.concat(['hover', 'focus']),
listStylePosition: customSpSectionVariant.concat(['hover', 'focus']),
listStyleType: customSpSectionVariant.concat(['hover', 'focus']),
margin: customSpSectionVariant.concat(['hover', 'focus']),
maxHeight: customSpSectionVariant.concat(['hover', 'focus']),
maxWidth: customSpSectionVariant.concat(['hover', 'focus']),
minHeight: customSpSectionVariant.concat(['hover', 'focus']),
minWidth: customSpSectionVariant.concat(['hover', 'focus']),
objectFit: customSpSectionVariant.concat(['hover', 'focus']),
objectPosition: customSpSectionVariant.concat(['hover', 'focus']),
opacity: customSpSectionVariant.concat(['hover', 'focus']),
order: customSpSectionVariant.concat(['hover', 'focus']),
outline: customSpSectionVariant.concat(['hover', 'focus']),
overflow: customSpSectionVariant.concat(['hover', 'focus']),
overscrollBehavior: customSpSectionVariant.concat(['hover', 'focus']),
padding: customSpSectionVariant.concat(['hover', 'focus']),
placeContent: customSpSectionVariant.concat(['hover', 'focus']),
placeItems: customSpSectionVariant.concat(['hover', 'focus']),
placeSelf: customSpSectionVariant.concat(['hover', 'focus']),
placeholderColor: customSpSectionVariant.concat(['focus']),
placeholderOpacity: customSpSectionVariant.concat(['focus']),
pointerEvents: customSpSectionVariant.concat(['hover', 'focus']),
position: customSpSectionVariant.concat(['hover', 'focus']),
resize: customSpSectionVariant.concat(['hover', 'focus']),
space: customSpSectionVariant.concat(['hover', 'focus']),
stroke: customSpSectionVariant.concat(['hover', 'focus']),
strokeWidth: customSpSectionVariant.concat(['hover', 'focus']),
tableLayout: customSpSectionVariant.concat(['hover', 'focus']),
textAlign: customSpSectionVariant.concat(['hover', 'focus']),
textColor: customSpSectionVariant.concat(['hover', 'focus']),
textOpacity: customSpSectionVariant.concat(['hover', 'focus']),
textDecoration: customSpSectionVariant.concat(['hover', 'focus']),
textTransform: customSpSectionVariant.concat(['hover', 'focus']),
userSelect: customSpSectionVariant.concat(['hover', 'focus']),
verticalAlign: customSpSectionVariant.concat(['hover', 'focus']),
visibility: customSpSectionVariant.concat(['hover', 'focus']),
whitespace: customSpSectionVariant.concat(['hover', 'focus']),
width: customSpSectionVariant.concat(['hover', 'focus']),
wordBreak: customSpSectionVariant.concat(['hover', 'focus']),
zIndex: customSpSectionVariant.concat(['hover', 'focus']),
gap: customSpSectionVariant.concat(['hover', 'focus']),
gridAutoFlow: customSpSectionVariant.concat(['hover', 'focus']),
gridTemplateColumns: customSpSectionVariant.concat(['hover', 'focus']),
gridColumn: customSpSectionVariant.concat(['hover', 'focus']),
gridColumnStart: customSpSectionVariant.concat(['hover', 'focus']),
gridColumnEnd: customSpSectionVariant.concat(['hover', 'focus']),
gridTemplateRows: customSpSectionVariant.concat(['hover', 'focus']),
gridRow: customSpSectionVariant.concat(['hover', 'focus']),
gridRowStart: customSpSectionVariant.concat(['hover', 'focus']),
gridRowEnd: customSpSectionVariant.concat(['hover', 'focus']),
transform: customSpSectionVariant.concat(['hover', 'focus']),
transformOrigin: customSpSectionVariant.concat(['hover', 'focus']),
scale: customSpSectionVariant.concat(['hover', 'focus']),
rotate: customSpSectionVariant.concat(['hover', 'focus']),
translate: customSpSectionVariant.concat(['hover', 'focus']),
skew: customSpSectionVariant.concat(['hover', 'focus']),
transitionProperty: customSpSectionVariant.concat(['hover', 'focus']),
transitionTimingFunction: customSpSectionVariant.concat(['hover', 'focus']),
transitionDuration: customSpSectionVariant.concat(['hover', 'focus']),
transitionDelay: customSpSectionVariant.concat(['hover', 'focus']),
animation: customSpSectionVariant
},
plugins: [
plugin(function ({ addVariant, e }) {
addVariant('sp-col-1', ({ modifySelectors, separator }) => {
modifySelectors(({ className }) => {
return `.CanvasSection.CanvasSection-col.CanvasSection-xl12 .${e(`sp-col-1${separator}${className}`)}`
})
}),
addVariant('sp-col-1/2', ({ modifySelectors, separator }) => {
modifySelectors(({ className }) => {
return `.CanvasSection.CanvasSection-col.CanvasSection-xl6 .${e(`sp-col-1/2${separator}${className}`)}`
})
}),
addVariant('sp-col-1/3', ({ modifySelectors, separator }) => {
modifySelectors(({ className }) => {
return `.CanvasSection.CanvasSection-col.CanvasSection-xl4 .${e(`sp-col-1/3${separator}${className}`)}`
})
}),
addVariant('sp-col-2/3', ({ modifySelectors, separator }) => {
modifySelectors(({ className }) => {
return `.CanvasSection.CanvasSection-col.CanvasSection-xl8 .${e(`sp-col-2/3${separator}${className}`)}`
})
})
})
],
future: {
removeDeprecatedGapUtilities: true,
purgeLayersByDefault: true
}
};

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 383 B

View File

@ -0,0 +1,39 @@
{
"extends": "./node_modules/@microsoft/rush-stack-compiler-3.3/includes/tsconfig-web.json",
"compilerOptions": {
"target": "es5",
"forceConsistentCasingInFileNames": true,
"module": "esnext",
"moduleResolution": "node",
"jsx": "react",
"declaration": true,
"sourceMap": true,
"experimentalDecorators": true,
"skipLibCheck": true,
"outDir": "lib",
"inlineSources": false,
"strictNullChecks": false,
"noUnusedLocals": false,
"typeRoots": [
"./node_modules/@types",
"./node_modules/@microsoft"
],
"types": [
"es6-promise",
"webpack-env"
],
"lib": [
"es5",
"dom",
"es2015.collection"
]
},
"include": [
"src/**/*.ts",
"src/**/*.tsx"
],
"exclude": [
"node_modules",
"lib"
]
}

View File

@ -0,0 +1,30 @@
{
"extends": "@microsoft/sp-tslint-rules/base-tslint.json",
"rules": {
"class-name": false,
"export-name": false,
"forin": false,
"label-position": false,
"member-access": true,
"no-arg": false,
"no-console": false,
"no-construct": false,
"no-duplicate-variable": true,
"no-eval": false,
"no-function-expression": true,
"no-internal-module": true,
"no-shadowed-variable": true,
"no-switch-case-fall-through": true,
"no-unnecessary-semicolons": true,
"no-unused-expression": true,
"no-use-before-declare": true,
"no-with-statement": true,
"semicolon": true,
"trailing-comma": false,
"typedef": false,
"typedef-whitespace": false,
"use-named-parameter": true,
"variable-name": false,
"whitespace": false
}
}

View File

@ -16,7 +16,7 @@
"environment": "spo", "environment": "spo",
"framework": "react", "framework": "react",
"isCreatingSolution": true, "isCreatingSolution": true,
"version": "1.7.1", "version": "1.11.0",
"libraryName": "react-tenant-properties", "libraryName": "react-tenant-properties",
"libraryId": "d5bc38a6-0b5c-4644-9087-efa6de87ece1", "libraryId": "d5bc38a6-0b5c-4644-9087-efa6de87ece1",
"packageManager": "npm", "packageManager": "npm",

View File

@ -1,28 +1,35 @@
# React Tenant Properties Web Part # React Tenant Properties Web Part
## Summary ## Summary
This web part allows tenant administrators to manage tenant properties through a graphical interface. This web part allows tenant administrators to manage tenant properties through a graphical interface.
We can create, edit or delete tenant properties. We can create, edit or delete tenant properties.
Only users with Tenant Admin Role are allowed to managed tenant properties. Only users with Tenant Admin Role are allowed to managed tenant properties.
#### User without Tenant Admin Role got this message
### User without Tenant Admin Role got this message
![tenant properties](./assets/TenantProperties5.jpg) ![tenant properties](./assets/TenantProperties5.jpg)
#### List tenant properties
### List tenant properties
![tenant properties](./assets/TenantProperties1.jpg) ![tenant properties](./assets/TenantProperties1.jpg)
#### Add Tenant property ### Add Tenant property
![tenant properties](./assets/TenantProperties2.jpg) ![tenant properties](./assets/TenantProperties2.jpg)
#### Edit tenant property ### Edit tenant property
![tenant properties](./assets/TenantProperties3.jpg) ![tenant properties](./assets/TenantProperties3.jpg)
#### Delete tenant property ### Delete tenant property
![tenant properties](./assets/TenantProperties4.jpg)
![tenant properties](./assets/TenantProperties4.jpg)
## Used SharePoint Framework Version ## Used SharePoint Framework Version
![drop](https://img.shields.io/badge/version-1.7.1-green.svg) ![SPFx 1.11](https://img.shields.io/badge/version-1.11.0-green.svg)
## Applies to ## Applies to
@ -38,6 +45,7 @@ WebPart Title| Text| no|
## Solution ## Solution
The Web Part Use MSGraph API and need to SharePoint Administrator approve de scope "Directory.ReadWrite.All" in SharePoint Admin Center. The Web Part Use MSGraph API and need to SharePoint Administrator approve de scope "Directory.ReadWrite.All" in SharePoint Admin Center.
Solution|Author(s) Solution|Author(s)
@ -49,8 +57,10 @@ Tenant Properties WebPart|João Mendes
Version|Date|Comments Version|Date|Comments
-------|----|-------- -------|----|--------
1.0.0|Mar 08, 2019|Initial release 1.0.0|Mar 08, 2019|Initial release
1.0.1|October 20, 2020|Update to SPFx 1.11.0
## Disclaimer ## Disclaimer
**THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT.** **THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT.**
--- ---
@ -63,7 +73,7 @@ Version|Date|Comments
- `gulp build` - `gulp build`
- `gulp bundle --ship` - `gulp bundle --ship`
- `gulp package-solution --ship` - `gulp package-solution --ship`
- `Add to AppCatalog and deploy` - Add to AppCatalog and deploy
- `Approve API permission on SharePoint Admin Center` - Approve API permission on SharePoint Admin Center
<img src="https://telemetry.sharepointpnp.com/sp-dev-fx-webparts/samples/react-tenant-properties" /> <img src="https://telemetry.sharepointpnp.com/sp-dev-fx-webparts/samples/react-tenant-properties" />

View File

@ -3,7 +3,7 @@
"solution": { "solution": {
"name": "react-tenant-properties-client-side-solution", "name": "react-tenant-properties-client-side-solution",
"id": "d5bc38a6-0b5c-4644-9087-efa6de87ece1", "id": "d5bc38a6-0b5c-4644-9087-efa6de87ece1",
"version": "1.0.0.0", "version": "1.0.1.0",
"includeClientSideAssets": true, "includeClientSideAssets": true,
"skipFeatureDeployment": true, "skipFeatureDeployment": true,
"webApiPermissionRequests": [ "webApiPermissionRequests": [
@ -11,7 +11,14 @@
"resource": "Microsoft Graph", "resource": "Microsoft Graph",
"scope": "Directory.ReadWrite.All" "scope": "Directory.ReadWrite.All"
} }
] ],
"developer": {
"name": "Contoso",
"privacyUrl": "https://contoso.com/privacy",
"termsOfUseUrl": "https://contoso.com/terms-of-use",
"websiteUrl": "https://contoso.com/my-app",
"mpnId": "000000"
}
}, },
"paths": { "paths": {
"zippedPackage": "solution/react-tenant-properties.sppkg" "zippedPackage": "solution/react-tenant-properties.sppkg"

File diff suppressed because it is too large Load Diff

View File

@ -2,6 +2,7 @@
"name": "react-tenant-properties", "name": "react-tenant-properties",
"version": "0.0.1", "version": "0.0.1",
"private": true, "private": true,
"main": "lib/index.js",
"engines": { "engines": {
"node": ">=0.10.0" "node": ">=0.10.0"
}, },
@ -15,30 +16,29 @@
}, },
"dependencies": { "dependencies": {
"@microsoft/office-ui-fabric-react-bundle": "^1.7.1", "@microsoft/office-ui-fabric-react-bundle": "^1.7.1",
"@microsoft/sp-core-library": "1.7.1", "@microsoft/sp-core-library": "1.11.0",
"@microsoft/sp-lodash-subset": "1.7.1", "@microsoft/sp-lodash-subset": "1.11.0",
"@microsoft/sp-office-ui-fabric-core": "1.7.1", "@microsoft/sp-office-ui-fabric-core": "1.11.0",
"@microsoft/sp-webpart-base": "1.7.1", "@microsoft/sp-property-pane": "1.11.0",
"@microsoft/sp-webpart-base": "1.11.0",
"@pnp/pnpjs": "^1.3.0", "@pnp/pnpjs": "^1.3.0",
"@pnp/spfx-controls-react": "1.12.0", "@pnp/spfx-controls-react": "1.12.0",
"@pnp/spfx-property-controls": "1.14.1", "@pnp/spfx-property-controls": "1.14.1",
"@types/es6-promise": "0.0.33",
"@types/jquery": "^3.3.29", "@types/jquery": "^3.3.29",
"@types/react": "16.4.2",
"@types/react-dom": "16.0.5",
"@types/webpack-env": "1.13.1",
"jquery": "^3.3.1", "jquery": "^3.3.1",
"react": "16.3.2", "office-ui-fabric-react": "6.214.0",
"react-dom": "16.3.2" "react": "16.8.5",
"react-dom": "16.8.5"
}, },
"resolutions": { "resolutions": {
"@types/react": "16.4.2" "@types/react": "16.8.8"
}, },
"devDependencies": { "devDependencies": {
"@microsoft/sp-build-web": "1.7.1", "@microsoft/rush-stack-compiler-3.3": "0.3.5",
"@microsoft/sp-module-interfaces": "1.7.1", "@microsoft/sp-build-web": "1.11.0",
"@microsoft/sp-tslint-rules": "1.7.1", "@microsoft/sp-module-interfaces": "1.11.0",
"@microsoft/sp-webpart-workbench": "1.7.1", "@microsoft/sp-tslint-rules": "1.11.0",
"@microsoft/sp-webpart-workbench": "1.11.0",
"@types/chai": "3.4.34", "@types/chai": "3.4.34",
"@types/mocha": "2.2.38", "@types/mocha": "2.2.38",
"@voitanos/jest-preset-spfx-react16": "^1.1.0", "@voitanos/jest-preset-spfx-react16": "^1.1.0",

View File

@ -12,6 +12,7 @@
// Components that allow authors to embed arbitrary script code should set this to true. // Components that allow authors to embed arbitrary script code should set this to true.
// https://support.office.com/en-us/article/Turn-scripting-capabilities-on-or-off-1f2c515f-5d7e-448a-9fd7-835da935584f // https://support.office.com/en-us/article/Turn-scripting-capabilities-on-or-off-1f2c515f-5d7e-448a-9fd7-835da935584f
"requiresCustomScript": false, "requiresCustomScript": false,
"supportedHosts": ["SharePointWebPart"],
"preconfiguredEntries": [{ "preconfiguredEntries": [{
"groupId": "5c03119e-3074-46fd-976b-c60198311f70", // Other "groupId": "5c03119e-3074-46fd-976b-c60198311f70", // Other

View File

@ -1,11 +1,8 @@
import * as React from 'react'; import * as React from 'react';
import * as ReactDom from 'react-dom'; import * as ReactDom from 'react-dom';
import { Version } from '@microsoft/sp-core-library'; import { Version } from '@microsoft/sp-core-library';
import { import { BaseClientSideWebPart } from "@microsoft/sp-webpart-base";
BaseClientSideWebPart, import { IPropertyPaneConfiguration, PropertyPaneTextField } from "@microsoft/sp-property-pane";
IPropertyPaneConfiguration,
PropertyPaneTextField
} from '@microsoft/sp-webpart-base';
import * as strings from 'TenantPropertiesWebPartStrings'; import * as strings from 'TenantPropertiesWebPartStrings';
import TenantProperties from './components/TenantProperties'; import TenantProperties from './components/TenantProperties';

View File

@ -1,5 +1,6 @@
import {IListViewItems } from "./IListViewItems"; import {IListViewItems } from "./IListViewItems";
import { WebPartContext } from '@microsoft/sp-webpart-base'; import { WebPartContext } from '@microsoft/sp-webpart-base';
import { SyntheticEvent } from "react";
export enum panelMode { export enum panelMode {
"New", "New",
"edit", "edit",
@ -10,6 +11,6 @@ export interface ITenantPropertyPanelProps {
mode:panelMode; mode:panelMode;
showPanel: boolean; showPanel: boolean;
TenantProperty: IListViewItems ; TenantProperty: IListViewItems ;
onDismiss(refresh?:boolean) : void; onDismiss(ev?: SyntheticEvent<HTMLElement, Event>, refresh?: boolean) : void;
context: WebPartContext; context: WebPartContext;
} }

View File

@ -1,4 +1,4 @@
@import '~@microsoft/sp-office-ui-fabric-core/dist/sass/SPFabricCore.scss'; @import '~office-ui-fabric-react/dist/sass/References.scss';
.tenantProperties { .tenantProperties {
.container { .container {

View File

@ -19,6 +19,7 @@ import { MessageBar, MessageBarType } from 'office-ui-fabric-react/lib/MessageBa
import { Spinner, SpinnerSize } from 'office-ui-fabric-react/lib/Spinner'; import { Spinner, SpinnerSize } from 'office-ui-fabric-react/lib/Spinner';
import { WebPartTitle } from "@pnp/spfx-controls-react/lib/WebPartTitle"; import { WebPartTitle } from "@pnp/spfx-controls-react/lib/WebPartTitle";
import * as strings from 'TenantPropertiesWebPartStrings'; import * as strings from 'TenantPropertiesWebPartStrings';
import { SyntheticEvent } from 'react';
// ListView Columns // ListView Columns
const viewFields: IViewField[] = [ const viewFields: IViewField[] = [
@ -97,7 +98,7 @@ export default class TenantProperties extends React.Component<ITenantPropertiesP
} }
// Panel Dismiss CallBack // Panel Dismiss CallBack
// @param refresh refresh list? // @param refresh refresh list?
public async onDismissPanel(refresh?: boolean) { public async onDismissPanel(ev?: SyntheticEvent<HTMLElement, Event>, refresh?: boolean) {
this.setState({ this.setState({
showPanel: false showPanel: false
}); });

View File

@ -58,7 +58,9 @@ export default class TenantPropertyPanel extends React.Component<ITenantProperty
Description: this.state.tenantProperty.tenantPropertyDescription, Description: this.state.tenantProperty.tenantPropertyDescription,
Comment: this.state.tenantProperty.tenantPropertyComment Comment: this.state.tenantProperty.tenantPropertyComment
}); });
result ? this.props.onDismiss(true) : null; if (result) {
this.props.onDismiss(null, true);
}
}catch(error){ }catch(error){
this.setState({errorMessage:error}); this.setState({errorMessage:error});
} }
@ -72,7 +74,9 @@ export default class TenantPropertyPanel extends React.Component<ITenantProperty
Description: this.state.tenantProperty.tenantPropertyDescription, Description: this.state.tenantProperty.tenantPropertyDescription,
Comment: this.state.tenantProperty.tenantPropertyComment Comment: this.state.tenantProperty.tenantPropertyComment
}); });
result ? this.props.onDismiss(true) : null; if (result) {
this.props.onDismiss(null, true);
}
}catch(error){ }catch(error){
this.setState({errorMessage:error}); this.setState({errorMessage:error});
} }
@ -86,7 +90,9 @@ export default class TenantPropertyPanel extends React.Component<ITenantProperty
Description: this.state.tenantProperty.tenantPropertyDescription, Description: this.state.tenantProperty.tenantPropertyDescription,
Comment: this.state.tenantProperty.tenantPropertyComment Comment: this.state.tenantProperty.tenantPropertyComment
}); });
result ? this.props.onDismiss(true) : null; if (result) {
this.props.onDismiss(null, true);
}
}catch(error){ }catch(error){
this.setState({errorMessage:error}); this.setState({errorMessage:error});
} }

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 933 B

View File

@ -1,4 +1,5 @@
{ {
"extends": "./node_modules/@microsoft/rush-stack-compiler-3.3/includes/tsconfig-web.json",
"compilerOptions": { "compilerOptions": {
"target": "es5", "target": "es5",
"forceConsistentCasingInFileNames": true, "forceConsistentCasingInFileNames": true,
@ -10,6 +11,9 @@
"experimentalDecorators": true, "experimentalDecorators": true,
"skipLibCheck": true, "skipLibCheck": true,
"outDir": "lib", "outDir": "lib",
"inlineSources": false,
"strictNullChecks": false,
"noUnusedLocals": false,
"typeRoots": [ "typeRoots": [
"./node_modules/@types", "./node_modules/@types",
"./node_modules/@microsoft" "./node_modules/@microsoft"
@ -25,7 +29,7 @@
] ]
}, },
"include": [ "include": [
"src/**/*.ts", "src/webparts/tenantProperties/components/TenantPropertyPanel.tsx" "src/**/*.ts"
], ],
"exclude": [ "exclude": [
"node_modules", "node_modules",

View File

@ -0,0 +1,653 @@
# Upgrade project react-tenant-properties to v1.11.0
Date: 10/20/2020
## Findings
Following is the list of steps required to upgrade your project to SharePoint Framework version 1.11.0. [Summary](#Summary) of the modifications is included at the end of the report.
### FN001001 @microsoft/sp-core-library | Required
Upgrade SharePoint Framework dependency package @microsoft/sp-core-library
Execute the following command:
```sh
npm i -SE @microsoft/sp-core-library@1.11.0
```
File: [./package.json](./package.json)
### FN001002 @microsoft/sp-lodash-subset | Required
Upgrade SharePoint Framework dependency package @microsoft/sp-lodash-subset
Execute the following command:
```sh
npm i -SE @microsoft/sp-lodash-subset@1.11.0
```
File: [./package.json](./package.json)
### FN001003 @microsoft/sp-office-ui-fabric-core | Required
Upgrade SharePoint Framework dependency package @microsoft/sp-office-ui-fabric-core
Execute the following command:
```sh
npm i -SE @microsoft/sp-office-ui-fabric-core@1.11.0
```
File: [./package.json](./package.json)
### FN001004 @microsoft/sp-webpart-base | Required
Upgrade SharePoint Framework dependency package @microsoft/sp-webpart-base
Execute the following command:
```sh
npm i -SE @microsoft/sp-webpart-base@1.11.0
```
File: [./package.json](./package.json)
### FN001005 @types/react | Required
Remove SharePoint Framework dependency package @types/react
Execute the following command:
```sh
npm un -S @types/react
```
File: [./package.json](./package.json)
### FN001006 @types/react-dom | Required
Remove SharePoint Framework dependency package @types/react-dom
Execute the following command:
```sh
npm un -S @types/react-dom
```
File: [./package.json](./package.json)
### FN001007 @types/webpack-env | Required
Remove SharePoint Framework dependency package @types/webpack-env
Execute the following command:
```sh
npm un -S @types/webpack-env
```
File: [./package.json](./package.json)
### FN001010 @types/es6-promise | Required
Remove SharePoint Framework dependency package @types/es6-promise
Execute the following command:
```sh
npm un -S @types/es6-promise
```
File: [./package.json](./package.json)
### FN001021 @microsoft/sp-property-pane | Required
Install SharePoint Framework dependency package @microsoft/sp-property-pane
Execute the following command:
```sh
npm i -SE @microsoft/sp-property-pane@1.11.0
```
File: [./package.json](./package.json)
### FN001022 office-ui-fabric-react | Required
Install SharePoint Framework dependency package office-ui-fabric-react
Execute the following command:
```sh
npm i -SE office-ui-fabric-react@6.214.0
```
File: [./package.json](./package.json)
### FN002001 @microsoft/sp-build-web | Required
Upgrade SharePoint Framework dev dependency package @microsoft/sp-build-web
Execute the following command:
```sh
npm i -DE @microsoft/sp-build-web@1.11.0
```
File: [./package.json](./package.json)
### FN002002 @microsoft/sp-module-interfaces | Required
Upgrade SharePoint Framework dev dependency package @microsoft/sp-module-interfaces
Execute the following command:
```sh
npm i -DE @microsoft/sp-module-interfaces@1.11.0
```
File: [./package.json](./package.json)
### FN002003 @microsoft/sp-webpart-workbench | Required
Upgrade SharePoint Framework dev dependency package @microsoft/sp-webpart-workbench
Execute the following command:
```sh
npm i -DE @microsoft/sp-webpart-workbench@1.11.0
```
File: [./package.json](./package.json)
### FN002009 @microsoft/sp-tslint-rules | Required
Upgrade SharePoint Framework dev dependency package @microsoft/sp-tslint-rules
Execute the following command:
```sh
npm i -DE @microsoft/sp-tslint-rules@1.11.0
```
File: [./package.json](./package.json)
### FN002013 @types/webpack-env | Required
Install SharePoint Framework dev dependency package @types/webpack-env
Execute the following command:
```sh
npm i -DE @types/webpack-env@1.13.1
```
File: [./package.json](./package.json)
### FN002014 @types/es6-promise | Required
Install SharePoint Framework dev dependency package @types/es6-promise
Execute the following command:
```sh
npm i -DE @types/es6-promise@0.0.33
```
File: [./package.json](./package.json)
### FN002015 @types/react | Required
Install SharePoint Framework dev dependency package @types/react
Execute the following command:
```sh
npm i -DE @types/react@16.8.8
```
File: [./package.json](./package.json)
### FN002016 @types/react-dom | Required
Install SharePoint Framework dev dependency package @types/react-dom
Execute the following command:
```sh
npm i -DE @types/react-dom@16.8.3
```
File: [./package.json](./package.json)
### FN006004 package-solution.json developer | Optional
In package-solution.json add developer section
In file [./config/package-solution.json](./config/package-solution.json) update the code as follows:
```json
{
"solution": {
"developer": {
"name": "Contoso",
"privacyUrl": "https://contoso.com/privacy",
"termsOfUseUrl": "https://contoso.com/terms-of-use",
"websiteUrl": "https://contoso.com/my-app",
"mpnId": "000000"
}
}
}
```
File: [./config/package-solution.json](./config/package-solution.json)
### FN010001 .yo-rc.json version | Recommended
Update version in .yo-rc.json
In file [./.yo-rc.json](./.yo-rc.json) update the code as follows:
```json
{
"@microsoft/generator-sharepoint": {
"version": "1.11.0"
}
}
```
File: [./.yo-rc.json](./.yo-rc.json)
### FN012012 tsconfig.json include property | Required
Update tsconfig.json include property
In file [./tsconfig.json](./tsconfig.json) update the code as follows:
```json
{
"include": [
"src/**/*.tsx"
]
}
```
File: [./tsconfig.json](./tsconfig.json)
### FN002012 @microsoft/rush-stack-compiler-3.3 | Required
Install SharePoint Framework dev dependency package @microsoft/rush-stack-compiler-3.3
Execute the following command:
```sh
npm i -DE @microsoft/rush-stack-compiler-3.3@0.3.5
```
File: [./package.json](./package.json)
### FN012017 tsconfig.json extends property | Required
Update tsconfig.json extends property
In file [./tsconfig.json](./tsconfig.json) update the code as follows:
```json
{
"extends": "./node_modules/@microsoft/rush-stack-compiler-3.3/includes/tsconfig-web.json"
}
```
File: [./tsconfig.json](./tsconfig.json)
### FN016004 Property pane property import change to @microsoft/sp-property-pane | Required
Refactor the code to import property pane property from the @microsoft/sp-property-pane npm package instead of the @microsoft/sp-webpart-base package
In file [src/webparts/tenantProperties/TenantPropertiesWebPart.ts](src/webparts/tenantProperties/TenantPropertiesWebPart.ts) update the code as follows:
```ts
import { BaseClientSideWebPart } from "@microsoft/sp-webpart-base";
import { IPropertyPaneConfiguration, PropertyPaneTextField } from "@microsoft/sp-property-pane";
```
File: [src/webparts/tenantProperties/TenantPropertiesWebPart.ts:4:1](src/webparts/tenantProperties/TenantPropertiesWebPart.ts)
### FN020001 @types/react | Required
Add resolution for package @types/react
In file [./package.json](./package.json) update the code as follows:
```json
{
"resolutions": {
"@types/react": "16.8.8"
}
}
```
File: [./package.json](./package.json)
### FN021001 main | Required
Add package.json property
In file [./package.json](./package.json) update the code as follows:
```json
{
"main": "lib/index.js"
}
```
File: [./package.json](./package.json)
### FN001008 react | Required
Upgrade SharePoint Framework dependency package react
Execute the following command:
```sh
npm i -SE react@16.8.5
```
File: [./package.json](./package.json)
### FN001009 react-dom | Required
Upgrade SharePoint Framework dependency package react-dom
Execute the following command:
```sh
npm i -SE react-dom@16.8.5
```
File: [./package.json](./package.json)
### FN022001 Scss file import | Required
Remove scss file import
In file [src/webparts/tenantProperties/components/TenantProperties.module.scss](src/webparts/tenantProperties/components/TenantProperties.module.scss) update the code as follows:
```scss
@import '~@microsoft/sp-office-ui-fabric-core/dist/sass/SPFabricCore.scss'
```
File: [src/webparts/tenantProperties/components/TenantProperties.module.scss](src/webparts/tenantProperties/components/TenantProperties.module.scss)
### FN022002 Scss file import | Optional
Add scss file import
In file [src/webparts/tenantProperties/components/TenantProperties.module.scss](src/webparts/tenantProperties/components/TenantProperties.module.scss) update the code as follows:
```scss
@import '~office-ui-fabric-react/dist/sass/References.scss'
```
File: [src/webparts/tenantProperties/components/TenantProperties.module.scss](src/webparts/tenantProperties/components/TenantProperties.module.scss)
### FN011011 Web part manifest supportedHosts | Required
Update the supportedHosts property in the manifest
In file [src/webparts/tenantProperties/TenantPropertiesWebPart.manifest.json](src/webparts/tenantProperties/TenantPropertiesWebPart.manifest.json) update the code as follows:
```json
{
"supportedHosts": ["SharePointWebPart"]
}
```
File: [src/webparts/tenantProperties/TenantPropertiesWebPart.manifest.json](src/webparts/tenantProperties/TenantPropertiesWebPart.manifest.json)
### FN012014 tsconfig.json compiler options inlineSources | Required
Update tsconfig.json inlineSources value
In file [./tsconfig.json](./tsconfig.json) update the code as follows:
```json
{
"compilerOptions": {
"inlineSources": false
}
}
```
File: [./tsconfig.json](./tsconfig.json)
### FN012015 tsconfig.json compiler options strictNullChecks | Required
Update tsconfig.json strictNullChecks value
In file [./tsconfig.json](./tsconfig.json) update the code as follows:
```json
{
"compilerOptions": {
"strictNullChecks": false
}
}
```
File: [./tsconfig.json](./tsconfig.json)
### FN012016 tsconfig.json compiler options noUnusedLocals | Required
Update tsconfig.json noUnusedLocals value
In file [./tsconfig.json](./tsconfig.json) update the code as follows:
```json
{
"compilerOptions": {
"noUnusedLocals": false
}
}
```
File: [./tsconfig.json](./tsconfig.json)
### FN018001 Web part Microsoft Teams tab resources folder | Optional
Create folder for Microsoft Teams tab resources
Execute the following command:
```sh
mkdir /home/jfmr/dev/github/sp-dev-fx-webparts/samples/react-tenant-properties/teams
```
File: [teams](teams)
### FN018003 Web part Microsoft Teams tab small icon | Optional
Create Microsoft Teams tab small icon for the web part
Execute the following command:
```sh
cp /home/jfmr/.nvm/versions/node/v10.20.1/lib/node_modules/@pnp/cli-microsoft365/dist/m365/spfx/commands/project/project-upgrade/assets/tab20x20.png /home/jfmr/dev/github/sp-dev-fx-webparts/samples/react-tenant-properties/teams/fe85bc01-3650-4533-9c42-5c078fdd579c_outline.png
```
File: [teams/fe85bc01-3650-4533-9c42-5c078fdd579c_outline.png](teams/fe85bc01-3650-4533-9c42-5c078fdd579c_outline.png)
### FN018004 Web part Microsoft Teams tab large icon | Optional
Create Microsoft Teams tab large icon for the web part
Execute the following command:
```sh
cp /home/jfmr/.nvm/versions/node/v10.20.1/lib/node_modules/@pnp/cli-microsoft365/dist/m365/spfx/commands/project/project-upgrade/assets/tab96x96.png /home/jfmr/dev/github/sp-dev-fx-webparts/samples/react-tenant-properties/teams/fe85bc01-3650-4533-9c42-5c078fdd579c_color.png
```
File: [teams/fe85bc01-3650-4533-9c42-5c078fdd579c_color.png](teams/fe85bc01-3650-4533-9c42-5c078fdd579c_color.png)
### FN017001 Run npm dedupe | Optional
If, after upgrading npm packages, when building the project you have errors similar to: "error TS2345: Argument of type 'SPHttpClientConfiguration' is not assignable to parameter of type 'SPHttpClientConfiguration'", try running 'npm dedupe' to cleanup npm packages.
Execute the following command:
```sh
npm dedupe
```
File: [./package.json](./package.json)
## Summary
### Execute script
```sh
npm i -SE @microsoft/sp-core-library@1.11.0 @microsoft/sp-lodash-subset@1.11.0 @microsoft/sp-office-ui-fabric-core@1.11.0 @microsoft/sp-webpart-base@1.11.0 @microsoft/sp-property-pane@1.11.0 office-ui-fabric-react@6.214.0 react@16.8.5 react-dom@16.8.5
npm i -DE @microsoft/sp-build-web@1.11.0 @microsoft/sp-module-interfaces@1.11.0 @microsoft/sp-webpart-workbench@1.11.0 @microsoft/sp-tslint-rules@1.11.0 @types/webpack-env@1.13.1 @types/es6-promise@0.0.33 @types/react@16.8.8 @types/react-dom@16.8.3 @microsoft/rush-stack-compiler-3.3@0.3.5
npm un -S @types/react @types/react-dom @types/webpack-env @types/es6-promise
npm dedupe
mkdir /home/jfmr/dev/github/sp-dev-fx-webparts/samples/react-tenant-properties/teams
cp /home/jfmr/.nvm/versions/node/v10.20.1/lib/node_modules/@pnp/cli-microsoft365/dist/m365/spfx/commands/project/project-upgrade/assets/tab20x20.png /home/jfmr/dev/github/sp-dev-fx-webparts/samples/react-tenant-properties/teams/fe85bc01-3650-4533-9c42-5c078fdd579c_outline.png
cp /home/jfmr/.nvm/versions/node/v10.20.1/lib/node_modules/@pnp/cli-microsoft365/dist/m365/spfx/commands/project/project-upgrade/assets/tab96x96.png /home/jfmr/dev/github/sp-dev-fx-webparts/samples/react-tenant-properties/teams/fe85bc01-3650-4533-9c42-5c078fdd579c_color.png
```
### Modify files
#### [./config/package-solution.json](./config/package-solution.json)
In package-solution.json add developer section:
```json
{
"solution": {
"developer": {
"name": "Contoso",
"privacyUrl": "https://contoso.com/privacy",
"termsOfUseUrl": "https://contoso.com/terms-of-use",
"websiteUrl": "https://contoso.com/my-app",
"mpnId": "000000"
}
}
}
```
#### [./.yo-rc.json](./.yo-rc.json)
Update version in .yo-rc.json:
```json
{
"@microsoft/generator-sharepoint": {
"version": "1.11.0"
}
}
```
#### [./tsconfig.json](./tsconfig.json)
Update tsconfig.json include property:
```json
{
"include": [
"src/**/*.tsx"
]
}
```
Update tsconfig.json extends property:
```json
{
"extends": "./node_modules/@microsoft/rush-stack-compiler-3.3/includes/tsconfig-web.json"
}
```
Update tsconfig.json inlineSources value:
```json
{
"compilerOptions": {
"inlineSources": false
}
}
```
Update tsconfig.json strictNullChecks value:
```json
{
"compilerOptions": {
"strictNullChecks": false
}
}
```
Update tsconfig.json noUnusedLocals value:
```json
{
"compilerOptions": {
"noUnusedLocals": false
}
}
```
#### [src/webparts/tenantProperties/TenantPropertiesWebPart.ts](src/webparts/tenantProperties/TenantPropertiesWebPart.ts)
Refactor the code to import property pane property from the @microsoft/sp-property-pane npm package instead of the @microsoft/sp-webpart-base package:
```ts
import { BaseClientSideWebPart } from "@microsoft/sp-webpart-base";
import { IPropertyPaneConfiguration, PropertyPaneTextField } from "@microsoft/sp-property-pane";
```
#### [./package.json](./package.json)
Add resolution for package @types/react:
```json
{
"resolutions": {
"@types/react": "16.8.8"
}
}
```
Add package.json property:
```json
{
"main": "lib/index.js"
}
```
#### [src/webparts/tenantProperties/components/TenantProperties.module.scss](src/webparts/tenantProperties/components/TenantProperties.module.scss)
Remove scss file import:
```scss
@import '~@microsoft/sp-office-ui-fabric-core/dist/sass/SPFabricCore.scss'
```
Add scss file import:
```scss
@import '~office-ui-fabric-react/dist/sass/References.scss'
```
#### [src/webparts/tenantProperties/TenantPropertiesWebPart.manifest.json](src/webparts/tenantProperties/TenantPropertiesWebPart.manifest.json)
Update the supportedHosts property in the manifest:
```json
{
"supportedHosts": ["SharePointWebPart"]
}
```