Updates FluentUI9 demo and react-group-membership-manager to SPFX 1.16.1 and FluentUI 9.10.1 (#3423)
Merging conflicts
This commit is contained in:
parent
c87ed9d054
commit
f3af010335
|
@ -9,7 +9,7 @@ This demos the use of the new [Fluent UI version 9](https://github.com/microsoft
|
|||
|
||||
## Compatibility
|
||||
|
||||
![SPFx 1.16.0](https://img.shields.io/badge/SPFx-1.16-green.svg)
|
||||
![SPFx 1.16.1](https://img.shields.io/badge/SPFx-1.16.1-green.svg)
|
||||
![Node.js v16](https://img.shields.io/badge/Node.js-v16-green.svg)
|
||||
![Compatible with SharePoint Online](https://img.shields.io/badge/SharePoint%20Online-Compatible-green.svg)
|
||||
![Does not work with SharePoint 2019](https://img.shields.io/badge/SharePoint%20Server%202019-Incompatible-red.svg "SharePoint Server 2019 requires SPFx 1.4.1 or lower")
|
||||
|
@ -46,7 +46,7 @@ Version|Date|Comments
|
|||
-------|----|--------
|
||||
1.0|April 20, 2022|Initial release
|
||||
1.0.1|November 14, 2022|Updated to SPFx 15, latest Fluent UI 9, shim based theme mapping
|
||||
1.0.2|November 16, 2022|
|
||||
1.0.2|January 18, 2023|Updated SPFx 16.1
|
||||
|
||||
- Clone this repository (or [download this solution as a .ZIP file](https://pnp.github.io/download-partial/?url=https://github.com/pnp/sp-dev-fx-webparts/tree/main/samples/react-fluentui-9) then unzip it)
|
||||
- From your command-line, change your current directory to the directory containing this sample (`react-fluentui-9`, located under `samples`)
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
"description"
|
||||
],
|
||||
"creationDateTime": "2022-04-20",
|
||||
"updateDateTime": "2022-11-16",
|
||||
"updateDateTime": "2023-01-18",
|
||||
"products": [
|
||||
"SharePoint"
|
||||
],
|
||||
|
@ -21,7 +21,7 @@
|
|||
},
|
||||
{
|
||||
"key": "SPFX-VERSION",
|
||||
"value": "1.16"
|
||||
"value": "1.16.1"
|
||||
}
|
||||
],
|
||||
"thumbnails": [
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -12,22 +12,22 @@
|
|||
"test": "gulp test"
|
||||
},
|
||||
"dependencies": {
|
||||
"@fluentui/react-components": "^9.7.0",
|
||||
"@microsoft/sp-core-library": "1.16.0",
|
||||
"@microsoft/sp-lodash-subset": "1.16.0",
|
||||
"@microsoft/sp-office-ui-fabric-core": "1.16.0",
|
||||
"@microsoft/sp-property-pane": "1.16.0",
|
||||
"@microsoft/sp-webpart-base": "1.16.0",
|
||||
"@fluentui/react-components": "^9.10.1",
|
||||
"@microsoft/sp-core-library": "1.16.1",
|
||||
"@microsoft/sp-lodash-subset": "1.16.1",
|
||||
"@microsoft/sp-office-ui-fabric-core": "1.16.1",
|
||||
"@microsoft/sp-property-pane": "1.16.1",
|
||||
"@microsoft/sp-webpart-base": "1.16.1",
|
||||
"react": "17.0.1",
|
||||
"react-dom": "17.0.1",
|
||||
"tslib": "2.3.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@microsoft/eslint-config-spfx": "1.16.0",
|
||||
"@microsoft/eslint-plugin-spfx": "1.16.0",
|
||||
"@microsoft/eslint-config-spfx": "1.16.1",
|
||||
"@microsoft/eslint-plugin-spfx": "1.16.1",
|
||||
"@microsoft/rush-stack-compiler-4.5": "0.2.2",
|
||||
"@microsoft/sp-build-web": "1.16.0",
|
||||
"@microsoft/sp-module-interfaces": "1.16.0",
|
||||
"@microsoft/sp-build-web": "1.16.1",
|
||||
"@microsoft/sp-module-interfaces": "1.16.1",
|
||||
"@rushstack/eslint-config": "2.5.1",
|
||||
"@types/react": "17.0.45",
|
||||
"@types/react-dom": "17.0.17",
|
||||
|
|
|
@ -154,6 +154,8 @@ const mapAliasColors = (palette: Partial<IPalette>, inverted: boolean): ColorTok
|
|||
colorNeutralShadowKeyDarker: 'rgba(0,0,0,0.24)',
|
||||
colorBrandShadowAmbient: 'rgba(0,0,0,0.30)',
|
||||
colorBrandShadowKey: 'rgba(0,0,0,0.25)',
|
||||
colorNeutralStencil1Alpha: webLightTheme.colorNeutralStencil1Alpha,
|
||||
colorNeutralStencil2Alpha: webLightTheme.colorNeutralStencil2Alpha,
|
||||
};
|
||||
};
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
"@microsoft/generator-sharepoint": {
|
||||
"plusBeta": false,
|
||||
"isCreatingSolution": true,
|
||||
"version": "1.15.2",
|
||||
"version": "1.16.1",
|
||||
"libraryName": "group-membership-manager",
|
||||
"libraryId": "2a487e9a-b62a-484a-9bc0-e78b65550b61",
|
||||
"environment": "spo",
|
||||
|
@ -11,6 +11,9 @@
|
|||
"solutionShortDescription": "Group Membership Manager description",
|
||||
"skipFeatureDeployment": true,
|
||||
"isDomainIsolated": false,
|
||||
"componentType": "webpart"
|
||||
"componentType": "webpart",
|
||||
"sdkVersions": {
|
||||
"@microsoft/teams-js": "2.4.1"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,7 +8,7 @@ This app is an example of managing the membership of a group you own including t
|
|||
|
||||
## Compatibility
|
||||
|
||||
![SPFx 1.15.2](https://img.shields.io/badge/SPFx-1.15.2-green.svg)
|
||||
![SPFx 1.16.1](https://img.shields.io/badge/SPFx-1.16.1-green.svg)
|
||||
![Node.js v16](https://img.shields.io/badge/Node.js-v16-green.svg)
|
||||
![Compatible with SharePoint Online](https://img.shields.io/badge/SharePoint%20Online-Compatible-green.svg)
|
||||
![Does not work with SharePoint 2019](https://img.shields.io/badge/SharePoint%20Server%202019-Incompatible-red.svg "SharePoint Server 2019 requires SPFx 1.4.1 or lower")
|
||||
|
@ -35,6 +35,7 @@ This app is an example of managing the membership of a group you own including t
|
|||
| Version | Date | Comments |
|
||||
| ------- | ---------------- | --------------- |
|
||||
| 1.0 | August 25, 2022 | Initial release |
|
||||
| 1.1| | Jan 18, 2022 | Updated to SPFx 1.16.1 |
|
||||
|
||||
|
||||
## Minimal Path to Awesome
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
"This app is an example of managing the membership of a group you own including the owners of the group as well as using FluentUI v9"
|
||||
],
|
||||
"creationDateTime": "2022-08-25",
|
||||
"updateDateTime": "2022-08-25",
|
||||
"updateDateTime": "2023-01-18",
|
||||
"products": [
|
||||
"SharePoint"
|
||||
],
|
||||
|
@ -21,7 +21,7 @@
|
|||
},
|
||||
{
|
||||
"key": "SPFX-VERSION",
|
||||
"value": "1.14"
|
||||
"value": "1.16.1"
|
||||
}
|
||||
],
|
||||
"thumbnails": [
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
"solution": {
|
||||
"name": "group-membership-manager-client-side-solution",
|
||||
"id": "2a487e9a-b62a-484a-9bc0-e78b65550b61",
|
||||
"version": "1.0.0.0",
|
||||
"version": "1.1.0.0",
|
||||
"includeClientSideAssets": true,
|
||||
"title": "Group Membership Manager",
|
||||
"skipFeatureDeployment": true,
|
||||
|
@ -13,7 +13,7 @@
|
|||
"websiteUrl": "https://nbdev.uk",
|
||||
"privacyUrl": "",
|
||||
"termsOfUseUrl": "",
|
||||
"mpnId": "Undefined-1.15.2"
|
||||
"mpnId": "Undefined-1.16.1"
|
||||
},
|
||||
"metadata": {
|
||||
"shortDescription": {
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -3,32 +3,36 @@
|
|||
"version": "0.0.1",
|
||||
"private": true,
|
||||
"main": "lib/index.js",
|
||||
"engines": {
|
||||
"node": ">=16.13.0 <17.0.0"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "gulp bundle",
|
||||
"clean": "gulp clean",
|
||||
"test": "gulp test"
|
||||
},
|
||||
"dependencies": {
|
||||
"@fluentui/react-components": "^9.3.1",
|
||||
"@microsoft/sp-core-library": "1.15.2",
|
||||
"@microsoft/sp-lodash-subset": "1.15.2",
|
||||
"@microsoft/sp-office-ui-fabric-core": "1.15.2",
|
||||
"@microsoft/sp-property-pane": "1.15.2",
|
||||
"@microsoft/sp-webpart-base": "1.15.2",
|
||||
"react": "16.13.1",
|
||||
"react-dom": "16.13.1",
|
||||
"@fluentui/react-components": "^9.10.1",
|
||||
"@microsoft/sp-component-base": "^1.16.1",
|
||||
"@microsoft/sp-core-library": "1.16.1",
|
||||
"@microsoft/sp-lodash-subset": "1.16.1",
|
||||
"@microsoft/sp-office-ui-fabric-core": "1.16.1",
|
||||
"@microsoft/sp-property-pane": "1.16.1",
|
||||
"@microsoft/sp-webpart-base": "1.16.1",
|
||||
"react": "17.0.1",
|
||||
"react-dom": "17.0.1",
|
||||
"tslib": "2.3.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@microsoft/eslint-config-spfx": "1.15.2",
|
||||
"@microsoft/eslint-plugin-spfx": "1.15.2",
|
||||
"@microsoft/microsoft-graph-types": "^2.24.0",
|
||||
"@microsoft/eslint-config-spfx": "1.16.1",
|
||||
"@microsoft/eslint-plugin-spfx": "1.16.1",
|
||||
"@microsoft/microsoft-graph-types": "^2.25.0",
|
||||
"@microsoft/rush-stack-compiler-4.5": "0.2.2",
|
||||
"@microsoft/sp-build-web": "1.15.2",
|
||||
"@microsoft/sp-module-interfaces": "1.15.2",
|
||||
"@microsoft/sp-build-web": "1.16.1",
|
||||
"@microsoft/sp-module-interfaces": "1.16.1",
|
||||
"@rushstack/eslint-config": "2.5.1",
|
||||
"@types/react": "16.9.51",
|
||||
"@types/react-dom": "16.9.8",
|
||||
"@types/react": "17.0.45",
|
||||
"@types/react-dom": "17.0.17",
|
||||
"@types/webpack-env": "~1.15.2",
|
||||
"ajv": "^6.12.5",
|
||||
"eslint-plugin-react-hooks": "^4.6.0",
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,173 @@
|
|||
import { IPalette, ITheme as ThemeV8 } from '@microsoft/sp-component-base';
|
||||
|
||||
import { ColorTokens, Theme as ThemeV9, webLightTheme } from '@fluentui/react-components';
|
||||
import { blackAlpha, whiteAlpha, grey } from './themeDuplicates';
|
||||
|
||||
/**
|
||||
* Creates v9 color tokens from a v8 palette.
|
||||
* https://github.com/microsoft/fluentui/blob/master/apps/public-docsite-v9/src/shims/ThemeShim/v9ThemeShim.ts
|
||||
*/
|
||||
const mapAliasColors = (palette: Partial<IPalette>, inverted: boolean): ColorTokens => {
|
||||
return {
|
||||
colorNeutralForeground1: palette.neutralPrimary || webLightTheme.colorNeutralForeground1,
|
||||
colorNeutralForeground1Hover: palette.neutralPrimary || webLightTheme.colorNeutralForeground1Hover,
|
||||
colorNeutralForeground1Pressed: palette.neutralPrimary || webLightTheme.colorNeutralForeground1Pressed,
|
||||
colorNeutralForeground1Selected: palette.neutralPrimary || webLightTheme.colorNeutralForeground1Selected,
|
||||
colorNeutralForeground2: palette.neutralSecondary || webLightTheme.colorNeutralForeground2,
|
||||
colorNeutralForeground2Hover: palette.neutralPrimary || webLightTheme.colorNeutralForeground2Hover,
|
||||
colorNeutralForeground2Pressed: palette.neutralPrimary || webLightTheme.colorNeutralForeground2Pressed,
|
||||
colorNeutralForeground2Selected: palette.neutralPrimary || webLightTheme.colorNeutralForeground2Selected,
|
||||
colorNeutralForeground2BrandHover: palette.themePrimary || webLightTheme.colorNeutralForeground2BrandHover,
|
||||
colorNeutralForeground2BrandPressed: palette.themeDarkAlt || webLightTheme.colorNeutralForeground2BrandPressed,
|
||||
colorNeutralForeground2BrandSelected: palette.themePrimary || webLightTheme.colorNeutralForeground2BrandSelected,
|
||||
colorNeutralForeground3: palette.neutralTertiary || webLightTheme.colorNeutralForeground3,
|
||||
colorNeutralForeground3Hover: palette.neutralSecondary || webLightTheme.colorNeutralForeground3Hover,
|
||||
colorNeutralForeground3Pressed: palette.neutralSecondary || webLightTheme.colorNeutralForeground3Pressed,
|
||||
colorNeutralForeground3Selected: palette.neutralSecondary || webLightTheme.colorNeutralForeground3Selected,
|
||||
colorNeutralForeground3BrandHover: palette.themePrimary || webLightTheme.colorNeutralForeground3BrandHover,
|
||||
colorNeutralForeground3BrandPressed: palette.themeDarkAlt || webLightTheme.colorNeutralForeground3BrandPressed,
|
||||
colorNeutralForeground3BrandSelected: palette.themePrimary || webLightTheme.colorNeutralForeground3BrandSelected,
|
||||
colorNeutralForeground4: palette.neutralQuaternary || webLightTheme.colorNeutralForeground4,
|
||||
colorNeutralForegroundDisabled: palette.neutralTertiaryAlt || webLightTheme.colorNeutralForegroundDisabled,
|
||||
colorNeutralForegroundInvertedDisabled: whiteAlpha[40],
|
||||
colorBrandForegroundLink: palette.themeDarkAlt || webLightTheme.colorBrandForegroundLink,
|
||||
colorBrandForegroundLinkHover: palette.themeDark || webLightTheme.colorBrandForegroundLinkHover,
|
||||
colorBrandForegroundLinkPressed: palette.themeDarker || webLightTheme.colorBrandForegroundLinkPressed,
|
||||
colorBrandForegroundLinkSelected: palette.themeDarkAlt || webLightTheme.colorBrandForegroundLinkSelected,
|
||||
colorNeutralForeground2Link: palette.neutralSecondary || webLightTheme.colorNeutralForeground2Link,
|
||||
colorNeutralForeground2LinkHover: palette.neutralPrimary || webLightTheme.colorNeutralForeground2LinkHover,
|
||||
colorNeutralForeground2LinkPressed: palette.neutralPrimary || webLightTheme.colorNeutralForeground2LinkPressed,
|
||||
colorNeutralForeground2LinkSelected: palette.neutralPrimary || webLightTheme.colorNeutralForeground2LinkSelected,
|
||||
colorCompoundBrandForeground1: palette.themePrimary || webLightTheme.colorCompoundBrandForeground1,
|
||||
colorCompoundBrandForeground1Hover: palette.themeDarkAlt || webLightTheme.colorCompoundBrandForeground1Hover,
|
||||
colorCompoundBrandForeground1Pressed: palette.themeDark || webLightTheme.colorCompoundBrandForeground1Pressed,
|
||||
colorBrandForeground1: palette.themePrimary || webLightTheme.colorBrandForeground1,
|
||||
colorBrandForeground2: palette.themeDarkAlt || webLightTheme.colorBrandForeground2,
|
||||
colorNeutralForeground1Static: palette.neutralPrimary || webLightTheme.colorNeutralForeground1Static,
|
||||
colorNeutralForegroundInverted: palette.white || webLightTheme.colorNeutralForegroundInverted,
|
||||
colorNeutralForegroundInvertedHover: palette.white || webLightTheme.colorNeutralForegroundInvertedHover,
|
||||
colorNeutralForegroundInvertedPressed: palette.white || webLightTheme.colorNeutralForegroundInvertedPressed,
|
||||
colorNeutralForegroundInvertedSelected: palette.white || webLightTheme.colorNeutralForegroundInvertedSelected,
|
||||
colorNeutralForegroundOnBrand: palette.white || webLightTheme.colorNeutralForegroundOnBrand,
|
||||
colorNeutralForegroundStaticInverted: palette.white || webLightTheme.colorNeutralForegroundStaticInverted,
|
||||
colorNeutralForegroundInvertedLink: palette.white || webLightTheme.colorNeutralForegroundInvertedLink,
|
||||
colorNeutralForegroundInvertedLinkHover: palette.white || webLightTheme.colorNeutralForegroundInvertedLinkHover,
|
||||
colorNeutralForegroundInvertedLinkPressed: palette.white || webLightTheme.colorNeutralForegroundInvertedLinkPressed,
|
||||
colorNeutralForegroundInvertedLinkSelected: palette.white || webLightTheme.colorNeutralForegroundInvertedLinkSelected,
|
||||
colorNeutralForegroundInverted2: palette.white || webLightTheme.colorNeutralForegroundInverted2,
|
||||
colorBrandForegroundInverted: palette.themeSecondary || webLightTheme.colorBrandForegroundInverted,
|
||||
colorBrandForegroundInvertedHover: palette.themeTertiary || webLightTheme.colorBrandForegroundInvertedHover,
|
||||
colorBrandForegroundInvertedPressed: palette.themeSecondary || webLightTheme.colorBrandForegroundInvertedPressed,
|
||||
colorBrandForegroundOnLight: palette.themePrimary || webLightTheme.colorBrandForegroundOnLight,
|
||||
colorBrandForegroundOnLightHover: palette.themeDarkAlt || webLightTheme.colorBrandForegroundOnLightHover,
|
||||
colorBrandForegroundOnLightPressed: palette.themeDark || webLightTheme.colorBrandForegroundOnLightPressed,
|
||||
colorBrandForegroundOnLightSelected: palette.themeDark || webLightTheme.colorBrandForegroundOnLightSelected,
|
||||
colorNeutralBackground1: palette.white || webLightTheme.colorNeutralBackground1,
|
||||
colorNeutralBackground1Hover: palette.neutralLighter || webLightTheme.colorNeutralBackground1Hover,
|
||||
colorNeutralBackground1Pressed: palette.neutralQuaternaryAlt || webLightTheme.colorNeutralBackground1Pressed,
|
||||
colorNeutralBackground1Selected: palette.neutralLight || webLightTheme.colorNeutralBackground1Selected,
|
||||
colorNeutralBackground2: palette.neutralLighterAlt || webLightTheme.colorNeutralBackground2,
|
||||
colorNeutralBackground2Hover: palette.neutralLighter || webLightTheme.colorNeutralBackground2Hover,
|
||||
colorNeutralBackground2Pressed: palette.neutralQuaternaryAlt || webLightTheme.colorNeutralBackground2Pressed,
|
||||
colorNeutralBackground2Selected: palette.neutralLight || webLightTheme.colorNeutralBackground2Selected,
|
||||
colorNeutralBackground3: palette.neutralLighter || webLightTheme.colorNeutralBackground3,
|
||||
colorNeutralBackground3Hover: palette.neutralLight || webLightTheme.colorNeutralBackground3Hover,
|
||||
colorNeutralBackground3Pressed: palette.neutralQuaternary || webLightTheme.colorNeutralBackground3Pressed,
|
||||
colorNeutralBackground3Selected: palette.neutralQuaternaryAlt || webLightTheme.colorNeutralBackground3Selected,
|
||||
colorNeutralBackground4: palette.neutralLighter || webLightTheme.colorNeutralBackground4,
|
||||
colorNeutralBackground4Hover: palette.neutralLighterAlt || webLightTheme.colorNeutralBackground4Hover,
|
||||
colorNeutralBackground4Pressed: palette.neutralLighter || webLightTheme.colorNeutralBackground4Pressed,
|
||||
colorNeutralBackground4Selected: palette.white || webLightTheme.colorNeutralBackground4Selected,
|
||||
colorNeutralBackground5: palette.neutralLight || webLightTheme.colorNeutralBackground5,
|
||||
colorNeutralBackground5Hover: palette.neutralLighter || webLightTheme.colorNeutralBackground5Hover,
|
||||
colorNeutralBackground5Pressed: palette.neutralLighter || webLightTheme.colorNeutralBackground5Pressed,
|
||||
colorNeutralBackground5Selected: palette.neutralLighterAlt || webLightTheme.colorNeutralBackground5Selected,
|
||||
colorNeutralBackground6: palette.neutralLight || webLightTheme.colorNeutralBackground6,
|
||||
colorNeutralBackgroundStatic: grey[20],
|
||||
colorNeutralBackgroundInverted: palette.neutralSecondary || webLightTheme.colorNeutralBackgroundInverted,
|
||||
colorSubtleBackground: 'transparent',
|
||||
colorSubtleBackgroundHover: palette.neutralLighter || webLightTheme.colorSubtleBackgroundHover,
|
||||
colorSubtleBackgroundPressed: palette.neutralQuaternaryAlt || webLightTheme.colorSubtleBackgroundPressed,
|
||||
colorSubtleBackgroundSelected: palette.neutralLight || webLightTheme.colorSubtleBackgroundSelected,
|
||||
colorSubtleBackgroundLightAlphaHover: inverted ? whiteAlpha[10] : whiteAlpha[80],
|
||||
colorSubtleBackgroundLightAlphaPressed: inverted ? whiteAlpha[5] : whiteAlpha[50],
|
||||
colorSubtleBackgroundLightAlphaSelected: 'transparent',
|
||||
colorSubtleBackgroundInverted: 'transparent',
|
||||
colorSubtleBackgroundInvertedHover: blackAlpha[10],
|
||||
colorSubtleBackgroundInvertedPressed: blackAlpha[30],
|
||||
colorSubtleBackgroundInvertedSelected: blackAlpha[20],
|
||||
colorTransparentBackground: 'transparent',
|
||||
colorTransparentBackgroundHover: 'transparent',
|
||||
colorTransparentBackgroundPressed: 'transparent',
|
||||
colorTransparentBackgroundSelected: 'transparent',
|
||||
colorNeutralBackgroundDisabled: palette.neutralLighter || webLightTheme.colorNeutralBackgroundDisabled,
|
||||
colorNeutralBackgroundInvertedDisabled: whiteAlpha[10],
|
||||
colorNeutralStencil1: palette.neutralLight || webLightTheme.colorNeutralStencil1,
|
||||
colorNeutralStencil2: palette.neutralLighterAlt || webLightTheme.colorNeutralStencil2,
|
||||
colorBackgroundOverlay: blackAlpha[10],
|
||||
colorScrollbarOverlay: blackAlpha[50],
|
||||
colorBrandBackground: palette.themePrimary || webLightTheme.colorBrandBackground,
|
||||
colorBrandBackgroundHover: palette.themeDarkAlt || webLightTheme.colorBrandBackgroundHover,
|
||||
colorBrandBackgroundPressed: palette.themeDarker || webLightTheme.colorBrandBackgroundPressed,
|
||||
colorBrandBackgroundSelected: palette.themeDark || webLightTheme.colorBrandBackgroundSelected,
|
||||
colorCompoundBrandBackground: palette.themePrimary || webLightTheme.colorCompoundBrandBackground,
|
||||
colorCompoundBrandBackgroundHover: palette.themeDarkAlt || webLightTheme.colorCompoundBrandBackgroundHover,
|
||||
colorCompoundBrandBackgroundPressed: palette.themeDark || webLightTheme.colorCompoundBrandBackgroundPressed,
|
||||
colorBrandBackgroundStatic: palette.themePrimary || webLightTheme.colorBrandBackgroundStatic,
|
||||
colorBrandBackground2: palette.themeLighterAlt || webLightTheme.colorBrandBackground2,
|
||||
colorBrandBackgroundInverted: palette.white || webLightTheme.colorBrandBackgroundInverted,
|
||||
colorBrandBackgroundInvertedHover: palette.themeLighterAlt || webLightTheme.colorBrandBackgroundInvertedHover,
|
||||
colorBrandBackgroundInvertedPressed: palette.themeLight || webLightTheme.colorBrandBackgroundInvertedPressed,
|
||||
colorBrandBackgroundInvertedSelected: palette.themeLighter || webLightTheme.colorBrandBackgroundInvertedSelected,
|
||||
colorNeutralStrokeAccessible: palette.neutralSecondary || webLightTheme.colorNeutralStrokeAccessible,
|
||||
colorNeutralStrokeAccessibleHover: palette.neutralSecondary || webLightTheme.colorNeutralStrokeAccessibleHover,
|
||||
colorNeutralStrokeAccessiblePressed: palette.neutralSecondary || webLightTheme.colorNeutralStrokeAccessiblePressed,
|
||||
colorNeutralStrokeAccessibleSelected: palette.themePrimary || webLightTheme.colorNeutralStrokeAccessibleSelected,
|
||||
colorNeutralStroke1: palette.neutralQuaternary || webLightTheme.colorNeutralStroke1,
|
||||
colorNeutralStroke1Hover: palette.neutralTertiaryAlt || webLightTheme.colorNeutralStroke1Hover,
|
||||
colorNeutralStroke1Pressed: palette.neutralTertiaryAlt || webLightTheme.colorNeutralStroke1Pressed,
|
||||
colorNeutralStroke1Selected: palette.neutralTertiaryAlt || webLightTheme.colorNeutralStroke1Selected,
|
||||
colorNeutralStroke2: palette.neutralQuaternaryAlt || webLightTheme.colorNeutralStroke2,
|
||||
colorNeutralStroke3: palette.neutralLighter || webLightTheme.colorNeutralStroke3,
|
||||
colorNeutralStrokeOnBrand: palette.white || webLightTheme.colorNeutralStrokeOnBrand,
|
||||
colorNeutralStrokeOnBrand2: palette.white || webLightTheme.colorNeutralStrokeOnBrand2,
|
||||
colorNeutralStrokeOnBrand2Hover: palette.white || webLightTheme.colorNeutralStrokeOnBrand2Hover,
|
||||
colorNeutralStrokeOnBrand2Pressed: palette.white || webLightTheme.colorNeutralStrokeOnBrand2Pressed,
|
||||
colorNeutralStrokeOnBrand2Selected: palette.white || webLightTheme.colorNeutralStrokeOnBrand2Selected,
|
||||
colorBrandStroke1: palette.themePrimary || webLightTheme.colorBrandStroke1,
|
||||
colorBrandStroke2: palette.themeLight || webLightTheme.colorBrandStroke2,
|
||||
colorCompoundBrandStroke: palette.themePrimary || webLightTheme.colorCompoundBrandStroke,
|
||||
colorCompoundBrandStrokeHover: palette.themeDarkAlt || webLightTheme.colorCompoundBrandStrokeHover,
|
||||
colorCompoundBrandStrokePressed: palette.themeDark || webLightTheme.colorCompoundBrandStrokePressed,
|
||||
colorNeutralStrokeDisabled: palette.neutralQuaternaryAlt || webLightTheme.colorNeutralStrokeDisabled,
|
||||
colorNeutralStrokeInvertedDisabled: whiteAlpha[40],
|
||||
colorTransparentStroke: 'transparent',
|
||||
colorTransparentStrokeInteractive: 'transparent',
|
||||
colorTransparentStrokeDisabled: 'transparent',
|
||||
colorStrokeFocus1: palette.white || webLightTheme.colorStrokeFocus1,
|
||||
colorStrokeFocus2: palette.black || webLightTheme.colorStrokeFocus2,
|
||||
colorNeutralShadowAmbient: 'rgba(0,0,0,0.12)',
|
||||
colorNeutralShadowKey: 'rgba(0,0,0,0.14)',
|
||||
colorNeutralShadowAmbientLighter: 'rgba(0,0,0,0.06)',
|
||||
colorNeutralShadowKeyLighter: 'rgba(0,0,0,0.07)',
|
||||
colorNeutralShadowAmbientDarker: 'rgba(0,0,0,0.20)',
|
||||
colorNeutralShadowKeyDarker: 'rgba(0,0,0,0.24)',
|
||||
colorBrandShadowAmbient: 'rgba(0,0,0,0.30)',
|
||||
colorBrandShadowKey: 'rgba(0,0,0,0.25)',
|
||||
colorNeutralStencil1Alpha: webLightTheme.colorNeutralStencil1Alpha,
|
||||
colorNeutralStencil2Alpha: webLightTheme.colorNeutralStencil2Alpha,
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates a v9 theme from a v8 theme.
|
||||
* You can optional pass a base v9 theme; otherwise webLightTheme is used.
|
||||
*/
|
||||
export const createv9Theme = (themeV8: ThemeV8, baseThemeV9?: ThemeV9): ThemeV9 => {
|
||||
const baseTheme = baseThemeV9 ?? webLightTheme;
|
||||
|
||||
return {
|
||||
...baseTheme,
|
||||
...mapAliasColors(themeV8.palette, themeV8.isInverted)
|
||||
};
|
||||
};
|
|
@ -1,15 +1,15 @@
|
|||
import * as React from 'react';
|
||||
import * as ReactDom from 'react-dom';
|
||||
import { Version } from '@microsoft/sp-core-library';
|
||||
import { IPropertyPaneConfiguration } from '@microsoft/sp-property-pane';
|
||||
import { BaseClientSideWebPart } from '@microsoft/sp-webpart-base';
|
||||
import { IReadonlyTheme } from '@microsoft/sp-component-base';
|
||||
import GroupMembershipManager from './components/GroupMembershipManager';
|
||||
import { IGroupMembershipManagerProps } from './components/IGroupMembershipManagerProps';
|
||||
import { FluentProvider, FluentProviderProps, teamsDarkTheme, teamsLightTheme, webLightTheme, webDarkTheme, Theme } from '@fluentui/react-components';
|
||||
import { createv9Theme } from '../../shims/v9ThemeShim';
|
||||
|
||||
export enum AppMode {
|
||||
SharePoint, SharePointLocal, Teams, TeamsLocal
|
||||
SharePoint, Teams, Office, Outlook
|
||||
}
|
||||
|
||||
export default class GroupMembershipManagerWebPart extends BaseClientSideWebPart<{}> {
|
||||
|
@ -17,9 +17,16 @@ export default class GroupMembershipManagerWebPart extends BaseClientSideWebPart
|
|||
private _appMode: AppMode = AppMode.SharePoint;
|
||||
private _theme: Theme = webLightTheme;
|
||||
|
||||
protected onInit(): Promise<void> {
|
||||
//on initalizational set the App Mode
|
||||
this._appMode = !!this.context.sdks.microsoftTeams ? (this.context.isServedFromLocalhost ? AppMode.TeamsLocal : AppMode.Teams) : (this.context.isServedFromLocalhost) ? AppMode.SharePointLocal : AppMode.SharePoint;
|
||||
protected async onInit(): Promise<void> {
|
||||
if (!!this.context.sdks.microsoftTeams) {
|
||||
const teamsContext = await this.context.sdks.microsoftTeams.teamsJs.app.getContext();
|
||||
switch (teamsContext.app.host.name.toLowerCase()) {
|
||||
case 'teams': this._appMode = AppMode.Teams; break;
|
||||
case 'office': this._appMode = AppMode.Office; break;
|
||||
case 'outlook': this._appMode = AppMode.Outlook; break;
|
||||
default: throw new Error('Unknown host');
|
||||
}
|
||||
} else this._appMode = AppMode.SharePoint;
|
||||
return super.onInit();
|
||||
}
|
||||
|
||||
|
@ -37,9 +44,11 @@ export default class GroupMembershipManagerWebPart extends BaseClientSideWebPart
|
|||
const fluentElement: React.ReactElement<FluentProviderProps> = React.createElement(
|
||||
FluentProvider,
|
||||
{
|
||||
theme : this._appMode === AppMode.Teams || this._appMode === AppMode.TeamsLocal ?
|
||||
theme: this._appMode === AppMode.Teams ?
|
||||
this._isDarkTheme ? teamsDarkTheme : teamsLightTheme :
|
||||
this._isDarkTheme ? webDarkTheme : this._theme
|
||||
this._appMode === AppMode.SharePoint ?
|
||||
this._isDarkTheme ? webDarkTheme : this._theme :
|
||||
this._isDarkTheme ? webDarkTheme : webLightTheme
|
||||
},
|
||||
element
|
||||
);
|
||||
|
@ -50,30 +59,9 @@ export default class GroupMembershipManagerWebPart extends BaseClientSideWebPart
|
|||
protected onThemeChanged(currentTheme: IReadonlyTheme | undefined): void {
|
||||
if (!currentTheme) return;
|
||||
this._isDarkTheme = !!currentTheme.isInverted;
|
||||
|
||||
//if the app mode is sharepoint, adjust the fluent ui 9 web light theme to use the sharepoint theme color, teams/dark mode should be fine on default
|
||||
if (this._appMode === AppMode.SharePoint || this._appMode === AppMode.SharePointLocal) {
|
||||
this._theme = {...webLightTheme,
|
||||
colorBrandBackground: currentTheme.palette.themePrimary,
|
||||
colorBrandBackgroundHover: currentTheme.palette.themeDark,
|
||||
colorBrandBackgroundPressed: currentTheme.palette.themeDarker,
|
||||
colorCompoundBrandForeground1: currentTheme.palette.themePrimary,
|
||||
colorCompoundBrandForeground1Hover: currentTheme.palette.themeDark,
|
||||
colorCompoundBrandForeground1Pressed: currentTheme.palette.themeDarker,
|
||||
colorNeutralForeground2BrandHover: currentTheme.palette.themeSecondary,
|
||||
colorNeutralForeground2BrandPressed: currentTheme.palette.themeDarkAlt,
|
||||
colorNeutralForeground2BrandSelected: currentTheme.palette.themeDarkAlt,
|
||||
colorBrandForeground1: currentTheme.palette.themeSecondary,
|
||||
colorBrandStroke1: currentTheme.palette.themePrimary,
|
||||
colorBrandStroke2: currentTheme.palette.themeSecondary,
|
||||
colorCompoundBrandStroke: currentTheme.palette.themePrimary,
|
||||
colorCompoundBrandStrokeHover: currentTheme.palette.themeSecondary,
|
||||
colorCompoundBrandStrokePressed: currentTheme.palette.themeDarkAlt,
|
||||
colorCompoundBrandBackground: currentTheme.palette.themePrimary,
|
||||
colorCompoundBrandBackgroundHover: currentTheme.palette.themeDark,
|
||||
colorCompoundBrandBackgroundPressed: currentTheme.palette.themeDarker,
|
||||
|
||||
};
|
||||
if (this._appMode === AppMode.SharePoint) {
|
||||
this._theme = createv9Theme(currentTheme, webLightTheme);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -84,11 +72,4 @@ export default class GroupMembershipManagerWebPart extends BaseClientSideWebPart
|
|||
protected get dataVersion(): Version {
|
||||
return Version.parse('1.0');
|
||||
}
|
||||
|
||||
protected getPropertyPaneConfiguration(): IPropertyPaneConfiguration {
|
||||
return {
|
||||
pages: [
|
||||
]
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,15 +2,14 @@ import * as React from 'react';
|
|||
import * as MicrosoftGraph from '@microsoft/microsoft-graph-types';
|
||||
import styles from './GroupMembershipManager.module.scss';
|
||||
import * as strings from 'GroupMembershipManagerWebPartStrings'
|
||||
import { Dialog, DialogTrigger, DialogSurface, DialogTitle, DialogBody, DialogActions, Alert } from "@fluentui/react-components/unstable";
|
||||
import { Button, Checkbox, Divider, Input, Label, Spinner, useId } from "@fluentui/react-components";
|
||||
import { Alert } from "@fluentui/react-components/unstable";
|
||||
import { Dialog, DialogTrigger, DialogSurface, DialogTitle, DialogBody, DialogActions, Button, Checkbox, Divider, Input, Label, Spinner, useId, DialogContent } from "@fluentui/react-components";
|
||||
import { PersonAddRegular } from '@fluentui/react-icons';
|
||||
import { WebPartContext } from '@microsoft/sp-webpart-base';
|
||||
import { MSGraphClientV3 } from '@microsoft/sp-http';
|
||||
import { PersonaSize } from 'office-ui-fabric-react/lib/Persona';
|
||||
import SPFxPeopleCard from './SPFxPeopleCard';
|
||||
import { GraphError } from '@microsoft/microsoft-graph-client';
|
||||
|
||||
import { GraphError } from '@microsoft/microsoft-graph-clientv1';
|
||||
|
||||
export enum AddUserMode { Member, Owner }
|
||||
enum rState { Idle, Running, Error, Completed }
|
||||
|
@ -44,14 +43,13 @@ export default function AddUser({ Group, Mode, context, onCompleted }: Props): R
|
|||
"members@odata.bind": users.map(m => `https://graph.microsoft.com/v1.0/directoryObjects/${m.id}`)
|
||||
} : {
|
||||
"owners@odata.bind": users.map(m => `https://graph.microsoft.com/v1.0/directoryObjects/${m.id}`)
|
||||
}, (e: GraphError, response ) => {
|
||||
}, (e: GraphError) => {
|
||||
if (e) throw e.message;
|
||||
else setRunning(rState.Completed)
|
||||
}).catch(handleError);
|
||||
} catch (error) {
|
||||
} catch (error: unknown) {
|
||||
handleError(error);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
React.useEffect(() => {
|
||||
|
@ -59,7 +57,7 @@ export default function AddUser({ Group, Mode, context, onCompleted }: Props): R
|
|||
setOpen(false);
|
||||
setRunning(rState.Idle);
|
||||
if (onCompleted) onCompleted();
|
||||
} , 5000);
|
||||
}, 5000);
|
||||
}, [running]);
|
||||
|
||||
React.useEffect(() => {
|
||||
|
@ -76,21 +74,20 @@ export default function AddUser({ Group, Mode, context, onCompleted }: Props): R
|
|||
|
||||
const inputId = useId('input-with-placeholder');
|
||||
|
||||
return (
|
||||
return (<>
|
||||
<Button appearance='primary' icon={<PersonAddRegular />} onClick={() => setOpen(true)}>{strings.Add}</Button>
|
||||
<Dialog open={open} onOpenChange={(event, data) => setOpen(data.open)}>
|
||||
<DialogTrigger>
|
||||
<Button appearance='primary' icon={<PersonAddRegular />}>{strings.Add}</Button>
|
||||
</DialogTrigger>
|
||||
<DialogSurface aria-label="label">
|
||||
<DialogTitle>{Mode === AddUserMode.Owner ? strings.AddDialogTitleOwner : strings.AddDialogTitle}{Group.displayName}</DialogTitle>
|
||||
<DialogSurface>
|
||||
<DialogBody>
|
||||
<DialogTitle>{Mode === AddUserMode.Owner ? strings.AddDialogTitleOwner : strings.AddDialogTitle}{Group.displayName}</DialogTitle>
|
||||
<DialogContent>
|
||||
<div className={styles.stack}>
|
||||
{running !== rState.Error && _error && <Alert intent="error">{_error}</Alert>}
|
||||
{running === rState.Error && <Alert intent="error" action={{ children: 'Retry', onClick: () => setRunning(rState.Running) }}>{_error}</Alert>}
|
||||
{running === rState.Completed && <Alert intent="success">{Mode === AddUserMode.Owner ? strings.Owners : strings.Members} {strings.Added} {Group.displayName}</Alert>}
|
||||
{users.length > 0 && <div className={styles.stackHoz} style={{ flexWrap: 'wrap' }}>
|
||||
{users.map(u => <div key={u.id} className={styles.stackHoz} style={{ maxWidth: 200, whiteSpace: 'nowrap' }}>
|
||||
{running !== rState.Completed && <Checkbox disabled={running === rState.Running} defaultChecked onChange={(e, d?) => setUsers(d?.checked ? users.concat([u]) : users.filter(_u => _u.id !== u.id)) } />}
|
||||
{running !== rState.Completed && <Checkbox disabled={running === rState.Running} defaultChecked onChange={(e, d?) => setUsers(d?.checked ? users.concat([u]) : users.filter(_u => _u.id !== u.id))} />}
|
||||
<SPFxPeopleCard primaryText={u.displayName} serviceScope={context.serviceScope} email={u.userPrincipalName} size={PersonaSize.size24} secondaryText={u.scoredEmailAddresses[0].address} />
|
||||
</div>)}
|
||||
</div>}
|
||||
|
@ -99,20 +96,21 @@ export default function AddUser({ Group, Mode, context, onCompleted }: Props): R
|
|||
<Label htmlFor={inputId}>{strings.Search}</Label>
|
||||
<Input placeholder={strings.SearchPlaceholder} onChange={(e, d) => setSearchTerm(d.value)} id={inputId} />
|
||||
{searchResults && searchResults.map(u => <div key={u.id} className={styles.stackHoz}>
|
||||
<Checkbox onChange={(e, d?) => setUsers(d?.checked ? users.concat([u]) : users.filter(_u => _u.id !== u.id)) } />
|
||||
<Checkbox onChange={(e, d?) => setUsers(d?.checked ? users.concat([u]) : users.filter(_u => _u.id !== u.id))} />
|
||||
<SPFxPeopleCard primaryText={u.displayName} serviceScope={context.serviceScope} email={u.userPrincipalName} size={PersonaSize.size24} secondaryText={u.scoredEmailAddresses[0].address} />
|
||||
</div>)}
|
||||
</>}
|
||||
{running === rState.Running && <Spinner labelPosition='below' label={strings.Adding} /> }
|
||||
{running === rState.Running && <Spinner labelPosition='below' label={strings.Adding} />}
|
||||
</div>
|
||||
</DialogBody>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<DialogTrigger>
|
||||
<Button appearance="secondary" disabled={running === rState.Running}>{strings.Close}</Button>
|
||||
</DialogTrigger>
|
||||
{users.length > 0 && <Button appearance="primary" disabled={running === rState.Completed || running === rState.Running} onClick={add}>{strings.Add}</Button>}
|
||||
</DialogActions>
|
||||
</DialogBody>
|
||||
</DialogSurface>
|
||||
</Dialog>
|
||||
)
|
||||
</>)
|
||||
}
|
|
@ -2,15 +2,15 @@ import * as React from 'react';
|
|||
import * as MicrosoftGraph from '@microsoft/microsoft-graph-types';
|
||||
import styles from './GroupMembershipManager.module.scss';
|
||||
import * as strings from 'GroupMembershipManagerWebPartStrings'
|
||||
import { Dialog, DialogTrigger, DialogSurface, DialogTitle, DialogBody, DialogActions, Alert } from "@fluentui/react-components/unstable";
|
||||
import { Button, Spinner } from "@fluentui/react-components";
|
||||
import { Alert } from "@fluentui/react-components/unstable";
|
||||
import { Dialog, DialogTrigger, DialogSurface, DialogTitle, DialogBody, DialogActions, Button, Spinner, DialogContent } from "@fluentui/react-components";
|
||||
import { AddUserMode } from './AddUser';
|
||||
import { WebPartContext } from '@microsoft/sp-webpart-base';
|
||||
import { PersonDeleteRegular } from '@fluentui/react-icons';
|
||||
import { MSGraphClientV3 } from '@microsoft/sp-http';
|
||||
import { PersonaSize } from 'office-ui-fabric-react/lib/Persona';
|
||||
import SPFxPeopleCard from './SPFxPeopleCard';
|
||||
import { BatchRequestStep, BatchRequestContent, BatchRequestBody, BatchResponseContent } from '@microsoft/microsoft-graph-client';
|
||||
import { BatchRequestStep, BatchRequestContent, BatchRequestBody, BatchResponseContent } from '@microsoft/microsoft-graph-clientv1';
|
||||
|
||||
enum rState { Idle, Running, Error, Completed }
|
||||
|
||||
|
@ -74,14 +74,14 @@ export default function RemoveUser({ Group, Users, Mode, context, onCompleted }:
|
|||
}, [running]);
|
||||
|
||||
|
||||
return (
|
||||
return (<>
|
||||
<Button appearance='primary' icon={<PersonDeleteRegular />} disabled={Users.length === 0} onClick={() => setOpen(true)}>{strings.Remove}</Button>
|
||||
<Dialog open={open} onOpenChange={(event, data) => setOpen(data.open)}>
|
||||
<DialogTrigger>
|
||||
<Button appearance='primary' icon={<PersonDeleteRegular />} disabled={Users.length === 0}>{strings.Remove}</Button>
|
||||
</DialogTrigger>
|
||||
<DialogSurface aria-label="label">
|
||||
<DialogTitle>{Mode === AddUserMode.Owner ? strings.RemoveDialogTitleOwner : strings.RemoveDialogTitle}{Group.displayName}</DialogTitle>
|
||||
<DialogBody>
|
||||
<DialogTitle>{Mode === AddUserMode.Owner ? strings.RemoveDialogTitleOwner : strings.RemoveDialogTitle}{Group.displayName}</DialogTitle>
|
||||
|
||||
<DialogContent>
|
||||
<div className={styles.stack}>
|
||||
{running !== rState.Error && _error && <Alert intent="error">{_error.split('||').map((v, i) => (<div key={i}>{v}</div>))}</Alert>}
|
||||
{running === rState.Error && _error && <Alert intent="error" action={{ children: 'Retry', onClick: () => setRunning(rState.Running) }}>{_error.split('||').map((v, i) => (<div key={i}>{v}</div>))}</Alert>}
|
||||
|
@ -93,14 +93,15 @@ export default function RemoveUser({ Group, Users, Mode, context, onCompleted }:
|
|||
</div>}
|
||||
{running === rState.Running && <Spinner labelPosition='below' label={strings.Removing} />}
|
||||
</div>
|
||||
</DialogBody>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<DialogTrigger>
|
||||
<Button appearance="secondary" disabled={running === rState.Running}>{strings.Close}</Button>
|
||||
</DialogTrigger>
|
||||
<Button appearance="primary" disabled={running === rState.Completed || running === rState.Running} onClick={remove}>{strings.Remove}</Button>
|
||||
</DialogActions>
|
||||
</DialogBody>
|
||||
</DialogSurface>
|
||||
</Dialog>
|
||||
)
|
||||
</>)
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
@import '~office-ui-fabric-react/dist/sass/References.scss';
|
||||
@import '~@fluentui/react/dist/sass/References.scss';
|
||||
|
||||
.groupMembershipManager {
|
||||
overflow: hidden;
|
||||
|
|
|
@ -4,15 +4,13 @@ import { IGroupMembershipManagerProps } from './IGroupMembershipManagerProps';
|
|||
import * as strings from 'GroupMembershipManagerWebPartStrings';
|
||||
import { MSGraphClientV3 } from '@microsoft/sp-http';
|
||||
import * as MicrosoftGraph from '@microsoft/microsoft-graph-types';
|
||||
import { Checkbox, CheckboxOnChangeData, Spinner, Subtitle1, Textarea, Tooltip } from '@fluentui/react-components';
|
||||
import { Spinner, Subtitle1, Textarea, Tooltip } from '@fluentui/react-components';
|
||||
import { Dropdown, Option, Toolbar } from "@fluentui/react-components/unstable";
|
||||
import { TableBody, TableCell, TableRow, Table } from '@fluentui/react-table';
|
||||
import AddUser, { AddUserMode } from './AddUser';
|
||||
import RemoveUser from './DeleteUser';
|
||||
import { GraphError } from '@microsoft/microsoft-graph-client';
|
||||
import SPFxPeopleCard from './SPFxPeopleCard';
|
||||
import { PersonaSize } from 'office-ui-fabric-react/lib/Persona';
|
||||
import { PeopleTeam24Regular, MailLink24Regular,ShieldLock24Regular, Eye24Regular, EyeOff24Regular } from '@fluentui/react-icons'
|
||||
import { GraphError } from '@microsoft/microsoft-graph-clientv1';
|
||||
import { PeopleTeam24Regular, MailLink24Regular, ShieldLock24Regular, Eye24Regular, EyeOff24Regular } from '@fluentui/react-icons'
|
||||
import TableGrid from './Table';
|
||||
|
||||
type OnSelectData = {
|
||||
optionValue: string;
|
||||
|
@ -34,7 +32,7 @@ export default function GroupMembershipManager(props: IGroupMembershipManagerPro
|
|||
|
||||
React.useEffect(() => {
|
||||
context.msGraphClientFactory.getClient("3").then((client: MSGraphClientV3) => {
|
||||
client.api('/me/ownedObjects').select("id,displayName,groupTypes,visibility,mailEnabled,resourceProvisioningOptions,securityEnabled,description").get((error, response: { value: MicrosoftGraph.Group[] }) => {
|
||||
client.api('/me/ownedObjects').select("id,displayName,groupTypes,visibility,mailEnabled,resourceProvisioningOptions,securityEnabled,description").get((error: undefined, response: { value: MicrosoftGraph.Group[] }) => {
|
||||
if (error) throw error;
|
||||
setGroups(response.value.filter(g => g.groupTypes).sort((a, b) => a.displayName.localeCompare(b.displayName)));
|
||||
}).catch(console.error);
|
||||
|
@ -61,22 +59,14 @@ export default function GroupMembershipManager(props: IGroupMembershipManagerPro
|
|||
if (group) loadGroup().catch(console.error);
|
||||
}, [group]);
|
||||
|
||||
const checkUser = (data: CheckboxOnChangeData, user: MicrosoftGraph.User): void => {
|
||||
setToRemove(data.checked ? toRemove.concat([user]) : toRemove.filter(u => u.id !== user.id));
|
||||
};
|
||||
|
||||
const checkOwner = (data: CheckboxOnChangeData, user: MicrosoftGraph.User): void => {
|
||||
setRemoveOwner(data.checked ? removeOwner.concat([user]) : removeOwner.filter(u => u.id !== user.id));
|
||||
};
|
||||
|
||||
return (
|
||||
<section className={`${styles.groupMembershipManager} ${hasTeamsContext ? styles.teams : ''}`}>
|
||||
{!groups && <Spinner labelPosition='below' label={strings.LoadingGroups} />}
|
||||
{groups && <section>
|
||||
<Subtitle1 block>{strings.PickGroup}</Subtitle1>
|
||||
<div className={styles.stackHoz} style={{ alignItems: 'center'}}>
|
||||
<Dropdown placeholder={strings.PickGroup} size="large" onSelect={(e, d?: OnSelectData) => setGroup(d ? d.optionValue : null)}>
|
||||
{groups.map(group => <Option id={group.id} key={group.id}>{group.displayName}</Option>)}
|
||||
<div className={styles.stackHoz} style={{ alignItems: 'center' }}>
|
||||
<Dropdown placeholder={strings.PickGroup} size="large" onOptionSelect={(e, d?: OnSelectData) => { setGroup(d?.optionValue); }}>
|
||||
{groups.map(group => <Option key={group.mailNickname}>{group.displayName}</Option>)}
|
||||
</Dropdown>
|
||||
{group && groups.filter(g => g.displayName === group)[0].resourceProvisioningOptions?.filter(g => g === "Team").length === 1 &&
|
||||
<Tooltip relationship='label' content="Team"><span><PeopleTeam24Regular /></span></Tooltip>
|
||||
|
@ -96,10 +86,10 @@ export default function GroupMembershipManager(props: IGroupMembershipManagerPro
|
|||
</div>
|
||||
</section>}
|
||||
{(!members || !owners) && groups && group && <Spinner labelPosition='below' label={strings.LoadingMembers} />}
|
||||
{groups && group && <div className={styles.stack} style={{marginTop: 10 }}>
|
||||
{groups && group && <div className={styles.stack} style={{ marginTop: 10 }}>
|
||||
<Textarea value={groups.filter(g => g.displayName === group)[0].description} resize='vertical' readOnly />
|
||||
<div className={`${styles.stackHoz} ${styles.spaceBetween}`} style={{marginTop: 10 }}>
|
||||
<div style={{width: '49%'}}>
|
||||
<div className={`${styles.stackHoz} ${styles.spaceBetween}`} style={{ marginTop: 10 }}>
|
||||
<div style={{ width: '49%' }}>
|
||||
<Subtitle1>{strings.Members}</Subtitle1>
|
||||
{!members && <Spinner labelPosition='below' label={strings.LoadingMembers} />}
|
||||
{members && <>
|
||||
|
@ -108,22 +98,10 @@ export default function GroupMembershipManager(props: IGroupMembershipManagerPro
|
|||
<AddUser context={context} Group={groups.filter(g => g.displayName === group)[0]} Mode={AddUserMode.Member} onCompleted={loadGroup} />
|
||||
<RemoveUser Users={toRemove} context={context} Group={groups.filter(g => g.displayName === group)[0]} Mode={AddUserMode.Member} onCompleted={loadGroup} />
|
||||
</Toolbar>}
|
||||
<Table>
|
||||
<TableBody>
|
||||
{members.map(user => (
|
||||
<TableRow key={user.id}>
|
||||
<TableCell>
|
||||
{!(groups.filter(g => g.displayName === group)[0].groupTypes?.filter(g => g === "DynamicMembership").length > 0) &&
|
||||
<Checkbox onChange={(ev, data) => checkUser(data, user)} />}
|
||||
<SPFxPeopleCard primaryText={user.displayName} serviceScope={context.serviceScope} email={user.userPrincipalName} size={PersonaSize.size24} secondaryText={user.mail} />
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
<TableGrid Users={members} ServiceScope={context.serviceScope} onSelectionChange={setToRemove} />
|
||||
</>}
|
||||
</div>
|
||||
<div style={{width: '49%'}}>
|
||||
<div style={{ width: '49%' }}>
|
||||
<Subtitle1>{strings.Owners}</Subtitle1>
|
||||
{!owners && <Spinner labelPosition='below' label={strings.LoadingOwners} />}
|
||||
{owners && <>
|
||||
|
@ -131,19 +109,7 @@ export default function GroupMembershipManager(props: IGroupMembershipManagerPro
|
|||
<AddUser context={context} Group={groups.filter(g => g.displayName === group)[0]} Mode={AddUserMode.Owner} onCompleted={loadGroup} />
|
||||
<RemoveUser Users={removeOwner} context={context} Group={groups.filter(g => g.displayName === group)[0]} Mode={AddUserMode.Owner} onCompleted={loadGroup} />
|
||||
</Toolbar>
|
||||
<Table>
|
||||
<TableBody>
|
||||
{owners.map(user => (
|
||||
<TableRow key={user.id}>
|
||||
<TableCell>
|
||||
{!(groups.filter(g => g.displayName === group)[0].groupTypes?.filter(g => g === "DynamicMembership").length > 0) &&
|
||||
<Checkbox onChange={(ev, data) => checkOwner(data, user)} />}
|
||||
<SPFxPeopleCard primaryText={user.displayName} serviceScope={context.serviceScope} email={user.userPrincipalName} size={PersonaSize.size24} secondaryText={user.mail} />
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
<TableGrid Users={owners} ServiceScope={context.serviceScope} onSelectionChange={setRemoveOwner} ReadOnly={groups.filter(g => g.displayName === group)[0].groupTypes?.filter(g => g === "DynamicMembership").length > 0} />
|
||||
</>}
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -0,0 +1,106 @@
|
|||
import { TableColumnDefinition, createTableColumn, TableRowId, useTableFeatures, useTableSelection, TableHeader, TableRow, Table, TableSelectionCell, TableHeaderCell, TableBody, TableCell, TableCellLayout } from "@fluentui/react-components/unstable";
|
||||
import * as React from 'react';
|
||||
import * as MicrosoftGraph from '@microsoft/microsoft-graph-types';
|
||||
import SPFxPeopleCard from "./SPFxPeopleCard";
|
||||
import { ServiceScope } from "@microsoft/sp-core-library";
|
||||
import { PersonaSize } from "office-ui-fabric-react/lib/Persona";
|
||||
|
||||
export interface TableGridProps {
|
||||
Users: MicrosoftGraph.User[];
|
||||
ServiceScope: ServiceScope;
|
||||
ReadOnly?: boolean;
|
||||
onSelectionChange?: (SelectedUsers: MicrosoftGraph.User[]) => void;
|
||||
}
|
||||
|
||||
export default function TableGrid(props: TableGridProps): React.ReactElement<TableGridProps> {
|
||||
const columns: TableColumnDefinition<MicrosoftGraph.User>[] = React.useMemo(
|
||||
() => [
|
||||
createTableColumn<MicrosoftGraph.User>({
|
||||
columnId: 'username',
|
||||
})
|
||||
],
|
||||
[],
|
||||
);
|
||||
|
||||
const [selectedRows, setSelectedRows] = React.useState(() => new Set<TableRowId>([]));
|
||||
|
||||
const {
|
||||
getRows,
|
||||
selection: { allRowsSelected, someRowsSelected, toggleAllRows, toggleRow, isRowSelected },
|
||||
} = useTableFeatures(
|
||||
{
|
||||
columns,
|
||||
items: props.Users,
|
||||
},
|
||||
[
|
||||
useTableSelection({
|
||||
selectionMode: 'multiselect',
|
||||
selectedItems: selectedRows,
|
||||
onSelectionChange: (e, data) => {
|
||||
if (props.ReadOnly) return;
|
||||
if (props.onSelectionChange) props.onSelectionChange(props.Users.filter((v, i) => data.selectedItems.has(i)));
|
||||
setSelectedRows(data.selectedItems);
|
||||
}
|
||||
}),
|
||||
],
|
||||
);
|
||||
|
||||
const rows = getRows(row => {
|
||||
const selected = isRowSelected(row.rowId);
|
||||
return {
|
||||
...row,
|
||||
onClick: (e: React.MouseEvent) => { if (!props.ReadOnly) toggleRow(e, row.rowId) },
|
||||
onKeyDown: (e: React.KeyboardEvent) => {
|
||||
if (e.key === ' ') {
|
||||
e.preventDefault();
|
||||
if (!props.ReadOnly) toggleRow(e, row.rowId);
|
||||
}
|
||||
},
|
||||
selected,
|
||||
appearance: selected ? ('brand' as const) : ('none' as const),
|
||||
};
|
||||
});
|
||||
|
||||
const toggleAllKeydown = React.useCallback(
|
||||
(e: React.KeyboardEvent<HTMLDivElement>) => {
|
||||
if (e.key === ' ') {
|
||||
toggleAllRows(e);
|
||||
e.preventDefault();
|
||||
}
|
||||
},
|
||||
[toggleAllRows],
|
||||
);
|
||||
|
||||
|
||||
return (<Table aria-label="Table with multiselect">
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
{!props.ReadOnly && <TableSelectionCell
|
||||
checked={allRowsSelected ? true : someRowsSelected ? 'mixed' : false}
|
||||
onClick={toggleAllRows}
|
||||
onKeyDown={toggleAllKeydown}
|
||||
checkboxIndicator={{ 'aria-label': 'Select all rows ' }}
|
||||
/>}
|
||||
<TableHeaderCell>User</TableHeaderCell>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{rows.map(({ item, selected, onClick, onKeyDown, appearance }) => (
|
||||
<TableRow
|
||||
key={item.id}
|
||||
onClick={onClick}
|
||||
onKeyDown={onKeyDown}
|
||||
aria-selected={selected}
|
||||
appearance={appearance}
|
||||
>
|
||||
{!props.ReadOnly && <TableSelectionCell checked={selected} checkboxIndicator={{ 'aria-label': 'Select row' }} />}
|
||||
<TableCell>
|
||||
<TableCellLayout>
|
||||
<SPFxPeopleCard primaryText={item.displayName} serviceScope={props.ServiceScope} email={item.userPrincipalName} size={PersonaSize.size24} secondaryText={item.mail} />
|
||||
</TableCellLayout>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>);
|
||||
}
|
|
@ -0,0 +1,63 @@
|
|||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/teams/v1.14/MicrosoftTeams.schema.json",
|
||||
"manifestVersion": "1.14",
|
||||
"packageName": "react-group-membership-manager-client-side-solution",
|
||||
"id": "698a4f5b-686f-475d-81fb-9bcf98850b96",
|
||||
"version": "1.1.0",
|
||||
"developer": {
|
||||
"name": "Nick Brown",
|
||||
"websiteUrl": "https://nbdev.uk",
|
||||
"privacyUrl": "https://nbdev.uk",
|
||||
"termsOfUseUrl": "https://nbdev.uk"
|
||||
},
|
||||
"name": {
|
||||
"short": "Group Membership Manager",
|
||||
"full": "React Group Membership Manager"
|
||||
},
|
||||
"description": {
|
||||
"short": "Group Membership Manager",
|
||||
"full": "React Group Membership Manager using Fluent UI 9"
|
||||
},
|
||||
"icons": {
|
||||
"outline": "698a4f5b-686f-475d-81fb-9bcf98850b96_outline.png",
|
||||
"color": "698a4f5b-686f-475d-81fb-9bcf98850b96_color.png"
|
||||
},
|
||||
"accentColor": "#004578",
|
||||
"staticTabs": [
|
||||
{
|
||||
"contentUrl": "https://{teamSiteDomain}/_layouts/15/TeamsLogon.aspx?SPFX=true&dest=/_layouts/15/teamshostedapp.aspx%3Fteams%26personal%26componentId=698a4f5b-686f-475d-81fb-9bcf98850b96%26forceLocale={locale}",
|
||||
"name": "Group Membership Manager",
|
||||
"scopes": ["personal"],
|
||||
"entityId": "100001"
|
||||
}
|
||||
],
|
||||
"configurableTabs": [
|
||||
{
|
||||
"configurationUrl": "https://{teamSiteDomain}{teamSitePath}/_layouts/15/TeamsLogon.aspx?SPFX=true&dest={teamSitePath}/_layouts/15/teamshostedapp.aspx%3FopenPropertyPane=true%26teams%26componentId=698a4f5b-686f-475d-81fb-9bcf98850b96%26forceLocale={locale}",
|
||||
"canUpdateConfiguration": false,
|
||||
"scopes": [
|
||||
"team",
|
||||
"groupchat"
|
||||
],
|
||||
"context": [
|
||||
"channelTab",
|
||||
"privateChatTab",
|
||||
"meetingSidePanel",
|
||||
"meetingDetailsTab",
|
||||
"meetingChatTab"
|
||||
]
|
||||
}
|
||||
],
|
||||
"permissions": [
|
||||
"identity"
|
||||
],
|
||||
"validDomains": [
|
||||
"*.login.microsoftonline.com",
|
||||
"*.sharepoint.com",
|
||||
"resourceseng.blob.core.windows.net"
|
||||
],
|
||||
"webApplicationInfo": {
|
||||
"resource": "https://{teamSiteDomain}",
|
||||
"id": "00000003-0000-0ff1-ce00-000000000000"
|
||||
}
|
||||
}
|
|
@ -13,9 +13,7 @@
|
|||
"outDir": "lib",
|
||||
"inlineSources": false,
|
||||
"strictNullChecks": false,
|
||||
"noUnusedLocals": false,
|
||||
"noImplicitAny": true,
|
||||
|
||||
"typeRoots": [
|
||||
"./node_modules/@types",
|
||||
"./node_modules/@microsoft"
|
||||
|
|
Loading…
Reference in New Issue