Skip to content

Commit 5b08293

Browse files
committed
Merge branch 'joelfmrodrigues-add-folder-picker' into dev
2 parents b7acb1c + a16abdb commit 5b08293

File tree

15 files changed

+335
-9
lines changed

15 files changed

+335
-9
lines changed
2.31 KB
Loading
2.26 KB
Loading
17.9 KB
Loading
56.9 KB
Loading
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
# FolderPicker control
2+
3+
This control allows you to explore and select a folder.
4+
It also allows the user to create a new folder at the current level being explored.
5+
6+
Here is an example of the control:
7+
8+
![FolderPicker](../assets/FolderPicker.png)
9+
10+
`FolderPicker` no selection:
11+
12+
![FolderPicker no selection](../assets/FolderPicker-no-selection.png)
13+
14+
`FolderPicker` selection:
15+
16+
![FolderPicker selection](../assets/FolderPicker-selection.png)
17+
18+
`FolderPicker` selected:
19+
20+
![FolderPicker selected](../assets/FolderPicker-selected.png)
21+
22+
## How to use this control in your solutions
23+
24+
- 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.
25+
- Import the control into your component:
26+
27+
```TypeScript
28+
import { FolderPicker, IFolder } from "@pnp/spfx-controls-react/lib/FolderPicker";
29+
```
30+
31+
- Use the `FolderPicker` control in your code as follows:
32+
33+
```TypeScript
34+
<FolderPicker context={this.props.context}
35+
label='Folder Picker'
36+
required={true}
37+
rootFolder={{
38+
Name: 'Documents',
39+
ServerRelativeUrl: `/sites/TestSite/Shared Documents`
40+
}}
41+
onSelect={this._onFolderSelect}
42+
canCreateFolders={true} />
43+
```
44+
45+
- The `onSelect` change event returns the selected folder and can be implemented as follows:
46+
47+
```TypeScript
48+
private _onFolderSelect = (folder: IFolder): void => {
49+
console.log('selected folder', folder);
50+
}
51+
```
52+
53+
## Implementation
54+
55+
The `FolderPicker` control can be configured with the following properties:
56+
57+
| Property | Type | Required | Description |
58+
| ---- | ---- | ---- | ---- |
59+
| context | WebPartContext \| ExtensionContext | yes | The context object of the SPFx loaded webpart or customizer. |
60+
| label | string | yes | The label for the control. |
61+
| rootFolder | IFolder | yes | The lowest level folder that can be explored. This can be the root folder of a library. |
62+
| defaultFolder | IFolder | no | The default folder to be selected or explored. |
63+
| required | boolean | no | Is selection required. |
64+
| disabled | boolean | no | Is the control disabled. |
65+
| canCreateFolders | boolean | no | Allow current user to create folders on the target location. If enabled, you need to ensure that the user has the required permissions. |
66+
| onSelect | (folder: IFolder): void | no | Callback function called after a folder is selected. |
67+
68+
![](https://telemetry.sharepointpnp.com/sp-dev-fx-controls-react/wiki/controls/FolderPicker)

docs/documentation/docs/index.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ The following controls are currently available:
5454
- [FilePicker](./controls/FilePicker) (control that allows to browse and select a file from various places)
5555
- [FileTypeIcon](./controls/FileTypeIcon) (Control that shows the icon of a specified file path or application)
5656
- [FolderExplorer](./controls/FolderExplorer) (Control that allows to browse the folders and sub-folders from a root folder)
57+
- [FolderPicker](./controls/FolderPicker) (Control that allows to browse and select a folder)
5758
- [GridLayout](./controls/GridLayout) (control that renders a responsive grid layout for your web parts)
5859
- [IconPicker](./controls/IconPicker) (control that allows to search and select an icon from office-ui-fabric icons)
5960
- [IFrameDialog](./controls/IFrameDialog) (renders a Dialog with an iframe as a content)

docs/documentation/mkdocs.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ nav:
1818
- FilePicker: 'controls/FilePicker.md'
1919
- FileTypeIcon: 'controls/FileTypeIcon.md'
2020
- FolderExplorer: 'controls/FolderExplorer.md'
21+
- FolderPicker: 'controls/FolderPicker.md'
2122
- GridLayout: 'controls/GridLayout.md'
2223
- IconPicker: 'controls/IconPicker.md'
2324
- IFrameDialog: 'controls/IFrameDialog.md'

src/FolderPicker.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from './controls/folderPicker/index';
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
// @import "~@microsoft/sp-office-ui-fabric-core/dist/sass/SPFabricCore.scss";
2+
@import '~office-ui-fabric-react/dist/sass/References.scss';
3+
4+
5+
.folderPicker {
6+
display: flex;
7+
align-items: center;
8+
9+
.selection {
10+
width: 90%;
11+
}
12+
13+
.selectFolderLabel {
14+
color: $ms-color-neutralSecondary;
15+
}
16+
17+
.selectFolder {
18+
align-items: center;
19+
display: flex;
20+
margin-right: 5px;
21+
max-width: 90%;
22+
span {
23+
white-space: nowrap;
24+
overflow: hidden;
25+
text-overflow: ellipsis;
26+
}
27+
}
28+
29+
& .selectButton {
30+
width: 10%;
31+
display: flex;
32+
justify-content: center;
33+
}
34+
}
35+
36+
.actions {
37+
button {
38+
margin-right: 15px;
39+
}
40+
}
41+
42+
label.required::after {
43+
content: " *";
44+
color: rgb(168, 0, 0);
45+
padding-right: 12px;
46+
}
Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
import * as React from 'react';
2+
import styles from './FolderPicker.module.scss';
3+
import { IFolderPickerProps, IFolderPickerState } from '.';
4+
import { IFolder } from '../../services/IFolderExplorerService';
5+
import { IconButton, PrimaryButton, DefaultButton } from 'office-ui-fabric-react/lib/Button';
6+
import { Label } from 'office-ui-fabric-react/lib/Label';
7+
import { Link } from 'office-ui-fabric-react/lib/Link';
8+
import { getId } from 'office-ui-fabric-react/lib/Utilities';
9+
import { Panel, PanelType } from 'office-ui-fabric-react/lib/Panel';
10+
import { FolderExplorer } from '../folderExplorer/FolderExplorer';
11+
12+
13+
export default class FolderPicker extends React.Component<IFolderPickerProps, IFolderPickerState> {
14+
15+
private _folderLinkId = getId('folderLink');
16+
private _selectedFolder: IFolder;
17+
18+
constructor(props: IFolderPickerProps) {
19+
super(props);
20+
21+
this.state = {
22+
showPanel: false,
23+
selectedFolder: this.props.defaultFolder
24+
};
25+
}
26+
27+
public componentWillReceiveProps(nextProps: IFolderPickerProps) {
28+
29+
this.setState({
30+
selectedFolder: nextProps.defaultFolder,
31+
});
32+
33+
}
34+
35+
public render(): React.ReactElement<IFolderPickerProps> {
36+
return (
37+
<div>
38+
{this.props.label &&
39+
<Label className={this.props.required ? styles.required : ''} htmlFor={this._folderLinkId}>{this.props.label}</Label>
40+
}
41+
<div className={styles.folderPicker}>
42+
<div className={styles.selection}>
43+
{!this.state.selectedFolder &&
44+
<span className={styles.selectFolderLabel}>Select a folder</span>
45+
}
46+
{this.state.selectedFolder &&
47+
<div className={styles.selectFolder}>
48+
<Link className={styles.selectFolder} target='_blank' data-interception="off" id={this._folderLinkId} href={this.state.selectedFolder.ServerRelativeUrl}>
49+
<span title={this.state.selectedFolder.Name}>{this.state.selectedFolder.Name}</span>
50+
</Link>
51+
<IconButton
52+
iconProps={{ iconName: 'Cancel' }}
53+
title="Delete selection"
54+
ariaLabel="Delete selection"
55+
onClick={this._resetSelection}
56+
disabled={this.props.disabled}
57+
/>
58+
</div>
59+
}
60+
</div>
61+
<div className={styles.selectButton}>
62+
<IconButton
63+
iconProps={{ iconName: 'FolderList' }}
64+
title="Select folder"
65+
ariaLabel="Select folder"
66+
disabled={this.props.disabled}
67+
onClick={this._showPanel}
68+
/>
69+
</div>
70+
</div>
71+
72+
<Panel
73+
isOpen={this.state.showPanel}
74+
type={PanelType.medium}
75+
onDismiss={this._hidePanel}
76+
headerText="Select folder"
77+
closeButtonAriaLabel="Close"
78+
onRenderFooterContent={this._onRenderFooterContent}
79+
>
80+
<div>
81+
<FolderExplorer
82+
context={this.props.context}
83+
rootFolder={this.props.rootFolder}
84+
defaultFolder={this.state.selectedFolder}
85+
onSelect={this._onFolderSelect}
86+
canCreateFolders={this.props.canCreateFolders}
87+
/>
88+
</div>
89+
</Panel>
90+
91+
</div>
92+
);
93+
}
94+
95+
private _showPanel = () => {
96+
this.setState({ showPanel: true });
97+
}
98+
99+
private _hidePanel = () => {
100+
this.setState({ showPanel: false });
101+
}
102+
103+
private _onRenderFooterContent = () => {
104+
return (
105+
<div className={styles.actions}>
106+
<PrimaryButton iconProps={{ iconName: 'Save' }} onClick={this._onFolderSave}>
107+
Save
108+
</PrimaryButton>
109+
<DefaultButton iconProps={{ iconName: 'Cancel' }} onClick={this._hidePanel}>
110+
Cancel
111+
</DefaultButton>
112+
</div>
113+
);
114+
}
115+
116+
private _onFolderSelect = (folder: IFolder): void => {
117+
this._selectedFolder = folder;
118+
}
119+
120+
private _onFolderSave = (): void => {
121+
this.setState({
122+
selectedFolder: this._selectedFolder,
123+
showPanel: false,
124+
});
125+
126+
this.props.onSelect(this._selectedFolder);
127+
}
128+
129+
private _resetSelection = (): void => {
130+
this._selectedFolder = null;
131+
132+
this.setState({
133+
selectedFolder: this._selectedFolder,
134+
});
135+
136+
this.props.onSelect(this._selectedFolder);
137+
}
138+
139+
140+
141+
}

0 commit comments

Comments
 (0)