mirror of
https://github.com/microsoft/frontend-bootcamp.git
synced 2026-01-26 14:56:42 +08:00
adding docs subtree
This commit is contained in:
20
docs/step1-07/exercise/README.md
Normal file
20
docs/step1-07/exercise/README.md
Normal file
@@ -0,0 +1,20 @@
|
||||
## Exercise
|
||||
|
||||
### TodoFooter
|
||||
|
||||
1. Open TodoFooter and write a TodoFooterProps interface. It should include two values, a function and an object. Assign this interface to props like this: `(props: TodoFooterProps)`
|
||||
2. Write an `_onClick` function that calls `props.clear`.
|
||||
> Since TodoFooter is not a class the `_onClick` needs to be declared as a const, and placed before the `return`.
|
||||
3. Add `_onClick` to the button's `onClick`. You won't need to use `this` since this isn't a class.
|
||||
> We can't assign our `clear` function directly to `onClick`. We always need to create a function that calls our callbacks. `() => props.clear()`
|
||||
4. Test out this functionality. Check a few todos complete and click the `Clear Completed` button
|
||||
|
||||
### TodoHeader
|
||||
|
||||
1. Open TodoHeader and write TodoHeaderProps which will include 3 values. Replace the first `any` with this interface.
|
||||
2. This component also has state. Write TodoHeaderState (there's just one item), and add this where the second `any` was.
|
||||
3. Add `_onFilter` to each of the filter buttons
|
||||
> Note that we can't add new parameters to onClick, but we can pull information from the event target!
|
||||
4. Write an `_onAdd` method that calls `addTodo` on the current `labelInput`, then sets the `labelInput` in state to an empty string
|
||||
5. Call `_onAdd` from the submit button
|
||||
6. Check out this new functionality! We can now add and filter todos!
|
||||
1
docs/step1-07/exercise/index.html
Normal file
1
docs/step1-07/exercise/index.html
Normal file
@@ -0,0 +1 @@
|
||||
<!doctype html><html><link rel="stylesheet" href="./src/style.css"><body><div id="app"></div><script src="../../step1-07/exercise/step1-07/exercise.js"></script><script src="../../markdownReadme/markdownReadme.js"></script></body></html>
|
||||
69
docs/step1-07/exercise/src/TodoApp.tsx
Normal file
69
docs/step1-07/exercise/src/TodoApp.tsx
Normal file
@@ -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<any, { todos: Todos; filter: FilterTypes }> {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
todos: {},
|
||||
filter: 'all'
|
||||
};
|
||||
}
|
||||
|
||||
render() {
|
||||
const { filter, todos } = this.state;
|
||||
return (
|
||||
<div>
|
||||
<TodoHeader addTodo={this._addTodo} setFilter={this._setFilter} filter={filter} />
|
||||
<TodoList complete={this._complete} todos={todos} filter={filter} />
|
||||
<TodoFooter clear={this._clear} todos={todos} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// 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
|
||||
});
|
||||
};
|
||||
}
|
||||
10
docs/step1-07/exercise/src/TodoApp.types.ts
Normal file
10
docs/step1-07/exercise/src/TodoApp.types.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
export type FilterTypes = 'all' | 'active' | 'completed';
|
||||
|
||||
export interface TodoItem {
|
||||
label: string;
|
||||
completed: boolean;
|
||||
}
|
||||
|
||||
export interface Todos {
|
||||
[id: string]: TodoItem;
|
||||
}
|
||||
14
docs/step1-07/exercise/src/components/TodoFooter.tsx
Normal file
14
docs/step1-07/exercise/src/components/TodoFooter.tsx
Normal file
@@ -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 (
|
||||
<footer>
|
||||
<span>
|
||||
{itemCount} item{itemCount > 1 ? 's' : ''} left
|
||||
</span>
|
||||
<button className="submit">Clear Completed</button>
|
||||
</footer>
|
||||
);
|
||||
};
|
||||
35
docs/step1-07/exercise/src/components/TodoHeader.tsx
Normal file
35
docs/step1-07/exercise/src/components/TodoHeader.tsx
Normal file
@@ -0,0 +1,35 @@
|
||||
import React from 'react';
|
||||
import { FilterTypes } from '../TodoApp.types';
|
||||
|
||||
export class TodoHeader extends React.Component<any, any> {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = { labelInput: '' };
|
||||
}
|
||||
|
||||
render() {
|
||||
const { filter } = this.props;
|
||||
return (
|
||||
<header>
|
||||
<h1>todos</h1>
|
||||
<div className="addTodo">
|
||||
<input value={this.state.labelInput} onChange={this._onChange} className="textfield" placeholder="add todo" />
|
||||
<button className="submit">Add</button>
|
||||
</div>
|
||||
<nav className="filter">
|
||||
<button className={filter == 'all' ? 'selected' : ''}>all</button>
|
||||
<button className={filter == 'active' ? 'selected' : ''}>active</button>
|
||||
<button className={filter == 'completed' ? 'selected' : ''}>completed</button>
|
||||
</nav>
|
||||
</header>
|
||||
);
|
||||
}
|
||||
|
||||
_onFilter = evt => {
|
||||
this.props.setFilter(evt.target.textContet);
|
||||
};
|
||||
|
||||
_onChange = evt => {
|
||||
this.setState({ labelInput: evt.target.value });
|
||||
};
|
||||
}
|
||||
27
docs/step1-07/exercise/src/components/TodoList.tsx
Normal file
27
docs/step1-07/exercise/src/components/TodoList.tsx
Normal file
@@ -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<TodoListProps, any> {
|
||||
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 (
|
||||
<ul className="todos">
|
||||
{filteredTodos.map(id => (
|
||||
<TodoListItem key={id} id={id} complete={complete} {...todos[id]} />
|
||||
))}
|
||||
</ul>
|
||||
);
|
||||
}
|
||||
}
|
||||
21
docs/step1-07/exercise/src/components/TodoListItem.tsx
Normal file
21
docs/step1-07/exercise/src/components/TodoListItem.tsx
Normal file
@@ -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<TodoListItemProps, any> {
|
||||
render() {
|
||||
const { label, completed, complete, id } = this.props;
|
||||
|
||||
return (
|
||||
<li className="todo">
|
||||
<label>
|
||||
<input type="checkbox" checked={completed} onChange={() => complete(id)} /> {label}
|
||||
</label>
|
||||
</li>
|
||||
);
|
||||
}
|
||||
}
|
||||
4
docs/step1-07/exercise/src/index.tsx
Normal file
4
docs/step1-07/exercise/src/index.tsx
Normal file
@@ -0,0 +1,4 @@
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import { TodoApp } from './TodoApp';
|
||||
ReactDOM.render(<TodoApp />, document.getElementById('app'));
|
||||
49
docs/step1-07/exercise/src/style.css
Normal file
49
docs/step1-07/exercise/src/style.css
Normal file
@@ -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 .selected {
|
||||
border-bottom: 2px solid blue;
|
||||
}
|
||||
|
||||
.todos {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
footer {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
footer span {
|
||||
flex-grow: 1;
|
||||
}
|
||||
32
docs/step1-07/exercise/step1-07/exercise.js
Normal file
32
docs/step1-07/exercise/step1-07/exercise.js
Normal file
File diff suppressed because one or more lines are too long
1
docs/step1-07/exercise/step1-07/exercise.js.map
Normal file
1
docs/step1-07/exercise/step1-07/exercise.js.map
Normal file
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user