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:
122
docs/step1-06/demo/README.md
Normal file
122
docs/step1-06/demo/README.md
Normal file
@@ -0,0 +1,122 @@
|
||||
# Creating a State Driven UI
|
||||
|
||||
In React data travels two directions, top down in the form of state propegating throughout controls, and bottom up, as interacting with the UI flows back up to modify the state. When writing an application it's often helpful to think of these two directions as separate parts of the development process.
|
||||
|
||||
## demo
|
||||
|
||||
[Step #3 of Thinking in React](https://reactjs.org/docs/thinking-in-react.html) suggests finding the "minimal set of mutable state" that your application requires. So in this demo we are going to add that "minimal state" to our application and drive our UI off of that data. With that done the next step will be to create ways to modify that state, which will in turn cascade down through our UI. This [reconcilation](https://reactjs.org/docs/reconciliation.html) process, figuring out what in your UI needs to change based on changing state, is what React excels in.
|
||||
|
||||
### Adding State to App
|
||||
|
||||
For our minimal state, we're going to include just two keys: `todos` and `filter`. We don't need to worry about a `remaining` value because we can calculate that by looking at the number of unchecked todos.
|
||||
|
||||
So here is our full constructor
|
||||
|
||||
```jsx
|
||||
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'
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
> You could use an array to represent your todos. Array manipulation can be easier in some cases, but this object approach simplifies other functionality and will ultimately be more performant.
|
||||
|
||||
### Passing State Through to UI
|
||||
|
||||
To avoid reaching into state over and over, we once again use deconstruction to pull out the pieces we need.
|
||||
|
||||
```jsx
|
||||
const { filter, todos } = this.state;
|
||||
```
|
||||
|
||||
Now we can pass `filter` and `todos` into our components.
|
||||
|
||||
```jsx
|
||||
return (
|
||||
<div>
|
||||
<TodoHeader filter={filter} />
|
||||
<TodoList todos={todos} filter={filter} />
|
||||
<TodoFooter todos={todos} />
|
||||
</div>
|
||||
);
|
||||
```
|
||||
|
||||
### State Driven TodoList
|
||||
|
||||
I've already pulled out our props into `filter` and `todos` variables, and written a bit of JS that will return an array of filtered todo `id`s. We'll be using that filtered array to render out todo items.
|
||||
|
||||
```jsx
|
||||
{
|
||||
filteredTodos.map(id => <TodoListItem key={id} id={id} {...todos[id]} />);
|
||||
}
|
||||
```
|
||||
|
||||
- A JavaScript map takes in an array and transforms it into a new array
|
||||
- We use the `id` from the `filterTodos` array as the [list key](https://reactjs.org/docs/lists-and-keys.html) to help React track each item as state changes.
|
||||
- The key is not actually passed into the component, so we pass the key in as `id` as well. This will help us out later.
|
||||
- Lastly we use the `id` to grab the todo from our `todos` object, then use the [spread operator](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax) to pass through the todo's `label` and `completed` values.
|
||||
> This spread opperator is the same as saying `label={todos[id].label} completed={todos[id].completed}`. Pretty obvious why spread is so handy!
|
||||
|
||||
### State Driven and Stateful Header
|
||||
|
||||
Within the header we've got a situation where we not only want to pass `filter` state down to it, but we also want to maintain state within the control. Fortunatly, this is no problem at all for React. First off let's deal with the incoming state.
|
||||
|
||||
#### Conditional ClassNames
|
||||
|
||||
In CSS based styling, visual states are applied by adding and removing classes. We can use the filter value to conditionally add a class thereby lighting up the correct filter button.
|
||||
|
||||
```jsx
|
||||
<nav className="filter">
|
||||
<button className={filter == 'all' ? 'completed' : ''}>all</button>
|
||||
<button className={filter == 'active' ? 'completed' : ''}>active</button>
|
||||
<button className={filter == 'completed' ? 'completed' : ''}>completed</button>
|
||||
</nav>
|
||||
```
|
||||
|
||||
> Terniary operators are very popular in React code as each expression could be a string for a className, or even a JSX element.
|
||||
|
||||
#### Creating a Controled Input
|
||||
|
||||
In tradition HTML forms users interact with the form, and on submit, those values are captured and transmitted. Those are called **uncontrolled inputs**. A **controlled input** is one whos value is defined by state, and interaction with that input updates state with each keystroke. This round trip process might sound inefficient, but in reality it has little to no impact, and it enables some advanced form functionality.
|
||||
|
||||
To create a controlled component, we need two things, which our demo already provides:
|
||||
|
||||
1. A state variable to hold the input's value
|
||||
|
||||
```jsx
|
||||
this.state = { labelInput: '' };
|
||||
```
|
||||
|
||||
2. A function to update that value
|
||||
|
||||
```jsx
|
||||
_onChange = evt => {
|
||||
this.setState({ labelInput: evt.target.value });
|
||||
};
|
||||
```
|
||||
|
||||
With those two pieces in place, we can update our uncontrolled input to being controlled.
|
||||
|
||||
```jsx
|
||||
<input value={this.state.labelInput} onChange={this._onChange} className="textfield" placeholder="add todo" />
|
||||
```
|
||||
1
docs/step1-06/demo/index.html
Normal file
1
docs/step1-06/demo/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-06/demo/step1-06/demo.js"></script><script src="../../markdownReadme/markdownReadme.js"></script></body></html>
|
||||
16
docs/step1-06/demo/src/TodoApp.tsx
Normal file
16
docs/step1-06/demo/src/TodoApp.tsx
Normal file
@@ -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 (
|
||||
<div>
|
||||
<TodoHeader />
|
||||
<TodoList />
|
||||
<TodoFooter />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
11
docs/step1-06/demo/src/components/TodoFooter.tsx
Normal file
11
docs/step1-06/demo/src/components/TodoFooter.tsx
Normal file
@@ -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 (
|
||||
<footer>
|
||||
<span>4 items left</span>
|
||||
<button className="submit">Clear Completed</button>
|
||||
</footer>
|
||||
);
|
||||
};
|
||||
30
docs/step1-06/demo/src/components/TodoHeader.tsx
Normal file
30
docs/step1-06/demo/src/components/TodoHeader.tsx
Normal file
@@ -0,0 +1,30 @@
|
||||
import React from 'react';
|
||||
|
||||
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 className="textfield" placeholder="add todo" />
|
||||
<button className="submit">Add</button>
|
||||
</div>
|
||||
<nav className="filter">
|
||||
<button className="completed">all</button>
|
||||
<button>active</button>
|
||||
<button>completed</button>
|
||||
</nav>
|
||||
</header>
|
||||
);
|
||||
}
|
||||
|
||||
_onChange = evt => {
|
||||
this.setState({ labelInput: evt.target.value });
|
||||
};
|
||||
}
|
||||
20
docs/step1-06/demo/src/components/TodoList.tsx
Normal file
20
docs/step1-06/demo/src/components/TodoList.tsx
Normal file
@@ -0,0 +1,20 @@
|
||||
import React from 'react';
|
||||
import { TodoListItem } from './TodoListItem';
|
||||
|
||||
export class TodoList extends React.Component<any, any> {
|
||||
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 (
|
||||
<ul className="todos">
|
||||
<TodoListItem />
|
||||
<TodoListItem />
|
||||
<TodoListItem />
|
||||
<TodoListItem />
|
||||
</ul>
|
||||
);
|
||||
}
|
||||
}
|
||||
13
docs/step1-06/demo/src/components/TodoListItem.tsx
Normal file
13
docs/step1-06/demo/src/components/TodoListItem.tsx
Normal file
@@ -0,0 +1,13 @@
|
||||
import React from "react";
|
||||
|
||||
export class TodoListItem extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<li className="todo">
|
||||
<label>
|
||||
<input type="checkbox" /> Todo 1
|
||||
</label>
|
||||
</li>
|
||||
);
|
||||
}
|
||||
}
|
||||
4
docs/step1-06/demo/src/index.tsx
Normal file
4
docs/step1-06/demo/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-06/demo/src/style.css
Normal file
49
docs/step1-06/demo/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-06/demo/step1-06/demo.js
Normal file
32
docs/step1-06/demo/step1-06/demo.js
Normal file
File diff suppressed because one or more lines are too long
1
docs/step1-06/demo/step1-06/demo.js.map
Normal file
1
docs/step1-06/demo/step1-06/demo.js.map
Normal file
File diff suppressed because one or more lines are too long
12
docs/step1-06/exercise/README.md
Normal file
12
docs/step1-06/exercise/README.md
Normal file
@@ -0,0 +1,12 @@
|
||||
## Exercise
|
||||
|
||||
### TodoFooter
|
||||
|
||||
1. Use the provided `itemCount` value drive the number of items left.
|
||||
2. Use a ternary operator to print `item` vs `items` based on if `itemCount > 1`
|
||||
|
||||
### TodoListItem
|
||||
|
||||
1. Pull in `label` and `completed` from props using [destructuring](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment#Object_destructuring)
|
||||
2. Set the todo's text to `label` and the `checked` prop to `completed`
|
||||
> Note that this is only half the work we need to do to make these controlled inputs. What is the other half?
|
||||
1
docs/step1-06/exercise/index.html
Normal file
1
docs/step1-06/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-06/exercise/step1-06/exercise.js"></script><script src="../../markdownReadme/markdownReadme.js"></script></body></html>
|
||||
41
docs/step1-06/exercise/src/TodoApp.tsx
Normal file
41
docs/step1-06/exercise/src/TodoApp.tsx
Normal file
@@ -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<any, any> {
|
||||
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 (
|
||||
<div>
|
||||
<TodoHeader filter={filter} />
|
||||
<TodoList todos={todos} filter={filter} />
|
||||
<TodoFooter todos={todos} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
11
docs/step1-06/exercise/src/components/TodoFooter.tsx
Normal file
11
docs/step1-06/exercise/src/components/TodoFooter.tsx
Normal file
@@ -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 (
|
||||
<footer>
|
||||
<span>4 items left</span>
|
||||
<button className="submit">Clear Completed</button>
|
||||
</footer>
|
||||
);
|
||||
};
|
||||
30
docs/step1-06/exercise/src/components/TodoHeader.tsx
Normal file
30
docs/step1-06/exercise/src/components/TodoHeader.tsx
Normal file
@@ -0,0 +1,30 @@
|
||||
import React from 'react';
|
||||
|
||||
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' ? 'completed' : ''}>all</button>
|
||||
<button className={filter == 'active' ? 'completed' : ''}>active</button>
|
||||
<button className={filter == 'completed' ? 'completed' : ''}>completed</button>
|
||||
</nav>
|
||||
</header>
|
||||
);
|
||||
}
|
||||
_onChange = evt => {
|
||||
this.setState({ labelInput: evt.target.value });
|
||||
};
|
||||
}
|
||||
19
docs/step1-06/exercise/src/components/TodoList.tsx
Normal file
19
docs/step1-06/exercise/src/components/TodoList.tsx
Normal file
@@ -0,0 +1,19 @@
|
||||
import React from 'react';
|
||||
import { TodoListItem } from './TodoListItem';
|
||||
|
||||
export class TodoList extends React.Component<any, any> {
|
||||
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 (
|
||||
<ul className="todos">
|
||||
{filteredTodos.map(id => (
|
||||
<TodoListItem key={id} id={id} {...todos[id]} />
|
||||
))}
|
||||
</ul>
|
||||
);
|
||||
}
|
||||
}
|
||||
13
docs/step1-06/exercise/src/components/TodoListItem.tsx
Normal file
13
docs/step1-06/exercise/src/components/TodoListItem.tsx
Normal file
@@ -0,0 +1,13 @@
|
||||
import React from 'react';
|
||||
|
||||
export class TodoListItem extends React.Component<any, any> {
|
||||
render() {
|
||||
return (
|
||||
<li className="todo">
|
||||
<label>
|
||||
<input type="checkbox" /> Todo 1
|
||||
</label>
|
||||
</li>
|
||||
);
|
||||
}
|
||||
}
|
||||
4
docs/step1-06/exercise/src/index.tsx
Normal file
4
docs/step1-06/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-06/exercise/src/style.css
Normal file
49
docs/step1-06/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-06/exercise/step1-06/exercise.js
Normal file
32
docs/step1-06/exercise/step1-06/exercise.js
Normal file
File diff suppressed because one or more lines are too long
1
docs/step1-06/exercise/step1-06/exercise.js.map
Normal file
1
docs/step1-06/exercise/step1-06/exercise.js.map
Normal file
File diff suppressed because one or more lines are too long
1
docs/step1-06/final/index.html
Normal file
1
docs/step1-06/final/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-06/final/step1-06/final.js"></script><script src="../../markdownReadme/markdownReadme.js"></script></body></html>
|
||||
41
docs/step1-06/final/src/TodoApp.tsx
Normal file
41
docs/step1-06/final/src/TodoApp.tsx
Normal file
@@ -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<any, any> {
|
||||
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 (
|
||||
<div>
|
||||
<TodoHeader filter={filter} />
|
||||
<TodoList todos={todos} filter={filter} />
|
||||
<TodoFooter todos={todos} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
13
docs/step1-06/final/src/components/TodoFooter.tsx
Normal file
13
docs/step1-06/final/src/components/TodoFooter.tsx
Normal file
@@ -0,0 +1,13 @@
|
||||
import React from 'react';
|
||||
|
||||
export const TodoFooter = (props: any) => {
|
||||
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-06/final/src/components/TodoHeader.tsx
Normal file
35
docs/step1-06/final/src/components/TodoHeader.tsx
Normal file
@@ -0,0 +1,35 @@
|
||||
import React from 'react';
|
||||
|
||||
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' ? 'completed' : ''}>all</button>
|
||||
<button className={filter == 'active' ? 'completed' : ''}>active</button>
|
||||
<button className={filter == 'completed' ? 'completed' : ''}>completed</button>
|
||||
</nav>
|
||||
</header>
|
||||
);
|
||||
}
|
||||
_onChange = evt => {
|
||||
this.setState({ labelInput: evt.target.value });
|
||||
};
|
||||
|
||||
_onAdd = () => {
|
||||
console.log(this.state.labelInput);
|
||||
this.setState({ labelInput: '' });
|
||||
};
|
||||
}
|
||||
19
docs/step1-06/final/src/components/TodoList.tsx
Normal file
19
docs/step1-06/final/src/components/TodoList.tsx
Normal file
@@ -0,0 +1,19 @@
|
||||
import React from 'react';
|
||||
import { TodoListItem } from './TodoListItem';
|
||||
|
||||
export class TodoList extends React.Component<any, any> {
|
||||
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 (
|
||||
<ul className="todos">
|
||||
{filteredTodos.map(id => (
|
||||
<TodoListItem key={id} id={id} {...todos[id]} />
|
||||
))}
|
||||
</ul>
|
||||
);
|
||||
}
|
||||
}
|
||||
14
docs/step1-06/final/src/components/TodoListItem.tsx
Normal file
14
docs/step1-06/final/src/components/TodoListItem.tsx
Normal file
@@ -0,0 +1,14 @@
|
||||
import React from 'react';
|
||||
|
||||
export class TodoListItem extends React.Component<any, any> {
|
||||
render() {
|
||||
const { label, completed } = this.props;
|
||||
return (
|
||||
<li className="todo">
|
||||
<label>
|
||||
<input type="checkbox" checked={completed} /> {label}
|
||||
</label>
|
||||
</li>
|
||||
);
|
||||
}
|
||||
}
|
||||
4
docs/step1-06/final/src/index.tsx
Normal file
4
docs/step1-06/final/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-06/final/src/style.css
Normal file
49
docs/step1-06/final/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-06/final/step1-06/final.js
Normal file
32
docs/step1-06/final/step1-06/final.js
Normal file
File diff suppressed because one or more lines are too long
1
docs/step1-06/final/step1-06/final.js.map
Normal file
1
docs/step1-06/final/step1-06/final.js.map
Normal file
File diff suppressed because one or more lines are too long
15
docs/step1-06/index.html
Normal file
15
docs/step1-06/index.html
Normal file
@@ -0,0 +1,15 @@
|
||||
<html>
|
||||
<head>
|
||||
<link rel="stylesheet" href="../assets/shared.css" />
|
||||
<link rel="stylesheet" href="https://static2.sharepointonline.com/files/fabric/office-ui-fabric-core/9.6.1/css/fabric.min.css" />
|
||||
</head>
|
||||
|
||||
<body class="ms-Fabric">
|
||||
<div class="Container">
|
||||
<ul class="Tiles">
|
||||
<li class="Tile"><a href="./demo/index.html" class="Tile-link">Demo Start</a></li>
|
||||
<li class="Tile"><a href="./final/index.html" class="Tile-link">Final</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user