diff --git a/samples/react-my-groups/README.md b/samples/react-my-groups/README.md index fd148f335..e8476a8af 100644 --- a/samples/react-my-groups/README.md +++ b/samples/react-my-groups/README.md @@ -19,7 +19,14 @@ extensions: Using Microsoft Graph, this webpart grabs the Office 365 groups the current user is a member of with links to the groups SharePoint site. -![Demo](./assets/example.png) +The webpart has been updated to include a grid like in addition to the compact layout as seen below: +![Grid Demo](./assets/React-MyGroups_Grid.png) + +Compact Layout: +![Compact Demo](./assets/React-MyGroups_Compact.png) + +You can change between the grid and compact layout through the settings in the property pane: +![Property Pane Demo](./assets/React-MyGroups_Property.png) ## Used SharePoint Framework Version @@ -42,6 +49,7 @@ Version|Date|Comments -------|----|-------- 1.0|September 13, 2019|Initial release 1.1|June 1, 2020| Updated to SPFX 1.10.0 +1.2|July 8, 2020| Added Grid Layout ## Disclaimer diff --git a/samples/react-my-groups/assets/React-MyGroups_Compact.png b/samples/react-my-groups/assets/React-MyGroups_Compact.png new file mode 100644 index 000000000..44257935e Binary files /dev/null and b/samples/react-my-groups/assets/React-MyGroups_Compact.png differ diff --git a/samples/react-my-groups/assets/React-MyGroups_Grid.png b/samples/react-my-groups/assets/React-MyGroups_Grid.png new file mode 100644 index 000000000..7063041d2 Binary files /dev/null and b/samples/react-my-groups/assets/React-MyGroups_Grid.png differ diff --git a/samples/react-my-groups/assets/React-MyGroups_Property.png b/samples/react-my-groups/assets/React-MyGroups_Property.png new file mode 100644 index 000000000..75e1d584c Binary files /dev/null and b/samples/react-my-groups/assets/React-MyGroups_Property.png differ diff --git a/samples/react-my-groups/assets/example.png b/samples/react-my-groups/assets/example.png deleted file mode 100644 index 5acf7e161..000000000 Binary files a/samples/react-my-groups/assets/example.png and /dev/null differ diff --git a/samples/react-my-groups/src/webparts/reactMyGroups/ReactMyGroupsWebPart.ts b/samples/react-my-groups/src/webparts/reactMyGroups/ReactMyGroupsWebPart.ts index b686ed2d8..11310ef78 100644 --- a/samples/react-my-groups/src/webparts/reactMyGroups/ReactMyGroupsWebPart.ts +++ b/samples/react-my-groups/src/webparts/reactMyGroups/ReactMyGroupsWebPart.ts @@ -2,13 +2,14 @@ import * as React from 'react'; import * as ReactDom from 'react-dom'; import { Version } from '@microsoft/sp-core-library'; import { BaseClientSideWebPart } from "@microsoft/sp-webpart-base"; -import { IPropertyPaneConfiguration, PropertyPaneTextField } from "@microsoft/sp-property-pane"; +import { IPropertyPaneConfiguration, PropertyPaneTextField, PropertyPaneChoiceGroup } from "@microsoft/sp-property-pane"; import GroupService from '../../services/GroupService'; import * as strings from 'ReactMyGroupsWebPartStrings'; import { ReactMyGroups, IReactMyGroupsProps } from './components'; export interface IReactMyGroupsWebPartProps { - description: string; + title: string; + layout: string; } export default class ReactMyGroupsWebPart extends BaseClientSideWebPart { @@ -17,7 +18,8 @@ export default class ReactMyGroupsWebPart extends BaseClientSideWebPart = React.createElement( ReactMyGroups, { - description: this.properties.description + title: this.properties.title, + layout: this.properties.layout } ); @@ -39,6 +41,7 @@ export default class ReactMyGroupsWebPart extends BaseClientSideWebPart { + constructor(props: IGridLayoutProps) { + super(props); + this.state = { + isLoading: true + }; + } + private _columnCount: number; + private _columnWidth: number; + private _rowHeight: number; + private _isCompact: boolean; + + public render(): React.ReactElement { + return ( +
+ + + +
+ ); + } + + public componentDidMount = (): void => { + } + + private _getItemCountForPage = (itemIndex: number, surfaceRect: IRectangle): number => { + if (itemIndex === 0) { + this._isCompact = surfaceRect.width < COMPACT_THRESHOLD; + if (this._isCompact) { + this._columnCount = 1; + this._columnWidth = surfaceRect.width; + } else { + this._columnCount = Math.ceil(surfaceRect.width / (MAX_ROW_HEIGHT)); + this._columnWidth = Math.max(MIN_WIDTH, Math.floor(surfaceRect.width / this._columnCount) + Math.floor(PADDING / this._columnCount)); + this._rowHeight = this._columnWidth; + } + } + + return this._columnCount * ROWS_PER_PAGE; + } + + private _getPageHeight = (): number => { + return this._rowHeight * ROWS_PER_PAGE; + } + + private _onRenderCell = (item: any, index: number | undefined): JSX.Element => { + console.log(item.displayName); + const isCompact: boolean = this._isCompact; + const cellPadding: number = index % this._columnCount !== this._columnCount - 1 && !isCompact ? PADDING : 0; + const finalSize: ISize = { width: this._columnWidth, height: this._rowHeight }; + const cellWidth: number = isCompact ? this._columnWidth + PADDING : this._columnWidth - PADDING; + return ( +
+ {this.props.onRenderGridItem(item, finalSize, isCompact)} +
+ ); + } +} diff --git a/samples/react-my-groups/src/webparts/reactMyGroups/components/GridList/GridLayout.types.ts b/samples/react-my-groups/src/webparts/reactMyGroups/components/GridList/GridLayout.types.ts new file mode 100644 index 000000000..21b7f6a86 --- /dev/null +++ b/samples/react-my-groups/src/webparts/reactMyGroups/components/GridList/GridLayout.types.ts @@ -0,0 +1,28 @@ +import { ISize } from 'office-ui-fabric-react/lib/Utilities'; +import { IListProps } from 'office-ui-fabric-react/lib/List'; + +export interface IGridLayoutProps { + /** + * Accessible text for the grid layout + */ + ariaLabel?: string; + + /** + * The array of items to display + */ + items: any[]; + + /** + * In case you want to override the underlying list + */ + listProps?: Partial; + + /** + * The method to render each cell item + */ + onRenderGridItem: (item: any, finalSize: ISize, isCompact: boolean) => JSX.Element; +} + +export interface IGridLayoutState { + isLoading?: boolean; +} diff --git a/samples/react-my-groups/src/webparts/reactMyGroups/components/GridList/index.ts b/samples/react-my-groups/src/webparts/reactMyGroups/components/GridList/index.ts new file mode 100644 index 000000000..9874f894a --- /dev/null +++ b/samples/react-my-groups/src/webparts/reactMyGroups/components/GridList/index.ts @@ -0,0 +1,2 @@ +export * from './GridLayout.types'; +export * from './GridLayout'; \ No newline at end of file diff --git a/samples/react-my-groups/src/webparts/reactMyGroups/components/GroupList/GroupList.module.scss b/samples/react-my-groups/src/webparts/reactMyGroups/components/GroupList/GroupList.module.scss index 4d737db2f..ef0406510 100644 --- a/samples/react-my-groups/src/webparts/reactMyGroups/components/GroupList/GroupList.module.scss +++ b/samples/react-my-groups/src/webparts/reactMyGroups/components/GroupList/GroupList.module.scss @@ -1,12 +1,14 @@ @import "~@microsoft/sp-office-ui-fabric-core/dist/sass/SPFabricCore.scss"; :export { - padding: 20; + padding: 5; minWidth: 210; maxWidth: 320; rowsPerPage: 3; } + + .compactLayout { overflow: hidden; font-size: 0; @@ -44,6 +46,5 @@ :global(.ms-List-cell) { vertical-align: top; display: inline-block; - margin-bottom: 20px; } } diff --git a/samples/react-my-groups/src/webparts/reactMyGroups/components/reactMyGroups/IReactMyGroupsProps.ts b/samples/react-my-groups/src/webparts/reactMyGroups/components/reactMyGroups/IReactMyGroupsProps.ts index 54ff8ddce..57c456a2e 100644 --- a/samples/react-my-groups/src/webparts/reactMyGroups/components/reactMyGroups/IReactMyGroupsProps.ts +++ b/samples/react-my-groups/src/webparts/reactMyGroups/components/reactMyGroups/IReactMyGroupsProps.ts @@ -1,3 +1,4 @@ export interface IReactMyGroupsProps { - description: string; + title: string; + layout: string; } diff --git a/samples/react-my-groups/src/webparts/reactMyGroups/components/reactMyGroups/IReactMyGroupsState.ts b/samples/react-my-groups/src/webparts/reactMyGroups/components/reactMyGroups/IReactMyGroupsState.ts index c313871a1..f60e173b0 100644 --- a/samples/react-my-groups/src/webparts/reactMyGroups/components/reactMyGroups/IReactMyGroupsState.ts +++ b/samples/react-my-groups/src/webparts/reactMyGroups/components/reactMyGroups/IReactMyGroupsState.ts @@ -2,4 +2,5 @@ import * as MicrosoftGroup from '@microsoft/microsoft-graph-types'; export interface IReactMyGroupsState { groups: MicrosoftGroup.Group[]; + isLoading: boolean; } diff --git a/samples/react-my-groups/src/webparts/reactMyGroups/components/reactMyGroups/ReactMyGroups.module.scss b/samples/react-my-groups/src/webparts/reactMyGroups/components/reactMyGroups/ReactMyGroups.module.scss index 025a518d3..a9e5823d7 100644 --- a/samples/react-my-groups/src/webparts/reactMyGroups/components/reactMyGroups/ReactMyGroups.module.scss +++ b/samples/react-my-groups/src/webparts/reactMyGroups/components/reactMyGroups/ReactMyGroups.module.scss @@ -2,6 +2,131 @@ .reactMyGroups { + .compactContainer { + margin: 0px 5px 5px; + position: relative; + display: inline-block; + vertical-align: top; + } + + .compactA { + outline: 0; + text-decoration: none; + display: block; + margin-top: 4px; + width: 251.333px; + border: 1px solid transparent; + margin-bottom: 8px; + } + + .compactWrapper { + display: flex; + align-items: center; + position: relative; + } + + .compactBanner { + margin-right: 12px; + width: 32px; + height: 32px; + flex-shrink: 0; + } + + .compactDetails { + overflow: hidden; + display: flex; + flex-direction: column; + } + + .compactTitle { + font-size: 14px; + font-weight: 600; + margin: 6px 0; + text-overflow: ellipsis; + white-space: nowrap; + color: rgb(50,49,48); + display: block; + margin-block-start: 1em; + margin-block-end: 1em; + margin-inline-start: 0px; + margin-inline-end: 0px; + } + + .cardContainer{ + outline: transparent; + position: relative; + } + + .siteCard { + height: 200px; + max-width: 286px; + box-sizing: border-box; + margin-bottom: 20px; + outline: transparent; + position: relative; + background-color: rgb(255, 255, 255); + box-shadow: rgba(0, 0, 0, 0.133) 0px 1.6px 3.6px 0px, rgba(0, 0, 0, 0.11) 0px 0.3px 0.9px 0px; + border-width: 1px; + border-style: solid; + border-image: initial; + border-radius: 2px; + overflow: hidden; + border-color: rgb(237, 235, 233); + } + + .cardBanner { + position: relative; + height: 144px; + padding-top: 40px; + width: 100%; + } + + .cardTitle { + font-size: 16px; + margin-top: 28px; + font-weight: 600; + line-height: 20px; + display: -webkit-box; + -webkit-box-orient: vertical; + -webkit-line-clamp: 2; + box-sizing: content-box; + max-height: 2.7em; + overflow: hidden; + text-align: left; + padding: 0 12px; + color: rgb(50, 49, 48); + } + + .topBanner { + position: absolute; + top: 0; + height: 40px; + width: 100%;; + } + + .bannerImg { + position: absolute; + top: 12px; + left: 12px; + height: 48px; + width: 48px; + box-sizing: border-box; + box-shadow: 0 0 4px 0 rgba(0,0,0,.2); + border-width: 1px; + border-style: solid; + border-image: initial; + border-color: rgb(255,255,255); + background-position: 50%; + background-repeat: no-repeat; + background-size: contain; + } + + .docTile { + background-color: transparent; + outline: transparent; + position: relative; + } + a { text-decoration: none; } @@ -28,8 +153,16 @@ } .title { - @include ms-font-xl; - @include ms-fontColor-white; + text-align: left; + color: rgb(50, 49, 48); + white-space: pre-wrap; + font-family: "Segoe UI", "Segoe UI Web (West European)", "Segoe UI", -apple-system, BlinkMacSystemFont, Roboto, "Helvetica Neue", sans-serif; + -webkit-font-smoothing: antialiased; + font-size: 20px; + font-weight: 600; + margin-right: 32px; + margin-bottom: 18px; + flex-grow: 1; } .subTitle { diff --git a/samples/react-my-groups/src/webparts/reactMyGroups/components/reactMyGroups/ReactMyGroups.tsx b/samples/react-my-groups/src/webparts/reactMyGroups/components/reactMyGroups/ReactMyGroups.tsx index 971d36b5f..e269edc85 100644 --- a/samples/react-my-groups/src/webparts/reactMyGroups/components/reactMyGroups/ReactMyGroups.tsx +++ b/samples/react-my-groups/src/webparts/reactMyGroups/components/reactMyGroups/ReactMyGroups.tsx @@ -6,7 +6,10 @@ import GroupService from '../../../../services/GroupService'; import { IReactMyGroupsState } from './IReactMyGroupsState'; import { GroupList } from '../GroupList'; import { IGroup } from '../../../../models'; -import { DocumentCard, DocumentCardType, DocumentCardDetails, DocumentCardTitle, IDocumentCardPreviewProps, ImageFit, DocumentCardPreview } from 'office-ui-fabric-react'; +import { Spinner, DocumentCard, DocumentCardType, DocumentCardDetails, DocumentCardTitle, IDocumentCardPreviewProps, ImageFit, DocumentCardPreview, IconFontSizes, ISize, isVirtualElement, DocumentCardLocation, DocumentCardActivity } from 'office-ui-fabric-react'; +import { GridLayout } from '../GridList'; + +const colors = ['#17717A','#4A69DB','#303952','#A4262C','#3A96DD','#CA5010','#8764B8','#498205','#69797E']; export class ReactMyGroups extends React.Component { @@ -14,7 +17,8 @@ export class ReactMyGroups extends React.Component { return (
-

My Office 365 Groups

- this._onRenderItem(item, index)}/> +
{this.props.title}
+ {this.state.isLoading ? + + : +
+ {this.props.layout == 'Compact' ? + this._onRenderItem(item, index)}/> + : this._onRenderGridItem(item, finalSize, isCompact)}/> + } +
+ }
); } @@ -34,7 +47,6 @@ export class ReactMyGroups extends React.Component { GroupService.getGroups().then(groups => { - // console.log(groups); this.setState({ groups: groups }); @@ -45,7 +57,6 @@ export class ReactMyGroups extends React.Component { groups.map(groupItem => ( GroupService.getGroupLinks(groupItem).then(groupurl => { - // console.log(groupurl.value); this.setState(prevState => ({ groups: prevState.groups.map(group => group.id === groupItem.id ? {...group, url: groupurl.value} : group) })); @@ -57,41 +68,46 @@ export class ReactMyGroups extends React.Component { groups.map(groupItem => ( GroupService.getGroupThumbnails(groupItem).then(grouptb => { - console.log(grouptb); + //set group color: + const itemColor = colors[Math.floor(Math.random() * colors.length)]; this.setState(prevState => ({ - groups: prevState.groups.map(group => group.id === groupItem.id ? {...group, thumbnail: grouptb} : group) + groups: prevState.groups.map(group => group.id === groupItem.id ? {...group, thumbnail: grouptb, color: itemColor} : group) })); }) )); + console.log('Set False'); + this.setState({ + isLoading: false + }); } private _onRenderItem = (item: any, index: number): JSX.Element => { - const previewProps: IDocumentCardPreviewProps = { - previewImages: [ - { - previewImageSrc: item.thumbnail, - imageFit: ImageFit.center, - height: 48, - width: 48 - } - ] - }; return ( -
- - - - - - - - + ); } + private _onRenderGridItem = (item: any, finalSize: ISize, isCompact: boolean): JSX.Element => { + return ( + + ); + } + }