├── .editorconfig
├── .eslintrc.js
├── .gitignore
├── babel.config.js
├── config
├── dev.js
├── index.js
└── prod.js
├── package.json
├── project.config.json
├── src
├── actions
│ └── index.js
├── app.config.js
├── app.js
├── app.scss
├── components
│ ├── Footer
│ │ ├── Footer.js
│ │ └── Footer.scss
│ ├── TodoItem
│ │ ├── TodoItem.js
│ │ └── TodoItem.scss
│ └── TodoTextInput
│ │ ├── TodoTextInput.js
│ │ └── TodoTextInput.scss
├── constants
│ ├── ActionTypes.js
│ └── TodoFilters.js
├── containers
│ ├── FilterLink
│ │ ├── FilterLink.js
│ │ └── FilterLink.scss
│ ├── Header
│ │ ├── Header.js
│ │ └── Header.scss
│ ├── MainSection
│ │ ├── MainSection.js
│ │ └── MainSection.scss
│ └── TodoList
│ │ ├── TodoList.js
│ │ └── TodoList.scss
├── index.html
├── pages
│ └── index
│ │ ├── index.config.js
│ │ ├── index.js
│ │ └── index.scss
├── reducers
│ ├── index.js
│ ├── todos.js
│ └── visibilityFilter.js
├── selectors
│ └── index.js
├── store
│ └── index.js
└── utils
│ └── index.js
└── yarn.lock
/.editorconfig:
--------------------------------------------------------------------------------
1 | # http://editorconfig.org
2 | root = true
3 |
4 | [*]
5 | indent_style = space
6 | indent_size = 2
7 | charset = utf-8
8 | trim_trailing_whitespace = true
9 | insert_final_newline = true
10 |
11 | [*.md]
12 | trim_trailing_whitespace = false
13 |
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | 'extends': [
3 | // add more generic rulesets here, such as:
4 | // 'eslint:recommended',
5 | 'taro/react'
6 | ]
7 | }
8 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | dist/
2 | .temp/
3 | .rn_temp/
4 | node_modules/
5 | .DS_Store
6 |
--------------------------------------------------------------------------------
/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | presets: [
3 | ['taro', {
4 | framework: 'react',
5 | ts: false
6 | }]
7 | ]
8 | }
--------------------------------------------------------------------------------
/config/dev.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | env: {
3 | NODE_ENV: '"development"'
4 | },
5 | defineConstants: {
6 | },
7 | mini: {},
8 | h5: {}
9 | }
10 |
--------------------------------------------------------------------------------
/config/index.js:
--------------------------------------------------------------------------------
1 | const config = {
2 | projectName: 'todo-list',
3 | date: '2018-7-9',
4 | designWidth: 750,
5 | sourceRoot: 'src',
6 | outputRoot: 'dist',
7 | framework: 'react',
8 | defineConstants: {
9 | },
10 | mini: {
11 |
12 | },
13 | h5: {
14 | publicPath: '/',
15 | staticDirectory: 'static',
16 | esnextModules: ['taro-ui'],
17 | module: {
18 | postcss: {
19 | autoprefixer: {
20 | enable: true
21 | }
22 | }
23 | }
24 | }
25 | }
26 |
27 | module.exports = function (merge) {
28 | if (process.env.NODE_ENV === 'development') {
29 | return merge({}, config, require('./dev'))
30 | }
31 | return merge({}, config, require('./prod'))
32 | }
33 |
--------------------------------------------------------------------------------
/config/prod.js:
--------------------------------------------------------------------------------
1 | const wba = require('webpack-bundle-analyzer').BundleAnalyzerPlugin
2 |
3 | module.exports = {
4 | env: {
5 | NODE_ENV: '"production"'
6 | },
7 | defineConstants: {
8 | },
9 | mini: {},
10 | h5: {
11 | webpackChain (chain) {
12 | chain.plugin('analyzer')
13 | .use(wba, [])
14 | },
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "todo-list",
3 | "version": "1.0.0",
4 | "private": true,
5 | "description": "todo-list",
6 | "main": "index.js",
7 | "scripts": {
8 | "build:rn": "taro build --type rn",
9 | "build:weapp": "taro build --type weapp",
10 | "build:h5": "taro build --type h5",
11 | "dev:rn": "npm run build:rn -- --watch",
12 | "dev:weapp": "npm run build:weapp -- --watch",
13 | "dev:h5": "npm run build:h5 -- --watch",
14 | "test": "echo \"Error: no test specified\" && exit 1"
15 | },
16 | "author": "",
17 | "license": "MIT",
18 | "dependencies": {
19 | "@babel/runtime": "7.11.2",
20 | "@tarojs/components": "3.4.9",
21 | "@tarojs/helper": "3.4.9",
22 | "@tarojs/plugin-platform-weapp": "3.4.9",
23 | "@tarojs/plugin-platform-alipay": "3.4.9",
24 | "@tarojs/plugin-platform-tt": "3.4.9",
25 | "@tarojs/plugin-platform-swan": "3.4.9",
26 | "@tarojs/plugin-platform-jd": "3.4.9",
27 | "@tarojs/plugin-platform-qq": "3.4.9",
28 | "@tarojs/router": "3.4.9",
29 | "@tarojs/react": "3.4.9",
30 | "@tarojs/runtime": "3.4.9",
31 | "@tarojs/shared": "3.4.9",
32 | "@tarojs/taro": "3.4.9",
33 | "@tarojs/taro-h5": "3.4.9",
34 | "@tarojs/plugin-framework-react": "3.4.9",
35 | "classnames": "2.2.6",
36 | "react": "^17.0.0",
37 | "react-dom": "^17.0.0",
38 | "react-redux": "^7.2.0",
39 | "redux": "^4.0.0",
40 | "redux-devtools-extension": "2.13.8",
41 | "redux-logger": "3.0.6",
42 | "redux-thunk": "2.3.0",
43 | "reselect": "4.0.0"
44 | },
45 | "devDependencies": {
46 | "@babel/core": "7.11.4",
47 | "@types/webpack-env": "1.15.2",
48 | "@types/react": "16.9.48",
49 | "@types/redux-actions": "2.6.1",
50 | "@tarojs/mini-runner": "3.4.9",
51 | "@tarojs/webpack-runner": "3.4.9",
52 | "@pmmmwh/react-refresh-webpack-plugin": "0.5.5",
53 | "react-refresh": "0.11.0",
54 | "babel-preset-taro": "3.4.9",
55 | "eslint": "^8.12.0",
56 | "eslint-config-taro": "3.4.9",
57 | "eslint-plugin-import": "^2.12.0",
58 | "eslint-plugin-react": "^7.8.2",
59 | "eslint-plugin-react-hooks": "^4.2.0",
60 | "stylelint": "13.6.1",
61 | "typescript": "4.0.2",
62 | "webpack": "4.46.0",
63 | "webpack-bundle-analyzer": "3.8.0"
64 | },
65 | "resolutions": {
66 | "@types/react": "^17.0.2"
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/project.config.json:
--------------------------------------------------------------------------------
1 | {
2 | "miniprogramRoot": "dist/",
3 | "projectname": "open-todo",
4 | "description": "todo-list",
5 | "appid": "touristappid",
6 | "setting": {
7 | "urlCheck": true,
8 | "scopeDataCheck": false,
9 | "coverView": true,
10 | "es6": false,
11 | "postcss": false,
12 | "compileHotReLoad": false,
13 | "preloadBackgroundData": false,
14 | "minified": false,
15 | "autoAudits": false,
16 | "newFeature": true,
17 | "uglifyFileName": false,
18 | "uploadWithSourceMap": true,
19 | "useIsolateContext": true,
20 | "nodeModules": false,
21 | "enhance": false,
22 | "useCompilerModule": false,
23 | "userConfirmedUseCompilerModuleSwitch": false,
24 | "showShadowRootInWxmlPanel": true
25 | },
26 | "compileType": "miniprogram",
27 | "simulatorType": "wechat",
28 | "simulatorPluginLibVersion": {},
29 | "condition": {}
30 | }
--------------------------------------------------------------------------------
/src/actions/index.js:
--------------------------------------------------------------------------------
1 | import * as types from '../constants/ActionTypes'
2 |
3 | export const addTodo = text => ({ type: types.ADD_TODO, text })
4 | export const deleteTodo = id => ({ type: types.DELETE_TODO, id })
5 | export const editTodo = (id, text) => ({ type: types.EDIT_TODO, id, text })
6 | export const completeTodo = id => ({ type: types.COMPLETE_TODO, id })
7 | export const completeAllTodos = () => ({ type: types.COMPLETE_ALL_TODOS })
8 | export const clearCompleted = () => ({ type: types.CLEAR_COMPLETED })
9 | export const setVisibilityFilter = filter => ({ type: types.SET_VISIBILITY_FILTER, filter})
10 |
--------------------------------------------------------------------------------
/src/app.config.js:
--------------------------------------------------------------------------------
1 | export default {
2 | pages: [
3 | 'pages/index/index'
4 | ],
5 | window: {
6 | backgroundTextStyle: 'light',
7 | navigationBarBackgroundColor: '#fff',
8 | navigationBarTitleText: 'WeChat',
9 | navigationBarTextStyle: 'black'
10 | }
11 | }
--------------------------------------------------------------------------------
/src/app.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { Provider } from 'react-redux'
3 | import { createStore, applyMiddleware } from 'redux'
4 | import { composeWithDevTools } from 'redux-devtools-extension'
5 |
6 | import reducer from './reducers'
7 |
8 | import './app.scss'
9 |
10 | const store = createStore(reducer, composeWithDevTools(
11 | applyMiddleware()
12 | // other store enhancers if any
13 | ))
14 |
15 | class App extends React.Component {
16 | render () {
17 | return (
18 |
19 | {this.props.children}
20 |
21 | )
22 | }
23 | }
24 |
25 | export default App
26 |
--------------------------------------------------------------------------------
/src/app.scss:
--------------------------------------------------------------------------------
1 | html,
2 | body {
3 | margin: 0;
4 | padding: 0;
5 | }
6 |
7 | // 元素选择器
8 | button {
9 | margin: 0;
10 | padding: 0;
11 | border: 0;
12 | //background: none;
13 | // font-size: 100%;
14 | vertical-align: baseline;
15 | font-family: inherit;
16 | font-weight: inherit;
17 | color: inherit;
18 | //-webkit-appearance: none;
19 | //appearance: none;
20 | //-webkit-font-smoothing: antialiased;
21 | //-moz-osx-font-smoothing: grayscale;
22 | //&::after {
23 | // border: 0;
24 | //}
25 | }
26 |
27 | body, page {
28 | font: 28px 'Helvetica Neue', Helvetica, Arial, sans-serif;
29 | background: #f5f5f5;
30 | color: #4d4d4d;
31 | margin: 0 auto;
32 | //-webkit-font-smoothing: antialiased;
33 | //-moz-osx-font-smoothing: grayscale;
34 | font-weight: 300;
35 | }
36 |
37 | :focus {
38 | outline: 0;
39 | }
40 |
41 | .hidden {
42 | display: none;
43 | }
44 |
45 | .todoapp {
46 | background: #fff;
47 | margin: 210px 0 40px 0;
48 | position: relative;
49 | //box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.2),
50 | // 0 25px WebkitTextRendering50px 0 rgba(0, 0, 0, 0.1);
51 | }
52 |
53 | input::-webkit-input-placeholder {
54 | font-style: italic;
55 | font-weight: 300;
56 | color: #e6e6e6;
57 | }
58 |
59 | input::-moz-placeholder {
60 | font-style: italic;
61 | font-weight: 300;
62 | color: #e6e6e6;
63 | }
64 |
65 | //input::input-placeholder {
66 | // font-style: italic;
67 | // font-weight: 300;
68 | // color: #e6e6e6;
69 | //}
70 |
71 | .info {
72 | margin: 130px auto 0;
73 | color: #bfbfbf;
74 | //font-size: 20px;
75 | text-shadow: 0 2px 0 rgba(255, 255, 255, 0.5);
76 | //text-align: center;
77 | }
78 |
79 | .info p {
80 | line-height: 1;
81 | }
82 |
83 | .info a {
84 | color: inherit;
85 | text-decoration: none;
86 | font-weight: 400;
87 | }
88 |
89 | .info a:hover {
90 | text-decoration: underline;
91 | }
92 |
93 | .toggle-all,
94 | .todo-list .list-item .toggle {
95 | //background: none;
96 | }
97 |
98 | .todo-list .list-item .toggle {
99 | //height: 80px;
100 | }
101 |
102 | .weui-cells_checkbox {
103 | margin-right: 10px;
104 | }
105 |
--------------------------------------------------------------------------------
/src/components/Footer/Footer.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { View, Text } from '@tarojs/components'
3 |
4 | import './Footer.scss'
5 |
6 | export default class Footer extends React.Component {
7 |
8 | onClearCompleted = () => {
9 | console.log('onClearCompleted')
10 | this.props.onClearCompleted()
11 | }
12 |
13 | render () {
14 | const {activeCount, completedCount} = this.props
15 | const itemWord = activeCount === 1 ? 'item' : 'items'
16 | return (
17 |
18 |
19 |
20 | {activeCount || 'No'}{' '}{itemWord} left
21 |
22 | {
23 | !!completedCount &&
24 |
28 | Clear completed
29 |
30 | }
31 |
32 |
33 | )
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/src/components/Footer/Footer.scss:
--------------------------------------------------------------------------------
1 | /*postcss-pxtransform rn eject enable*/
2 | .footer:before {
3 | content: '';
4 | position: absolute;
5 | z-index: -1;
6 | right: 0;
7 | bottom: 0;
8 | left: 0;
9 | height: 100px;
10 | overflow: hidden;
11 | box-shadow: 0 1px 1px rgba(0, 0, 0, 0.2),
12 | 0 8px 0 -3px #f6f6f6,
13 | 0 9px 1px -3px rgba(0, 0, 0, 0.2),
14 | 0 16px 0 -6px #f6f6f6,
15 | 0 17px 2px -6px rgba(0, 0, 0, 0.2);
16 | }
17 |
18 | /*postcss-pxtransform rn eject disable*/
19 |
20 | .footer {
21 | padding: 20px 30px;
22 | border-top-width: 2px;
23 | border-top-color: #e6e6e6;
24 | border-style: solid;
25 | border-bottom-width: 0;
26 | border-left-width: 0;
27 | border-right-width: 0;
28 | }
29 |
30 | .footer-content {
31 | display: flex;
32 | flex-direction: row;
33 | }
34 |
35 | .todo-count {
36 | flex: 1;
37 | color: #777;
38 | }
39 |
40 | .todo-count strong {
41 | font-weight: 300;
42 | }
43 |
44 | .clear-completed-text {
45 | display: flex;
46 | color: #777;
47 | justify-content: flex-end;
48 | }
49 |
50 | @media (max-width: 430px) {
51 | .footer {
52 | height: 50px;
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/src/components/TodoItem/TodoItem.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { View, Checkbox, Label, CheckboxGroup, Text } from '@tarojs/components'
3 | import classnames from 'classnames'
4 |
5 | import TodoTextInput from '../TodoTextInput/TodoTextInput'
6 | import './TodoItem.scss'
7 |
8 | export default class TodoItem extends React.Component {
9 | static defaultProps = {
10 | todo: {}
11 | }
12 |
13 | state = {
14 | editing: false
15 | }
16 |
17 | handleSubmit = () => {
18 | const val = this.state.editText.trim()
19 | if (val) {
20 | this.props.onSave(val)
21 | this.setState({editText: val})
22 | } else {
23 | this.props.onDestroy()
24 | }
25 | }
26 |
27 | handleDoubleClick = () => {
28 | this.setState({editing: true})
29 | }
30 |
31 | handleSave = (id, text) => {
32 | if (text.length === 0) {
33 | this.props.onDeleteTodo(id)
34 | } else {
35 | this.props.onEditTodo(id, text)
36 | }
37 | this.setState({editing: false})
38 | }
39 |
40 | handleCompleteTodo = (todo) => {
41 | console.log('handleCompleteTodo')
42 | this.props.onCompleteTodo(todo.id)
43 | }
44 |
45 | render () {
46 | const {todo} = this.props
47 |
48 | let element
49 | if (this.state.editing) {
50 | element = (
51 |
52 | )
53 | } else {
54 | element = (
55 |
56 |
57 |
61 |
62 |
63 | )
64 | }
65 |
66 | return (
67 |
73 | {element}
74 |
75 | )
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/src/components/TodoItem/TodoItem.scss:
--------------------------------------------------------------------------------
1 | .checkbox-label {
2 | display: flex;
3 | margin: 10px;
4 | flex-direction: row;
5 | align-items: center;
6 | }
7 |
8 | .checkbox {
9 | margin-right: 10px;
10 | }
11 |
--------------------------------------------------------------------------------
/src/components/TodoTextInput/TodoTextInput.js:
--------------------------------------------------------------------------------
1 | import Taro from '@tarojs/taro'
2 | import React from 'react'
3 | import classnames from 'classnames'
4 | import { Input } from '@tarojs/components'
5 |
6 | import './TodoTextInput.scss'
7 |
8 | export default class TodoTextInput extends React.Component {
9 | state = {
10 | todoText: this.props.text || ''
11 | }
12 |
13 | handleSubmit = e => {
14 | console.log('handleSubmit', e)
15 | const text = e.target.value.trim()
16 | this.props.onSave(text)
17 | if (this.props.newTodo) {
18 | this.setState({todoText: ''})
19 | }
20 | }
21 |
22 | handleSubmitKey = e => {
23 | console.log('handleSubmitKey', e)
24 | const text = e.target.value.trim()
25 | if (e.which === 13) {
26 | this.props.onSave(text)
27 | if (this.props.newTodo) {
28 | this.setState({todoText: ''})
29 | }
30 | }
31 | }
32 |
33 | handleChange = e => {
34 | if (Taro.getEnv() === Taro.ENV_TYPE.WEAPP) return
35 | console.log('handleChange', e)
36 | this.setState({todoText: e.target.value})
37 | }
38 |
39 | handleInput = e => {
40 | console.log('handleChange', e)
41 | this.setState({todoText: e.target.value})
42 | }
43 |
44 | handleBlur = e => {
45 | console.log('handleBlur', e)
46 | if (!this.props.newTodo) {
47 | this.props.onSave(e.target.value)
48 | }
49 | }
50 |
51 | render () {
52 | return (
53 |
71 | )
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/src/components/TodoTextInput/TodoTextInput.scss:
--------------------------------------------------------------------------------
1 | .new-todo {
2 | padding: 32px;
3 | font-size: 48px;
4 | font-style: italic;
5 | font-weight: 300;
6 | color: black;
7 | box-shadow: none;
8 | background: rgba(0, 0, 0, 0.003);
9 | }
10 |
11 |
--------------------------------------------------------------------------------
/src/constants/ActionTypes.js:
--------------------------------------------------------------------------------
1 | export const ADD_TODO = 'ADD_TODO'
2 | export const DELETE_TODO = 'DELETE_TODO'
3 | export const EDIT_TODO = 'EDIT_TODO'
4 | export const COMPLETE_TODO = 'COMPLETE_TODO'
5 | export const COMPLETE_ALL_TODOS = 'COMPLETE_ALL_TODOS'
6 | export const CLEAR_COMPLETED = 'CLEAR_COMPLETED'
7 | export const SET_VISIBILITY_FILTER = 'SET_VISIBILITY_FILTER'
8 |
--------------------------------------------------------------------------------
/src/constants/TodoFilters.js:
--------------------------------------------------------------------------------
1 | export const SHOW_ALL = 'show_all'
2 | export const SHOW_COMPLETED = 'show_completed'
3 | export const SHOW_ACTIVE = 'show_active'
4 |
--------------------------------------------------------------------------------
/src/containers/FilterLink/FilterLink.js:
--------------------------------------------------------------------------------
1 | import { connect } from 'react-redux'
2 | import React from 'react'
3 | import { Text } from '@tarojs/components'
4 | import classnames from 'classnames'
5 |
6 | import { setVisibilityFilter } from '../../actions'
7 | import { SHOW_ALL, SHOW_COMPLETED, SHOW_ACTIVE } from '../../constants/TodoFilters'
8 |
9 | import './FilterLink.scss'
10 |
11 | const FILTER_TITLES = {
12 | [SHOW_ALL]: 'All',
13 | [SHOW_ACTIVE]: 'Active',
14 | [SHOW_COMPLETED]: 'Completed'
15 | }
16 |
17 | const mapStateToProps = ({visibilityFilter}) => ({
18 | visibilityFilter
19 | })
20 |
21 | const mapDispatchToProps = (dispatch) => ({
22 | setFilter: (filter) => {
23 | dispatch(setVisibilityFilter(filter))
24 | }
25 | })
26 |
27 | @connect(
28 | mapStateToProps,
29 | mapDispatchToProps
30 | )
31 | class FilterLink extends React.Component {
32 | onClickHandler = () => {
33 | console.log('onClickHandler', this.props.filter)
34 | this.props.setFilter(this.props.filter)
35 | }
36 |
37 | render () {
38 | const {filter, visibilityFilter} = this.props
39 | const text = FILTER_TITLES[filter]
40 | const active = visibilityFilter === filter
41 | return (
42 |
46 | {text}
47 |
48 | )
49 | }
50 | }
51 |
52 | export default FilterLink
--------------------------------------------------------------------------------
/src/containers/FilterLink/FilterLink.scss:
--------------------------------------------------------------------------------
1 | .filters-link {
2 | display: flex;
3 | padding: 6px 14px;
4 | // transparent 不支持?? TODO
5 | border-width: 2px;
6 | border-style: solid;
7 | border-color: transparent;
8 | border-radius: 6px;
9 | }
10 |
11 | .filters-link:hover {
12 | border-color: #efd5d5;
13 | }
14 |
15 | .selected {
16 | border-color: #efd5d5;
17 | border-width: 2px;
18 | }
19 |
--------------------------------------------------------------------------------
/src/containers/Header/Header.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { View, Text } from '@tarojs/components'
3 | import { connect } from 'react-redux'
4 | import { bindActionCreators } from 'redux'
5 |
6 | import TodoTextInput from '../../components/TodoTextInput/TodoTextInput'
7 | import FilterLink from '../FilterLink/FilterLink'
8 | import { SHOW_ALL, SHOW_COMPLETED, SHOW_ACTIVE } from '../../constants/TodoFilters'
9 | import * as TodoActions from '../../actions'
10 | import './Header.scss'
11 |
12 | const FILTER_TITLES = {
13 | [SHOW_ALL]: 'All',
14 | [SHOW_ACTIVE]: 'Active',
15 | [SHOW_COMPLETED]: 'Completed'
16 | }
17 |
18 | const mapDispatchToProps = dispatch => ({
19 | actions: bindActionCreators(TodoActions, dispatch)
20 | })
21 |
22 | @connect(
23 | () => ({}),
24 | mapDispatchToProps
25 | )
26 | class Header extends React.Component {
27 | onCheckClickHandler = () => {
28 | console.log('onCheckClickHandler')
29 | const {actions} = this.props
30 | actions.completeAllTodos()
31 | }
32 |
33 | onSaveHandler = (text) => {
34 | if (text.length !== 0) {
35 | this.props.actions.addTodo(text)
36 | }
37 | }
38 |
39 | render () {
40 | return (
41 |
42 |
43 | todos
44 |
45 |
46 | {Object.keys(FILTER_TITLES).map((filter, index) =>
47 |
48 |
49 |
50 | )}
51 |
52 |
53 | ❯
54 |
55 |
61 |
62 |
63 |
64 | )
65 | }
66 | }
67 |
68 | export default Header
69 |
--------------------------------------------------------------------------------
/src/containers/Header/Header.scss:
--------------------------------------------------------------------------------
1 | .title {
2 | width: 100%;
3 | font-size: 100px;
4 | font-weight: 100;
5 | text-align: center;
6 | color: rgba(175, 47, 47, 0.55);
7 | //-webkit-text-rendering: optimizeLegibility;
8 | //-moz-text-rendering: optimizeLegibility;
9 | //text-rendering: optimizeLegibility;
10 | }
11 |
12 | .header-title-wrap {
13 | display: flex;
14 | background-color: #F5F5F5;
15 | }
16 |
17 | .textinput-wrap {
18 | display: flex;
19 | flex-direction: row;
20 | align-items: center;
21 | border: 1px solid #e6e6e6;
22 | }
23 |
24 | .textinput-wrap-icon {
25 | font-size: 44px;
26 | color: #e6e6e6;
27 | padding: 20px 54px 20px 54px;
28 | transform: rotate(90deg);
29 | }
30 |
31 | .todo-text-input {
32 | flex: 1;
33 | }
34 |
35 | .textinput-wrap-input {
36 | flex: 1;
37 | }
38 |
39 | .filters {
40 | display: flex;
41 | font-weight: 400;
42 | margin: 0;
43 | padding: 0;
44 | flex-direction: row;
45 | background-color: #F5F5F5;
46 | }
47 |
48 | .filters-item {
49 | margin: 6px;
50 | }
51 |
52 |
--------------------------------------------------------------------------------
/src/containers/MainSection/MainSection.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { View } from '@tarojs/components'
3 | import { connect } from 'react-redux'
4 | import { bindActionCreators } from 'redux'
5 |
6 | import * as TodoActions from '../../actions'
7 | import Footer from '../../components/Footer/Footer'
8 | import TodoList from '../TodoList/TodoList'
9 | import { getCompletedTodoCount } from '../../selectors'
10 |
11 | import './MainSection.scss'
12 |
13 | const mapStateToProps = state => ({
14 | todosCount: state.todos.length,
15 | completedCount: getCompletedTodoCount(state)
16 | })
17 |
18 |
19 | const mapDispatchToProps = dispatch => ({
20 | actions: bindActionCreators(TodoActions, dispatch)
21 | })
22 |
23 | @connect(
24 | mapStateToProps,
25 | mapDispatchToProps
26 | )
27 | class MainSection extends React.Component {
28 | onCheckClickHandler = () => {
29 | const { actions } = this.props
30 | actions.completeAllTodos()
31 | }
32 |
33 | onChangeHandler = () => {}
34 |
35 | render () {
36 | const { todosCount, completedCount, actions } = this.props
37 | return (
38 |
39 |
40 | {
41 | !!todosCount &&
42 |
47 | }
48 |
49 | )
50 | }
51 | }
52 |
53 | export default MainSection
--------------------------------------------------------------------------------
/src/containers/MainSection/MainSection.scss:
--------------------------------------------------------------------------------
1 | .main {
2 | position: relative;
3 | z-index: 2;
4 | border-top-width: 1px;
5 | border-style: solid;
6 | border-top-color: #e6e6e6;
7 | border-bottom-width: 0;
8 | border-left-width: 0;
9 | border-right-width: 0;
10 | }
11 |
12 | .checkbox-group {
13 | margin-bottom: 10px;
14 | }
15 |
--------------------------------------------------------------------------------
/src/containers/TodoList/TodoList.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { connect } from 'react-redux'
3 | import { View } from '@tarojs/components'
4 | import { bindActionCreators } from 'redux'
5 |
6 | import TodoItem from '../../components/TodoItem/TodoItem'
7 |
8 | import * as TodoActions from '../../actions'
9 | import { getVisibleTodos } from '../../selectors'
10 |
11 | import './TodoList.scss'
12 |
13 | const mapStateToProps = state => ({
14 | filteredTodos: getVisibleTodos(state)
15 | })
16 |
17 | const mapDispatchToProps = dispatch => ({
18 | actions: bindActionCreators(TodoActions, dispatch)
19 | })
20 |
21 |
22 | @connect(
23 | mapStateToProps,
24 | mapDispatchToProps
25 | )
26 | class TodoList extends React.Component {
27 | render () {
28 | const { filteredTodos } = this.props
29 | console.log(filteredTodos)
30 | return (
31 |
32 | {filteredTodos.map((todo) =>
33 |
40 | )}
41 |
42 | )
43 | }
44 | }
45 |
46 | export default TodoList
--------------------------------------------------------------------------------
/src/containers/TodoList/TodoList.scss:
--------------------------------------------------------------------------------
1 | .todo-list {
2 | margin: 0;
3 | padding: 0;
4 | //list-style: none;
5 | }
6 |
--------------------------------------------------------------------------------
/src/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 | Taro
12 |
15 |
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/src/pages/index/index.config.js:
--------------------------------------------------------------------------------
1 | export default {
2 | navigationBarTitleText: 'TODO List'
3 | }
--------------------------------------------------------------------------------
/src/pages/index/index.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 | import { View } from '@tarojs/components'
3 |
4 | import Header from '../../containers/Header/Header'
5 | import MainSection from '../../containers/MainSection/MainSection'
6 |
7 | import './index.scss'
8 |
9 | class Index extends Component {
10 |
11 | componentWillReceiveProps (nextProps) {
12 | console.log(this.props, nextProps)
13 | }
14 |
15 | componentWillUnmount () { }
16 |
17 | componentDidShow () { }
18 |
19 | componentDidHide () { }
20 |
21 | render () {
22 | return (
23 |
24 |
25 |
26 |
27 | )
28 | }
29 | }
30 |
31 | export default Index
32 |
--------------------------------------------------------------------------------
/src/pages/index/index.scss:
--------------------------------------------------------------------------------
1 | .todaoapp{
2 | background-color: white;
3 | }
4 |
--------------------------------------------------------------------------------
/src/reducers/index.js:
--------------------------------------------------------------------------------
1 | import { combineReducers } from 'redux'
2 | import todos from './todos'
3 | import visibilityFilter from './visibilityFilter'
4 |
5 | const rootReducer = combineReducers({
6 | todos,
7 | visibilityFilter
8 | })
9 |
10 | export default rootReducer
11 |
--------------------------------------------------------------------------------
/src/reducers/todos.js:
--------------------------------------------------------------------------------
1 | import {
2 | ADD_TODO,
3 | DELETE_TODO,
4 | EDIT_TODO,
5 | COMPLETE_TODO,
6 | COMPLETE_ALL_TODOS,
7 | CLEAR_COMPLETED
8 | } from '../constants/ActionTypes'
9 |
10 | const initialState = [
11 | {
12 | text: 'Use Redux',
13 | completed: false,
14 | id: 0
15 | }
16 | ]
17 |
18 | export default function todos(state = initialState, action) {
19 | switch (action.type) {
20 | case ADD_TODO:
21 | return [
22 | ...state,
23 | {
24 | id: state.reduce((maxId, todo) => Math.max(todo.id, maxId), -1) + 1,
25 | completed: false,
26 | text: action.text
27 | }
28 | ]
29 |
30 | case DELETE_TODO:
31 | return state.filter(todo =>
32 | todo.id !== action.id
33 | )
34 |
35 | case EDIT_TODO:
36 | return state.map(todo =>
37 | todo.id === action.id ?
38 | { ...todo, text: action.text } :
39 | todo
40 | )
41 |
42 | case COMPLETE_TODO:
43 | return state.map(todo =>
44 | todo.id === action.id ?
45 | { ...todo, completed: !todo.completed } :
46 | todo
47 | )
48 |
49 | case COMPLETE_ALL_TODOS:
50 | const areAllMarked = state.every(todo => todo.completed)
51 | return state.map(todo => ({
52 | ...todo,
53 | completed: !areAllMarked
54 | }))
55 |
56 | case CLEAR_COMPLETED:
57 | return state.filter(todo => todo.completed === false)
58 |
59 | default:
60 | return state
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/src/reducers/visibilityFilter.js:
--------------------------------------------------------------------------------
1 | import { SET_VISIBILITY_FILTER } from '../constants/ActionTypes'
2 | import { SHOW_ALL } from '../constants/TodoFilters'
3 |
4 | const visibilityFilter = (state = SHOW_ALL, action) => {
5 | switch (action.type) {
6 | case SET_VISIBILITY_FILTER:
7 | return action.filter
8 | default:
9 | return state
10 | }
11 | }
12 |
13 | export default visibilityFilter
14 |
--------------------------------------------------------------------------------
/src/selectors/index.js:
--------------------------------------------------------------------------------
1 | import { createSelector } from 'reselect'
2 | import { SHOW_ALL, SHOW_COMPLETED, SHOW_ACTIVE } from '../constants/TodoFilters'
3 |
4 | const getVisibilityFilter = state => state.visibilityFilter
5 | const getTodos = state => state.todos
6 |
7 | export const getVisibleTodos = createSelector(
8 | [getVisibilityFilter, getTodos],
9 | (visibilityFilter, todos) => {
10 | switch (visibilityFilter) {
11 | case SHOW_ALL:
12 | return todos
13 | case SHOW_COMPLETED:
14 | return todos.filter(t => t.completed)
15 | case SHOW_ACTIVE:
16 | return todos.filter(t => !t.completed)
17 | default:
18 | throw new Error('Unknown filter: ' + visibilityFilter)
19 | }
20 | }
21 | )
22 |
23 | export const getCompletedTodoCount = createSelector(
24 | [getTodos],
25 | todos => (
26 | todos.reduce((count, todo) =>
27 | todo.completed ? count + 1 : count,
28 | 0
29 | )
30 | )
31 | )
32 |
--------------------------------------------------------------------------------
/src/store/index.js:
--------------------------------------------------------------------------------
1 | import { createStore, applyMiddleware } from 'redux'
2 | import thunkMiddleware from 'redux-thunk'
3 | import { createLogger } from 'redux-logger'
4 | import rootReducer from '../reducers'
5 |
6 | const middlewares = [
7 | thunkMiddleware,
8 | createLogger()
9 | ]
10 |
11 | export default function configStore () {
12 | const store = createStore(rootReducer, applyMiddleware(...middlewares))
13 | return store
14 | }
15 |
--------------------------------------------------------------------------------
/src/utils/index.js:
--------------------------------------------------------------------------------
1 | export function pluralize (count, word) {
2 | return count === 1 ? word : word + 's'
3 | }
4 |
--------------------------------------------------------------------------------