diff --git a/.gitignore b/.gitignore index f596c21..d31e34c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ node_modules dist lib -*.log \ No newline at end of file +*.log +.DS_Store \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..27eef54 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,5 @@ +{ + "prettier.printWidth": 140, + "prettier.singleQuote": true, + "editor.formatOnSave": true +} diff --git a/index.html b/index.html index 960cfca..9038cbb 100644 --- a/index.html +++ b/index.html @@ -1,13 +1,13 @@ -
-Step01 -Step02 -Step03 -Step04 -Step05 -Step06 -Step07 -Step08 -Step09 -Playground + Step01 + Step02 + Step03 + Step04 + Step05 + Step06 + Step07 + Step08 + Step09 + Step2-01 + Playground
diff --git a/playground/src/components/TodoListItem.tsx b/playground/src/components/TodoListItem.tsx index 4086b3c..f296c58 100644 --- a/playground/src/components/TodoListItem.tsx +++ b/playground/src/components/TodoListItem.tsx @@ -1,9 +1,9 @@ import React from 'react'; -import { Stack, Text } from '@uifabric/experiments'; +import { Stack } from '@uifabric/experiments'; import { Checkbox, IconButton, TextField } from 'office-ui-fabric-react'; import { mergeStyles } from '@uifabric/styling'; -import { Store, FilterTypes } from '../store'; -import { actionsWithService, actions } from '../actions'; +import { Store } from '../store'; +import { actionsWithService } from '../actions'; import { connect } from 'react-redux'; function mapStateToProps({ todos }: Store) { diff --git a/prettier.config.js b/prettier.config.js new file mode 100644 index 0000000..31931c9 --- /dev/null +++ b/prettier.config.js @@ -0,0 +1,4 @@ +module.exports = { + singleQuote: true, + printWidth: 140 +}; diff --git a/step2-01/README.md b/step2-01/README.md new file mode 100644 index 0000000..e775930 --- /dev/null +++ b/step2-01/README.md @@ -0,0 +1,8 @@ +# Step 2.1 + +Typescript + +- modules +- a look at types +- defining interfaces +- diff --git a/step2-01/index.html b/step2-01/index.html index eb4211b..454cef5 100644 --- a/step2-01/index.html +++ b/step2-01/index.html @@ -1,6 +1,6 @@ - hi +
diff --git a/step2-01/src/components/TodoApp.tsx b/step2-01/src/components/TodoApp.tsx new file mode 100644 index 0000000..9966c46 --- /dev/null +++ b/step2-01/src/components/TodoApp.tsx @@ -0,0 +1,87 @@ +import React from 'react'; +import { Stack } from '@uifabric/experiments'; +import { TodoFooter } from './TodoFooter'; +import { TodoHeader } from './TodoHeader'; +import { TodoList } from './TodoList'; +import { Store } from '../store'; + +let index = 0; + +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-01/src/components/TodoFooter.tsx b/step2-01/src/components/TodoFooter.tsx new file mode 100644 index 0000000..54129c1 --- /dev/null +++ b/step2-01/src/components/TodoFooter.tsx @@ -0,0 +1,22 @@ +import React from 'react'; +import { Text, Stack } from '@uifabric/experiments'; +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-01/src/components/TodoHeader.tsx b/step2-01/src/components/TodoHeader.tsx new file mode 100644 index 0000000..960777a --- /dev/null +++ b/step2-01/src/components/TodoHeader.tsx @@ -0,0 +1,59 @@ +import React from 'react'; +import { Text, Stack } from '@uifabric/experiments'; +import { Pivot, PivotItem, TextField } 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 }; + } + + onKeyPress = (evt: React.KeyboardEvent) => { + if (evt.charCode === 13) { + this.props.addTodo(this.state.labelInput); + this.setState({ labelInput: undefined }); + } + }; + + onChange = (evt: React.FormEvent, newValue: string) => { + this.setState({ labelInput: newValue }); + }; + + onFilter = (item: PivotItem) => { + this.props.setFilter(item.props.headerText as FilterTypes); + }; + + render() { + return ( + + + todos + + + + + + + + + + + ); + } +} diff --git a/step2-01/src/components/TodoList.tsx b/step2-01/src/components/TodoList.tsx new file mode 100644 index 0000000..0e10b82 --- /dev/null +++ b/step2-01/src/components/TodoList.tsx @@ -0,0 +1,29 @@ +import React from 'react'; +import { Stack } from '@uifabric/experiments'; +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 class TodoList extends React.Component { + render() { + const { filter, todos, complete, remove, edit } = this.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-01/src/components/TodoListItem.tsx b/step2-01/src/components/TodoListItem.tsx new file mode 100644 index 0000000..42face0 --- /dev/null +++ b/step2-01/src/components/TodoListItem.tsx @@ -0,0 +1,88 @@ +import React from 'react'; +import { Stack } from '@uifabric/experiments'; +import { Checkbox, IconButton, TextField } from 'office-ui-fabric-react'; +import { mergeStyles } from '@uifabric/styling'; +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; +} + +const className = mergeStyles({ + selectors: { + '.clearButton': { + visibility: 'hidden' + }, + '&:hover .clearButton': { + visibility: 'visible' + } + } +}); + +export class TodoListItem extends React.Component { + /** + * + */ + constructor(props: TodoListItemProps) { + super(props); + this.state = { editing: false, editLabel: undefined }; + } + + onEdit = () => { + const { todos, id } = this.props; + const { label } = todos[id]; + + this.setState(prevState => ({ + editing: true, + editLabel: prevState.editLabel || label + })); + }; + + onDoneEdit = () => { + this.props.edit(this.props.id, this.state.editLabel); + this.setState(prevState => ({ + editing: false, + editLabel: undefined + })); + }; + + onKeyDown = (evt: React.KeyboardEvent) => { + if (evt.which === 13) { + this.onDoneEdit(); + } + }; + + onChange = (evt: React.FormEvent, newValue: string) => { + this.setState({ editLabel: newValue }); + }; + + render() { + const { todos, id, complete, remove } = this.props; + const item = todos[id]; + + return ( + + {!this.state.editing && ( + <> + complete(id)} /> +
+ + remove(id)} /> +
+ + )} + + {this.state.editing && } +
+ ); + } +} diff --git a/step2-01/src/index.tsx b/step2-01/src/index.tsx index ec10f3e..2587243 100644 --- a/step2-01/src/index.tsx +++ b/step2-01/src/index.tsx @@ -1 +1,10 @@ -console.log('hi'); +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-01/src/store/index.ts b/step2-01/src/store/index.ts new file mode 100644 index 0000000..221b5f4 --- /dev/null +++ b/step2-01/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; +}