diff --git a/.babelrc b/.babelrc new file mode 100644 index 0000000..d0962f5 --- /dev/null +++ b/.babelrc @@ -0,0 +1,8 @@ +{ + "presets": ["es2015", "react"], + "env": { + "development": { + "presets": ["react-hmre"] + } + } +} diff --git a/actions/index.js b/actions/index.js new file mode 100644 index 0000000..2e52a7c --- /dev/null +++ b/actions/index.js @@ -0,0 +1,25 @@ +import * as types from '../constants/ActionTypes' + +export function addTodo(text) { + return { type: types.ADD_TODO, text } +} + +export function deleteTodo(id) { + return { type: types.DELETE_TODO, id } +} + +export function editTodo(id, text) { + return { type: types.EDIT_TODO, id, text } +} + +export function completeTodo(id) { + return { type: types.COMPLETE_TODO, id } +} + +export function completeAll() { + return { type: types.COMPLETE_ALL } +} + +export function clearCompleted() { + return { type: types.CLEAR_COMPLETED } +} diff --git a/components/Footer.js b/components/Footer.js new file mode 100644 index 0000000..8ae9c59 --- /dev/null +++ b/components/Footer.js @@ -0,0 +1,73 @@ +import React, { PropTypes, Component } from 'react' +import classnames from 'classnames' +import { SHOW_ALL, SHOW_COMPLETED, SHOW_ACTIVE } from '../constants/TodoFilters' + +const FILTER_TITLES = { + [SHOW_ALL]: 'All', + [SHOW_ACTIVE]: 'Active', + [SHOW_COMPLETED]: 'Completed' +} + +class Footer extends Component { + renderTodoCount() { + const { activeCount } = this.props + const itemWord = activeCount === 1 ? 'item' : 'items' + + return ( + + {activeCount || 'No'} {itemWord} left + + ) + } + + renderFilterLink(filter) { + const title = FILTER_TITLES[filter] + const { filter: selectedFilter, onShow } = this.props + + return ( + onShow(filter)}> + {title} + + ) + } + + renderClearButton() { + const { completedCount, onClearCompleted } = this.props + if (completedCount > 0) { + return ( + + ) + } + } + + render() { + return ( + + ) + } +} + +Footer.propTypes = { + completedCount: PropTypes.number.isRequired, + activeCount: PropTypes.number.isRequired, + filter: PropTypes.string.isRequired, + onClearCompleted: PropTypes.func.isRequired, + onShow: PropTypes.func.isRequired +} + +export default Footer diff --git a/components/Header.js b/components/Header.js new file mode 100644 index 0000000..2fa405b --- /dev/null +++ b/components/Header.js @@ -0,0 +1,27 @@ +import React, { PropTypes, Component } from 'react' +import TodoTextInput from './TodoTextInput' + +class Header extends Component { + handleSave(text) { + if (text.length !== 0) { + this.props.addTodo(text) + } + } + + render() { + return ( +
+

todos

+ +
+ ) + } +} + +Header.propTypes = { + addTodo: PropTypes.func.isRequired +} + +export default Header; diff --git a/components/MainSection.js b/components/MainSection.js new file mode 100644 index 0000000..f5c2804 --- /dev/null +++ b/components/MainSection.js @@ -0,0 +1,83 @@ +import React, { Component, PropTypes } from 'react' +import TodoItem from './TodoItem' +import Footer from './Footer' +import { SHOW_ALL, SHOW_COMPLETED, SHOW_ACTIVE } from '../constants/TodoFilters' + +const TODO_FILTERS = { + [SHOW_ALL]: () => true, + [SHOW_ACTIVE]: todo => !todo.completed, + [SHOW_COMPLETED]: todo => todo.completed +} + +class MainSection extends Component { + constructor(props, context) { + super(props, context) + this.state = { filter: SHOW_ALL } + } + + handleClearCompleted() { + this.props.actions.clearCompleted() + } + + handleShow(filter) { + this.setState({ filter }) + } + + renderToggleAll(completedCount) { + const { todos, actions } = this.props + if (todos.length > 0) { + return ( + + ) + } + } + + renderFooter(completedCount) { + const { todos } = this.props + const { filter } = this.state + const activeCount = todos.length - completedCount + + if (todos.length) { + return ( +