diff --git a/.node-version b/.node-version
new file mode 100644
index 0000000..826f5ce
--- /dev/null
+++ b/.node-version
@@ -0,0 +1 @@
+6.6.0
diff --git a/index.html b/index.html
index 5489acf..4eb5886 100644
--- a/index.html
+++ b/index.html
@@ -4,6 +4,11 @@
Minimal React/Redux Boilerplate
+
diff --git a/src/components/filter-link/index.js b/src/components/filter-link/index.js
new file mode 100644
index 0000000..e5e93b3
--- /dev/null
+++ b/src/components/filter-link/index.js
@@ -0,0 +1,14 @@
+import React from 'react';
+
+export default ({
+ handler = e => console.log( 'no handler method defined' ),
+ filter,
+ children
+}) => {
+ return (
+ {
+ e.preventDefault();
+ handler()
+ }}>{ children }
+ );
+}
diff --git a/src/components/filter-link/spec.js b/src/components/filter-link/spec.js
new file mode 100644
index 0000000..9f156b6
--- /dev/null
+++ b/src/components/filter-link/spec.js
@@ -0,0 +1,16 @@
+import React from 'react';
+import test from 'tape';
+import { shallow } from 'enzyme';
+
+import FilterLink from './';
+
+test( 'FilterLink', t => {
+ t.plan( 1 );
+
+ const wrapper = shallow( );
+ const expected = true;
+ const actual = wrapper.is( 'a' );
+
+ t.ok( actual === expected, 'renders a link' );
+});
+
diff --git a/src/components/filter-list/index.js b/src/components/filter-list/index.js
new file mode 100644
index 0000000..09fdd87
--- /dev/null
+++ b/src/components/filter-list/index.js
@@ -0,0 +1,45 @@
+import React from 'react';
+import { connect } from 'react-redux';
+
+import showAll from '../../store/actions/show-all';
+import showActive from '../../store/actions/show-active';
+import showCompleted from '../../store/actions/show-completed';
+
+import FilterLink from '../filter-link';
+
+export class FilterList extends React.Component {
+ constructor ( props ) {
+ super( props )
+ }
+
+ render () {
+ const {
+ onShowAll = e => e,
+ onShowActive = e => e,
+ onShowCompleted = e => e,
+ visibilityFilter = ''
+ } = this.props;
+
+ return (
+
+ - All
+ - Active
+ - Completed
+
+ );
+ }
+}
+
+export const mapStateToProps = ({ todos, visibilityFilter }) => ({
+ todos,
+ visibilityFilter,
+});
+
+export const mapDispatchToProps = dispatch => ({
+ onShowAll: () => dispatch( showAll() ),
+ onShowActive: () => dispatch( showActive() ),
+ onShowCompleted: () => dispatch( showCompleted() ),
+});
+
+export default connect( mapStateToProps, mapDispatchToProps )( FilterList );
+
diff --git a/src/components/filter-list/spec.js b/src/components/filter-list/spec.js
new file mode 100644
index 0000000..e69de29
diff --git a/src/components/todo-item/index.js b/src/components/todo-item/index.js
index 9ceeb49..d9010bb 100644
--- a/src/components/todo-item/index.js
+++ b/src/components/todo-item/index.js
@@ -2,11 +2,13 @@ import React from 'react';
export default ({
remove = e => console.log( 'no remove method defined' ),
+ toggle = e => console.log( 'no toggle method defined' ),
name,
+ completed
}) => {
return (
-
+
{name}
X
diff --git a/src/components/todo-items/index.js b/src/components/todo-items/index.js
index c685870..5769e19 100644
--- a/src/components/todo-items/index.js
+++ b/src/components/todo-items/index.js
@@ -4,11 +4,29 @@ import TodoItem from '../todo-item';
export default ({
items = [],
+ filter = 'SHOW_ALL',
onRemove = e => console.log( 'no onRemove method defined' ),
+ onToggle = e => console.log( 'no onToggle method defined' ),
}) => {
- const nodes = items.map( ( todo, i ) => (
+ const getVisibleTodos = (todos, filter) => {
+ switch ( filter ) {
+ case 'SHOW_ALL':
+ return todos;
+ case 'SHOW_ACTIVE':
+ return todos.filter( t => !t.completed );
+ case 'SHOW_COMPLETED':
+ return todos.filter( t => t.completed );
+ default:
+ return todos;
+ }
+ }
+
+ const nodes = getVisibleTodos(items, filter).map( ( todo, i ) => (
- onRemove( todo )} />
+ onRemove( todo )}
+ toggle={() => onToggle( todo )}
+ />
));
diff --git a/src/components/todo-list/index.js b/src/components/todo-list/index.js
index 46f80e0..be48255 100644
--- a/src/components/todo-list/index.js
+++ b/src/components/todo-list/index.js
@@ -3,7 +3,10 @@ import { connect } from 'react-redux';
import addItem from '../../store/actions/add-item';
import removeItem from '../../store/actions/remove-item';
+import toggleItem from '../../store/actions/toggle-item';
+
import TodoItems from '../todo-items';
+import FilterList from '../filter-list';
export class TodoList extends React.Component {
constructor ( props ) {
@@ -22,7 +25,9 @@ export class TodoList extends React.Component {
const {
onAdd = e => e,
onRemove = e => e,
+ onToggle = e => e,
todos = [],
+ visibilityFilter = 'SHOW_ALL'
} = this.props;
const {
@@ -47,11 +52,15 @@ export class TodoList extends React.Component {
?
:
You have no todos.
}
+
+
);
}
@@ -73,13 +82,15 @@ export class TodoList extends React.Component {
}
}
-export const mapStateToProps = ({ todos }) => ({
+export const mapStateToProps = ({ todos, visibilityFilter }) => ({
todos,
+ visibilityFilter,
});
export const mapDispatchToProps = dispatch => ({
onAdd: v => dispatch( addItem( v ) ),
- onRemove: todo => dispatch( removeItem( todo ) )
+ onRemove: todo => dispatch( removeItem( todo ) ),
+ onToggle: todo => dispatch( toggleItem( todo ) ),
});
export default connect( mapStateToProps, mapDispatchToProps )( TodoList );
diff --git a/src/store/actions/add-item/index.js b/src/store/actions/add-item/index.js
index 8b524d1..1ca290d 100644
--- a/src/store/actions/add-item/index.js
+++ b/src/store/actions/add-item/index.js
@@ -1,5 +1,6 @@
export default name => ({
type: 'ADD_TODO_ITEM',
name,
+ completed: false
});
diff --git a/src/store/actions/show-active/index.js b/src/store/actions/show-active/index.js
new file mode 100644
index 0000000..b764770
--- /dev/null
+++ b/src/store/actions/show-active/index.js
@@ -0,0 +1,4 @@
+export default () => ({
+ type: 'SET_VISIBILITY_FILTER',
+ filter: 'SHOW_ACTIVE'
+});
diff --git a/src/store/actions/show-all/index.js b/src/store/actions/show-all/index.js
new file mode 100644
index 0000000..72fa750
--- /dev/null
+++ b/src/store/actions/show-all/index.js
@@ -0,0 +1,4 @@
+export default () => ({
+ type: 'SET_VISIBILITY_FILTER',
+ filter: 'SHOW_ALL'
+});
diff --git a/src/store/actions/show-completed/index.js b/src/store/actions/show-completed/index.js
new file mode 100644
index 0000000..fcbb3c5
--- /dev/null
+++ b/src/store/actions/show-completed/index.js
@@ -0,0 +1,4 @@
+export default () => ({
+ type: 'SET_VISIBILITY_FILTER',
+ filter: 'SHOW_COMPLETED'
+});
diff --git a/src/store/actions/toggle-item/index.js b/src/store/actions/toggle-item/index.js
new file mode 100644
index 0000000..f0d7ef7
--- /dev/null
+++ b/src/store/actions/toggle-item/index.js
@@ -0,0 +1,5 @@
+export default todo => ({
+ type: 'TOGGLE_TODO_ITEM',
+ todo,
+});
+
diff --git a/src/store/reducers/index.js b/src/store/reducers/index.js
index a876f0c..8806bec 100644
--- a/src/store/reducers/index.js
+++ b/src/store/reducers/index.js
@@ -1,5 +1,6 @@
import { combineReducers } from 'redux';
import todos from './todos';
+import visibilityFilter from './visibility-filter'
-export default combineReducers({ todos });
+export default combineReducers({ todos, visibilityFilter });
diff --git a/src/store/reducers/todos.js b/src/store/reducers/todos.js
index e71dda3..ef240a4 100644
--- a/src/store/reducers/todos.js
+++ b/src/store/reducers/todos.js
@@ -2,12 +2,20 @@ export default ( todos = [], { type, ...payload } ) => {
switch ( type ) {
case 'ADD_TODO_ITEM':
return [ ...todos, payload ];
- break;
case 'REMOVE_TODO_ITEM':
return todos.filter( t => t !== payload.todo );
- break;
+ case 'TOGGLE_TODO_ITEM':
+ return todos.map( t => {
+ if (t.name !== payload.todo.name) {
+ return t;
+ }
+ return {
+ ...t,
+ completed: !t.completed
+ }
+ })
+ default:
+ return todos;
}
-
- return todos;
};
diff --git a/src/store/reducers/visibility-filter.js b/src/store/reducers/visibility-filter.js
new file mode 100644
index 0000000..1cc03c8
--- /dev/null
+++ b/src/store/reducers/visibility-filter.js
@@ -0,0 +1,8 @@
+export default ( visibilityFilter = 'SHOW_ALL', action ) => {
+ switch ( action.type ) {
+ case 'SET_VISIBILITY_FILTER':
+ return action.filter;
+ default:
+ return visibilityFilter;
+ }
+};