mirror of
https://github.com/pnp/sp-dev-fx-webparts.git
synced 2025-02-09 06:25:01 +00:00
added ui styling & added item creation component
This commit is contained in:
parent
b4e7e8b194
commit
c26e0113bd
@ -36,7 +36,10 @@ export class AppStore {
|
||||
|
||||
@computed
|
||||
public get appStatus(): string {
|
||||
return `The current status is: ${this.status}`;
|
||||
let result: string = `The current status is '${this.status}'. `;
|
||||
result += this.status === ApplicationStatus.CreateItems || this.items.length > 0 ? `List '${this.listTitle}' successfully created. ` : "";
|
||||
result += this.status === ApplicationStatus.Completed ? `In total there were ${this.items.length} items added. ` : "";
|
||||
return result + `${this.rootStore.configStore.allowImportantItems ? "" : "Adding important items is currently not allowed."}`;
|
||||
}
|
||||
|
||||
@computed
|
||||
@ -45,7 +48,7 @@ export class AppStore {
|
||||
}
|
||||
|
||||
@computed
|
||||
public get isLoading(): boolean {
|
||||
public get isInitializing(): boolean {
|
||||
return this.isLoadingConfiguration || this.isLoadingOtherStuff;
|
||||
}
|
||||
|
||||
@ -54,6 +57,7 @@ export class AppStore {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
// Mock creating list
|
||||
setTimeout(() => {
|
||||
// Make sure we change our state in an action.
|
||||
runInAction(() => {
|
||||
this.status = ApplicationStatus.CreateItems;
|
||||
this.listTitle = listTitle;
|
||||
|
@ -10,7 +10,7 @@ export class ConfigStore {
|
||||
constructor(private rootStore: RootStore) {
|
||||
this.setInitialState();
|
||||
|
||||
// Mock REST call for fetching configuration data, 5 seconds
|
||||
// Mock REST call for fetching configuration data
|
||||
setTimeout(() => {
|
||||
this.loadConfigration();
|
||||
}, 1000);
|
||||
@ -19,14 +19,13 @@ export class ConfigStore {
|
||||
@action
|
||||
public setInitialState(): void {
|
||||
this.isLoading = true;
|
||||
this.allowImportantItems = true;
|
||||
this.applicationTitle = null;
|
||||
this.allowImportantItems = false;
|
||||
}
|
||||
|
||||
@action
|
||||
private loadConfigration() {
|
||||
this.isLoading = false;
|
||||
this.allowImportantItems = true;
|
||||
this.rootStore.appStore.isLoadingConfiguration = false;
|
||||
}
|
||||
|
||||
@ -34,4 +33,9 @@ export class ConfigStore {
|
||||
public setApplicationTitle(title: string): void {
|
||||
this.applicationTitle = title;
|
||||
}
|
||||
|
||||
@action
|
||||
public setAllowImportantItems(allow: boolean): void {
|
||||
this.allowImportantItems = allow;
|
||||
}
|
||||
}
|
@ -21,7 +21,8 @@
|
||||
"description": { "default": "An example of a webpart using mobx for managing the application state" },
|
||||
"officeFabricIconFontName": "Page",
|
||||
"properties": {
|
||||
"ApplicationTitle": "Mobx Tutorial Title (change in webpart properties) 😎"
|
||||
"ApplicationTitle": "Mobx Tutorial Title (change in webpart properties) 😎",
|
||||
"AllowImportantItems": true
|
||||
}
|
||||
}]
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { Version } from '@microsoft/sp-core-library';
|
||||
import { IPropertyPaneConfiguration, PropertyPaneTextField } from '@microsoft/sp-property-pane';
|
||||
import { BaseClientSideWebPart } from '@microsoft/sp-webpart-base';
|
||||
import { BaseClientSideWebPart, PropertyPaneCheckbox } from '@microsoft/sp-webpart-base';
|
||||
import { configure } from "mobx";
|
||||
import * as strings from 'MobxTutorialWebPartStrings';
|
||||
import * as React from 'react';
|
||||
@ -8,10 +8,12 @@ import * as ReactDom from 'react-dom';
|
||||
import { RootStore } from '../../stores/RootStore';
|
||||
import MobxTutorialProvider from './components/MobxTutorialProvider';
|
||||
|
||||
// State modification should always happen through actions
|
||||
configure({ enforceActions: "always" });
|
||||
|
||||
export interface IMobxTutorialWebPartProps {
|
||||
ApplicationTitle: string;
|
||||
AllowImportantItems: boolean;
|
||||
}
|
||||
|
||||
export default class MobxTutorialWebPart extends BaseClientSideWebPart<IMobxTutorialWebPartProps> {
|
||||
@ -20,6 +22,7 @@ export default class MobxTutorialWebPart extends BaseClientSideWebPart<IMobxTuto
|
||||
protected onInit() {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
const { configStore } = this.dependencies.rootStore;
|
||||
configStore.setAllowImportantItems(this.properties.AllowImportantItems);
|
||||
configStore.setApplicationTitle(this.properties.ApplicationTitle);
|
||||
resolve();
|
||||
});
|
||||
@ -37,10 +40,14 @@ export default class MobxTutorialWebPart extends BaseClientSideWebPart<IMobxTuto
|
||||
}
|
||||
|
||||
protected onPropertyPaneFieldChanged(propertyPath: string, oldValue: any, newValue: any): void {
|
||||
const { configStore } = this.dependencies.rootStore;
|
||||
|
||||
if (propertyPath === "ApplicationTitle") {
|
||||
const { configStore } = this.dependencies.rootStore;
|
||||
configStore.setApplicationTitle(newValue);
|
||||
}
|
||||
else if (propertyPath === "AllowImportantItems") {
|
||||
configStore.setAllowImportantItems(newValue);
|
||||
}
|
||||
}
|
||||
|
||||
protected onDispose(): void {
|
||||
@ -64,6 +71,9 @@ export default class MobxTutorialWebPart extends BaseClientSideWebPart<IMobxTuto
|
||||
groupFields: [
|
||||
PropertyPaneTextField('ApplicationTitle', {
|
||||
label: strings.AppTitleFieldLabel,
|
||||
}),
|
||||
PropertyPaneCheckbox('AllowImportantItems', {
|
||||
text: strings.AllowImportantItemsLabel
|
||||
})
|
||||
]
|
||||
}
|
||||
|
@ -1,21 +1,39 @@
|
||||
import { toJS } from 'mobx';
|
||||
import { DetailsList, DetailsListLayoutMode, IColumn } from 'office-ui-fabric-react/lib/DetailsList';
|
||||
import * as React from 'react';
|
||||
import { IFakeItem } from '../../../stores/AppStore';
|
||||
|
||||
|
||||
export type DetailedFakeItemViewerProps = {
|
||||
items: IFakeItem[];
|
||||
};
|
||||
|
||||
export class DetailedFakeItemViewer extends React.Component<DetailedFakeItemViewerProps, {}> {
|
||||
private _columns: IColumn[];
|
||||
public state = {
|
||||
items: []
|
||||
};
|
||||
|
||||
constructor(props: DetailedFakeItemViewerProps) {
|
||||
super(props);
|
||||
|
||||
this._columns = [
|
||||
{ key: 'Title', name: 'Title', fieldName: 'title', minWidth: 100, maxWidth: 200, isResizable: true },
|
||||
{ key: 'Important', name: 'Important', fieldName: 'important', minWidth: 100, maxWidth: 150, isResizable: true, onRender: (item: IFakeItem) => { return item.important ? "Yes" : "No"; } }
|
||||
];
|
||||
}
|
||||
|
||||
public render(): React.ReactElement<DetailedFakeItemViewerProps> {
|
||||
const { items } = this.props;
|
||||
return (
|
||||
<div>
|
||||
<ul>{items.map(x => <li>{x.title} {x.important ? '!IMPORTANT' : null}</li>)}</ul>
|
||||
<DetailsList
|
||||
compact={true}
|
||||
items={toJS<IFakeItem[]>(items)}
|
||||
columns={this._columns}
|
||||
setKey="set"
|
||||
layoutMode={DetailsListLayoutMode.fixedColumns}
|
||||
selectionPreservedOnEmptyClick={true}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -4,6 +4,8 @@ import * as React from 'react';
|
||||
import { AppStore } from "../../../stores/AppStore";
|
||||
import { Stores } from '../../../stores/RootStore';
|
||||
import { DetailedFakeItemViewer } from "./DetailedFakeItemViewer";
|
||||
import { FakeItemCreator } from "./FakeItemCreator";
|
||||
import styles from "./MobxTutorial.module.scss";
|
||||
|
||||
export type FakeItemContainerStoreProps = {
|
||||
appStore: AppStore;
|
||||
@ -18,14 +20,27 @@ export class FakeItemContainer extends React.Component<FakeItemContainerProps, {
|
||||
public render(): React.ReactElement<FakeItemContainerProps> {
|
||||
const { appStore } = this.props;
|
||||
return (
|
||||
<div>
|
||||
<PrimaryButton
|
||||
text="Add Fake Item"
|
||||
onClick={() => { appStore.addListItem({ title: "dsf", important: true }); }}
|
||||
allowDisabledFocus={true}
|
||||
/>
|
||||
Count items: {appStore.items.length} | Count important items: {appStore.importantItems.length}
|
||||
<DetailedFakeItemViewer items={appStore.items}></DetailedFakeItemViewer>
|
||||
<div className={styles.grid}>
|
||||
|
||||
<div className={styles.row}>
|
||||
<PrimaryButton
|
||||
text="Confirm items"
|
||||
onClick={() => { appStore.confirmItems(); }}
|
||||
className={styles.inputElement}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className={styles.row}>
|
||||
<div className={`${styles.columnCreateItems}`}>
|
||||
<FakeItemCreator></FakeItemCreator>
|
||||
</div>
|
||||
|
||||
<div className={`${styles.columnItemDetails}`}>
|
||||
<p>Count items: {appStore.items.length} | Count important items: {appStore.importantItems.length}</p>
|
||||
<DetailedFakeItemViewer items={appStore.items}></DetailedFakeItemViewer>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -0,0 +1,95 @@
|
||||
import { inject, observer } from "mobx-react";
|
||||
import { DefaultButton } from 'office-ui-fabric-react/lib/Button';
|
||||
import { Checkbox } from 'office-ui-fabric-react/lib/Checkbox';
|
||||
import { TextField } from "office-ui-fabric-react/lib/TextField";
|
||||
import * as React from 'react';
|
||||
import { AppStore } from "../../../stores/AppStore";
|
||||
import { Stores } from '../../../stores/RootStore';
|
||||
import styles from "./MobxTutorial.module.scss";
|
||||
import { ConfigStore } from "../../../stores/ConfigStore";
|
||||
|
||||
export type FakeItemCreatorStoreProps = {
|
||||
appStore: AppStore;
|
||||
configStore: ConfigStore;
|
||||
};
|
||||
|
||||
export type FakeItemCreatorOwnProps = {
|
||||
};
|
||||
|
||||
export type FakeItemCreatorState = {
|
||||
itemTitle: string;
|
||||
isImportant: boolean;
|
||||
requiredTitle: string;
|
||||
};
|
||||
|
||||
|
||||
export type FakeItemCreatorProps = Partial<FakeItemCreatorStoreProps> & FakeItemCreatorOwnProps;
|
||||
|
||||
const initialState: FakeItemCreatorState = {
|
||||
itemTitle: "",
|
||||
isImportant: false,
|
||||
requiredTitle: undefined
|
||||
};
|
||||
|
||||
@inject(Stores.AppStore, Stores.ConfigurationStore)
|
||||
@observer
|
||||
export class FakeItemCreator extends React.Component<FakeItemCreatorProps, FakeItemCreatorState> {
|
||||
public state = initialState;
|
||||
|
||||
public render(): React.ReactElement<FakeItemCreatorProps> {
|
||||
const { configStore } = this.props;
|
||||
return (
|
||||
<div className={styles.row}>
|
||||
|
||||
<TextField
|
||||
label="Title"
|
||||
errorMessage={this.state.requiredTitle}
|
||||
onChange={this._onChangeItemTitle}
|
||||
value={this.state.itemTitle}
|
||||
className={styles.inputElement}
|
||||
required
|
||||
/>
|
||||
|
||||
<Checkbox
|
||||
label="Important?"
|
||||
onChange={this._onIsImportantCheckboxChange}
|
||||
className={styles.inputElement}
|
||||
checked={configStore.allowImportantItems && this.state.isImportant}
|
||||
disabled={!configStore.allowImportantItems}
|
||||
/>
|
||||
|
||||
<DefaultButton
|
||||
onClick={this._onAddFakeItem}
|
||||
iconProps={{ iconName: 'Add' }}
|
||||
allowDisabledFocus={true}
|
||||
className={styles.inputElement}
|
||||
>Add</DefaultButton>
|
||||
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
private _onAddFakeItem = (): void => {
|
||||
if (this.state.itemTitle === "" && this.state.itemTitle.length === 0) {
|
||||
this.setState({ ...this.state, requiredTitle: "Required" });
|
||||
return;
|
||||
}
|
||||
|
||||
const { appStore, configStore } = this.props;
|
||||
appStore.addListItem({ title: this.state.itemTitle, important: configStore.allowImportantItems && this.state.isImportant });
|
||||
this.setState(initialState);
|
||||
}
|
||||
|
||||
private _onChangeItemTitle = (ev: React.FormEvent<HTMLInputElement>, newValue?: string): void => {
|
||||
if (newValue === "" && newValue.length === 0) {
|
||||
this.setState({ ...this.state, itemTitle: newValue, requiredTitle: "Required" });
|
||||
}
|
||||
else {
|
||||
this.setState({ ...this.state, itemTitle: newValue, requiredTitle: undefined });
|
||||
}
|
||||
}
|
||||
|
||||
private _onIsImportantCheckboxChange = (ev: React.FormEvent<HTMLElement>, isChecked: boolean): void => {
|
||||
this.setState({ ...this.state, isImportant: isChecked });
|
||||
}
|
||||
}
|
@ -5,6 +5,7 @@ import { TextField } from 'office-ui-fabric-react/lib/TextField';
|
||||
import * as React from 'react';
|
||||
import { AppStore } from '../../../stores/AppStore';
|
||||
import { Stores } from "../../../stores/RootStore";
|
||||
import styles from './MobxTutorial.module.scss';
|
||||
|
||||
export type ListCreatorStoreProps = {
|
||||
appStore: AppStore;
|
||||
@ -30,17 +31,20 @@ export class ListCreator extends React.Component<ListCreatorProps, ListCreatorSt
|
||||
const spinner = (<Spinner size={SpinnerSize.xSmall} label="Creating list ..." labelPosition="right" />);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className={styles.row}>
|
||||
<TextField
|
||||
label="List title"
|
||||
errorMessage={this.state.errorMessage}
|
||||
onChange={this._onChangeListTitle}
|
||||
value={this.state.listTitle}
|
||||
disabled={this.state.loading}
|
||||
required
|
||||
/>
|
||||
|
||||
<PrimaryButton
|
||||
onClick={() => this.createList()}
|
||||
disabled={this.state.loading}
|
||||
className={styles.inputElement}
|
||||
>
|
||||
{this.state.loading ? spinner : "Create List"}
|
||||
</PrimaryButton>
|
||||
@ -51,7 +55,7 @@ export class ListCreator extends React.Component<ListCreatorProps, ListCreatorSt
|
||||
public async createList() {
|
||||
if (this.state.listTitle && this.state.listTitle.length > 0) {
|
||||
this.setState({ ...this.state, loading: true, errorMessage: undefined });
|
||||
await this.props.appStore.createList("MOCK TITLE");
|
||||
await this.props.appStore.createList(this.state.listTitle);
|
||||
this.setState({ ...this.state, loading: false });
|
||||
}
|
||||
else {
|
||||
@ -66,7 +70,6 @@ export class ListCreator extends React.Component<ListCreatorProps, ListCreatorSt
|
||||
}
|
||||
else {
|
||||
this.setState({ ...this.state, listTitle: newValue });
|
||||
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -1,23 +1,30 @@
|
||||
@import '~@microsoft/sp-office-ui-fabric-core/dist/sass/SPFabricCore.scss';
|
||||
|
||||
.mobxTutorial {
|
||||
.container {
|
||||
max-width: 700px;
|
||||
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);
|
||||
margin: 20px;
|
||||
|
||||
.grid {
|
||||
@include ms-Grid;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.row {
|
||||
@include ms-Grid-row;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.column {
|
||||
.columnCreateItems {
|
||||
@include ms-Grid-col;
|
||||
@include ms-lg10;
|
||||
@include ms-lg4;
|
||||
@include ms-xl4;
|
||||
@include ms-sm12;
|
||||
padding-right: 50px;
|
||||
}
|
||||
|
||||
.columnItemDetails {
|
||||
@include ms-Grid-col;
|
||||
@include ms-lg8;
|
||||
@include ms-xl8;
|
||||
@include ms-xlPush2;
|
||||
@include ms-lgPush1;
|
||||
@include ms-sm12;
|
||||
}
|
||||
|
||||
.title {
|
||||
@ -28,38 +35,7 @@
|
||||
@include ms-font-l;
|
||||
}
|
||||
|
||||
.button {
|
||||
// Our button
|
||||
text-decoration: none;
|
||||
height: 32px;
|
||||
|
||||
// Primary Button
|
||||
min-width: 80px;
|
||||
background-color: $ms-color-themePrimary;
|
||||
border-color: $ms-color-themePrimary;
|
||||
color: $ms-color-white;
|
||||
|
||||
// Basic Button
|
||||
outline: transparent;
|
||||
position: relative;
|
||||
font-family: "Segoe UI WestEuropean", "Segoe UI", -apple-system, BlinkMacSystemFont, Roboto, "Helvetica Neue", sans-serif;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
font-size: $ms-font-size-m;
|
||||
font-weight: $ms-font-weight-regular;
|
||||
border-width: 0;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
display: inline-block;
|
||||
padding: 0 16px;
|
||||
|
||||
.label {
|
||||
font-weight: $ms-font-weight-semibold;
|
||||
font-size: $ms-font-size-m;
|
||||
height: 32px;
|
||||
line-height: 32px;
|
||||
margin: 0 4px;
|
||||
vertical-align: top;
|
||||
display: inline-block;
|
||||
}
|
||||
.inputElement {
|
||||
margin-top: 5px;
|
||||
}
|
||||
}
|
||||
|
@ -1,13 +1,13 @@
|
||||
import { inject, observer } from 'mobx-react';
|
||||
import { Spinner, SpinnerSize } from 'office-ui-fabric-react/lib/Spinner';
|
||||
import * as React from 'react';
|
||||
import { AppStore } from '../../../stores/AppStore';
|
||||
import { ApplicationStatus, AppStore } from '../../../stores/AppStore';
|
||||
import { ConfigStore } from '../../../stores/ConfigStore';
|
||||
import { Stores } from '../../../stores/RootStore';
|
||||
import { FakeItemContainer } from './FakeItemContainer';
|
||||
import { ProgressIndicator } from './ProgressIndicator';
|
||||
import { ListCreator } from './ListCreator';
|
||||
import styles from './MobxTutorial.module.scss';
|
||||
import { ProgressIndicator } from './ProgressIndicator';
|
||||
|
||||
export type MobxTutorialStoreProps = {
|
||||
appStore: AppStore;
|
||||
@ -22,26 +22,33 @@ export class MobxTutorial extends React.Component<MobxTutorialProps, {}> {
|
||||
public render(): React.ReactElement<MobxTutorialProps> {
|
||||
const { appStore, configStore } = this.props;
|
||||
|
||||
if (appStore.isLoading)
|
||||
return (<Spinner size={SpinnerSize.large} label="Loading... please hodl" ariaLive="assertive" labelPosition="left" />);
|
||||
if (appStore.isInitializing)
|
||||
return (<Spinner size={SpinnerSize.large} label="Initializing... please hodl" ariaLive="assertive" labelPosition="left" />);
|
||||
|
||||
return (
|
||||
<div className={styles.mobxTutorial}>
|
||||
|
||||
<div className={styles.row}>
|
||||
<div className={styles.title}>{configStore.applicationTitle}</div>
|
||||
<ProgressIndicator></ProgressIndicator>
|
||||
</div>
|
||||
|
||||
<div className={styles.row}>
|
||||
<div className={styles.subTitle}>1) Create List</div>
|
||||
<ListCreator></ListCreator>
|
||||
</div>
|
||||
{appStore.status === ApplicationStatus.CreateList ?
|
||||
<div className={styles.row}>
|
||||
<div className={styles.subTitle}>1) Create List</div>
|
||||
<ListCreator></ListCreator>
|
||||
</div>
|
||||
:
|
||||
null
|
||||
}
|
||||
|
||||
<div className={styles.row}>
|
||||
<div className={styles.subTitle}>2) Create Items</div>
|
||||
<FakeItemContainer></FakeItemContainer>
|
||||
</div>
|
||||
{appStore.status === ApplicationStatus.CreateItems ?
|
||||
<div className={styles.row}>
|
||||
<div className={styles.subTitle}>2) Create Items</div>
|
||||
<FakeItemContainer></FakeItemContainer>
|
||||
</div>
|
||||
:
|
||||
null
|
||||
}
|
||||
|
||||
</div>
|
||||
);
|
||||
|
@ -15,6 +15,6 @@ export type ProgressIndicatorProps = Partial<ProgressIndicatorStoreProps> & Prog
|
||||
export class ProgressIndicator extends React.Component<ProgressIndicatorProps, {}> {
|
||||
public render(): React.ReactElement<ProgressIndicatorProps> {
|
||||
const { appStore } = this.props;
|
||||
return (<>{appStore.appStatus}</>);
|
||||
return (<p>{appStore.appStatus}</p>);
|
||||
}
|
||||
}
|
||||
|
@ -2,6 +2,7 @@ define([], function() {
|
||||
return {
|
||||
"PropertyPaneDescription": "Application configuration",
|
||||
"BasicGroupName": "Group Name",
|
||||
"AppTitleFieldLabel": "Application Title Field"
|
||||
"AppTitleFieldLabel": "Application Title Field",
|
||||
"AllowImportantItemsLabel": "Allow important items"
|
||||
}
|
||||
});
|
@ -2,6 +2,7 @@ declare interface IMobxTutorialWebPartStrings {
|
||||
PropertyPaneDescription: string;
|
||||
BasicGroupName: string;
|
||||
AppTitleFieldLabel: string;
|
||||
AllowImportantItemsLabel: string;
|
||||
}
|
||||
|
||||
declare module 'MobxTutorialWebPartStrings' {
|
||||
|
Loading…
x
Reference in New Issue
Block a user