├── src ├── scss │ ├── app.scss │ ├── index.scss │ └── text-colors.scss ├── helpers │ └── index.js ├── App.test.js ├── components │ ├── Maybe │ │ └── index.js │ ├── ActiveTasks │ │ └── index.js │ ├── Title │ │ └── index.js │ ├── Filter │ │ └── index.js │ ├── AddTask │ │ └── index.js │ ├── Map │ │ └── index.js │ ├── Line │ │ └── index.js │ ├── List │ │ └── index.js │ └── Colors │ │ └── index.js ├── state │ ├── index.js │ └── todoListStore.js ├── index.js ├── App.js ├── logo.svg ├── containers │ └── TodoList │ │ └── index.js └── serviceWorker.js ├── public ├── favicon.ico ├── manifest.json └── index.html ├── .eslintrc ├── .prettierrc ├── .editorconfig ├── config ├── jest │ ├── cssTransform.js │ └── fileTransform.js ├── paths.js ├── env.js ├── webpackDevServer.config.js ├── webpack.config.dev.js └── webpack.config.prod.js ├── .github └── ISSUE_TEMPLATE │ └── --------------.md ├── .gitignore ├── .babelrc ├── README.md ├── scripts ├── test.js ├── start.js └── build.js └── package.json /src/scss/app.scss: -------------------------------------------------------------------------------- 1 | @import "./text-colors.scss"; 2 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iran-react-community/todo-list/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "react-app", 3 | "settings": { 4 | "react": { 5 | "version": "latest" 6 | } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/helpers/index.js: -------------------------------------------------------------------------------- 1 | export const generateString = (length = -1) => { 2 | return Math.random() 3 | .toString(16) 4 | .slice(2, length); 5 | }; 6 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 160, 3 | "tabWidth": 4, 4 | "useTabs": true, 5 | "semi": true, 6 | "trailingComma": "all", 7 | "overrides": [ 8 | { 9 | "files": ".prettierrc", 10 | "options": { "parser": "json" } 11 | } 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /src/App.test.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom"; 3 | import App from "./App"; 4 | 5 | it("renders without crashing", () => { 6 | const div = document.createElement("div"); 7 | ReactDOM.render(, div); 8 | ReactDOM.unmountComponentAtNode(div); 9 | }); 10 | -------------------------------------------------------------------------------- /src/components/Maybe/index.js: -------------------------------------------------------------------------------- 1 | import PropTypes from "prop-types"; 2 | 3 | const Maybe = ({ children, condition }) => (condition ? children : null); 4 | 5 | Maybe.propTypes = { 6 | children: PropTypes.node.isRequired, 7 | condition: PropTypes.bool.isRequired, 8 | }; 9 | 10 | export default Maybe; 11 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = tab 6 | indent_size = 4 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | [*.md] 13 | trim_trailing_whitespace = false 14 | 15 | [*.json] 16 | indent_style = space 17 | indent_size = 2 18 | -------------------------------------------------------------------------------- /src/components/ActiveTasks/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import styled from "@emotion/styled"; 3 | 4 | const ActiveBox = styled.div` 5 | float: right; 6 | margin-top: 5px; 7 | `; 8 | 9 | const ActiveTasks = ({ value }) => { 10 | return {value}/Active Tasks; 11 | }; 12 | 13 | export default ActiveTasks; 14 | -------------------------------------------------------------------------------- /config/jest/cssTransform.js: -------------------------------------------------------------------------------- 1 | // This is a custom Jest transformer turning style imports into empty objects. 2 | // http://facebook.github.io/jest/docs/en/webpack.html 3 | 4 | module.exports = { 5 | process() { 6 | return "module.exports = {};"; 7 | }, 8 | getCacheKey() { 9 | // The output is always the same. 10 | return "cssTransform"; 11 | }, 12 | }; 13 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/--------------.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: ثبت موضوع جدید 3 | about: چینش درست فارسی 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 |
11 | 12 | برای چینش درست فارسی داخل این تگ باز شده بنویسید، متاسفانه داخل مارک‌آپ، کد‌های مارک‌دان کار نمی‌کند. برای استفاده از کد‌های مارک‌دان بیرون تگ باز شده بنویسید. 13 | 14 |
15 | -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | } 10 | ], 11 | "start_url": ".", 12 | "display": "standalone", 13 | "theme_color": "#000000", 14 | "background_color": "#ffffff" 15 | } 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # IDE & Code editors 4 | .idea/ 5 | 6 | # dependencies 7 | /node_modules 8 | /.pnp 9 | .pnp.js 10 | 11 | /ignore 12 | 13 | # testing 14 | /coverage 15 | 16 | # production 17 | /build 18 | 19 | # misc 20 | .DS_Store 21 | .env.local 22 | .env.development.local 23 | .env.test.local 24 | .env.production.local 25 | 26 | npm-debug.log* 27 | yarn-debug.log* 28 | yarn-error.log* 29 | -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "react-app", 4 | [ 5 | "@emotion/babel-preset-css-prop", 6 | { 7 | "autoLabel": true, 8 | "labelFormat": "[local]" 9 | } 10 | ] 11 | ], 12 | "plugins": [ 13 | [ 14 | "@babel/plugin-proposal-decorators", 15 | { 16 | "legacy": true 17 | } 18 | ], 19 | [ 20 | "@babel/plugin-proposal-class-properties", 21 | { 22 | "loose": true 23 | } 24 | ], 25 | [ 26 | "import", 27 | { 28 | "libraryName": "antd", 29 | "libraryDirectory": "es", 30 | "style": true 31 | } 32 | ] 33 | ] 34 | } 35 | -------------------------------------------------------------------------------- /src/components/Title/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import styled from "@emotion/styled"; 3 | 4 | import Line from "../Line"; 5 | import { GradTealBlue } from "../Colors"; 6 | 7 | const Wrap = styled.div` 8 | text-align: center; 9 | font-size: 1.3em; 10 | width: 65%; 11 | margin: 10px auto; 12 | color: #373352; 13 | font-weight: 300; 14 | margin-bottom: 30px; 15 | `; 16 | 17 | const Title = () => { 18 | return ( 19 | 20 | To-do list application 21 | 22 | 23 | ); 24 | }; 25 | 26 | export default Title; 27 | -------------------------------------------------------------------------------- /src/components/Filter/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import styled from "@emotion/styled"; 3 | import { Radio } from "antd"; 4 | 5 | const FilterBox = styled.div` 6 | margin-bottom: 40px; 7 | display: inline-block; 8 | `; 9 | 10 | const FilterTodo = ({ onChange }) => { 11 | return ( 12 | 13 | onChange(e.target.value)}> 14 | All 15 | Completed 16 | Active 17 | 18 | 19 | ); 20 | }; 21 | 22 | export default FilterTodo; 23 | -------------------------------------------------------------------------------- /src/state/index.js: -------------------------------------------------------------------------------- 1 | import { observable } from "mobx"; 2 | import { create } from "mobx-persist"; 3 | 4 | import todoListStore from "./todoListStore"; 5 | 6 | // Compact and organization other stores and main store in one place. 7 | export default class AppStore { 8 | @observable serverSide = false; 9 | 10 | constructor() { 11 | this.todoListStore = new todoListStore(this); 12 | 13 | // Mobx Persist data [into localStorage by default] 14 | const hydratedTodoList = create(); 15 | hydratedTodoList("todoList", this.todoListStore); 16 | } 17 | 18 | stores() { 19 | const appStore = this; 20 | return { 21 | appStore, 22 | todoList: this.todoListStore, 23 | }; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom"; 3 | 4 | import App from "./App"; 5 | // Store and mobx 6 | import { Provider } from "mobx-react"; 7 | import AppStore from "./state"; 8 | 9 | import * as serviceWorker from "./serviceWorker"; 10 | 11 | import "./scss/index.scss"; 12 | // create an instance of main app store 13 | const appStore = new AppStore(); 14 | 15 | ReactDOM.render( 16 | 17 | 18 | , 19 | document.getElementById("root"), 20 | ); 21 | 22 | // If you want your app to work offline and load faster, you can change 23 | // unregister() to register() below. Note this comes with some pitfalls. 24 | // Learn more about service workers: http://bit.ly/CRA-PWA 25 | serviceWorker.unregister(); 26 | -------------------------------------------------------------------------------- /config/jest/fileTransform.js: -------------------------------------------------------------------------------- 1 | const path = require("path"); 2 | 3 | // This is a custom Jest transformer turning file imports into filenames. 4 | // http://facebook.github.io/jest/docs/en/webpack.html 5 | 6 | module.exports = { 7 | process(src, filename) { 8 | const assetFilename = JSON.stringify(path.basename(filename)); 9 | 10 | if (filename.match(/\.svg$/)) { 11 | return `module.exports = { 12 | __esModule: true, 13 | default: ${assetFilename}, 14 | ReactComponent: (props) => ({ 15 | $$typeof: Symbol.for('react.element'), 16 | type: 'svg', 17 | ref: null, 18 | key: null, 19 | props: Object.assign({}, props, { 20 | children: ${assetFilename} 21 | }) 22 | }), 23 | };`; 24 | } 25 | 26 | return `module.exports = ${assetFilename};`; 27 | }, 28 | }; 29 | -------------------------------------------------------------------------------- /src/components/AddTask/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import styled from "@emotion/styled"; 3 | import { Input } from "antd"; 4 | 5 | const Search = Input.Search; 6 | 7 | const SearchBox = styled.div` 8 | margin-bottom: 10px; 9 | `; 10 | 11 | class AddTask extends React.PureComponent { 12 | constructor(props) { 13 | super(props); 14 | 15 | this.onSearch = this.onSearch.bind(this); 16 | } 17 | 18 | onSearch(value) { 19 | if(!value) return; 20 | 21 | this.props.onChange(value); 22 | 23 | this.refs.todoInput.input.input.value = ""; 24 | } 25 | 26 | componentDidMount() { 27 | this.refs.todoInput.focus(); 28 | } 29 | 30 | render() { 31 | return ( 32 | 33 | 34 | 35 | ); 36 | } 37 | } 38 | 39 | export default AddTask; 40 | -------------------------------------------------------------------------------- /src/components/Map/index.js: -------------------------------------------------------------------------------- 1 | import React, { cloneElement } from "react"; 2 | import PropTypes from "prop-types"; 3 | 4 | import Maybe from "../Maybe"; 5 | import { generateString } from "../../helpers"; 6 | /** 7 | * @description The component automatically transmits map callback data to all its sub - component children as prop. 8 | * @template 9 | * 10 | * // Now DisplayItems has 2 prop by default which the value and index prop are adapted from map callback. 11 | * 12 | */ 13 | const Map = ({ children, data, name = generateString() }) => { 14 | const childComponents = data.map((value, index) => cloneElement(children, { value, index, key: `$${name}_${index}` })); 15 | 16 | return {childComponents}; 17 | }; 18 | 19 | Map.propTypes = { 20 | children: PropTypes.node.isRequired, 21 | data: PropTypes.array.isRequired, 22 | }; 23 | 24 | export default Map; 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | A simple and small todo list application built with: 2 | 3 | - React 16.8 4 | - Ant Design _for User interface_ 5 | - MobX _for state management_ 6 | - Webpack 4 _for module bundling_ 7 | - EmotionJS _for using css-in-js_ 8 | 9 | Demo: https://iran-react-community.github.io/todo-list/ 10 | 11 | Regards to [TodoMVC](http://todomvc.com) 12 | 13 | ## Available Scripts 14 | 15 | In the project directory, you can run: 16 | 17 | ### `npm start` 18 | 19 | Runs the app in the development mode.
20 | Open [http://localhost:3000](http://localhost:3000) to view it in the browser. 21 | 22 | ### `npm run build` 23 | 24 | Builds the app for production to the `build` folder.
25 | It correctly bundles React in production mode and optimizes the build for the best performance. 26 | 27 | The build is minified and the filenames include the hashes.
28 | Your app is ready to be deployed! 29 | 30 | See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information. 31 | -------------------------------------------------------------------------------- /src/App.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | import styled from "@emotion/styled"; 3 | import { Layout } from "antd"; 4 | 5 | import TodoList from "./containers/TodoList"; 6 | 7 | import "./scss/app.scss"; 8 | 9 | const { Content, Footer } = Layout; 10 | const Wrapper = styled.div` 11 | width: 560px; 12 | /* min-height: 300px; */ 13 | margin: 50px auto; 14 | background-color: #fff; 15 | border-radius: 25px; 16 | padding: 20px; 17 | box-shadow: rgba(50, 50, 50, 0.1) 0px 10px 35px; 18 | `; 19 | 20 | class BootCamp extends Component { 21 | render() { 22 | return ( 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | ); 34 | } 35 | } 36 | 37 | export default BootCamp; 38 | -------------------------------------------------------------------------------- /src/scss/index.scss: -------------------------------------------------------------------------------- 1 | :root { 2 | /* Silver family */ 3 | --silver: "#f7f7f7"; 4 | --silver-light: "#838ba1"; 5 | --silver-lighter: "#eaebee"; 6 | --silver-dark: "#c4c8d1"; 7 | /* Gray family */ 8 | --gray: "#38404d"; 9 | --gray-light: "#f8f9fe"; 10 | --gray-lighter: "#ccd8ed"; 11 | /* Blue family */ 12 | --blue: "#3c74ff"; 13 | --blue-light: "#1a2d5e"; 14 | --blue-lighter: "#c9d8ff"; 15 | --blue-lighter-1: "#eaf2ff"; 16 | --blue-dark: "#2459f1"; 17 | --blue-darker: "#3e6eff"; 18 | /* Yellow family */ 19 | --yellow: "#ffcb00"; 20 | --yellow-light: "#fff3bf"; 21 | /* Green family */ 22 | --green: "#00ca88"; 23 | --gray-darker: "#50586e"; 24 | --font-family: "Roboto Mono", monospace; 25 | } 26 | 27 | @import url("https://fonts.googleapis.com/css?family=Roboto+Mono"); 28 | @import "~antd/dist/antd.css"; 29 | 30 | body { 31 | margin: 0; 32 | padding: 0; 33 | font-size: 16px; 34 | -webkit-font-smoothing: antialiased; 35 | -moz-osx-font-smoothing: grayscale; 36 | background: #f0f2f5 !important; 37 | color: #38404d !important; 38 | cursor: default; 39 | font-family: var(--font-family) !important; 40 | } 41 | -------------------------------------------------------------------------------- /src/components/Line/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import propTypes from "prop-types"; 3 | 4 | import styled from "@emotion/styled"; 5 | import { keyframes } from "@emotion/core"; 6 | 7 | const LineBox = styled.div` 8 | height: 2px; 9 | width: 100%; 10 | overflow: hidden; 11 | width: 100%; 12 | margin: 24px auto 40px; 13 | `; 14 | 15 | const lineAnimate = keyframes` 16 | 0%, 17 | 25% { 18 | transform: translateX(-64px); 19 | } 20 | 75%, 21 | 100% { 22 | transform: translateX(372px); 23 | } 24 | `; 25 | 26 | const LineBar = styled.div` 27 | height: 100%; 28 | width: 64px; 29 | transform: translateX(-64px); 30 | animation: ${lineAnimate} 3s ease-in-out 1.5s infinite; 31 | background: ${({ from, to }) => `linear-gradient(to right, ${from} 0%, ${to} 100%)`}; 32 | `; 33 | 34 | const Line = ({ from, to }) => ( 35 | 36 | 37 | 38 | ); 39 | 40 | Line.propTypes = { 41 | from: propTypes.string, 42 | to: propTypes.string, 43 | }; 44 | Line.defaultProps = { 45 | from: "rgba(24, 144, 255, 0)", 46 | to: "#1890ff", 47 | }; 48 | 49 | export default React.memo(Line); 50 | -------------------------------------------------------------------------------- /scripts/test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Do this as the first thing so that any code reading it knows the right env. 4 | process.env.BABEL_ENV = 'test'; 5 | process.env.NODE_ENV = 'test'; 6 | process.env.PUBLIC_URL = ''; 7 | 8 | // Makes the script crash on unhandled rejections instead of silently 9 | // ignoring them. In the future, promise rejections that are not handled will 10 | // terminate the Node.js process with a non-zero exit code. 11 | process.on('unhandledRejection', err => { 12 | throw err; 13 | }); 14 | 15 | // Ensure environment variables are read. 16 | require('../config/env'); 17 | 18 | 19 | const jest = require('jest'); 20 | const execSync = require('child_process').execSync; 21 | let argv = process.argv.slice(2); 22 | 23 | function isInGitRepository() { 24 | try { 25 | execSync('git rev-parse --is-inside-work-tree', { stdio: 'ignore' }); 26 | return true; 27 | } catch (e) { 28 | return false; 29 | } 30 | } 31 | 32 | function isInMercurialRepository() { 33 | try { 34 | execSync('hg --cwd . root', { stdio: 'ignore' }); 35 | return true; 36 | } catch (e) { 37 | return false; 38 | } 39 | } 40 | 41 | // Watch unless on CI, in coverage mode, or explicitly running all tests 42 | if ( 43 | !process.env.CI && 44 | argv.indexOf('--coverage') === -1 && 45 | argv.indexOf('--watchAll') === -1 46 | ) { 47 | // https://github.com/facebook/create-react-app/issues/5210 48 | const hasSourceControl = isInGitRepository() || isInMercurialRepository(); 49 | argv.push(hasSourceControl ? '--watch' : '--watchAll'); 50 | } 51 | 52 | 53 | jest.run(argv); 54 | -------------------------------------------------------------------------------- /src/components/List/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import styled from "@emotion/styled"; 3 | 4 | // ant components 5 | import List from "antd/es/list"; 6 | import Button from "antd/es/button"; 7 | import { Tooltip, Checkbox } from "antd"; 8 | 9 | const TodoBox = styled.div` 10 | background: ${({ done }) => (done ? "#92C65A" : "#f5f5f5")}; 11 | padding: 15px; 12 | border-radius: 5px; 13 | box-shadow: 0px 5px 15px rgba(50, 50, 50, 0.1); 14 | border: 1px solid ${({ done }) => (done ? "#92c65a" : "#cfcfcf")}; 15 | 16 | & .ant-checkbox-checked { 17 | &:before { 18 | border-color: #53861d; 19 | } 20 | 21 | .ant-checkbox-inner { 22 | background-color: #53861d; 23 | border-color: #53861d; 24 | } 25 | } 26 | 27 | & .ant-checkbox-wrapper { 28 | user-select: none; 29 | 30 | & > span:last-of-type { 31 | margin-left: 5px; 32 | text-decoration: ${({ done }) => (done ? "line-through" : "none")}; 33 | color: ${({ done }) => (done ? "#FFF" : "rgba(0, 0, 0, 0.65)")}; 34 | } /* Checkbox label */ 35 | } /* checkbox wrapper */ 36 | `; 37 | 38 | const TodoList = ({ value: { value, done }, index, onChange, onDelete }) => { 39 | return ( 40 | 41 | 42 | onChange({ index, done: e.target.checked })}> 43 | {value} 44 | 45 | 46 |