├── .gitignore ├── src ├── app │ ├── index │ │ ├── constants │ │ │ ├── CategoryTypes.js │ │ │ └── ActionTypes.js │ │ ├── actions │ │ │ ├── category.js │ │ │ └── list.js │ │ ├── reducers │ │ │ ├── index.js │ │ │ ├── filter.js │ │ │ ├── category.js │ │ │ └── list.js │ │ ├── store │ │ │ └── index.js │ │ ├── index.js │ │ ├── containers │ │ │ ├── Root.js │ │ │ └── App.js │ │ └── components │ │ │ ├── list │ │ │ ├── Toggle.jsx │ │ │ ├── AddTodo.jsx │ │ │ ├── Todo.jsx │ │ │ ├── List.jsx │ │ │ └── index.jsx │ │ │ ├── index.jsx │ │ │ └── category │ │ │ └── index.jsx │ └── learn.jsx ├── index.html └── css │ └── base.css ├── README.md ├── webpack.config.js ├── package.json └── app.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | src/dist -------------------------------------------------------------------------------- /src/app/index/constants/CategoryTypes.js: -------------------------------------------------------------------------------- 1 | export const ADD_CATEGORY = 'ADD_CATEGORY'; -------------------------------------------------------------------------------- /src/app/index/actions/category.js: -------------------------------------------------------------------------------- 1 | import * as types from '../constants/CategoryTypes'; 2 | 3 | export function addCategory(name) { 4 | return { 5 | type: types.ADD_CATEGORY, 6 | name 7 | } 8 | } -------------------------------------------------------------------------------- /src/app/index/reducers/index.js: -------------------------------------------------------------------------------- 1 | import { combineReducers } from 'redux'; 2 | import category from './category'; 3 | import list from './list'; 4 | import filter from './filter'; 5 | 6 | export default combineReducers({category, list, filter}); -------------------------------------------------------------------------------- /src/app/index/store/index.js: -------------------------------------------------------------------------------- 1 | import { createStore } from 'redux'; 2 | import rootReducers from '../reducers'; 3 | 4 | // 之所以不直接返回 store 而是返回 createStore 是为了让外界可以传递initialState。 5 | export default initialState => createStore(rootReducers, initialState); -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | React 6 | 7 | 8 | 9 |
10 | 11 | 12 | -------------------------------------------------------------------------------- /src/app/index/constants/ActionTypes.js: -------------------------------------------------------------------------------- 1 | export const ADD_ITEM = 'ADD_ITEM'; 2 | export const UPDATE_ITEM = 'UPDATE_ITEM'; 3 | export const DELETE_ITEM = 'DELETE_ITEM'; 4 | export const UPDATE_ITEMS = 'UPDATE_ITEMS'; 5 | export const DELETE_ITEMS = 'DELETE_ITEMS'; 6 | export const FILTER_ITEMS = 'FILTER_ITEMS'; -------------------------------------------------------------------------------- /src/app/index/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import createStore from './store'; 4 | import Root from './containers/Root'; 5 | 6 | let store = createStore(); 7 | 8 | ReactDOM.render( 9 | , 10 | document.getElementById('app') 11 | ); -------------------------------------------------------------------------------- /src/app/index/reducers/filter.js: -------------------------------------------------------------------------------- 1 | import { FILTER_ITEMS } from '../constants/ActionTypes'; 2 | 3 | export default (state = {active: true}, action) => { 4 | switch (action.type) { 5 | case FILTER_ITEMS: 6 | return Object.assign({}, state, {active: action.active}) 7 | 8 | default: 9 | return state 10 | } 11 | } -------------------------------------------------------------------------------- /src/app/index/containers/Root.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { Provider } from 'react-redux'; 3 | import App from './App'; 4 | 5 | export default class Root extends Component { 6 | render() { 7 | return ( 8 | 9 | 10 | 11 | ) 12 | } 13 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # learn-react 2 | 3 | [![Todo-list](http://p5.qhimg.com/d/inn/ed827624/Todo-list.png)](http://todos.berwin.me) 4 | 5 | ## install 6 | 7 | ```shell 8 | npm install 9 | 10 | npm run build 11 | ``` 12 | 13 | ## run 14 | 15 | ```shell 16 | npm run start 17 | ``` 18 | 19 | Run in the Browser 20 | 21 | ```shell 22 | http://127.0.0.1:3000 23 | ``` -------------------------------------------------------------------------------- /src/app/index/containers/App.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { Router, Route, Link } from 'react-router'; 3 | import { createHistory, useBasename } from 'history'; 4 | import Index from '../components/index.jsx'; 5 | 6 | export default class View extends Component { 7 | render () { 8 | return ( 9 | 10 | 11 | 12 | ) 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/app/index/components/list/Toggle.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | 3 | export default class Toggle extends Component { 4 | 5 | handleChange(e) { 6 | let { updateItems } = this.props; 7 | 8 | updateItems({status: e.currentTarget.checked}); 9 | } 10 | 11 | render () { 12 | let { isAllCompleted } = this.props; 13 | 14 | return () 15 | } 16 | } -------------------------------------------------------------------------------- /src/app/index/components/index.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { Link } from 'react-router'; 3 | import Category from './category/index.jsx'; 4 | import ListBox from './list/index.jsx'; 5 | 6 | export default class Index extends Component { 7 | render () { 8 | return ( 9 |
10 |

Todo-List

11 |
12 | 13 | 14 |
15 |
16 | ); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/app/index/reducers/category.js: -------------------------------------------------------------------------------- 1 | import { ADD_CATEGORY } from '../constants/CategoryTypes'; 2 | 3 | 4 | /** 5 | * 创建分类 6 | * 7 | * 8 | */ 9 | let createCategory = name => { 10 | let time = Date.now(); 11 | 12 | return { 13 | id: Math.random().toString(36).split('.').join(''), 14 | addTime: time, 15 | updateTime: time, 16 | name 17 | } 18 | } 19 | 20 | export default (state = [], action) => { 21 | switch (action.type) { 22 | case ADD_CATEGORY: 23 | return [createCategory(action.name)] 24 | 25 | default: 26 | return state 27 | } 28 | } -------------------------------------------------------------------------------- /src/app/index/components/list/AddTodo.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | 3 | export default class AddTodo extends Component { 4 | 5 | handleKeyUp(e) { 6 | if (e.keyCode === 13) { 7 | let text = e.target.value; 8 | if (!text) return; 9 | 10 | this.props.addItem(text); 11 | e.target.value = ''; 12 | } 13 | } 14 | 15 | render () { 16 | return ( 17 | 18 | ); 19 | } 20 | } 21 | 22 | AddTodo.propTypes = { 23 | addItem: React.PropTypes.func.isRequired 24 | } -------------------------------------------------------------------------------- /src/app/index/components/list/Todo.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | 3 | export default class Item extends Component { 4 | toggleTodo(e) { 5 | this.props.updateItem({ 6 | status: e.currentTarget.checked 7 | }) 8 | } 9 | 10 | render () { 11 | let { deleteItem, status } = this.props; 12 | 13 | return ( 14 |
  • 15 | 16 | 17 | 18 |
  • 19 | ) 20 | } 21 | } -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | var webpack = require("webpack"); 2 | 3 | module.exports = { 4 | entry: './src/app/index/index.js', 5 | output: { 6 | path: __dirname + '/src/dist', 7 | filename: 'bundle.js' 8 | }, 9 | module: { 10 | loaders: [ 11 | {test: /(\.js|\.jsx)$/, loader: 'babel-loader'} 12 | ] 13 | }, 14 | 15 | resolve: { 16 | root: [__dirname + '/node_modules'] 17 | } 18 | }; 19 | 20 | /* 21 | * 压缩代码配置项 22 | * 23 | plugins: [ 24 | new webpack.optimize.UglifyJsPlugin({ 25 | compress: { 26 | warnings: false 27 | } 28 | }) 29 | ] 30 | */ -------------------------------------------------------------------------------- /src/app/index/components/category/index.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { bindActionCreators } from 'redux'; 3 | import { connect } from 'react-redux'; 4 | import * as actions from '../../actions/category.js'; 5 | 6 | export default class Category extends Component { 7 | render () { 8 | return ( 9 |
    10 |
  • Default
  • 11 |
    12 | ); 13 | } 14 | } 15 | 16 | let mapStateToProps = state => ({category: state.category}); 17 | let mapDispatchToProps = dispatch => bindActionCreators(actions, dispatch); 18 | 19 | export default connect(mapStateToProps, mapDispatchToProps)(Category); -------------------------------------------------------------------------------- /src/app/index/components/list/List.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import Todo from './Todo.jsx'; 3 | 4 | export default class List extends Component { 5 | render () { 6 | let { deleteItem, updateItem, activeFilter, filter } = this.props; 7 | 8 | return ( 9 | 20 | ) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/app/index/actions/list.js: -------------------------------------------------------------------------------- 1 | import * as types from '../constants/ActionTypes'; 2 | 3 | export function addItem(text) { 4 | return { 5 | type: types.ADD_ITEM, 6 | text 7 | } 8 | } 9 | 10 | export function updateItem(data) { 11 | return { 12 | type: types.UPDATE_ITEM, 13 | data 14 | } 15 | } 16 | 17 | export function deleteItem(id) { 18 | return { 19 | type: types.DELETE_ITEM, 20 | id 21 | } 22 | } 23 | 24 | export function updateItems(data) { 25 | return { 26 | type: types.UPDATE_ITEMS, 27 | data 28 | } 29 | } 30 | 31 | export function deleteItems(query) { 32 | return { 33 | type: types.DELETE_ITEMS, 34 | query 35 | } 36 | } 37 | 38 | export function activeFilter(action) { 39 | return { 40 | type: types.FILTER_ITEMS, 41 | active: action 42 | } 43 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "learn-react", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "app.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1", 8 | "build": "webpack -p", 9 | "watch": "webpack -w", 10 | "start": "node app.js" 11 | }, 12 | "repository": { 13 | "type": "git", 14 | "url": "git+https://github.com/berwin/learn-react.git" 15 | }, 16 | "author": "", 17 | "license": "ISC", 18 | "bugs": { 19 | "url": "https://github.com/berwin/learn-react/issues" 20 | }, 21 | "homepage": "https://github.com/berwin/learn-react#readme", 22 | "dependencies": { 23 | "babel-core": "^5.8.25", 24 | "babel-loader": "^5.3.2", 25 | "history": "^1.13.0", 26 | "react": "^0.14.0", 27 | "react-dom": "^0.14.0", 28 | "react-redux": "^4.0.0", 29 | "react-router": "^1.0.0-rc3", 30 | "redux": "^3.0.3", 31 | "request": "^2.65.0" 32 | }, 33 | "devDependencies": { 34 | "webpack": "^1.12.2" 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /app.js: -------------------------------------------------------------------------------- 1 | var http = require('http'); 2 | var fs = require('fs'); 3 | var request = require('request'); 4 | 5 | function statPV() { 6 | var options = { 7 | url: 'http://stat.berwin.me/api/v1/content', 8 | json: true, 9 | method: 'POST', 10 | timeout: 30000, 11 | body: { 12 | groupID : '56372245135c3a806b54c2e1', 13 | sourceID : '563721f8135c3a806b54c2e0', 14 | token: '563721f8135c3a806b54c2df', 15 | data : { 16 | value: 'pv' 17 | } 18 | } 19 | }; 20 | 21 | request(options, function(e, r, body) {}); 22 | } 23 | 24 | var server = http.createServer(function (req, res) { 25 | if (req.url == '/favicon.ico') return res.end(); 26 | 27 | var file = ''; 28 | 29 | if (req.url === '/') { 30 | statPV(); 31 | file = fs.readFileSync(__dirname + '/src/index.html'); 32 | res.setHeader("Content-Type", "text/html"); 33 | } else { 34 | file = fs.readFileSync(__dirname + '/src' + req.url); 35 | if (~req.url.indexOf('.css')) res.setHeader("Content-Type", "text/css"); 36 | } 37 | 38 | res.end(file); 39 | }); 40 | 41 | server.listen(3000, function () { 42 | console.log('\x1B[32m', 'Server started http://127.0.0.1:3000'); 43 | }); -------------------------------------------------------------------------------- /src/app/index/reducers/list.js: -------------------------------------------------------------------------------- 1 | import { ADD_ITEM, UPDATE_ITEM, DELETE_ITEM, UPDATE_ITEMS, DELETE_ITEMS } from '../constants/ActionTypes'; 2 | 3 | /** 4 | * 添加 5 | * 6 | * @param {String} 添加的文字 7 | * 8 | * @return {Object} 将要添加的数据 9 | */ 10 | let createItem = text => { 11 | let time = Date.now(); 12 | 13 | return { 14 | id: Math.random().toString(36).split('.').join(''), 15 | addTime: time, 16 | updateTime: time, 17 | status: false, 18 | text 19 | } 20 | } 21 | 22 | /** 23 | * 更新 24 | * 25 | * @param {String} 添加的文字 26 | * 27 | * @return {Array} 更新后的数据 28 | */ 29 | let updateItem = ({id, ...other}, state) => { 30 | let time = Date.now(); 31 | 32 | return state.map(item => 33 | item.id === id ? 34 | Object.assign({}, item, other, { 35 | updateTime: time 36 | }) : 37 | item 38 | ); 39 | } 40 | 41 | export default (state = [], action) => { 42 | switch (action.type) { 43 | case ADD_ITEM: 44 | return [createItem(action.text), ...state] 45 | 46 | case UPDATE_ITEM: 47 | return updateItem(action.data, state) 48 | 49 | case DELETE_ITEM: 50 | return state.filter(item => item.id !== action.id) 51 | 52 | case UPDATE_ITEMS: 53 | return state.map(item => Object.assign({}, item, action.data)) 54 | 55 | case DELETE_ITEMS: 56 | return [] 57 | 58 | default: 59 | return state 60 | } 61 | } -------------------------------------------------------------------------------- /src/app/index/components/list/index.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { bindActionCreators } from 'redux'; 3 | import { connect } from 'react-redux'; 4 | import * as actions from '../../actions/list.js'; 5 | import AddTodo from './AddTodo.jsx'; 6 | import List from './List.jsx'; 7 | import Toggle from './Toggle.jsx'; 8 | 9 | export default class ListBox extends Component { 10 | toggleComplated() { 11 | let { filter, activeFilter } = this.props; 12 | 13 | activeFilter(!filter.active); 14 | } 15 | 16 | render () { 17 | let { filter, addItem, updateItems, isAllCompleted } = this.props; 18 | 19 | return ( 20 |
    21 |
    22 | 23 | 24 |
    25 | 26 | 29 |
    30 | ); 31 | } 32 | } 33 | 34 | let mapStateToProps = state => { 35 | let list = state.list; 36 | 37 | return { 38 | isAllCompleted: !!list.length && list.every(item => item.status), 39 | ...state 40 | } 41 | }; 42 | let mapDispatchToProps = dispatch => bindActionCreators(actions, dispatch); 43 | 44 | export default connect(mapStateToProps, mapDispatchToProps)(ListBox); -------------------------------------------------------------------------------- /src/app/learn.jsx: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import {bindActionCreators, combineReducers, createStore} from 'redux'; 4 | import {Provider, connect} from 'react-redux'; 5 | 6 | // React Component 7 | class Counter extends React.Component { 8 | render () { 9 | const {value, onIncreaseClick} = this.props; 10 | 11 | console.log(this.props) 12 | 13 | return ( 14 |
    15 | {value} 16 | 17 |
    18 | ) 19 | } 20 | } 21 | 22 | Counter.propTypes = { 23 | value: PropTypes.number.isRequired, 24 | onIncreaseClick: PropTypes.func.isRequired 25 | }; 26 | 27 | // Action 28 | const increaseAction = {type: 'increase'}; 29 | 30 | // Reducer 31 | function count (state = 0, action) { 32 | 33 | switch (action.type) { 34 | case 'increase': 35 | return state + 1 36 | default: 37 | return state 38 | } 39 | } 40 | 41 | function count2 (state = 0, action) { 42 | 43 | switch (action.type) { 44 | case 'increase2': 45 | return state + 1 46 | default: 47 | return state 48 | } 49 | } 50 | 51 | let todoApp = combineReducers({ 52 | count, count2 53 | }); 54 | 55 | // Store 56 | let store = createStore(todoApp); 57 | 58 | function mapStateToProps (state) { 59 | return {value: state.count}; 60 | } 61 | 62 | function mapDispatchToProps (dispatch) { 63 | store.getState() 64 | // return {onIncreaseClick: dispatch(increaseAction)} 65 | return {onIncreaseClick: () => dispatch(increaseAction)} 66 | // return bindActionCreators(increaseAction, dispatch); 67 | } 68 | 69 | let App = connect(mapStateToProps, mapDispatchToProps)(Counter); 70 | 71 | ReactDOM.render( 72 | 73 | 74 | , 75 | document.getElementById('app') 76 | ); -------------------------------------------------------------------------------- /src/css/base.css: -------------------------------------------------------------------------------- 1 | html,body {margin: 0; padding: 0;} 2 | .clearfix:before,.clearfix:after{content:''; display:table;}.clearfix:after{clear:both;}.clearfix{*zoom:1;}/*清浮动用的*/ 3 | li{list-style: none;} 4 | button { 5 | margin: 0; 6 | padding: 0; 7 | border: 0; 8 | background: none; 9 | font-size: 100%; 10 | vertical-align: baseline; 11 | font-family: inherit; 12 | font-weight: inherit; 13 | color: inherit; 14 | -webkit-appearance: none; 15 | appearance: none; 16 | -webkit-font-smoothing: antialiased; 17 | -moz-font-smoothing: antialiased; 18 | font-smoothing: antialiased; 19 | } 20 | a { 21 | color: rgba(42, 174, 245, 1); 22 | text-decoration:none; 23 | } 24 | 25 | button, 26 | input[type="checkbox"] { 27 | outline: none; 28 | } 29 | 30 | #app { 31 | font: 14px 'Helvetica Neue', Helvetica, Arial, sans-serif; 32 | color: #4d4d4d; 33 | min-width: 430px; 34 | max-width: 750px; 35 | margin: 0 auto; 36 | -webkit-font-smoothing: antialiased; 37 | -moz-font-smoothing: antialiased; 38 | font-smoothing: antialiased; 39 | font-weight: 300; 40 | } 41 | #app h1 { 42 | width: 100%; 43 | font-size: 80px; 44 | font-weight: 200; 45 | text-align: center; 46 | color: rgba(42, 174, 245, 1); 47 | -webkit-text-rendering: optimizeLegibility; 48 | -moz-text-rendering: optimizeLegibility; 49 | text-rendering: optimizeLegibility; 50 | } 51 | 52 | #app .box { 53 | border:1px solid #ccc; 54 | border-radius:5px; 55 | box-shadow:0 20px 50px rgba(0,0,0,.4); 56 | } 57 | 58 | .category, .list { 59 | float:left; 60 | box-sizing:border-box; 61 | } 62 | 63 | .category { 64 | padding:10px 0; 65 | width:40%; 66 | } 67 | .category li { 68 | padding:0 10px; 69 | line-height: 30px; 70 | } 71 | .category li.active { 72 | color:#fff; 73 | background: rgba(42, 174, 245, 1); 74 | /*background: rgba(206, 206, 206, 1);*/ 75 | } 76 | 77 | label[for='toggle-all'] { 78 | display: none; 79 | } 80 | 81 | #app .list-header { 82 | position:relative; 83 | height:65px; 84 | } 85 | 86 | #toggle-all { 87 | position: absolute; 88 | top: 12px; 89 | left: -12px; 90 | width: 60px; 91 | height: 34px; 92 | text-align: center; 93 | border: none; /* Mobile Safari */ 94 | } 95 | 96 | #toggle-all:before { 97 | content: '❯'; 98 | font-size: 22px; 99 | color: #e6e6e6; 100 | padding: 10px 27px 10px 27px; 101 | } 102 | 103 | #toggle-all:checked:before { 104 | color: #737373; 105 | } 106 | 107 | #app .list { 108 | width:60%; 109 | border-left:1px solid #ededed; 110 | } 111 | 112 | #new-todo { 113 | padding: 16px 16px 16px 60px; 114 | border: none; 115 | background: rgba(0, 0, 0, 0.003); 116 | box-shadow: inset 0 -2px 1px rgba(0,0,0,0.03); 117 | 118 | width: 100%; 119 | font-size: 24px; 120 | font-family: inherit; 121 | font-weight: inherit; 122 | line-height: 1.4em; 123 | outline: none; 124 | color: inherit; 125 | box-sizing: border-box; 126 | } 127 | 128 | #app .todo-list { 129 | padding:0;margin:0; 130 | } 131 | 132 | #app .list li { 133 | position: relative; 134 | font-size: 24px; 135 | border-bottom: 1px solid #ededed; 136 | } 137 | 138 | 139 | #app .list li .toggle { 140 | text-align: center; 141 | width: 40px; 142 | /* auto, since non-WebKit browsers doesn't support input styling */ 143 | height: 40px; 144 | position: absolute; 145 | top: 0; 146 | bottom: 0; 147 | margin: auto 0; 148 | border: none; /* Mobile Safari */ 149 | -webkit-appearance: none; 150 | appearance: none; 151 | } 152 | 153 | #app .list li .toggle:after { 154 | content: url('data:image/svg+xml;utf8,'); 155 | } 156 | 157 | #app .list li .toggle:checked:after { 158 | content: url('data:image/svg+xml;utf8,'); 159 | } 160 | 161 | #app .list li label { 162 | white-space: pre; 163 | word-break: break-word; 164 | padding: 15px 60px 15px 15px; 165 | margin-left: 45px; 166 | display: block; 167 | line-height: 1.2; 168 | transition: color 0.4s; 169 | } 170 | 171 | #app .list li.completed label { 172 | color: #d9d9d9; 173 | text-decoration: line-through; 174 | } 175 | 176 | #app .list li .destroy { 177 | display: none; 178 | position: absolute; 179 | top: 0; 180 | right: 10px; 181 | bottom: 0; 182 | width: 40px; 183 | height: 40px; 184 | margin: auto 0; 185 | font-size: 30px; 186 | /*color: #cc9a9a;*/ 187 | color: rgba(42, 174, 245, .5); 188 | /*margin-bottom: 11px;*/ 189 | transition: color 0.2s ease-out; 190 | font-family: monospace; 191 | } 192 | 193 | #app .list li .destroy:hover { 194 | color: rgba(42, 174, 245, 1); 195 | } 196 | 197 | #app .list li .destroy:after { 198 | content: '×'; 199 | } 200 | 201 | #app .list li:hover .destroy { 202 | display: block; 203 | } 204 | 205 | .list-footer { 206 | color: #777; 207 | padding: 10px 15px; 208 | height: 20px; 209 | text-align: center; 210 | } 211 | 212 | 213 | 214 | /* 215 | Hack to remove background from Mobile Safari. 216 | Can't use it globally since it destroys checkboxes in Firefox 217 | */ 218 | @media screen and (-webkit-min-device-pixel-ratio:0) { 219 | #toggle-all, 220 | #todo-list li .toggle { 221 | background: none; 222 | } 223 | 224 | #todo-list li .toggle { 225 | height: 40px; 226 | } 227 | 228 | #toggle-all { 229 | -webkit-transform: rotate(90deg); 230 | transform: rotate(90deg); 231 | -webkit-appearance: none; 232 | appearance: none; 233 | } 234 | } 235 | --------------------------------------------------------------------------------