From 88cb5ddff3567696138e2ec1d43420757d26177b Mon Sep 17 00:00:00 2001 From: Micah Godbolt Date: Thu, 21 Feb 2019 21:49:50 -0800 Subject: [PATCH] set up demo, exercise, final folders --- step1-05/final/src/{App.tsx => TodoApp.tsx} | 0 step1-05/final/src/index.tsx | 8 +-- step1-06/README.md | 23 ++++--- step1-06/demo/index.html | 9 +++ step1-06/demo/src/TodoApp.tsx | 16 +++++ step1-06/demo/src/components/TodoFooter.tsx | 11 +++ step1-06/demo/src/components/TodoHeader.tsx | 29 ++++++++ step1-06/demo/src/components/TodoList.tsx | 20 ++++++ step1-06/demo/src/components/TodoListItem.tsx | 13 ++++ step1-06/{ => demo}/src/index.tsx | 0 step1-06/{ => demo}/src/style.css | 0 step1-06/exercise/index.html | 9 +++ step1-06/{ => exercise}/src/TodoApp.tsx | 0 .../exercise/src/components/TodoFooter.tsx | 11 +++ .../exercise/src/components/TodoHeader.tsx | 30 ++++++++ .../src/components/TodoList.tsx | 0 .../exercise/src/components/TodoListItem.tsx | 13 ++++ {step1-07 => step1-06/exercise}/src/index.tsx | 0 {step1-07 => step1-06/exercise}/src/style.css | 0 step1-06/final/index.html | 9 +++ step1-06/final/src/TodoApp.tsx | 41 +++++++++++ .../{ => final}/src/components/TodoFooter.tsx | 0 .../{ => final}/src/components/TodoHeader.tsx | 0 step1-06/final/src/components/TodoList.tsx | 19 +++++ .../src/components/TodoListItem.tsx | 0 step1-06/final/src/index.tsx | 4 ++ step1-06/final/src/style.css | 49 +++++++++++++ step1-06/index.html | 18 +++-- step1-07/README.md | 32 +++++++-- step1-07/demo/index.html | 7 ++ step1-07/demo/src/TodoApp.tsx | 69 +++++++++++++++++++ step1-07/{ => demo}/src/TodoApp.types.ts | 0 step1-07/demo/src/components/TodoFooter.tsx | 14 ++++ step1-07/demo/src/components/TodoHeader.tsx | 31 +++++++++ step1-07/demo/src/components/TodoList.tsx | 21 ++++++ step1-07/demo/src/components/TodoListItem.tsx | 16 +++++ step1-07/demo/src/index.tsx | 4 ++ step1-07/demo/src/style.css | 49 +++++++++++++ step1-07/exercise/index.html | 9 +++ step1-07/{ => exercise}/src/TodoApp.tsx | 0 step1-07/exercise/src/TodoApp.types.ts | 10 +++ .../exercise/src/components/TodoFooter.tsx | 14 ++++ .../exercise/src/components/TodoHeader.tsx | 31 +++++++++ .../src/components/TodoList.tsx | 0 .../src/components/TodoListItem.tsx | 0 step1-07/exercise/src/index.tsx | 4 ++ step1-07/exercise/src/style.css | 49 +++++++++++++ step1-07/final/index.html | 9 +++ step1-07/final/src/TodoApp.tsx | 69 +++++++++++++++++++ step1-07/final/src/TodoApp.types.ts | 10 +++ .../{ => final}/src/components/TodoFooter.tsx | 0 .../{ => final}/src/components/TodoHeader.tsx | 0 step1-07/final/src/components/TodoList.tsx | 27 ++++++++ .../final/src/components/TodoListItem.tsx | 21 ++++++ step1-07/final/src/index.tsx | 4 ++ step1-07/final/src/style.css | 49 +++++++++++++ step1-07/index.html | 18 +++-- 57 files changed, 869 insertions(+), 30 deletions(-) rename step1-05/final/src/{App.tsx => TodoApp.tsx} (100%) create mode 100644 step1-06/demo/index.html create mode 100644 step1-06/demo/src/TodoApp.tsx create mode 100644 step1-06/demo/src/components/TodoFooter.tsx create mode 100644 step1-06/demo/src/components/TodoHeader.tsx create mode 100644 step1-06/demo/src/components/TodoList.tsx create mode 100644 step1-06/demo/src/components/TodoListItem.tsx rename step1-06/{ => demo}/src/index.tsx (100%) rename step1-06/{ => demo}/src/style.css (100%) create mode 100644 step1-06/exercise/index.html rename step1-06/{ => exercise}/src/TodoApp.tsx (100%) create mode 100644 step1-06/exercise/src/components/TodoFooter.tsx create mode 100644 step1-06/exercise/src/components/TodoHeader.tsx rename step1-06/{ => exercise}/src/components/TodoList.tsx (100%) create mode 100644 step1-06/exercise/src/components/TodoListItem.tsx rename {step1-07 => step1-06/exercise}/src/index.tsx (100%) rename {step1-07 => step1-06/exercise}/src/style.css (100%) create mode 100644 step1-06/final/index.html create mode 100644 step1-06/final/src/TodoApp.tsx rename step1-06/{ => final}/src/components/TodoFooter.tsx (100%) rename step1-06/{ => final}/src/components/TodoHeader.tsx (100%) create mode 100644 step1-06/final/src/components/TodoList.tsx rename step1-06/{ => final}/src/components/TodoListItem.tsx (100%) create mode 100644 step1-06/final/src/index.tsx create mode 100644 step1-06/final/src/style.css create mode 100644 step1-07/demo/index.html create mode 100644 step1-07/demo/src/TodoApp.tsx rename step1-07/{ => demo}/src/TodoApp.types.ts (100%) create mode 100644 step1-07/demo/src/components/TodoFooter.tsx create mode 100644 step1-07/demo/src/components/TodoHeader.tsx create mode 100644 step1-07/demo/src/components/TodoList.tsx create mode 100644 step1-07/demo/src/components/TodoListItem.tsx create mode 100644 step1-07/demo/src/index.tsx create mode 100644 step1-07/demo/src/style.css create mode 100644 step1-07/exercise/index.html rename step1-07/{ => exercise}/src/TodoApp.tsx (100%) create mode 100644 step1-07/exercise/src/TodoApp.types.ts create mode 100644 step1-07/exercise/src/components/TodoFooter.tsx create mode 100644 step1-07/exercise/src/components/TodoHeader.tsx rename step1-07/{ => exercise}/src/components/TodoList.tsx (100%) rename step1-07/{ => exercise}/src/components/TodoListItem.tsx (100%) create mode 100644 step1-07/exercise/src/index.tsx create mode 100644 step1-07/exercise/src/style.css create mode 100644 step1-07/final/index.html create mode 100644 step1-07/final/src/TodoApp.tsx create mode 100644 step1-07/final/src/TodoApp.types.ts rename step1-07/{ => final}/src/components/TodoFooter.tsx (100%) rename step1-07/{ => final}/src/components/TodoHeader.tsx (100%) create mode 100644 step1-07/final/src/components/TodoList.tsx create mode 100644 step1-07/final/src/components/TodoListItem.tsx create mode 100644 step1-07/final/src/index.tsx create mode 100644 step1-07/final/src/style.css diff --git a/step1-05/final/src/App.tsx b/step1-05/final/src/TodoApp.tsx similarity index 100% rename from step1-05/final/src/App.tsx rename to step1-05/final/src/TodoApp.tsx diff --git a/step1-05/final/src/index.tsx b/step1-05/final/src/index.tsx index 3289ec8..ccf63e2 100644 --- a/step1-05/final/src/index.tsx +++ b/step1-05/final/src/index.tsx @@ -1,4 +1,4 @@ -import React from "react"; -import ReactDOM from "react-dom"; -import { TodoApp } from "./App"; -ReactDOM.render(, document.getElementById("app")); +import React from 'react'; +import ReactDOM from 'react-dom'; +import { TodoApp } from './TodoApp'; +ReactDOM.render(, document.getElementById('app')); diff --git a/step1-06/README.md b/step1-06/README.md index 696dc0c..8825198 100644 --- a/step1-06/README.md +++ b/step1-06/README.md @@ -1,13 +1,20 @@ -already done -itemCount filtering +## demo -demo - -add state +add state to AppTodo pass to header and list -add filter class stuff -controlled components (header) with consolelog -exercise +### header + +- pull filter from props +- update buttons with classNames for active +- Add value/onChange to input + +### TodoList + +- write map to loop through items, pass key and todos + +## exercise + update footer to include todos add item count and item(s) +update ListItem, pull in props, use label/completed already passed in diff --git a/step1-06/demo/index.html b/step1-06/demo/index.html new file mode 100644 index 0000000..de2c99d --- /dev/null +++ b/step1-06/demo/index.html @@ -0,0 +1,9 @@ + + + + +
+ + + + diff --git a/step1-06/demo/src/TodoApp.tsx b/step1-06/demo/src/TodoApp.tsx new file mode 100644 index 0000000..0f6e89a --- /dev/null +++ b/step1-06/demo/src/TodoApp.tsx @@ -0,0 +1,16 @@ +import React from 'react'; +import { TodoFooter } from './components/TodoFooter'; +import { TodoHeader } from './components/TodoHeader'; +import { TodoList } from './components/TodoList'; + +export class TodoApp extends React.Component { + render() { + return ( +
+ + + +
+ ); + } +} diff --git a/step1-06/demo/src/components/TodoFooter.tsx b/step1-06/demo/src/components/TodoFooter.tsx new file mode 100644 index 0000000..c936963 --- /dev/null +++ b/step1-06/demo/src/components/TodoFooter.tsx @@ -0,0 +1,11 @@ +import React from 'react'; + +export const TodoFooter = (props: any) => { + const itemCount = Object.keys(props.todos).filter(id => !props.todos[id].completed).length; + return ( +
+ 4 items left + +
+ ); +}; diff --git a/step1-06/demo/src/components/TodoHeader.tsx b/step1-06/demo/src/components/TodoHeader.tsx new file mode 100644 index 0000000..b5622ea --- /dev/null +++ b/step1-06/demo/src/components/TodoHeader.tsx @@ -0,0 +1,29 @@ +import React from 'react'; + +export class TodoHeader extends React.Component { + constructor(props) { + super(props); + this.state = { labelInput: '' }; + } + + render() { + return ( +
+

todos

+
+ + +
+ +
+ ); + } + + _onChange = evt => { + this.setState({ labelInput: evt.target.value }); + }; +} diff --git a/step1-06/demo/src/components/TodoList.tsx b/step1-06/demo/src/components/TodoList.tsx new file mode 100644 index 0000000..29ca769 --- /dev/null +++ b/step1-06/demo/src/components/TodoList.tsx @@ -0,0 +1,20 @@ +import React from 'react'; +import { TodoListItem } from './TodoListItem'; + +export class TodoList extends React.Component { + render() { + const { filter, todos } = this.props; + + const filteredTodos = Object.keys(todos).filter(id => { + return filter === 'all' || (filter === 'completed' && todos[id].completed) || (filter === 'active' && !todos[id].completed); + }); + return ( +
    + + + + +
+ ); + } +} diff --git a/step1-06/demo/src/components/TodoListItem.tsx b/step1-06/demo/src/components/TodoListItem.tsx new file mode 100644 index 0000000..1bff0f7 --- /dev/null +++ b/step1-06/demo/src/components/TodoListItem.tsx @@ -0,0 +1,13 @@ +import React from "react"; + +export class TodoListItem extends React.Component { + render() { + return ( +
  • + +
  • + ); + } +} diff --git a/step1-06/src/index.tsx b/step1-06/demo/src/index.tsx similarity index 100% rename from step1-06/src/index.tsx rename to step1-06/demo/src/index.tsx diff --git a/step1-06/src/style.css b/step1-06/demo/src/style.css similarity index 100% rename from step1-06/src/style.css rename to step1-06/demo/src/style.css diff --git a/step1-06/exercise/index.html b/step1-06/exercise/index.html new file mode 100644 index 0000000..de2c99d --- /dev/null +++ b/step1-06/exercise/index.html @@ -0,0 +1,9 @@ + + + + +
    + + + + diff --git a/step1-06/src/TodoApp.tsx b/step1-06/exercise/src/TodoApp.tsx similarity index 100% rename from step1-06/src/TodoApp.tsx rename to step1-06/exercise/src/TodoApp.tsx diff --git a/step1-06/exercise/src/components/TodoFooter.tsx b/step1-06/exercise/src/components/TodoFooter.tsx new file mode 100644 index 0000000..c936963 --- /dev/null +++ b/step1-06/exercise/src/components/TodoFooter.tsx @@ -0,0 +1,11 @@ +import React from 'react'; + +export const TodoFooter = (props: any) => { + const itemCount = Object.keys(props.todos).filter(id => !props.todos[id].completed).length; + return ( +
    + 4 items left + +
    + ); +}; diff --git a/step1-06/exercise/src/components/TodoHeader.tsx b/step1-06/exercise/src/components/TodoHeader.tsx new file mode 100644 index 0000000..408a91c --- /dev/null +++ b/step1-06/exercise/src/components/TodoHeader.tsx @@ -0,0 +1,30 @@ +import React from 'react'; + +export class TodoHeader extends React.Component { + constructor(props) { + super(props); + this.state = { labelInput: '' }; + } + + render() { + const { filter } = this.props; + + return ( +
    +

    todos

    +
    + + +
    + +
    + ); + } + _onChange = evt => { + this.setState({ labelInput: evt.target.value }); + }; +} diff --git a/step1-06/src/components/TodoList.tsx b/step1-06/exercise/src/components/TodoList.tsx similarity index 100% rename from step1-06/src/components/TodoList.tsx rename to step1-06/exercise/src/components/TodoList.tsx diff --git a/step1-06/exercise/src/components/TodoListItem.tsx b/step1-06/exercise/src/components/TodoListItem.tsx new file mode 100644 index 0000000..6bdcb59 --- /dev/null +++ b/step1-06/exercise/src/components/TodoListItem.tsx @@ -0,0 +1,13 @@ +import React from 'react'; + +export class TodoListItem extends React.Component { + render() { + return ( +
  • + +
  • + ); + } +} diff --git a/step1-07/src/index.tsx b/step1-06/exercise/src/index.tsx similarity index 100% rename from step1-07/src/index.tsx rename to step1-06/exercise/src/index.tsx diff --git a/step1-07/src/style.css b/step1-06/exercise/src/style.css similarity index 100% rename from step1-07/src/style.css rename to step1-06/exercise/src/style.css diff --git a/step1-06/final/index.html b/step1-06/final/index.html new file mode 100644 index 0000000..de2c99d --- /dev/null +++ b/step1-06/final/index.html @@ -0,0 +1,9 @@ + + + + +
    + + + + diff --git a/step1-06/final/src/TodoApp.tsx b/step1-06/final/src/TodoApp.tsx new file mode 100644 index 0000000..b0dbfba --- /dev/null +++ b/step1-06/final/src/TodoApp.tsx @@ -0,0 +1,41 @@ +import React from 'react'; +import { TodoFooter } from './components/TodoFooter'; +import { TodoHeader } from './components/TodoHeader'; +import { TodoList } from './components/TodoList'; + +export class TodoApp extends React.Component { + constructor(props) { + super(props); + this.state = { + todos: { + '04': { + label: 'Todo 4', + completed: true + }, + '03': { + label: 'Todo 3', + completed: false + }, + '02': { + label: 'Todo 2', + completed: false + }, + '01': { + label: 'Todo 1', + completed: false + } + }, + filter: 'all' + }; + } + render() { + const { filter, todos } = this.state; + return ( +
    + + + +
    + ); + } +} diff --git a/step1-06/src/components/TodoFooter.tsx b/step1-06/final/src/components/TodoFooter.tsx similarity index 100% rename from step1-06/src/components/TodoFooter.tsx rename to step1-06/final/src/components/TodoFooter.tsx diff --git a/step1-06/src/components/TodoHeader.tsx b/step1-06/final/src/components/TodoHeader.tsx similarity index 100% rename from step1-06/src/components/TodoHeader.tsx rename to step1-06/final/src/components/TodoHeader.tsx diff --git a/step1-06/final/src/components/TodoList.tsx b/step1-06/final/src/components/TodoList.tsx new file mode 100644 index 0000000..a92faa0 --- /dev/null +++ b/step1-06/final/src/components/TodoList.tsx @@ -0,0 +1,19 @@ +import React from 'react'; +import { TodoListItem } from './TodoListItem'; + +export class TodoList extends React.Component { + render() { + const { filter, todos } = 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/step1-06/src/components/TodoListItem.tsx b/step1-06/final/src/components/TodoListItem.tsx similarity index 100% rename from step1-06/src/components/TodoListItem.tsx rename to step1-06/final/src/components/TodoListItem.tsx diff --git a/step1-06/final/src/index.tsx b/step1-06/final/src/index.tsx new file mode 100644 index 0000000..ccf63e2 --- /dev/null +++ b/step1-06/final/src/index.tsx @@ -0,0 +1,4 @@ +import React from 'react'; +import ReactDOM from 'react-dom'; +import { TodoApp } from './TodoApp'; +ReactDOM.render(, document.getElementById('app')); diff --git a/step1-06/final/src/style.css b/step1-06/final/src/style.css new file mode 100644 index 0000000..4163a3a --- /dev/null +++ b/step1-06/final/src/style.css @@ -0,0 +1,49 @@ +body { + font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; + width: 400px; + margin: 20px auto; +} + +h1 { + text-align: center; +} + +.addTodo { + display: flex; +} + +.textfield { + flex-grow: 1; + margin-right: 10px; +} + +.submit { + border: none; + padding: 5px 10px; +} + +.filter { + margin: 10px 0 0; +} + +.filter button { + background: transparent; + border: none; +} + +.filter .active { + border-bottom: 2px solid blue; +} + +.todos { + list-style: none; + padding: 0; +} + +footer { + display: flex; +} + +footer span { + flex-grow: 1; +} diff --git a/step1-06/index.html b/step1-06/index.html index de2c99d..dadfb94 100644 --- a/step1-06/index.html +++ b/step1-06/index.html @@ -1,9 +1,15 @@ - - - -
    + + + + + + +
    + +
    - - diff --git a/step1-07/README.md b/step1-07/README.md index 6b9c40d..75c4945 100644 --- a/step1-07/README.md +++ b/step1-07/README.md @@ -5,14 +5,33 @@ TodoApp methods filteredTodos in List demo + +## app + Add Types to TodoApp -change 'filter' value -Types in List -pass complete to List - show types, change complete to boolean/, filter +change 'filter' state value to demonstrate + +## List (open list next to app) + +Demo TodoApp.types +Add Types in List + +## App + +Back to App, add complete={this.\_complete} to Todolist - show types, change complete to 'false', filter + +## List + add complete, pass to item (prop drilling) + +## List Item (move List Item into App window) + TodoListItemProps, extend, id, complete (possible abstraction) -Demo how you can't add random things to TodoListItem or this.props now -add complete to List item +add props, add complete to List item + +## List + +Demo how you can't add random things to TodoListItem or item's this.props now exercise @@ -20,4 +39,5 @@ Add types to footer Add onClick to button Add types to header Add setFilter to filter buttons -add 'addTodo' to onAdd function +write onAdd function +place onAdd to submit button diff --git a/step1-07/demo/index.html b/step1-07/demo/index.html new file mode 100644 index 0000000..c8c2823 --- /dev/null +++ b/step1-07/demo/index.html @@ -0,0 +1,7 @@ + + + + +
    + + diff --git a/step1-07/demo/src/TodoApp.tsx b/step1-07/demo/src/TodoApp.tsx new file mode 100644 index 0000000..759b8eb --- /dev/null +++ b/step1-07/demo/src/TodoApp.tsx @@ -0,0 +1,69 @@ +import React from 'react'; +import { TodoFooter } from './components/TodoFooter'; +import { TodoHeader } from './components/TodoHeader'; +import { TodoList } from './components/TodoList'; +import { Todos, FilterTypes } from './TodoApp.types'; + +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 ( +
    + + + +
    + ); + } + + // business logic + + private _addTodo = label => { + const { todos } = this.state; + const id = index++; + + this.setState({ + todos: { ...todos, [id]: { label, completed: false } } + }); + }; + + private _complete = id => { + const newTodos = { ...this.state.todos }; + newTodos[id].completed = !newTodos[id].completed; + + 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/step1-07/src/TodoApp.types.ts b/step1-07/demo/src/TodoApp.types.ts similarity index 100% rename from step1-07/src/TodoApp.types.ts rename to step1-07/demo/src/TodoApp.types.ts diff --git a/step1-07/demo/src/components/TodoFooter.tsx b/step1-07/demo/src/components/TodoFooter.tsx new file mode 100644 index 0000000..0d86f55 --- /dev/null +++ b/step1-07/demo/src/components/TodoFooter.tsx @@ -0,0 +1,14 @@ +import React from 'react'; +import { Todos } from '../TodoApp.types'; + +export const TodoFooter = (props: any) => { + const itemCount = Object.keys(props.todos).filter(id => !props.todos[id].completed).length; + return ( +
    + + {itemCount} item{itemCount > 1 ? 's' : ''} left + + +
    + ); +}; diff --git a/step1-07/demo/src/components/TodoHeader.tsx b/step1-07/demo/src/components/TodoHeader.tsx new file mode 100644 index 0000000..7db8f6e --- /dev/null +++ b/step1-07/demo/src/components/TodoHeader.tsx @@ -0,0 +1,31 @@ +import React from 'react'; +import { FilterTypes } from '../TodoApp.types'; + +export class TodoHeader extends React.Component { + constructor(props) { + super(props); + this.state = { labelInput: '' }; + } + + render() { + const { filter } = this.props; + return ( +
    +

    todos

    +
    + + +
    + +
    + ); + } + + _onChange = evt => { + this.setState({ labelInput: evt.target.value }); + }; +} diff --git a/step1-07/demo/src/components/TodoList.tsx b/step1-07/demo/src/components/TodoList.tsx new file mode 100644 index 0000000..a281227 --- /dev/null +++ b/step1-07/demo/src/components/TodoList.tsx @@ -0,0 +1,21 @@ +import React from 'react'; +import { TodoListItem } from './TodoListItem'; +import { FilterTypes, Todos } from '../TodoApp.types'; + +export class TodoList extends React.Component { + render() { + const { filter, todos, complete } = 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/step1-07/demo/src/components/TodoListItem.tsx b/step1-07/demo/src/components/TodoListItem.tsx new file mode 100644 index 0000000..b4b49b6 --- /dev/null +++ b/step1-07/demo/src/components/TodoListItem.tsx @@ -0,0 +1,16 @@ +import React from 'react'; +import { TodoItem } from '../TodoApp.types'; + +export class TodoListItem extends React.Component { + render() { + const { label, completed } = this.props; + + return ( +
  • + +
  • + ); + } +} diff --git a/step1-07/demo/src/index.tsx b/step1-07/demo/src/index.tsx new file mode 100644 index 0000000..ccf63e2 --- /dev/null +++ b/step1-07/demo/src/index.tsx @@ -0,0 +1,4 @@ +import React from 'react'; +import ReactDOM from 'react-dom'; +import { TodoApp } from './TodoApp'; +ReactDOM.render(, document.getElementById('app')); diff --git a/step1-07/demo/src/style.css b/step1-07/demo/src/style.css new file mode 100644 index 0000000..4163a3a --- /dev/null +++ b/step1-07/demo/src/style.css @@ -0,0 +1,49 @@ +body { + font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; + width: 400px; + margin: 20px auto; +} + +h1 { + text-align: center; +} + +.addTodo { + display: flex; +} + +.textfield { + flex-grow: 1; + margin-right: 10px; +} + +.submit { + border: none; + padding: 5px 10px; +} + +.filter { + margin: 10px 0 0; +} + +.filter button { + background: transparent; + border: none; +} + +.filter .active { + border-bottom: 2px solid blue; +} + +.todos { + list-style: none; + padding: 0; +} + +footer { + display: flex; +} + +footer span { + flex-grow: 1; +} diff --git a/step1-07/exercise/index.html b/step1-07/exercise/index.html new file mode 100644 index 0000000..de2c99d --- /dev/null +++ b/step1-07/exercise/index.html @@ -0,0 +1,9 @@ + + + + +
    + + + + diff --git a/step1-07/src/TodoApp.tsx b/step1-07/exercise/src/TodoApp.tsx similarity index 100% rename from step1-07/src/TodoApp.tsx rename to step1-07/exercise/src/TodoApp.tsx diff --git a/step1-07/exercise/src/TodoApp.types.ts b/step1-07/exercise/src/TodoApp.types.ts new file mode 100644 index 0000000..eb5437e --- /dev/null +++ b/step1-07/exercise/src/TodoApp.types.ts @@ -0,0 +1,10 @@ +export type FilterTypes = 'all' | 'active' | 'completed'; + +export interface TodoItem { + label: string; + completed: boolean; +} + +export interface Todos { + [id: string]: TodoItem; +} diff --git a/step1-07/exercise/src/components/TodoFooter.tsx b/step1-07/exercise/src/components/TodoFooter.tsx new file mode 100644 index 0000000..fdda04a --- /dev/null +++ b/step1-07/exercise/src/components/TodoFooter.tsx @@ -0,0 +1,14 @@ +import React from 'react'; +import { Todos } from '../TodoApp.types'; + +export const TodoFooter = props => { + const itemCount = Object.keys(props.todos).filter(id => !props.todos[id].completed).length; + return ( +
    + + {itemCount} item{itemCount > 1 ? 's' : ''} left + + +
    + ); +}; diff --git a/step1-07/exercise/src/components/TodoHeader.tsx b/step1-07/exercise/src/components/TodoHeader.tsx new file mode 100644 index 0000000..7db8f6e --- /dev/null +++ b/step1-07/exercise/src/components/TodoHeader.tsx @@ -0,0 +1,31 @@ +import React from 'react'; +import { FilterTypes } from '../TodoApp.types'; + +export class TodoHeader extends React.Component { + constructor(props) { + super(props); + this.state = { labelInput: '' }; + } + + render() { + const { filter } = this.props; + return ( +
    +

    todos

    +
    + + +
    + +
    + ); + } + + _onChange = evt => { + this.setState({ labelInput: evt.target.value }); + }; +} diff --git a/step1-07/src/components/TodoList.tsx b/step1-07/exercise/src/components/TodoList.tsx similarity index 100% rename from step1-07/src/components/TodoList.tsx rename to step1-07/exercise/src/components/TodoList.tsx diff --git a/step1-07/src/components/TodoListItem.tsx b/step1-07/exercise/src/components/TodoListItem.tsx similarity index 100% rename from step1-07/src/components/TodoListItem.tsx rename to step1-07/exercise/src/components/TodoListItem.tsx diff --git a/step1-07/exercise/src/index.tsx b/step1-07/exercise/src/index.tsx new file mode 100644 index 0000000..ccf63e2 --- /dev/null +++ b/step1-07/exercise/src/index.tsx @@ -0,0 +1,4 @@ +import React from 'react'; +import ReactDOM from 'react-dom'; +import { TodoApp } from './TodoApp'; +ReactDOM.render(, document.getElementById('app')); diff --git a/step1-07/exercise/src/style.css b/step1-07/exercise/src/style.css new file mode 100644 index 0000000..4163a3a --- /dev/null +++ b/step1-07/exercise/src/style.css @@ -0,0 +1,49 @@ +body { + font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; + width: 400px; + margin: 20px auto; +} + +h1 { + text-align: center; +} + +.addTodo { + display: flex; +} + +.textfield { + flex-grow: 1; + margin-right: 10px; +} + +.submit { + border: none; + padding: 5px 10px; +} + +.filter { + margin: 10px 0 0; +} + +.filter button { + background: transparent; + border: none; +} + +.filter .active { + border-bottom: 2px solid blue; +} + +.todos { + list-style: none; + padding: 0; +} + +footer { + display: flex; +} + +footer span { + flex-grow: 1; +} diff --git a/step1-07/final/index.html b/step1-07/final/index.html new file mode 100644 index 0000000..de2c99d --- /dev/null +++ b/step1-07/final/index.html @@ -0,0 +1,9 @@ + + + + +
    + + + + diff --git a/step1-07/final/src/TodoApp.tsx b/step1-07/final/src/TodoApp.tsx new file mode 100644 index 0000000..b24da27 --- /dev/null +++ b/step1-07/final/src/TodoApp.tsx @@ -0,0 +1,69 @@ +import React from 'react'; +import { TodoFooter } from './components/TodoFooter'; +import { TodoHeader } from './components/TodoHeader'; +import { TodoList } from './components/TodoList'; +import { Todos, FilterTypes } from './TodoApp.types'; + +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 ( +
    + + + +
    + ); + } + + // business logic + + private _addTodo = label => { + const { todos } = this.state; + const id = index++; + + this.setState({ + todos: { ...todos, [id]: { label, completed: false } } + }); + }; + + private _complete = id => { + const newTodos = { ...this.state.todos }; + newTodos[id].completed = !newTodos[id].completed; + + 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/step1-07/final/src/TodoApp.types.ts b/step1-07/final/src/TodoApp.types.ts new file mode 100644 index 0000000..eb5437e --- /dev/null +++ b/step1-07/final/src/TodoApp.types.ts @@ -0,0 +1,10 @@ +export type FilterTypes = 'all' | 'active' | 'completed'; + +export interface TodoItem { + label: string; + completed: boolean; +} + +export interface Todos { + [id: string]: TodoItem; +} diff --git a/step1-07/src/components/TodoFooter.tsx b/step1-07/final/src/components/TodoFooter.tsx similarity index 100% rename from step1-07/src/components/TodoFooter.tsx rename to step1-07/final/src/components/TodoFooter.tsx diff --git a/step1-07/src/components/TodoHeader.tsx b/step1-07/final/src/components/TodoHeader.tsx similarity index 100% rename from step1-07/src/components/TodoHeader.tsx rename to step1-07/final/src/components/TodoHeader.tsx diff --git a/step1-07/final/src/components/TodoList.tsx b/step1-07/final/src/components/TodoList.tsx new file mode 100644 index 0000000..e6419e9 --- /dev/null +++ b/step1-07/final/src/components/TodoList.tsx @@ -0,0 +1,27 @@ +import React from 'react'; +import { TodoListItem } from './TodoListItem'; +import { FilterTypes, Todos } from '../TodoApp.types'; + +interface TodoListProps { + complete: (id: string) => void; + todos: Todos; + filter: FilterTypes; +} + +export class TodoList extends React.Component { + render() { + const { filter, todos, complete } = 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/step1-07/final/src/components/TodoListItem.tsx b/step1-07/final/src/components/TodoListItem.tsx new file mode 100644 index 0000000..3f359ce --- /dev/null +++ b/step1-07/final/src/components/TodoListItem.tsx @@ -0,0 +1,21 @@ +import React from 'react'; +import { TodoItem } from '../TodoApp.types'; + +interface TodoListItemProps extends TodoItem { + id: string; + complete: (id: string) => void; +} + +export class TodoListItem extends React.Component { + render() { + const { label, completed, complete, id } = this.props; + + return ( +
  • + +
  • + ); + } +} diff --git a/step1-07/final/src/index.tsx b/step1-07/final/src/index.tsx new file mode 100644 index 0000000..ccf63e2 --- /dev/null +++ b/step1-07/final/src/index.tsx @@ -0,0 +1,4 @@ +import React from 'react'; +import ReactDOM from 'react-dom'; +import { TodoApp } from './TodoApp'; +ReactDOM.render(, document.getElementById('app')); diff --git a/step1-07/final/src/style.css b/step1-07/final/src/style.css new file mode 100644 index 0000000..4163a3a --- /dev/null +++ b/step1-07/final/src/style.css @@ -0,0 +1,49 @@ +body { + font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; + width: 400px; + margin: 20px auto; +} + +h1 { + text-align: center; +} + +.addTodo { + display: flex; +} + +.textfield { + flex-grow: 1; + margin-right: 10px; +} + +.submit { + border: none; + padding: 5px 10px; +} + +.filter { + margin: 10px 0 0; +} + +.filter button { + background: transparent; + border: none; +} + +.filter .active { + border-bottom: 2px solid blue; +} + +.todos { + list-style: none; + padding: 0; +} + +footer { + display: flex; +} + +footer span { + flex-grow: 1; +} diff --git a/step1-07/index.html b/step1-07/index.html index de2c99d..dadfb94 100644 --- a/step1-07/index.html +++ b/step1-07/index.html @@ -1,9 +1,15 @@ - - - -
    + + + + + + +
    + +
    - -