diff --git a/index.html b/index.html index 7a5a48f..196de56 100644 --- a/index.html +++ b/index.html @@ -68,8 +68,24 @@

Day 2

Setup, HTML, CSS, Javascript and Intro to React -
  • Step 1
  • -
  • Step 2
  • +
  • + + Step 1
    + Typescript Basics +
    +
  • +
  • + + Step 2
    + UI Fabric +
    +
  • +
  • + + Step 3
    + UI Fabric: Theming and Styling +
    +
  • diff --git a/package-lock.json b/package-lock.json index 344ef02..62799b8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3355,8 +3355,7 @@ "ansi-regex": { "version": "2.1.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "aproba": { "version": "1.2.0", @@ -3377,14 +3376,12 @@ "balanced-match": { "version": "1.0.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "brace-expansion": { "version": "1.1.11", "bundled": true, "dev": true, - "optional": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -3399,20 +3396,17 @@ "code-point-at": { "version": "1.1.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "concat-map": { "version": "0.0.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "console-control-strings": { "version": "1.1.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "core-util-is": { "version": "1.0.2", @@ -3529,8 +3523,7 @@ "inherits": { "version": "2.0.3", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "ini": { "version": "1.3.5", @@ -3542,7 +3535,6 @@ "version": "1.0.0", "bundled": true, "dev": true, - "optional": true, "requires": { "number-is-nan": "^1.0.0" } @@ -3557,7 +3549,6 @@ "version": "3.0.4", "bundled": true, "dev": true, - "optional": true, "requires": { "brace-expansion": "^1.1.7" } @@ -3565,14 +3556,12 @@ "minimist": { "version": "0.0.8", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "minipass": { "version": "2.3.5", "bundled": true, "dev": true, - "optional": true, "requires": { "safe-buffer": "^5.1.2", "yallist": "^3.0.0" @@ -3591,7 +3580,6 @@ "version": "0.5.1", "bundled": true, "dev": true, - "optional": true, "requires": { "minimist": "0.0.8" } @@ -3672,8 +3660,7 @@ "number-is-nan": { "version": "1.0.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "object-assign": { "version": "4.1.1", @@ -3685,7 +3672,6 @@ "version": "1.4.0", "bundled": true, "dev": true, - "optional": true, "requires": { "wrappy": "1" } @@ -3771,8 +3757,7 @@ "safe-buffer": { "version": "5.1.2", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "safer-buffer": { "version": "2.1.2", @@ -3808,7 +3793,6 @@ "version": "1.0.2", "bundled": true, "dev": true, - "optional": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", @@ -3828,7 +3812,6 @@ "version": "3.0.1", "bundled": true, "dev": true, - "optional": true, "requires": { "ansi-regex": "^2.0.0" } @@ -3872,14 +3855,12 @@ "wrappy": { "version": "1.0.2", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "yallist": { "version": "3.0.3", "bundled": true, - "dev": true, - "optional": true + "dev": true } } }, diff --git a/package.json b/package.json index 7415989..aa9fda8 100644 --- a/package.json +++ b/package.json @@ -41,6 +41,7 @@ }, "dependencies": { "@uifabric/experiments": "^6.57.0", + "@uifabric/fluent-theme": "^0.14.1", "express": "^4.16.4", "immer": "^1.12.1", "office-ui-fabric-react": "^6.140.0", diff --git a/step2-02/src/components/TodoHeader.tsx b/step2-02/src/components/TodoHeader.tsx index 0f91255..dec977f 100644 --- a/step2-02/src/components/TodoHeader.tsx +++ b/step2-02/src/components/TodoHeader.tsx @@ -1,7 +1,7 @@ import React from 'react'; import { Text } from '@uifabric/experiments'; import { Stack } from 'office-ui-fabric-react'; -import { Pivot, PivotItem, TextField, DefaultButton } from 'office-ui-fabric-react'; +import { Pivot, PivotItem, TextField, PrimaryButton } from 'office-ui-fabric-react'; import { FilterTypes } from '../store'; interface TodoHeaderProps { @@ -31,7 +31,7 @@ export class TodoHeader extends React.Component - Add + Add diff --git a/step2-03/README.md b/step2-03/README.md new file mode 100644 index 0000000..430fbea --- /dev/null +++ b/step2-03/README.md @@ -0,0 +1,9 @@ +# Step 2.2 + +Integrates Fabric + +Learn about Basic Components + +- Stack +- Text +- Show case some components diff --git a/step2-03/index.html b/step2-03/index.html new file mode 100644 index 0000000..454cef5 --- /dev/null +++ b/step2-03/index.html @@ -0,0 +1,6 @@ + + + +
    + + diff --git a/step2-03/src/components/TodoApp.tsx b/step2-03/src/components/TodoApp.tsx new file mode 100644 index 0000000..6a05102 --- /dev/null +++ b/step2-03/src/components/TodoApp.tsx @@ -0,0 +1,95 @@ +import React from 'react'; +import { Stack, Customizer, mergeStyles, getTheme } from 'office-ui-fabric-react'; +import { TodoFooter } from './TodoFooter'; +import { TodoHeader } from './TodoHeader'; +import { TodoList } from './TodoList'; +import { Store } from '../store'; +import { FluentCustomizations } from '@uifabric/fluent-theme'; + +let index = 0; + +const className = mergeStyles({ + padding: 25, + ...getTheme().effects.elevation4 +}); + +export class TodoApp extends React.Component { + constructor(props) { + super(props); + this.state = { + todos: {}, + filter: 'all' + }; + } + render() { + const { filter, todos } = this.state; + return ( + + + + + + + + + + ); + } + + private _addTodo = label => { + const { todos } = this.state; + const id = index++; + + this.setState({ + todos: { ...todos, [id]: { label } } + }); + }; + + private _remove = id => { + const newTodos = { ...this.state.todos }; + delete newTodos[id]; + + this.setState({ + todos: newTodos + }); + }; + + private _complete = id => { + const newTodos = { ...this.state.todos }; + newTodos[id].completed = !newTodos[id].completed; + + this.setState({ + todos: newTodos + }); + }; + + private _edit = (id, label) => { + const newTodos = { ...this.state.todos }; + newTodos[id] = { ...newTodos[id], label }; + + this.setState({ + todos: newTodos + }); + }; + + private _clear = () => { + const { todos } = this.state; + const newTodos = {}; + + Object.keys(this.state.todos).forEach(id => { + if (!todos[id].completed) { + newTodos[id] = todos[id]; + } + }); + + this.setState({ + todos: newTodos + }); + }; + + private _setFilter = filter => { + this.setState({ + filter: filter + }); + }; +} diff --git a/step2-03/src/components/TodoFooter.tsx b/step2-03/src/components/TodoFooter.tsx new file mode 100644 index 0000000..6a44d83 --- /dev/null +++ b/step2-03/src/components/TodoFooter.tsx @@ -0,0 +1,23 @@ +import React from 'react'; +import { Text } from '@uifabric/experiments'; +import { Stack } from 'office-ui-fabric-react'; +import { Store } from '../store'; +import { DefaultButton } from 'office-ui-fabric-react'; + +interface TodoFooterProps { + clear: () => void; + todos: Store['todos']; +} + +export const TodoFooter = (props: TodoFooterProps) => { + const itemCount = Object.keys(props.todos).filter(id => !props.todos[id].completed).length; + + return ( + + + {itemCount} item{itemCount > 1 ? 's' : ''} left + + props.clear()}>Clear Completed + + ); +}; diff --git a/step2-03/src/components/TodoHeader.tsx b/step2-03/src/components/TodoHeader.tsx new file mode 100644 index 0000000..0015717 --- /dev/null +++ b/step2-03/src/components/TodoHeader.tsx @@ -0,0 +1,58 @@ +import React from 'react'; +import { Text } from '@uifabric/experiments'; +import { Stack } from 'office-ui-fabric-react'; +import { Pivot, PivotItem, TextField, PrimaryButton } from 'office-ui-fabric-react'; +import { FilterTypes } from '../store'; + +interface TodoHeaderProps { + addTodo: (label: string) => void; + setFilter: (filter: FilterTypes) => void; + filter: string; +} + +interface TodoHeaderState { + labelInput: string; +} + +export class TodoHeader extends React.Component { + constructor(props: TodoHeaderProps) { + super(props); + this.state = { labelInput: undefined }; + } + + render() { + return ( + + + todos + + + + + + + Add + + + + + + + + + ); + } + + private onAdd = () => { + this.props.addTodo(this.state.labelInput); + this.setState({ labelInput: undefined }); + }; + + private onChange = (evt: React.FormEvent, newValue: string) => { + this.setState({ labelInput: newValue }); + }; + + private onFilter = (item: PivotItem) => { + this.props.setFilter(item.props.headerText as FilterTypes); + }; +} diff --git a/step2-03/src/components/TodoList.tsx b/step2-03/src/components/TodoList.tsx new file mode 100644 index 0000000..805bd8f --- /dev/null +++ b/step2-03/src/components/TodoList.tsx @@ -0,0 +1,27 @@ +import React from 'react'; +import { Stack } from 'office-ui-fabric-react'; +import { TodoListItem } from './TodoListItem'; +import { Store, FilterTypes } from '../store'; + +interface TodoListProps { + complete: (id: string) => void; + remove: (id: string) => void; + todos: Store['todos']; + filter: FilterTypes; + edit: (id: string, label: string) => void; +} + +export const TodoList = (props: TodoListProps) => { + const { filter, todos, complete, remove, edit } = props; + const filteredTodos = Object.keys(todos).filter(id => { + return filter === 'all' || (filter === 'completed' && todos[id].completed) || (filter === 'active' && !todos[id].completed); + }); + + return ( + + {filteredTodos.map(id => ( + + ))} + + ); +}; diff --git a/step2-03/src/components/TodoListItem.tsx b/step2-03/src/components/TodoListItem.tsx new file mode 100644 index 0000000..030aa73 --- /dev/null +++ b/step2-03/src/components/TodoListItem.tsx @@ -0,0 +1,75 @@ +import React from 'react'; +import { Stack, Checkbox, IconButton, TextField, DefaultButton } from 'office-ui-fabric-react'; +import { Store } from '../store'; + +interface TodoListItemProps { + id: string; + todos: Store['todos']; + remove: (id: string) => void; + complete: (id: string) => void; + edit: (id: string, label: string) => void; +} + +interface TodoListItemState { + editing: boolean; + editLabel: string; +} + +export class TodoListItem extends React.Component { + constructor(props: TodoListItemProps) { + super(props); + this.state = { editing: false, editLabel: undefined }; + } + + render() { + const { todos, id, complete, remove } = this.props; + const item = todos[id]; + + return ( + + {!this.state.editing && ( + <> + complete(id)} /> +
    + + remove(id)} /> +
    + + )} + + {this.state.editing && ( + + + + + + Save + + + )} +
    + ); + } + + private onEdit = () => { + const { todos, id } = this.props; + const { label } = todos[id]; + + this.setState({ + editing: true, + editLabel: this.state.editLabel || label + }); + }; + + private onDoneEdit = () => { + this.props.edit(this.props.id, this.state.editLabel); + this.setState({ + editing: false, + editLabel: undefined + }); + }; + + private onChange = (evt: React.FormEvent, newValue: string) => { + this.setState({ editLabel: newValue }); + }; +} diff --git a/step2-03/src/index.tsx b/step2-03/src/index.tsx new file mode 100644 index 0000000..2587243 --- /dev/null +++ b/step2-03/src/index.tsx @@ -0,0 +1,10 @@ +import React from 'react'; +import ReactDOM from 'react-dom'; +import { TodoApp } from './components/TodoApp'; +import { initializeIcons } from '@uifabric/icons'; + +// Initializes the UI Fabric icons that we can use +// Choose one from this list: https://developer.microsoft.com/en-us/fabric#/styles/icons +initializeIcons(); + +ReactDOM.render(, document.getElementById('app')); diff --git a/step2-03/src/store/index.ts b/step2-03/src/store/index.ts new file mode 100644 index 0000000..221b5f4 --- /dev/null +++ b/step2-03/src/store/index.ts @@ -0,0 +1,14 @@ +export type FilterTypes = 'all' | 'active' | 'completed'; + +export interface TodoItem { + label: string; + completed: boolean; +} + +export interface Store { + todos: { + [id: string]: TodoItem; + }; + + filter: FilterTypes; +} diff --git a/webpack.config.js b/webpack.config.js index f839b83..54cf914 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -1,18 +1,31 @@ // @ts-check const path = require('path'); +const fs = require('fs'); + const HtmlWebpackPlugin = require('html-webpack-plugin'); const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin'); -const entries = { - 'step1-04': './step1-04/src/index', - 'step1-05': './step1-05/src/index', - 'step1-06': './step1-06/src/index', - 'step1-07': './step1-07/src/index', - 'step2-01': './step2-01/src/index', - 'step2-02': './step2-02/src/index', - playground: './playground/src/index' -}; +const entries = {}; + +function getEntryPoint(entry) { + if (entry.includes('step') || entry.includes('playground')) { + for (let suffix of ['.js', '.jsx', '.ts', '.tsx']) { + if (fs.existsSync(`./${entry}/src/index${suffix}`)) { + return `./${entry}/src/index${suffix}`; + } + } + } + return false; +} + +fs.readdirSync('./').filter(entry => { + const entryPoint = getEntryPoint(entry); + + if (entryPoint) { + entries[entry] = entryPoint; + } +}); module.exports = function() { return {