Skip to content

Commit 5b97479

Browse files
committed
Merge branch 'hugoabernier-gridlayout' into dev
2 parents eb50ef4 + eabe118 commit 5b97479

File tree

11 files changed

+573
-19
lines changed

11 files changed

+573
-19
lines changed
1.13 MB
Loading
4.31 MB
Loading
Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
# Grid Layout control
2+
3+
This control renders a responsive grid layout for your web parts. The grid layout behaves according to the [SharePoint web part layouts design pattern](https://docs.microsoft.com/en-us/sharepoint/dev/design/layout-patterns#grid-layout).
4+
5+
![Grid Layout Control](../assets/GridLayout.png)
6+
7+
The grid layout will automatically reflow grid items according to the space available for the control. On mobile devices and 1/3 column layouts, it will render a compact layout.
8+
9+
![Grid Layout Reflow](../assets/GridLayoutReflow.gif)
10+
11+
Although it is best used with the Fabric UI [DocumentCard control](https://developer.microsoft.com/en-us/fabric#/controls/web/documentcard), it will render any rectangular content you wish to display.
12+
13+
## How to use this control in your solutions
14+
15+
- Check that you installed the `@pnp/spfx-controls-react` dependency. Check out the [getting started](../#getting-started) page for more information about installing the dependency.
16+
- Import the following modules to your component:
17+
18+
```TypeScript
19+
import { GridLayout } from "@pnp/spfx-controls-react/lib/GridLayout";
20+
```
21+
22+
- Retrieve the items you wish to display in your grid control. For example, you can place them in your component's `state`:
23+
24+
```TypeScript
25+
// This sample places loads items in the constructor. You may wish to load
26+
// your items in the componentDidUpdate
27+
constructor(props: IMyWebPartProps) {
28+
super(props);
29+
30+
this.state = {
31+
items: [{
32+
thumbnail: "https://pixabay.com/get/57e9dd474952a414f1dc8460825668204022dfe05555754d742e7bd6/hot-air-balloons-1984308_640.jpg",
33+
title: "Adventures in SPFx",
34+
name: "Perry Losselyong",
35+
profileImageSrc: "https://robohash.org/blanditiisadlabore.png?size=50x50&set=set1",
36+
location: "SharePoint",
37+
activity: "3/13/2019"
38+
}, {
39+
thumbnail: "https://pixabay.com/get/55e8d5474a52ad14f1dc8460825668204022dfe05555754d742d79d0/autumn-3804001_640.jpg",
40+
title: "The Wild, Untold Story of SharePoint!",
41+
name: "Ebonee Gallyhaock",
42+
profileImageSrc: "https://robohash.org/delectusetcorporis.bmp?size=50x50&set=set1",
43+
location: "SharePoint",
44+
activity: "6/29/2019"
45+
}, {
46+
thumbnail: "https://pixabay.com/get/57e8dd454c50ac14f1dc8460825668204022dfe05555754d742c72d7/log-cabin-1886620_640.jpg",
47+
title: "Low Code Solutions: PowerApps",
48+
name: "Seward Keith",
49+
profileImageSrc: "https://robohash.org/asperioresautquasi.jpg?size=50x50&set=set1",
50+
location: "PowerApps",
51+
activity: "12/31/2018"
52+
}, {
53+
thumbnail: "https://pixabay.com/get/55e3d445495aa514f1dc8460825668204022dfe05555754d742b7dd5/portrait-3316389_640.jpg",
54+
title: "Not Your Grandpa's SharePoint",
55+
name: "Sharona Selkirk",
56+
profileImageSrc: "https://robohash.org/velnammolestiae.png?size=50x50&set=set1",
57+
location: "SharePoint",
58+
activity: "11/20/2018"
59+
}, {
60+
thumbnail: "https://pixabay.com/get/57e6dd474352ae14f1dc8460825668204022dfe05555754d742a7ed1/faucet-1684902_640.jpg",
61+
title: "Get with the Flow",
62+
name: "Boyce Batstone",
63+
profileImageSrc: "https://robohash.org/nulladistinctiomollitia.jpg?size=50x50&set=set1",
64+
location: "Flow",
65+
activity: "5/26/2019"
66+
}]
67+
};
68+
}
69+
```
70+
71+
- Because you will implement the method to render each item in your web part, your items can be anything you'd like. Our sample data defines a `thumbnail`, `title`, `name`, `profileImageSrc`, `location` and `activity` to coincide with the Fabric UI `DocumentCard` elements, but you can use any properties you need.
72+
- In the component that will call the `GridLayout` control, create callback function to render every item in the grid. You can return any rectangular element you want. For example, this code uses the Fabric UI `DocumentCard` control.
73+
74+
```TypeScript
75+
import {
76+
DocumentCard,
77+
DocumentCardActivity,
78+
DocumentCardPreview,
79+
DocumentCardDetails,
80+
DocumentCardTitle,
81+
IDocumentCardPreviewProps,
82+
DocumentCardLocation,
83+
DocumentCardType
84+
} from 'office-ui-fabric-react/lib/DocumentCard';
85+
import { ImageFit } from 'office-ui-fabric-react/lib/Image';
86+
import { ISize } from 'office-ui-fabric-react/lib/Utilities';
87+
88+
...
89+
90+
private _onRenderGridItem = (item: any, finalSize: ISize, isCompact: boolean): JSX.Element => {
91+
const previewProps: IDocumentCardPreviewProps = {
92+
previewImages: [
93+
{
94+
previewImageSrc: item.thumbnail,
95+
imageFit: ImageFit.cover,
96+
height: 130
97+
}
98+
]
99+
};
100+
101+
return <div
102+
data-is-focusable={true}
103+
role="listitem"
104+
aria-label={item.title}
105+
>
106+
<DocumentCard
107+
type={isCompact ? DocumentCardType.compact : DocumentCardType.normal}
108+
onClick={(ev: React.SyntheticEvent<HTMLElement>) => alert("You clicked on a grid item")}
109+
110+
>
111+
<DocumentCardPreview {...previewProps} />
112+
{!isCompact && <DocumentCardLocation location={item.location} />}
113+
<DocumentCardDetails>
114+
<DocumentCardTitle
115+
title={item.title}
116+
shouldTruncate={true}
117+
/>
118+
<DocumentCardActivity
119+
activity={item.activity}
120+
people={[{ name: item.name, profileImageSrc: item.profileImageSrc }]}
121+
/>
122+
</DocumentCardDetails>
123+
</DocumentCard>
124+
</div>;
125+
}
126+
```
127+
128+
> Note that the sample code above uses the `isCompact` parameter to remove `DocumentCard` elements and to render a compact layout. You may choose to ignore the `isCompact` parameter if you do not wish to handle compact layouts.
129+
130+
- Use the `GridLayout` control in your code as follows:
131+
132+
```TypeScript
133+
<GridLayout
134+
ariaLabel="List of content, use right and left arrow keys to navigate, arrow down to access details."
135+
items={this.state.items}
136+
onRenderGridItem={(item: any, finalSize: ISize, isCompact: boolean) => this._onRenderGridItem(item, finalSize, isCompact)}
137+
/>
138+
```
139+
140+
## Implementation
141+
142+
The grid layout control can be configured with the following properties:
143+
144+
| Property | Type | Required | Description |
145+
| ---- | ---- | ---- | ---- |
146+
| ariaLabel | string | no | The accessible text you wish to display for the grid control. We recommend that you use `"List of content, use right and left arrow keys to navigate, arrow down to access details."`. |
147+
| items | any[] | yes | The array of items you wish to display. |
148+
| listProps | IListProps | no | Provides additional list properties to customize the underlaying list. |
149+
| onRenderGridItem | function | yes | onRenderGridItem handler for the grid layout. Use this handler to specify how you wish to render each grid item |
150+
151+
![Telemetry](https://telemetry.sharepointpnp.com/sp-dev-fx-controls-react/wiki/controls/gridlayout)

package-lock.json

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/GridLayout.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from './controls/gridLayout/index';
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
@import '~office-ui-fabric-react/dist/sass/References.scss';
2+
3+
// Declares UI values that will be used outside of the SCSS
4+
:export {
5+
padding: 20;
6+
minWidth: 210;
7+
maxWidth: 320;
8+
compactThreshold: 480;
9+
rowsPerPage: 3;
10+
}
11+
12+
.gridLayout {
13+
.gridLayoutList {
14+
overflow: hidden;
15+
font-size: 0;
16+
position: relative;
17+
background-color: transparent;
18+
19+
:global(.ms-DocumentCard) {
20+
position: relative;
21+
background-color: $ms-color-white;
22+
height: 100%;
23+
24+
&:global(.ms-DocumentCard--compact) {
25+
:global(.ms-DocumentCardPreview) {
26+
-ms-flex-negative: 0;
27+
flex-shrink: 0;
28+
width: 144px;
29+
}
30+
}
31+
32+
:global(.ms-DocumentCardPreview-icon) img {
33+
width: 32px;
34+
height: 32px;
35+
}
36+
}
37+
38+
:global(.ms-DocumentCard:not(.ms-DocumentCard--compact)) {
39+
min-width: 212px;
40+
max-width: 286px;
41+
42+
:global(.ms-DocumentCardActivity) {
43+
padding-bottom: 16px;
44+
}
45+
46+
:global(.ms-DocumentCardTile-titleArea) {
47+
height: 81px;
48+
}
49+
50+
:global(.ms-DocumentCardLocation) {
51+
padding: 12px 16px 5px 16px;
52+
overflow: hidden;
53+
text-overflow: ellipsis;
54+
}
55+
}
56+
57+
:global(.ms-List-cell) {
58+
vertical-align: top;
59+
display: inline-block;
60+
margin-bottom: 20px;
61+
}
62+
}
63+
}
64+
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
/// <reference types="sinon" />
2+
3+
import * as React from 'react';
4+
import { assert, expect } from 'chai';
5+
import { mount, ReactWrapper } from 'enzyme';
6+
import { GridLayout } from './GridLayout';
7+
8+
import {
9+
DocumentCard,
10+
DocumentCardActivity,
11+
DocumentCardPreview,
12+
//DocumentCardDetails,
13+
DocumentCardTitle,
14+
IDocumentCardPreviewProps,
15+
DocumentCardLocation,
16+
DocumentCardType
17+
} from 'office-ui-fabric-react/lib/DocumentCard';
18+
import { ImageFit } from 'office-ui-fabric-react/lib/Image';
19+
import { ISize } from 'office-ui-fabric-react/lib/Utilities';
20+
21+
22+
declare const sinon;
23+
24+
describe('<GridLayout />', () => {
25+
let gridLayout: ReactWrapper;
26+
const dummyItems: any[] = [{
27+
thumbnail: "https://pixabay.com/get/57e9dd474952a414f1dc8460825668204022dfe05555754d742e7bd6/hot-air-balloons-1984308_640.jpg",
28+
title: "Adventures in SPFx",
29+
name: "Perry Losselyong",
30+
profileImageSrc: "https://robohash.org/blanditiisadlabore.png?size=50x50&set=set1",
31+
location: "SharePoint",
32+
activity: "3/13/2019"
33+
}, {
34+
thumbnail: "https://pixabay.com/get/55e8d5474a52ad14f1dc8460825668204022dfe05555754d742d79d0/autumn-3804001_640.jpg",
35+
title: "The Wild, Untold Story of SharePoint!",
36+
name: "Ebonee Gallyhaock",
37+
profileImageSrc: "https://robohash.org/delectusetcorporis.bmp?size=50x50&set=set1",
38+
location: "SharePoint",
39+
activity: "6/29/2019"
40+
}, {
41+
thumbnail: "https://pixabay.com/get/57e8dd454c50ac14f1dc8460825668204022dfe05555754d742c72d7/log-cabin-1886620_640.jpg",
42+
title: "Low Code Solutions: PowerApps",
43+
name: "Seward Keith",
44+
profileImageSrc: "https://robohash.org/asperioresautquasi.jpg?size=50x50&set=set1",
45+
location: "PowerApps",
46+
activity: "12/31/2018"
47+
}, {
48+
thumbnail: "https://pixabay.com/get/55e3d445495aa514f1dc8460825668204022dfe05555754d742b7dd5/portrait-3316389_640.jpg",
49+
title: "Not Your Grandpa's SharePoint",
50+
name: "Sharona Selkirk",
51+
profileImageSrc: "https://robohash.org/velnammolestiae.png?size=50x50&set=set1",
52+
location: "SharePoint",
53+
activity: "11/20/2018"
54+
}, {
55+
thumbnail: "https://pixabay.com/get/57e6dd474352ae14f1dc8460825668204022dfe05555754d742a7ed1/faucet-1684902_640.jpg",
56+
title: "Get with the Flow",
57+
name: "Boyce Batstone",
58+
profileImageSrc: "https://robohash.org/nulladistinctiomollitia.jpg?size=50x50&set=set1",
59+
location: "Flow",
60+
activity: "5/26/2019"
61+
}];
62+
63+
const dummyOnClick = sinon.spy((evt) => { /* Nothing to do here */ });
64+
65+
const dummyOnRenderGridItem = (item: any, _finalSize: ISize, isCompact: boolean): JSX.Element => {
66+
const previewProps: IDocumentCardPreviewProps = {
67+
previewImages: [
68+
{
69+
previewImageSrc: item.thumbnail,
70+
imageFit: ImageFit.cover,
71+
height: 130
72+
}
73+
]
74+
};
75+
76+
return <div
77+
//className={styles.documentTile}
78+
data-is-focusable={true}
79+
role="listitem"
80+
aria-label={item.title}
81+
>
82+
<DocumentCard
83+
type={isCompact ? DocumentCardType.compact : DocumentCardType.normal}
84+
onClick={(ev: React.SyntheticEvent<HTMLElement>) => alert("You clicked on a grid item")}
85+
86+
>
87+
<DocumentCardPreview {...previewProps} />
88+
{!isCompact && <DocumentCardLocation location={item.location} />}
89+
<div>
90+
<DocumentCardTitle
91+
title={item.title}
92+
shouldTruncate={true}
93+
/>
94+
<DocumentCardActivity
95+
activity={item.activity}
96+
people={[{ name: item.name, profileImageSrc: item.profileImageSrc }]}
97+
/>
98+
</div>
99+
</DocumentCard>
100+
</div>;
101+
};
102+
103+
afterEach(() => {
104+
gridLayout.unmount();
105+
});
106+
107+
it('Test grid layout', (done) => {
108+
gridLayout = mount(<GridLayout items={dummyItems} onRenderGridItem={(item: any, finalSize: ISize, isCompact: boolean)=>dummyOnRenderGridItem(item, finalSize, isCompact)} />);
109+
expect(gridLayout.find('.ms-List-surface')).to.have.length(1);
110+
done();
111+
});
112+
});

0 commit comments

Comments
 (0)