├── .gitignore
├── src
├── components
│ ├── todo-form
│ │ ├── todo-form.html
│ │ └── index.js
│ ├── todo-footer
│ │ ├── index.js
│ │ └── todo-footer.html
│ ├── index.js
│ ├── todo-item
│ │ ├── todo-item.html
│ │ └── index.js
│ ├── todo-list
│ │ ├── todo-list.html
│ │ └── index.js
│ └── containers
│ │ └── todo
│ │ ├── todo.html
│ │ └── index.js
├── index.html
├── index.js
├── stores
│ └── todoStore.js
└── index.less
├── .editorconfig
├── README.md
├── package.json
└── webpack.config.js
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 | typings/
3 | dist/
4 |
--------------------------------------------------------------------------------
/src/components/todo-form/todo-form.html:
--------------------------------------------------------------------------------
1 |
7 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | # http://editorconfig.org
2 | root = true
3 |
4 | [*]
5 | indent_style = space
6 | indent_size = 2
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 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | A small ES5 demo app built with:
2 | - Angular 1.5 _for component architecture_
3 | - UI-router 1.0.0-beta.1 _for routing to components_
4 | - MobX _for state management_
5 | - Webpack _for module bundling_
6 |
7 | Demo: http://gaui.is/github/todoApp-angular-mobx-webpack
8 |
9 | Implementation details: http://gaui.is/angular-1-5-todo-app
10 |
11 | Regards to [TodoMVC](http://todomvc.com)
12 |
--------------------------------------------------------------------------------
/src/components/todo-footer/index.js:
--------------------------------------------------------------------------------
1 | var todoFooter = {
2 | controller: todoFooterController,
3 | template: require('./todo-footer.html'),
4 | bindings: {
5 | itemsLeft: '<',
6 | onClearCompleted: '&'
7 | }
8 | };
9 |
10 | function todoFooterController() {
11 | var self = this;
12 |
13 | self.clearCompleted = function clearCompleted() {
14 | // Call parent
15 | self.onClearCompleted();
16 | };
17 | }
18 |
19 | module.exports = todoFooter;
20 |
--------------------------------------------------------------------------------
/src/components/index.js:
--------------------------------------------------------------------------------
1 | var todoContainer = require('./containers/todo');
2 | var todoForm = require('./todo-form');
3 | var todoList = require('./todo-list');
4 | var todoItem = require('./todo-item');
5 | var todoFooter = require('./todo-footer');
6 |
7 | angular.module('todoApp')
8 | .component('todo', todoContainer)
9 | .component('todoForm', todoForm)
10 | .component('todoList', todoList)
11 | .component('todoItem', todoItem)
12 | .component('todoFooter', todoFooter);
13 |
--------------------------------------------------------------------------------
/src/components/todo-item/todo-item.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 |
9 |
10 |
11 |
14 |
15 |
--------------------------------------------------------------------------------
/src/components/todo-list/todo-list.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/src/components/todo-form/index.js:
--------------------------------------------------------------------------------
1 | var todoForm = {
2 | controller: todoFormController,
3 | template: require('./todo-form.html'),
4 | bindings: {
5 | onSubmit: '&'
6 | }
7 | };
8 |
9 | function todoFormController() {
10 | var self = this;
11 |
12 | self.$onInit = function $onInit() {
13 | self.newTodo = {};
14 | resetTodo();
15 | };
16 |
17 | self.submitForm = function submitForm() {
18 | // Call parent
19 | self.onSubmit({
20 | $event: {
21 | todo: self.newTodo
22 | }
23 | });
24 |
25 | resetTodo();
26 | };
27 |
28 | function resetTodo() {
29 | self.newTodo = {};
30 | }
31 | }
32 |
33 | module.exports = todoForm;
34 |
--------------------------------------------------------------------------------
/src/components/todo-footer/todo-footer.html:
--------------------------------------------------------------------------------
1 |
19 |
--------------------------------------------------------------------------------
/src/components/containers/todo/todo.html:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
17 |
18 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/src/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | TodoMVC
8 |
9 |
10 |
11 |
12 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/src/components/todo-list/index.js:
--------------------------------------------------------------------------------
1 | var todoList = {
2 | controller: todoListController,
3 | template: require('./todo-list.html'),
4 | bindings: {
5 | list: '<',
6 | onDelete: '&',
7 | onUpdate: '&',
8 | onToggle: '&'
9 | }
10 | };
11 |
12 | function todoListController() {
13 | var self = this;
14 |
15 | self.deleteTodo = function deleteTodo(event) {
16 | // Call parent
17 | self.onDelete({
18 | $event: {
19 | index: event.index
20 | }
21 | });
22 | };
23 |
24 | self.updateTodo = function updateTodo(event) {
25 | // Call parent
26 | self.onUpdate({
27 | $event: {
28 | index: event.index,
29 | todo: event.todo
30 | }
31 | });
32 | };
33 |
34 | self.toggleTodo = function toggleTodo(event) {
35 | // Call parent
36 | self.onToggle({
37 | $event: {
38 | index: event.index
39 | }
40 | });
41 | };
42 | }
43 |
44 | module.exports = todoList;
45 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "todoApp",
3 | "version": "1.0.2",
4 | "description": "",
5 | "main": "webpack.config.js",
6 | "scripts": {
7 | "dev": "SET NODE_ENV=test && webpack-dev-server --hot --inline --progress --colors --content-base src",
8 | "build:test": "SET NODE_ENV=test && webpack",
9 | "build:live": "SET NODE_ENV=live && webpack --devtool source-map"
10 | },
11 | "author": "",
12 | "license": "ISC",
13 | "devDependencies": {
14 | "clean-webpack-plugin": "^0.1.10",
15 | "copy-webpack-plugin": "^3.0.1",
16 | "css-loader": "^0.23.1",
17 | "file-loader": "^0.9.0",
18 | "html-loader": "^0.4.3",
19 | "json-loader": "^0.5.4",
20 | "less": "^2.7.1",
21 | "less-loader": "^2.2.3",
22 | "path": "^0.12.7",
23 | "style-loader": "^0.13.1",
24 | "ts-loader": "^0.8.2",
25 | "url-loader": "^0.5.7",
26 | "webpack": "^1.13.1"
27 | },
28 | "dependencies": {
29 | "angular": "^1.5.8",
30 | "angular-ui-router": "^1.0.0-beta.1",
31 | "mobx": "^2.4.2"
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | var todoStore = require('./stores/todoStore');
2 |
3 | // Styles
4 | require('./index.less');
5 |
6 | angular.module('todoApp', ['ui.router'])
7 | .service('todoStore', todoStore)
8 | .config(config);
9 |
10 | function config($stateProvider, $httpProvider,
11 | $urlRouterProvider, $locationProvider) {
12 |
13 | $urlRouterProvider.otherwise('/');
14 |
15 | $locationProvider.html5Mode(false);
16 |
17 | $stateProvider
18 | .state('todo', {
19 | abstract: true,
20 | url: '/',
21 | template: ''
22 | })
23 | .state('todo.all', {
24 | url: '',
25 | component: 'todo',
26 | resolve: {
27 | filter: function () {
28 | return 'all'
29 | }
30 | }
31 | })
32 | .state('todo.active', {
33 | url: 'active',
34 | component: 'todo',
35 | resolve: {
36 | filter: function () {
37 | return 'active'
38 | }
39 | }
40 | })
41 | .state('todo.completed', {
42 | url: 'completed',
43 | component: 'todo',
44 | resolve: {
45 | filter: function () {
46 | return 'completed'
47 | }
48 | }
49 | });
50 | }
51 |
52 | require('./components');
53 |
--------------------------------------------------------------------------------
/src/stores/todoStore.js:
--------------------------------------------------------------------------------
1 | var mobx = require('mobx');
2 |
3 | function todoStore() {
4 | var store = this;
5 | var todoList = mobx.observable([]);
6 |
7 | store.getAllTodos = function getAllTodos() {
8 | return todoList;
9 | };
10 |
11 | store.addTodo = function addTodo(todo) {
12 | var newTodo = Object.assign({}, todo, {
13 | completed: false
14 | });
15 |
16 | todoList.push(newTodo);
17 | };
18 |
19 | store.deleteTodo = function deleteTodo(index) {
20 | todoList.splice(index, 1);
21 | };
22 |
23 | store.updateTodo = function updateTodo(index, todo) {
24 | todoList[index].description = todo.description;
25 | };
26 |
27 | store.toggleTodo = function toggleTodo(index) {
28 | todoList[index].completed = !todoList[index].completed;
29 | };
30 |
31 | store.toggleAllTodos = function toggleAllTodos() {
32 | todoList.forEach(function(item) {
33 | item.completed = !item.completed;
34 | });
35 | };
36 |
37 | store.clearCompleted = function clearCompleted() {
38 | var filteredArray = todoList.filter(function(item) {
39 | return !item.completed;
40 | });
41 |
42 | todoList.replace(filteredArray);
43 | };
44 | }
45 |
46 | module.exports = todoStore;
47 |
--------------------------------------------------------------------------------
/src/components/todo-item/index.js:
--------------------------------------------------------------------------------
1 | var todoItem = {
2 | controller: todoItemController,
3 | template: require('./todo-item.html'),
4 | bindings: {
5 | index: '<',
6 | description: '<',
7 | completed: '<',
8 | onDelete: '&',
9 | onUpdate: '&',
10 | onToggle: '&'
11 | }
12 | };
13 |
14 | function todoItemController() {
15 | var self = this;
16 |
17 | self.$onInit = function $onInit() {
18 | self.editing = false;
19 | };
20 |
21 | self.enableEditing = function enableEditing() {
22 | self.editing = true;
23 | };
24 |
25 | self.deleteTodo = function deleteTodo() {
26 | // Call parent
27 | self.onDelete({
28 | $event: {
29 | index: self.index
30 | }
31 | });
32 | };
33 |
34 | self.updateTodo = function updateTodo() {
35 | // Call parent
36 | self.onUpdate({
37 | $event: {
38 | index: self.index,
39 | todo: {
40 | description: self.description
41 | }
42 | }
43 | });
44 |
45 | self.editing = false;
46 | };
47 |
48 | self.toggleTodo = function toggleTodo() {
49 | // Call parent
50 | self.onToggle({
51 | $event: {
52 | index: self.index
53 | }
54 | });
55 | };
56 | }
57 |
58 | module.exports = todoItem;
59 |
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | var webpack = require('webpack');
2 | var path = require('path');
3 | var CopyWebpackPlugin = require('copy-webpack-plugin');
4 | var CleanWebpackPlugin = require('clean-webpack-plugin');
5 |
6 | module.exports = {
7 | context: path.join(__dirname, 'src'),
8 | entry: {
9 | app: './index.js',
10 | vendors: [
11 | 'angular',
12 | 'angular-ui-router',
13 | 'lodash',
14 | 'mobx'
15 | ]
16 | },
17 | output: {
18 | path: path.join(__dirname, 'dist'),
19 | filename: 'app.js'
20 | },
21 | resolve: {
22 | extensions: ['', '.js']
23 | },
24 | module: {
25 | loaders: [
26 | { test: /\.json$/, loader: 'json', exclude: /node_modules/ },
27 | { test: /\.html$/, loader: 'html', exclude: /node_modules/ },
28 | { test: /\.css$/, loader: 'style!css', exclude: /node_modules/ },
29 | { test: /\.less$/, loader: 'style!css!less', exclude: /node_modules/ },
30 | { test: /\.(png|jpg)(\?]?.*)?$/, loader : 'file', exclude: /node_modules/ },
31 | { test: /\.woff(2)?(\?v=[0-9]\.[0-9]\.[0-9])?$/, loader: "url?limit=10000&mimetype=application/font-woff" },
32 | { test: /\.(ttf|eot|svg)(\?v=[0-9]\.[0-9]\.[0-9])?$/, loader: "file" },
33 | ]
34 | },
35 | plugins: [
36 | new webpack.optimize.CommonsChunkPlugin('vendors', 'vendors.js'),
37 | new webpack.DefinePlugin({
38 | 'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV || 'test')
39 | }),
40 | new CopyWebpackPlugin([
41 | { from: 'index.html' }
42 | ]),
43 | new CleanWebpackPlugin(['dist'])
44 | ]
45 | };
46 |
--------------------------------------------------------------------------------
/src/components/containers/todo/index.js:
--------------------------------------------------------------------------------
1 | var mobx = require('mobx');
2 |
3 | var todoContainer = {
4 | controller: todoContainerController,
5 | template: require('./todo.html'),
6 | bindings: {
7 | filter: '<'
8 | }
9 | };
10 |
11 | function todoContainerController(todoStore, $state) {
12 | var self = this;
13 |
14 | var dispose = mobx.autorun(function () {
15 | var todoList = todoStore.getAllTodos();
16 | self.todoList = getListBasedOnFilter(todoList, self.filter);
17 | self.incompletedItems = getListBasedOnFilter(todoList, 'active').length;
18 | console.log('%cNEW STATE:', 'font-weight: bold');
19 | console.log(JSON.stringify(mobx.toJS(todoList), null, 2));
20 | });
21 |
22 | self.$onDestroy = function $onDestroy() {
23 | dispose();
24 | };
25 |
26 | self.addTodo = function addTodo(event) {
27 | todoStore.addTodo(event.todo);
28 | };
29 |
30 | self.deleteTodo = function deleteTodo(event) {
31 | todoStore.deleteTodo(event.index);
32 | };
33 |
34 | self.updateTodo = function updateTodo(event) {
35 | todoStore.updateTodo(event.index, event.todo);
36 | };
37 |
38 | self.toggleTodo = function toggleTodo(event) {
39 | todoStore.toggleTodo(event.index);
40 | };
41 |
42 | self.toggleAllTodos = function toggleAllTodos() {
43 | todoStore.toggleAllTodos();
44 | };
45 |
46 | self.clearCompleted = function clearCompleted() {
47 | todoStore.clearCompleted();
48 | };
49 |
50 | function getListBasedOnFilter(list, filter) {
51 | if (!filter) return list;
52 |
53 | var filterMap = {
54 | all: function (item) { return true; },
55 | active: function (item) { return !item.completed },
56 | completed: function (item) { return item.completed }
57 | };
58 |
59 | return list.filter(filterMap[filter]);
60 | }
61 | }
62 |
63 | module.exports = todoContainer;
64 |
--------------------------------------------------------------------------------
/src/index.less:
--------------------------------------------------------------------------------
1 | html,
2 | body {
3 | margin: 0;
4 | padding: 0;
5 | }
6 |
7 | button {
8 | margin: 0;
9 | padding: 0;
10 | border: 0;
11 | background: none;
12 | font-size: 100%;
13 | vertical-align: baseline;
14 | font-family: inherit;
15 | font-weight: inherit;
16 | color: inherit;
17 | -webkit-appearance: none;
18 | appearance: none;
19 | -webkit-font-smoothing: antialiased;
20 | -moz-osx-font-smoothing: grayscale;
21 | }
22 |
23 | body {
24 | font: 14px 'Helvetica Neue', Helvetica, Arial, sans-serif;
25 | line-height: 1.4em;
26 | background: #f5f5f5;
27 | color: #4d4d4d;
28 | min-width: 230px;
29 | max-width: 550px;
30 | margin: 0 auto;
31 | -webkit-font-smoothing: antialiased;
32 | -moz-osx-font-smoothing: grayscale;
33 | font-weight: 300;
34 | }
35 |
36 | :focus {
37 | outline: 0;
38 | }
39 |
40 | .hidden {
41 | display: none;
42 | }
43 |
44 | .todoapp {
45 | background: #fff;
46 | margin: 130px 0 40px 0;
47 | position: relative;
48 | box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.2),
49 | 0 25px 50px 0 rgba(0, 0, 0, 0.1);
50 | }
51 |
52 | .todoapp input::-webkit-input-placeholder {
53 | font-style: italic;
54 | font-weight: 300;
55 | color: #e6e6e6;
56 | }
57 |
58 | .todoapp input::-moz-placeholder {
59 | font-style: italic;
60 | font-weight: 300;
61 | color: #e6e6e6;
62 | }
63 |
64 | .todoapp input::input-placeholder {
65 | font-style: italic;
66 | font-weight: 300;
67 | color: #e6e6e6;
68 | }
69 |
70 | .todoapp h1 {
71 | position: absolute;
72 | top: -155px;
73 | width: 100%;
74 | font-size: 100px;
75 | font-weight: 100;
76 | text-align: center;
77 | color: rgba(175, 47, 47, 0.15);
78 | -webkit-text-rendering: optimizeLegibility;
79 | -moz-text-rendering: optimizeLegibility;
80 | text-rendering: optimizeLegibility;
81 | }
82 |
83 | .new-todo,
84 | .edit {
85 | position: relative;
86 | margin: 0;
87 | width: 100%;
88 | font-size: 24px;
89 | font-family: inherit;
90 | font-weight: inherit;
91 | line-height: 1.4em;
92 | border: 0;
93 | color: inherit;
94 | padding: 6px;
95 | border: 1px solid #999;
96 | box-shadow: inset 0 -1px 5px 0 rgba(0, 0, 0, 0.2);
97 | box-sizing: border-box;
98 | -webkit-font-smoothing: antialiased;
99 | -moz-osx-font-smoothing: grayscale;
100 | }
101 |
102 | .new-todo {
103 | padding: 16px 16px 16px 60px;
104 | border: none;
105 | background: rgba(0, 0, 0, 0.003);
106 | box-shadow: inset 0 -2px 1px rgba(0,0,0,0.03);
107 | }
108 |
109 | .main {
110 | position: relative;
111 | z-index: 2;
112 | border-top: 1px solid #e6e6e6;
113 | }
114 |
115 | label[for='toggle-all'] {
116 | display: none;
117 | }
118 |
119 | .toggle-all {
120 | position: absolute;
121 | top: -55px;
122 | left: -12px;
123 | width: 60px;
124 | height: 34px;
125 | text-align: center;
126 | border: none; /* Mobile Safari */
127 | }
128 |
129 | .toggle-all:before {
130 | content: '❯';
131 | font-size: 22px;
132 | color: #e6e6e6;
133 | padding: 10px 27px 10px 27px;
134 | }
135 |
136 | .toggle-all:checked:before {
137 | color: #737373;
138 | }
139 |
140 | .todo-list {
141 | margin: 0;
142 | padding: 0;
143 | list-style: none;
144 | }
145 |
146 | .todo-list li {
147 | position: relative;
148 | font-size: 24px;
149 | border-bottom: 1px solid #ededed;
150 | }
151 |
152 | .todo-list li:last-child {
153 | border-bottom: none;
154 | }
155 |
156 | .todo-list li.editing {
157 | border-bottom: none;
158 | padding: 0;
159 | margin-left: 43px;
160 | }
161 |
162 | .todo-list li.editing .edit {
163 | display: block;
164 | width: 100%;
165 | padding: 12px 16px;
166 | }
167 |
168 | .todo-list li.editing .view {
169 | display: none;
170 | }
171 |
172 | .todo-list li .toggle {
173 | text-align: center;
174 | width: 40px;
175 | /* auto, since non-WebKit browsers doesn't support input styling */
176 | height: auto;
177 | position: absolute;
178 | top: 0;
179 | bottom: 0;
180 | margin: auto 0;
181 | border: none; /* Mobile Safari */
182 | -webkit-appearance: none;
183 | appearance: none;
184 | }
185 |
186 | .todo-list li .toggle:after {
187 | content: url('data:image/svg+xml;utf8,');
188 | }
189 |
190 | .todo-list li .toggle:checked:after {
191 | content: url('data:image/svg+xml;utf8,');
192 | }
193 |
194 | .todo-list li label {
195 | word-break: break-all;
196 | padding: 15px 60px 15px 15px;
197 | margin-left: 45px;
198 | display: block;
199 | line-height: 1.2;
200 | transition: color 0.4s;
201 | }
202 |
203 | .todo-list li.completed label {
204 | color: #d9d9d9;
205 | text-decoration: line-through;
206 | }
207 |
208 | .todo-list li .destroy {
209 | display: none;
210 | position: absolute;
211 | top: 0;
212 | right: 10px;
213 | bottom: 0;
214 | width: 40px;
215 | height: 40px;
216 | margin: auto 0;
217 | font-size: 30px;
218 | color: #cc9a9a;
219 | margin-bottom: 11px;
220 | transition: color 0.2s ease-out;
221 | }
222 |
223 | .todo-list li .destroy:hover {
224 | color: #af5b5e;
225 | }
226 |
227 | .todo-list li .destroy:after {
228 | content: '×';
229 | }
230 |
231 | .todo-list li:hover .destroy {
232 | display: block;
233 | }
234 |
235 | .todo-list li .edit {
236 | display: none;
237 | }
238 |
239 | .todo-list li.editing:last-child {
240 | margin-bottom: -1px;
241 | }
242 |
243 | .footer {
244 | color: #777;
245 | padding: 10px 15px;
246 | height: 20px;
247 | text-align: center;
248 | border-top: 1px solid #e6e6e6;
249 | }
250 |
251 | .footer:before {
252 | content: '';
253 | position: absolute;
254 | right: 0;
255 | bottom: 0;
256 | left: 0;
257 | height: 50px;
258 | overflow: hidden;
259 | box-shadow: 0 1px 1px rgba(0, 0, 0, 0.2),
260 | 0 8px 0 -3px #f6f6f6,
261 | 0 9px 1px -3px rgba(0, 0, 0, 0.2),
262 | 0 16px 0 -6px #f6f6f6,
263 | 0 17px 2px -6px rgba(0, 0, 0, 0.2);
264 | }
265 |
266 | .todo-count {
267 | float: left;
268 | text-align: left;
269 | }
270 |
271 | .todo-count strong {
272 | font-weight: 300;
273 | }
274 |
275 | .filters {
276 | margin: 0;
277 | padding: 0;
278 | list-style: none;
279 | position: absolute;
280 | right: 0;
281 | left: 0;
282 | }
283 |
284 | .filters li {
285 | display: inline;
286 | }
287 |
288 | .filters li a {
289 | color: inherit;
290 | margin: 3px;
291 | padding: 3px 7px;
292 | text-decoration: none;
293 | border: 1px solid transparent;
294 | border-radius: 3px;
295 | }
296 |
297 | .filters li a:hover {
298 | border-color: rgba(175, 47, 47, 0.1);
299 | }
300 |
301 | .filters li a.selected {
302 | border-color: rgba(175, 47, 47, 0.2);
303 | }
304 |
305 | .clear-completed,
306 | html .clear-completed:active {
307 | float: right;
308 | position: relative;
309 | line-height: 20px;
310 | text-decoration: none;
311 | cursor: pointer;
312 | }
313 |
314 | .clear-completed:hover {
315 | text-decoration: underline;
316 | }
317 |
318 | .info {
319 | margin: 65px auto 0;
320 | color: #bfbfbf;
321 | font-size: 10px;
322 | text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5);
323 | text-align: center;
324 | }
325 |
326 | .info p {
327 | line-height: 1;
328 | }
329 |
330 | .info a {
331 | color: inherit;
332 | text-decoration: none;
333 | font-weight: 400;
334 | }
335 |
336 | .info a:hover {
337 | text-decoration: underline;
338 | }
339 |
340 | /*
341 | Hack to remove background from Mobile Safari.
342 | Can't use it globally since it destroys checkboxes in Firefox
343 | */
344 | @media screen and (-webkit-min-device-pixel-ratio:0) {
345 | .toggle-all,
346 | .todo-list li .toggle {
347 | background: none;
348 | }
349 |
350 | .todo-list li .toggle {
351 | height: 40px;
352 | }
353 |
354 | .toggle-all {
355 | -webkit-transform: rotate(90deg);
356 | transform: rotate(90deg);
357 | -webkit-appearance: none;
358 | appearance: none;
359 | }
360 | }
361 |
362 | @media (max-width: 430px) {
363 | .footer {
364 | height: 50px;
365 | }
366 |
367 | .filters {
368 | bottom: 10px;
369 | }
370 | }
371 |
--------------------------------------------------------------------------------