├── .gitignore ├── styles ├── bg.png └── base.css ├── src ├── main │ ├── todo.ts │ ├── routes.ts │ ├── utils.ts │ ├── footer.ts │ ├── todoModel.ts │ ├── todoItem.ts │ └── app.ts └── declarations │ └── react.d.ts ├── .brackets.json ├── README.md ├── index.html └── package.json /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | temp -------------------------------------------------------------------------------- /styles/bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fdecampredon/react-typescript-todomvc/HEAD/styles/bg.png -------------------------------------------------------------------------------- /src/main/todo.ts: -------------------------------------------------------------------------------- 1 | interface Todo { 2 | id: string; 3 | title: string; 4 | completed: boolean; 5 | } 6 | 7 | export = Todo; -------------------------------------------------------------------------------- /src/main/routes.ts: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | export var ALL_TODOS = 'all'; 4 | export var ACTIVE_TODOS = 'active'; 5 | export var COMPLETED_TODOS = 'completed'; -------------------------------------------------------------------------------- /.brackets.json: -------------------------------------------------------------------------------- 1 | { 2 | "typescript": { 3 | "module": "commonjs", 4 | "target": "es5", 5 | "noImplicitAny": true, 6 | "sources": ["src/declarations/*.d.ts", "src/main/**.ts"], 7 | "typescriptPath": "/Users/kapit/Documents/workspaces/typescript/TypeScript/" 8 | } 9 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # React Rx TodoMVC Example 2 | 3 | > [TodoMVC](http://todomvc.com/) implementation built on top of [React](http://facebook.github.io/react/) with [JSX-TypeScript](https://github.com/fdecampredon/jsx-typescript) 4 | 5 | # Running 6 | 7 | Simply start a static server on this project folder. 8 | 9 | # Install/Build 10 | 11 | Clone this repository, then : 12 | ``` 13 | npm install 14 | ``` 15 | this will install all the dependencies. 16 | 17 | To build the project : 18 | ``` 19 | npm run build 20 | ``` 21 | this will bundle the project sources files. 22 | 23 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | React • TypeScript • TodoMVC 6 | 7 | 8 | 9 |
10 | 11 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-typescript-todomvc", 3 | "version": "0.1.0", 4 | "description": "TodoMVC written with React and jsx-typescript", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1", 8 | "build": "jsx-tsc --module commonjs --target es5 --noImplicitAny --outDir ./temp src/declarations/react.d.ts src/main/app.ts && browserify ./temp/app.js -o bundle.js" 9 | }, 10 | "keywords": [ 11 | "React", 12 | "typescript", 13 | "jsx" 14 | ], 15 | "author": "Francois de Campredon ", 16 | "license": "BSD", 17 | "dependencies": { 18 | "director": "^1.2.8", 19 | "jsx-typescript": "^1.5.0-alpha.4", 20 | "react": "^0.13.0-beta.1" 21 | }, 22 | "devDependencies": { 23 | "jsx-typescript": "^1.5.0-alpha.1", 24 | "browserify": "^8.1.3" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/main/utils.ts: -------------------------------------------------------------------------------- 1 | export function uuid() { 2 | /*jshint bitwise:false */ 3 | var i = 0, random = 0; 4 | var uuid = ''; 5 | 6 | for (i = 0; i < 32; i++) { 7 | random = Math.random() * 16 | 0; 8 | if (i === 8 || i === 12 || i === 16 || i === 20) { 9 | uuid += '-'; 10 | } 11 | uuid += (i === 12 ? 4 : (i === 16 ? (random & 3 | 8) : random)) 12 | .toString(16); 13 | } 14 | 15 | return uuid; 16 | } 17 | 18 | export function pluralize(count: number, word: string) { 19 | return count === 1 ? word : word + 's'; 20 | } 21 | 22 | export function store(namespace: string, data?: any): any { 23 | if (data) { 24 | return localStorage.setItem(namespace, JSON.stringify(data)); 25 | } 26 | 27 | var store = localStorage.getItem(namespace); 28 | return (store && JSON.parse(store)) || []; 29 | } 30 | 31 | export function extend(...objects: any[]): any { 32 | var newObj: any = {}; 33 | for (var i = 0; i < objects.length; i++) { 34 | var obj = objects[i]; 35 | for (var key in obj) { 36 | if (obj.hasOwnProperty(key)) { 37 | newObj[key] = obj[key]; 38 | } 39 | } 40 | } 41 | return newObj; 42 | } -------------------------------------------------------------------------------- /src/main/footer.ts: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import React = require('react'); 4 | import Todo = require('./todo'); 5 | import Utils = require('./utils'); 6 | import routes = require('./routes'); 7 | 8 | interface Props { 9 | count: number; 10 | completedCount: number; 11 | onClearCompleted: () => void; 12 | nowShowing: string; 13 | } 14 | 15 | class TodoFooter extends React.Component { 16 | render(): React.ReactElement { 17 | var activeTodoWord = Utils.pluralize(this.props.count, 'item'); 18 | var clearButton: React.ReactHTMLElement = null; 19 | 20 | if (this.props.completedCount > 0) { 21 | clearButton = ( 22 | 25 | ); 26 | } 27 | 28 | var nowShowing = this.props.nowShowing; 29 | 30 | return ( 31 | 62 | ); 63 | 64 | } 65 | } 66 | 67 | 68 | export = TodoFooter; -------------------------------------------------------------------------------- /src/main/todoModel.ts: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import Utils = require('./utils'); 4 | import Todo = require('./todo'); 5 | 6 | 7 | 8 | // Generic "model" object. You can use whatever 9 | // framework you want. For this application it 10 | // may not even be worth separating this logic 11 | // out, but we do this to demonstrate one way to 12 | // separate out parts of your application. 13 | class TodoModel{ 14 | 15 | todos: Todo[]; 16 | onChanges: { (): void }[] = []; 17 | 18 | 19 | constructor(private key: string) { 20 | this.todos = Utils.store(key); 21 | } 22 | 23 | subscribe(callback: () => void) { 24 | this.onChanges.push(callback); 25 | } 26 | 27 | inform() { 28 | Utils.store(this.key, this.todos); 29 | this.onChanges.forEach(callback => callback()); 30 | } 31 | 32 | addTodo(title: string) { 33 | this.todos = this.todos.concat({ 34 | id: Utils.uuid(), 35 | title: title, 36 | completed: false 37 | }); 38 | 39 | this.inform(); 40 | } 41 | 42 | toggleAll(checked: boolean) { 43 | // Note: it's usually better to use immutable data structures since they're 44 | // easier to reason about and React works very well with them. That's why we 45 | // use map() and filter() everywhere instead of mutating the array or todo 46 | // items themselves. 47 | this.todos = this.todos.map(todo => { 48 | return Utils.extend({}, todo, {completed: checked}); 49 | }); 50 | 51 | this.inform(); 52 | } 53 | 54 | toggle(todoToToggle: Todo) { 55 | this.todos = this.todos.map(todo => { 56 | return todo !== todoToToggle ? 57 | todo : 58 | Utils.extend({}, todo, {completed: !todo.completed}); 59 | }); 60 | 61 | this.inform(); 62 | } 63 | 64 | destroy(todo: Todo) { 65 | this.todos = this.todos.filter(candidate => candidate !== todo); 66 | this.inform(); 67 | } 68 | 69 | save(todoToSave: Todo, text: string) { 70 | this.todos = this.todos.map(todo => 71 | todo !== todoToSave ? todo : Utils.extend({}, todo, {title: text})); 72 | 73 | this.inform(); 74 | } 75 | 76 | clearCompleted() { 77 | this.todos = this.todos.filter(todo => !todo.completed); 78 | this.inform(); 79 | } 80 | } 81 | 82 | export = TodoModel; -------------------------------------------------------------------------------- /src/main/todoItem.ts: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | 4 | import React = require('react'); 5 | import Todo = require('./todo'); 6 | 7 | 8 | var ESCAPE_KEY = 27; 9 | var ENTER_KEY = 13; 10 | 11 | 12 | 13 | interface Props extends React.BaseProps { 14 | onSave: (val: string) => void; 15 | onDestroy: () => void; 16 | onEdit: (callback: () => void) => void; 17 | onCancel: () => void; 18 | todo: Todo; 19 | onToggle: () => void; 20 | editing: boolean; 21 | } 22 | 23 | interface State { 24 | editText: string 25 | } 26 | 27 | class TodoItem extends React.Component { 28 | 29 | editField: HTMLInputElement; 30 | 31 | state: State = { editText: this.props.todo.title} 32 | 33 | handleSubmit = () => { 34 | var val = this.state.editText.trim(); 35 | if (val) { 36 | this.props.onSave(val); 37 | this.setState({editText: val}); 38 | } else { 39 | this.props.onDestroy(); 40 | } 41 | return false; 42 | } 43 | 44 | editFieldCallback = (editField: React.Component) => { 45 | this.editField = editField? editField.getDOMNode() : null; 46 | } 47 | 48 | handleEdit = () => { 49 | // react optimizes renders by batching them. This means you can't call 50 | // parent's `onEdit` (which in this case triggeres a re-render), and 51 | // immediately manipulate the DOM as if the rendering's over. Put it as a 52 | // callback. Refer to app.js' `edit` method 53 | this.props.onEdit(() => { 54 | this.editField.focus(); 55 | this.editField.setSelectionRange(this.editField.value.length, this.editField.value.length); 56 | }); 57 | this.setState({editText: this.props.todo.title}); 58 | } 59 | 60 | handleKeyDown = (event: React.KeyboardEvent) => { 61 | if (event.which === ESCAPE_KEY) { 62 | this.setState({editText: this.props.todo.title}); 63 | this.props.onCancel(); 64 | } else if (event.which === ENTER_KEY) { 65 | this.handleSubmit(); 66 | } 67 | } 68 | 69 | 70 | handleChange = (event: React.SyntheticEvent) => { 71 | this.setState({editText: this.editField.value}); 72 | } 73 | 74 | 75 | /** 76 | * This is a completely optional performance enhancement that you can implement 77 | * on any React component. If you were to delete this method the app would still 78 | * work correctly (and still be very performant!), we just use it as an example 79 | * of how little code it takes to get an order of magnitude performance improvement. 80 | */ 81 | shouldComponentUpdate(nextProps: Props, nextState: State) { 82 | return ( 83 | nextProps.todo !== this.props.todo || 84 | nextProps.editing !== this.props.editing || 85 | nextState.editText !== this.state.editText 86 | ); 87 | } 88 | 89 | 90 | render() { 91 | 92 | var className = this.props.editing ? 'editing' : ' ' + this.props.todo.completed ? 'complete' : ''; 93 | return ( 94 |
  • 95 |
    96 | 101 | 102 |
    104 | 105 | 112 |
  • 113 | ); 114 | } 115 | } 116 | 117 | 118 | 119 | 120 | export = TodoItem; -------------------------------------------------------------------------------- /src/main/app.ts: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import React = require('react'); 4 | import Todo = require('./todo'); 5 | import Utils = require('./utils'); 6 | import routes = require('./routes'); 7 | import TodoModel = require('./todoModel'); 8 | import TodoFooter = require('./footer'); 9 | import TodoItem = require('./todoItem'); 10 | 11 | 12 | 13 | 14 | 15 | //todo 16 | declare var require: any; 17 | var Router: any = require('director').Router; 18 | 19 | var ENTER_KEY = 13; 20 | 21 | class TodoApp extends React.Component { 22 | newField: HTMLInputElement; 23 | 24 | newFieldCallback = (newField: React.Component) => { 25 | this.newField = newField ? newField.getDOMNode() : null; 26 | } 27 | 28 | 29 | state : TodoApp.State = { 30 | nowShowing: routes.ALL_TODOS, 31 | editing: null 32 | }; 33 | 34 | componentDidMount() { 35 | var setState = this.setState; 36 | var router = Router({ 37 | '/': () => this.setState({nowShowing: routes.ALL_TODOS}), 38 | '/active': () => this.setState({nowShowing: routes.ACTIVE_TODOS}), 39 | '/completed': () => this.setState({nowShowing: routes.COMPLETED_TODOS}) 40 | }); 41 | router.init('/'); 42 | } 43 | 44 | handleNewTodoKeyDown = (event: React.KeyboardEvent) => { 45 | if (event.which !== ENTER_KEY) { 46 | return; 47 | } 48 | 49 | var val = this.newField.value.trim(); 50 | 51 | if (val) { 52 | this.props.model.addTodo(val); 53 | this.newField.value = ''; 54 | } 55 | 56 | return false; 57 | } 58 | 59 | toggleAll = (event: React.MouseEvent) => { 60 | var checked: boolean = ( event.target).checked; 61 | this.props.model.toggleAll(checked); 62 | } 63 | 64 | toggle(todoToToggle: Todo) { 65 | this.props.model.toggle(todoToToggle); 66 | } 67 | 68 | destroy(todo: Todo) { 69 | this.props.model.destroy(todo); 70 | } 71 | 72 | edit(todo: Todo, callback: () => void) { 73 | // refer to todoItem.js `handleEdit` for the reasoning behind the 74 | // callback 75 | this.setState({editing: todo.id}, function () { 76 | callback(); 77 | }); 78 | } 79 | 80 | save(todoToSave: Todo, text: string) { 81 | this.props.model.save(todoToSave, text); 82 | this.setState({editing: null}); 83 | } 84 | 85 | cancel() { 86 | this.setState({editing: null}); 87 | } 88 | 89 | clearCompleted() { 90 | this.props.model.clearCompleted(); 91 | } 92 | 93 | render() { 94 | var footer: React.ReactElement; 95 | var main: React.ReactElement; 96 | var todos = this.props.model.todos; 97 | 98 | var shownTodos = todos.filter( todo => { 99 | switch (this.state.nowShowing) { 100 | case routes.ACTIVE_TODOS: 101 | return !todo.completed; 102 | case routes.COMPLETED_TODOS: 103 | return todo.completed; 104 | default: 105 | return true; 106 | } 107 | }); 108 | 109 | var todoItems = shownTodos.map(todo => 110 | this.toggle(todo)} 114 | onDestroy={() => this.destroy(todo)} 115 | onEdit={callback => this.edit(todo, callback)} 116 | editing={this.state.editing === todo.id} 117 | onSave={text => this.save(todo, text)} 118 | onCancel={() => this.cancel()} 119 | /> 120 | ); 121 | 122 | var activeTodoCount = todos.reduce(function (accum, todo) { 123 | return todo.completed ? accum : accum + 1; 124 | }, 0); 125 | 126 | var completedCount = todos.length - activeTodoCount; 127 | 128 | if (activeTodoCount || completedCount) { 129 | footer = 130 | this.clearCompleted()} 135 | />; 136 | } 137 | 138 | if (todos.length) { 139 | main = ( 140 |
    141 | 147 |
      148 | {todoItems} 149 |
    150 |
    151 | ); 152 | } 153 | 154 | return ( 155 |
    156 | 166 | {main} 167 | {footer} 168 |
    169 | ); 170 | } 171 | } 172 | 173 | module TodoApp { 174 | export interface Props { 175 | model: TodoModel 176 | } 177 | 178 | export interface State { 179 | editing?: string; 180 | nowShowing?: string; 181 | } 182 | } 183 | 184 | var model = new TodoModel('react-todos'); 185 | 186 | function render() { 187 | React.render( 188 | , 189 | document.getElementById('todoapp') 190 | ); 191 | } 192 | 193 | model.subscribe(render); 194 | render(); -------------------------------------------------------------------------------- /styles/base.css: -------------------------------------------------------------------------------- 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 | color: inherit; 16 | -webkit-appearance: none; 17 | -ms-appearance: none; 18 | -o-appearance: none; 19 | appearance: none; 20 | } 21 | 22 | body { 23 | font: 14px 'Helvetica Neue', Helvetica, Arial, sans-serif; 24 | line-height: 1.4em; 25 | background: #eaeaea url('bg.png'); 26 | color: #4d4d4d; 27 | width: 550px; 28 | margin: 0 auto; 29 | -webkit-font-smoothing: antialiased; 30 | -moz-font-smoothing: antialiased; 31 | -ms-font-smoothing: antialiased; 32 | -o-font-smoothing: antialiased; 33 | font-smoothing: antialiased; 34 | } 35 | 36 | button, 37 | input[type="checkbox"] { 38 | outline: none; 39 | } 40 | 41 | #todoapp { 42 | background: #fff; 43 | background: rgba(255, 255, 255, 0.9); 44 | margin: 130px 0 40px 0; 45 | border: 1px solid #ccc; 46 | position: relative; 47 | border-top-left-radius: 2px; 48 | border-top-right-radius: 2px; 49 | box-shadow: 0 2px 6px 0 rgba(0, 0, 0, 0.2), 50 | 0 25px 50px 0 rgba(0, 0, 0, 0.15); 51 | } 52 | 53 | #todoapp:before { 54 | content: ''; 55 | border-left: 1px solid #f5d6d6; 56 | border-right: 1px solid #f5d6d6; 57 | width: 2px; 58 | position: absolute; 59 | top: 0; 60 | left: 40px; 61 | height: 100%; 62 | } 63 | 64 | #todoapp input::-webkit-input-placeholder { 65 | font-style: italic; 66 | } 67 | 68 | #todoapp input::-moz-placeholder { 69 | font-style: italic; 70 | color: #a9a9a9; 71 | } 72 | 73 | #todoapp h1 { 74 | position: absolute; 75 | top: -120px; 76 | width: 100%; 77 | font-size: 70px; 78 | font-weight: bold; 79 | text-align: center; 80 | color: #b3b3b3; 81 | color: rgba(255, 255, 255, 0.3); 82 | text-shadow: -1px -1px rgba(0, 0, 0, 0.2); 83 | -webkit-text-rendering: optimizeLegibility; 84 | -moz-text-rendering: optimizeLegibility; 85 | -ms-text-rendering: optimizeLegibility; 86 | -o-text-rendering: optimizeLegibility; 87 | text-rendering: optimizeLegibility; 88 | } 89 | 90 | #header { 91 | padding-top: 15px; 92 | border-radius: inherit; 93 | } 94 | 95 | #header:before { 96 | content: ''; 97 | position: absolute; 98 | top: 0; 99 | right: 0; 100 | left: 0; 101 | height: 15px; 102 | z-index: 2; 103 | border-bottom: 1px solid #6c615c; 104 | background: #8d7d77; 105 | background: -webkit-gradient(linear, left top, left bottom, from(rgba(132, 110, 100, 0.8)),to(rgba(101, 84, 76, 0.8))); 106 | background: -webkit-linear-gradient(top, rgba(132, 110, 100, 0.8), rgba(101, 84, 76, 0.8)); 107 | background: linear-gradient(top, rgba(132, 110, 100, 0.8), rgba(101, 84, 76, 0.8)); 108 | filter: progid:DXImageTransform.Microsoft.gradient(GradientType=0,StartColorStr='#9d8b83', EndColorStr='#847670'); 109 | border-top-left-radius: 1px; 110 | border-top-right-radius: 1px; 111 | } 112 | 113 | #new-todo, 114 | .edit { 115 | position: relative; 116 | margin: 0; 117 | width: 100%; 118 | font-size: 24px; 119 | font-family: inherit; 120 | line-height: 1.4em; 121 | border: 0; 122 | outline: none; 123 | color: inherit; 124 | padding: 6px; 125 | border: 1px solid #999; 126 | box-shadow: inset 0 -1px 5px 0 rgba(0, 0, 0, 0.2); 127 | -moz-box-sizing: border-box; 128 | -ms-box-sizing: border-box; 129 | -o-box-sizing: border-box; 130 | box-sizing: border-box; 131 | -webkit-font-smoothing: antialiased; 132 | -moz-font-smoothing: antialiased; 133 | -ms-font-smoothing: antialiased; 134 | -o-font-smoothing: antialiased; 135 | font-smoothing: antialiased; 136 | } 137 | 138 | #new-todo { 139 | padding: 16px 16px 16px 60px; 140 | border: none; 141 | background: rgba(0, 0, 0, 0.02); 142 | z-index: 2; 143 | box-shadow: none; 144 | } 145 | 146 | #main { 147 | position: relative; 148 | z-index: 2; 149 | border-top: 1px dotted #adadad; 150 | } 151 | 152 | label[for='toggle-all'] { 153 | display: none; 154 | } 155 | 156 | #toggle-all { 157 | position: absolute; 158 | top: -42px; 159 | left: -4px; 160 | width: 40px; 161 | text-align: center; 162 | /* Mobile Safari */ 163 | border: none; 164 | } 165 | 166 | #toggle-all:before { 167 | content: '»'; 168 | font-size: 28px; 169 | color: #d9d9d9; 170 | padding: 0 25px 7px; 171 | } 172 | 173 | #toggle-all:checked:before { 174 | color: #737373; 175 | } 176 | 177 | #todo-list { 178 | margin: 0; 179 | padding: 0; 180 | list-style: none; 181 | } 182 | 183 | #todo-list li { 184 | position: relative; 185 | font-size: 24px; 186 | border-bottom: 1px dotted #ccc; 187 | } 188 | 189 | #todo-list li:last-child { 190 | border-bottom: none; 191 | } 192 | 193 | #todo-list li.editing { 194 | border-bottom: none; 195 | padding: 0; 196 | } 197 | 198 | #todo-list li.editing .edit { 199 | display: block; 200 | width: 506px; 201 | padding: 13px 17px 12px 17px; 202 | margin: 0 0 0 43px; 203 | } 204 | 205 | #todo-list li.editing .view { 206 | display: none; 207 | } 208 | 209 | #todo-list li .toggle { 210 | text-align: center; 211 | width: 40px; 212 | /* auto, since non-WebKit browsers doesn't support input styling */ 213 | height: auto; 214 | position: absolute; 215 | top: 0; 216 | bottom: 0; 217 | margin: auto 0; 218 | /* Mobile Safari */ 219 | border: none; 220 | -webkit-appearance: none; 221 | -ms-appearance: none; 222 | -o-appearance: none; 223 | appearance: none; 224 | } 225 | 226 | #todo-list li .toggle:after { 227 | content: '✔'; 228 | /* 40 + a couple of pixels visual adjustment */ 229 | line-height: 43px; 230 | font-size: 20px; 231 | color: #d9d9d9; 232 | text-shadow: 0 -1px 0 #bfbfbf; 233 | } 234 | 235 | #todo-list li .toggle:checked:after { 236 | color: #85ada7; 237 | text-shadow: 0 1px 0 #669991; 238 | bottom: 1px; 239 | position: relative; 240 | } 241 | 242 | #todo-list li label { 243 | white-space: pre; 244 | word-break: break-word; 245 | padding: 15px 60px 15px 15px; 246 | margin-left: 45px; 247 | display: block; 248 | line-height: 1.2; 249 | -webkit-transition: color 0.4s; 250 | transition: color 0.4s; 251 | } 252 | 253 | #todo-list li.completed label { 254 | color: #a9a9a9; 255 | text-decoration: line-through; 256 | } 257 | 258 | #todo-list li .destroy { 259 | display: none; 260 | position: absolute; 261 | top: 0; 262 | right: 10px; 263 | bottom: 0; 264 | width: 40px; 265 | height: 40px; 266 | margin: auto 0; 267 | font-size: 22px; 268 | color: #a88a8a; 269 | -webkit-transition: all 0.2s; 270 | transition: all 0.2s; 271 | } 272 | 273 | #todo-list li .destroy:hover { 274 | text-shadow: 0 0 1px #000, 275 | 0 0 10px rgba(199, 107, 107, 0.8); 276 | -webkit-transform: scale(1.3); 277 | -ms-transform: scale(1.3); 278 | transform: scale(1.3); 279 | } 280 | 281 | #todo-list li .destroy:after { 282 | content: '✖'; 283 | } 284 | 285 | #todo-list li:hover .destroy { 286 | display: block; 287 | } 288 | 289 | #todo-list li .edit { 290 | display: none; 291 | } 292 | 293 | #todo-list li.editing:last-child { 294 | margin-bottom: -1px; 295 | } 296 | 297 | #footer { 298 | color: #777; 299 | padding: 0 15px; 300 | position: absolute; 301 | right: 0; 302 | bottom: -31px; 303 | left: 0; 304 | height: 20px; 305 | z-index: 1; 306 | text-align: center; 307 | } 308 | 309 | #footer:before { 310 | content: ''; 311 | position: absolute; 312 | right: 0; 313 | bottom: 31px; 314 | left: 0; 315 | height: 50px; 316 | z-index: -1; 317 | box-shadow: 0 1px 1px rgba(0, 0, 0, 0.3), 318 | 0 6px 0 -3px rgba(255, 255, 255, 0.8), 319 | 0 7px 1px -3px rgba(0, 0, 0, 0.3), 320 | 0 43px 0 -6px rgba(255, 255, 255, 0.8), 321 | 0 44px 2px -6px rgba(0, 0, 0, 0.2); 322 | } 323 | 324 | #todo-count { 325 | float: left; 326 | text-align: left; 327 | } 328 | 329 | #filters { 330 | margin: 0; 331 | padding: 0; 332 | list-style: none; 333 | position: absolute; 334 | right: 0; 335 | left: 0; 336 | } 337 | 338 | #filters li { 339 | display: inline; 340 | } 341 | 342 | #filters li a { 343 | color: #83756f; 344 | margin: 2px; 345 | text-decoration: none; 346 | } 347 | 348 | #filters li a.selected { 349 | font-weight: bold; 350 | } 351 | 352 | #clear-completed { 353 | float: right; 354 | position: relative; 355 | line-height: 20px; 356 | text-decoration: none; 357 | background: rgba(0, 0, 0, 0.1); 358 | font-size: 11px; 359 | padding: 0 10px; 360 | border-radius: 3px; 361 | box-shadow: 0 -1px 0 0 rgba(0, 0, 0, 0.2); 362 | } 363 | 364 | #clear-completed:hover { 365 | background: rgba(0, 0, 0, 0.15); 366 | box-shadow: 0 -1px 0 0 rgba(0, 0, 0, 0.3); 367 | } 368 | 369 | #info { 370 | margin: 65px auto 0; 371 | color: #a6a6a6; 372 | font-size: 12px; 373 | text-shadow: 0 1px 0 rgba(255, 255, 255, 0.7); 374 | text-align: center; 375 | } 376 | 377 | #info a { 378 | color: inherit; 379 | } 380 | 381 | /* 382 | Hack to remove background from Mobile Safari. 383 | Can't use it globally since it destroys checkboxes in Firefox and Opera 384 | */ 385 | 386 | @media screen and (-webkit-min-device-pixel-ratio:0) { 387 | #toggle-all, 388 | #todo-list li .toggle { 389 | background: none; 390 | } 391 | 392 | #todo-list li .toggle { 393 | height: 40px; 394 | } 395 | 396 | #toggle-all { 397 | top: -56px; 398 | left: -15px; 399 | width: 65px; 400 | height: 41px; 401 | -webkit-transform: rotate(90deg); 402 | -ms-transform: rotate(90deg); 403 | transform: rotate(90deg); 404 | -webkit-appearance: none; 405 | appearance: none; 406 | } 407 | } 408 | 409 | .hidden { 410 | display: none; 411 | } 412 | 413 | hr { 414 | margin: 20px 0; 415 | border: 0; 416 | border-top: 1px dashed #C5C5C5; 417 | border-bottom: 1px dashed #F7F7F7; 418 | } 419 | 420 | .learn a { 421 | font-weight: normal; 422 | text-decoration: none; 423 | color: #b83f45; 424 | } 425 | 426 | .learn a:hover { 427 | text-decoration: underline; 428 | color: #787e7e; 429 | } 430 | 431 | .learn h3, 432 | .learn h4, 433 | .learn h5 { 434 | margin: 10px 0; 435 | font-weight: 500; 436 | line-height: 1.2; 437 | color: #000; 438 | } 439 | 440 | .learn h3 { 441 | font-size: 24px; 442 | } 443 | 444 | .learn h4 { 445 | font-size: 18px; 446 | } 447 | 448 | .learn h5 { 449 | margin-bottom: 0; 450 | font-size: 14px; 451 | } 452 | 453 | .learn ul { 454 | padding: 0; 455 | margin: 0 0 30px 25px; 456 | } 457 | 458 | .learn li { 459 | line-height: 20px; 460 | } 461 | 462 | .learn p { 463 | font-size: 15px; 464 | font-weight: 300; 465 | line-height: 1.3; 466 | margin-top: 0; 467 | margin-bottom: 0; 468 | } 469 | 470 | .quote { 471 | border: none; 472 | margin: 20px 0 60px 0; 473 | } 474 | 475 | .quote p { 476 | font-style: italic; 477 | } 478 | 479 | .quote p:before { 480 | content: '“'; 481 | font-size: 50px; 482 | opacity: .15; 483 | position: absolute; 484 | top: -20px; 485 | left: 3px; 486 | } 487 | 488 | .quote p:after { 489 | content: '”'; 490 | font-size: 50px; 491 | opacity: .15; 492 | position: absolute; 493 | bottom: -42px; 494 | right: 3px; 495 | } 496 | 497 | .quote footer { 498 | position: absolute; 499 | bottom: -40px; 500 | right: 0; 501 | } 502 | 503 | .quote footer img { 504 | border-radius: 3px; 505 | } 506 | 507 | .quote footer a { 508 | margin-left: 5px; 509 | vertical-align: middle; 510 | } 511 | 512 | .speech-bubble { 513 | position: relative; 514 | padding: 10px; 515 | background: rgba(0, 0, 0, .04); 516 | border-radius: 5px; 517 | } 518 | 519 | .speech-bubble:after { 520 | content: ''; 521 | position: absolute; 522 | top: 100%; 523 | right: 30px; 524 | border: 13px solid transparent; 525 | border-top-color: rgba(0, 0, 0, .04); 526 | } 527 | 528 | .learn-bar > .learn { 529 | position: absolute; 530 | width: 272px; 531 | top: 8px; 532 | left: -300px; 533 | padding: 10px; 534 | border-radius: 5px; 535 | background-color: rgba(255, 255, 255, .6); 536 | -webkit-transition-property: left; 537 | transition-property: left; 538 | -webkit-transition-duration: 500ms; 539 | transition-duration: 500ms; 540 | } 541 | 542 | @media (min-width: 899px) { 543 | .learn-bar { 544 | width: auto; 545 | margin: 0 0 0 300px; 546 | } 547 | 548 | .learn-bar > .learn { 549 | left: 8px; 550 | } 551 | 552 | .learn-bar #todoapp { 553 | width: 550px; 554 | margin: 130px auto 40px auto; 555 | } 556 | } -------------------------------------------------------------------------------- /src/declarations/react.d.ts: -------------------------------------------------------------------------------- 1 | // Type definitions for React 0.13.0 2 | // Project: http://facebook.github.io/react/ 3 | // Definitions by: Asana , AssureSign 4 | // Definitions: https://github.com/borisyankov/DefinitelyTyped 5 | 6 | declare module 'react' { 7 | interface ComponentClass

    { 8 | new(props: P): Component; 9 | } 10 | // 11 | // Component 12 | // ---------------------------------------------------------------------- 13 | 14 | class Component

    { 15 | constructor(initialProps: P); 16 | 17 | public getDOMNode(): Element; 18 | public isMounted(): boolean; 19 | 20 | 21 | props: P; 22 | 23 | protected setProps(nextProps: P, callback?: () => void): void; 24 | protected replaceProps(nextProps: P, callback?: () => void): void; 25 | 26 | state: S; 27 | protected setState(nextState: S, callback?: () => void): void; 28 | protected replaceState(nextState: S, callback?: () => void): void; 29 | 30 | protected context: any; // I won't introduce a third generic type for that sorry 31 | 32 | 33 | protected render(): ReactElement | string; 34 | 35 | 36 | protected componentWillMount(): void; 37 | protected componentDidMount(): void; 38 | protected componentWillReceiveProps(nextProps: P): void; 39 | protected shouldComponentUpdate(nextProps: P, nextState: S): boolean; 40 | protected componentWillUpdate(nextProps: P, nextState: S): void; 41 | protected componentDidUpdate(prevProps: P, prevState: S): void; 42 | protected componentWillUnmount(): void; 43 | 44 | 45 | static defaultProps: any; 46 | static propTypes: ValidationMap; 47 | static contextTypes: ValidationMap; 48 | static childContextTypes: ValidationMap; 49 | } 50 | 51 | // 52 | // ReactElement 53 | // ---------------------------------------------------------------------- 54 | 55 | 56 | 57 | interface ReactCompositeElement

    { 58 | type: ComponentClass

    59 | ref: (c: Component) => any; 60 | key : string | boolean | number; 61 | props: P; 62 | } 63 | 64 | interface ReactHTMLElement { 65 | type: string; 66 | ref: (c: Component) => any; 67 | key : string | boolean | number; 68 | props: HTMLAttributes; 69 | } 70 | 71 | interface ReactSVGElement { 72 | type: string; 73 | ref: (c: Component) => any; 74 | key : string | boolean | number; 75 | props: SVGAttributes; 76 | } 77 | 78 | type ReactElement = ReactHTMLElement | ReactSVGElement | ReactCompositeElement; 79 | 80 | interface ReactElementArray extends Array {} 81 | 82 | 83 | function createElement(type: string, props: HTMLAttributes, ...children: any[]): ReactHTMLElement; 84 | function createElement(type: string, props: SVGAttributes, ...children: any[]): ReactSVGElement; 85 | function createElement

    (type: ComponentClass

    , props: P, ...children: any[]): ReactCompositeElement

    86 | 87 | 88 | 89 | // 90 | // Event System 91 | // ---------------------------------------------------------------------- 92 | 93 | interface SyntheticEvent { 94 | bubbles: boolean; 95 | cancelable: boolean; 96 | currentTarget: EventTarget; 97 | defaultPrevented: boolean; 98 | eventPhase: number; 99 | isTrusted: boolean; 100 | nativeEvent: Event; 101 | preventDefault(): void; 102 | stopPropagation(): void; 103 | target: EventTarget; 104 | timeStamp: Date; 105 | type: string; 106 | } 107 | 108 | interface ClipboardEvent extends SyntheticEvent { 109 | clipboardData: DataTransfer; 110 | } 111 | 112 | interface KeyboardEvent extends SyntheticEvent { 113 | altKey: boolean; 114 | charCode: number; 115 | ctrlKey: boolean; 116 | getModifierState(key: string): boolean; 117 | key: string; 118 | keyCode: number; 119 | locale: string; 120 | location: number; 121 | metaKey: boolean; 122 | repeat: boolean; 123 | shiftKey: boolean; 124 | which: number; 125 | } 126 | 127 | interface FocusEvent extends SyntheticEvent { 128 | relatedTarget: EventTarget; 129 | } 130 | 131 | interface FormEvent extends SyntheticEvent { 132 | } 133 | 134 | interface MouseEvent extends SyntheticEvent { 135 | altKey: boolean; 136 | button: number; 137 | buttons: number; 138 | clientX: number; 139 | clientY: number; 140 | ctrlKey: boolean; 141 | getModifierState(key: string): boolean; 142 | metaKey: boolean; 143 | pageX: number; 144 | pageY: number; 145 | relatedTarget: EventTarget; 146 | screenX: number; 147 | screenY: number; 148 | shiftKey: boolean; 149 | } 150 | 151 | interface TouchEvent extends SyntheticEvent { 152 | altKey: boolean; 153 | changedTouches: TouchList; 154 | ctrlKey: boolean; 155 | getModifierState(key: string): boolean; 156 | metaKey: boolean; 157 | shiftKey: boolean; 158 | targetTouches: TouchList; 159 | touches: TouchList; 160 | } 161 | 162 | interface UIEvent extends SyntheticEvent { 163 | detail: number; 164 | view: AbstractView; 165 | } 166 | 167 | interface WheelEvent extends SyntheticEvent { 168 | deltaMode: number; 169 | deltaX: number; 170 | deltaY: number; 171 | deltaZ: number; 172 | } 173 | 174 | // 175 | // Event Handler Types 176 | // ---------------------------------------------------------------------- 177 | 178 | interface EventHandler { 179 | (event: E): void; 180 | } 181 | 182 | interface ClipboardEventHandler extends EventHandler {} 183 | interface KeyboardEventHandler extends EventHandler {} 184 | interface FocusEventHandler extends EventHandler {} 185 | interface FormEventHandler extends EventHandler {} 186 | interface MouseEventHandler extends EventHandler {} 187 | interface TouchEventHandler extends EventHandler {} 188 | interface UIEventHandler extends EventHandler {} 189 | interface WheelEventHandler extends EventHandler {} 190 | 191 | 192 | // 193 | // Attributes 194 | // ---------------------------------------------------------------------- 195 | 196 | interface BaseProps { 197 | children?: string | ReactElement | ReactElementArray; 198 | key?: string; 199 | ref?: (c: Component) => void 200 | } 201 | 202 | 203 | interface ReactBaseElementAttributes extends BaseProps { 204 | // Event Attributes 205 | onCopy?: ClipboardEventHandler; 206 | onCut?: ClipboardEventHandler; 207 | onPaste?: ClipboardEventHandler; 208 | onKeyDown?: KeyboardEventHandler; 209 | onKeyPress?: KeyboardEventHandler; 210 | onKeyUp?: KeyboardEventHandler; 211 | onFocus?: FocusEventHandler; 212 | onBlur?: FocusEventHandler; 213 | onChange?: FormEventHandler; 214 | onInput?: FormEventHandler; 215 | onSubmit?: FormEventHandler; 216 | onClick?: MouseEventHandler; 217 | onDoubleClick?: MouseEventHandler; 218 | onDrag?: MouseEventHandler; 219 | onDragEnd?: MouseEventHandler; 220 | onDragEnter?: MouseEventHandler; 221 | onDragExit?: MouseEventHandler; 222 | onDragLeave?: MouseEventHandler; 223 | onDragOver?: MouseEventHandler; 224 | onDragStart?: MouseEventHandler; 225 | onDrop?: MouseEventHandler; 226 | onMouseDown?: MouseEventHandler; 227 | onMouseEnter?: MouseEventHandler; 228 | onMouseLeave?: MouseEventHandler; 229 | onMouseMove?: MouseEventHandler; 230 | onMouseOut?: MouseEventHandler; 231 | onMouseOver?: MouseEventHandler; 232 | onMouseUp?: MouseEventHandler; 233 | onTouchCancel?: TouchEventHandler; 234 | onTouchEnd?: TouchEventHandler; 235 | onTouchMove?: TouchEventHandler; 236 | onTouchStart?: TouchEventHandler; 237 | onScroll?: UIEventHandler; 238 | onWheel?: WheelEventHandler; 239 | 240 | dangerouslySetInnerHTML?: { 241 | __html: string; 242 | }; 243 | } 244 | 245 | interface CSSProperties { 246 | columnCount?: number; 247 | flex?: number | string; 248 | flexGrow?: number; 249 | flexShrink?: number; 250 | fontWeight?: number; 251 | lineClamp?: number; 252 | lineHeight?: number; 253 | opacity?: number; 254 | order?: number; 255 | orphans?: number; 256 | widows?: number; 257 | zIndex?: number; 258 | zoom?: number; 259 | 260 | // SVG-related properties 261 | fillOpacity?: number; 262 | strokeOpacity?: number; 263 | } 264 | 265 | interface HTMLAttributes extends ReactBaseElementAttributes { 266 | accept?: string; 267 | acceptCharset?: string; 268 | accessKey?: string; 269 | action?: string; 270 | allowFullScreen?: boolean; 271 | allowTransparency?: boolean; 272 | alt?: string; 273 | async?: boolean; 274 | autoComplete?: boolean; 275 | autoFocus?: boolean; 276 | autoPlay?: boolean; 277 | cellPadding?: number | string; 278 | cellSpacing?: number | string; 279 | charSet?: string; 280 | checked?: boolean; 281 | classID?: string; 282 | className?: string; 283 | cols?: number; 284 | colSpan?: number; 285 | content?: string; 286 | contentEditable?: boolean; 287 | contextMenu?: string; 288 | controls?: any; 289 | coords?: string; 290 | crossOrigin?: string; 291 | data?: string; 292 | dateTime?: string; 293 | defer?: boolean; 294 | dir?: string; 295 | disabled?: boolean; 296 | download?: any; 297 | draggable?: boolean; 298 | encType?: string; 299 | form?: string; 300 | formNoValidate?: boolean; 301 | frameBorder?: number | string; 302 | height?: number | string; 303 | hidden?: boolean; 304 | href?: string; 305 | hrefLang?: string; 306 | htmlFor?: string; 307 | httpEquiv?: string; 308 | icon?: string; 309 | id?: string; 310 | label?: string; 311 | lang?: string; 312 | list?: string; 313 | loop?: boolean; 314 | manifest?: string; 315 | max?: number | string; 316 | maxLength?: number; 317 | media?: string; 318 | mediaGroup?: string; 319 | method?: string; 320 | min?: number | string; 321 | multiple?: boolean; 322 | muted?: boolean; 323 | name?: string; 324 | noValidate?: boolean; 325 | open?: boolean; 326 | pattern?: string; 327 | placeholder?: string; 328 | poster?: string; 329 | preload?: string; 330 | radioGroup?: string; 331 | readOnly?: boolean; 332 | rel?: string; 333 | required?: boolean; 334 | role?: string; 335 | rows?: number; 336 | rowSpan?: number; 337 | sandbox?: string; 338 | scope?: string; 339 | scrollLeft?: number; 340 | scrolling?: string; 341 | scrollTop?: number; 342 | seamless?: boolean; 343 | selected?: boolean; 344 | shape?: string; 345 | size?: number; 346 | sizes?: string; 347 | span?: number; 348 | spellCheck?: boolean; 349 | src?: string; 350 | srcDoc?: string; 351 | srcSet?: string; 352 | start?: number; 353 | step?: number | string; 354 | style?: CSSProperties; 355 | tabIndex?: number; 356 | target?: string; 357 | title?: string; 358 | type?: string; 359 | useMap?: string; 360 | value?: string; 361 | width?: number | string; 362 | wmode?: string; 363 | 364 | // Non-standard Attributes 365 | autoCapitalize?: boolean; 366 | autoCorrect?: boolean; 367 | property?: string; 368 | itemProp?: string; 369 | itemScope?: boolean; 370 | itemType?: string; 371 | } 372 | 373 | interface SVGAttributes extends ReactBaseElementAttributes { 374 | cx?: SVGLength | SVGAnimatedLength; 375 | cy?: any; 376 | d?: string; 377 | dx?: SVGLength | SVGAnimatedLength; 378 | dy?: SVGLength | SVGAnimatedLength; 379 | fill?: any; // SVGPaint | string 380 | fillOpacity?: number | string; 381 | fontFamily?: string; 382 | fontSize?: number | string; 383 | fx?: SVGLength | SVGAnimatedLength; 384 | fy?: SVGLength | SVGAnimatedLength; 385 | gradientTransform?: SVGTransformList | SVGAnimatedTransformList; 386 | gradientUnits?: string; 387 | markerEnd?: string; 388 | markerMid?: string; 389 | markerStart?: string; 390 | offset?: number | string; 391 | opacity?: number | string; 392 | patternContentUnits?: string; 393 | patternUnits?: string; 394 | points?: string; 395 | preserveAspectRatio?: string; 396 | r?: SVGLength | SVGAnimatedLength; 397 | rx?: SVGLength | SVGAnimatedLength; 398 | ry?: SVGLength | SVGAnimatedLength; 399 | spreadMethod?: string; 400 | stopColor?: any; // SVGColor | string 401 | stopOpacity?: number | string; 402 | stroke?: any; // SVGPaint 403 | strokeDasharray?: string; 404 | strokeLinecap?: string; 405 | strokeOpacity?: number | string; 406 | strokeWidth?: SVGLength | SVGAnimatedLength; 407 | textAnchor?: string; 408 | transform?: SVGTransformList | SVGAnimatedTransformList; 409 | version?: string; 410 | viewBox?: string; 411 | x1?: SVGLength | SVGAnimatedLength; 412 | x2?: SVGLength | SVGAnimatedLength; 413 | x?: SVGLength | SVGAnimatedLength; 414 | y1?: SVGLength | SVGAnimatedLength; 415 | y2?: SVGLength | SVGAnimatedLength 416 | y?: SVGLength | SVGAnimatedLength; 417 | } 418 | 419 | 420 | // 421 | // Browser Interfaces 422 | // https://github.com/nikeee/2048-typescript/blob/master/2048/js/touch.d.ts 423 | // ---------------------------------------------------------------------- 424 | 425 | interface AbstractView { 426 | styleMedia: StyleMedia; 427 | document: Document; 428 | } 429 | 430 | interface Touch { 431 | identifier: number; 432 | target: EventTarget; 433 | screenX: number; 434 | screenY: number; 435 | clientX: number; 436 | clientY: number; 437 | pageX: number; 438 | pageY: number; 439 | } 440 | 441 | interface TouchList { 442 | [index: number]: Touch; 443 | length: number; 444 | item(index: number): Touch; 445 | identifiedTouch(identifier: number): Touch; 446 | } 447 | 448 | 449 | 450 | // 451 | // React.PropTypes 452 | // ---------------------------------------------------------------------- 453 | 454 | interface Validator { 455 | (object: T, key: string, componentName: string): Error; 456 | } 457 | 458 | interface Requireable extends Validator { 459 | isRequired: Validator; 460 | } 461 | 462 | interface ValidationMap { 463 | [key: string]: Validator; 464 | } 465 | 466 | var PropTypes: { 467 | any: Requireable; 468 | array: Requireable; 469 | bool: Requireable; 470 | func: Requireable; 471 | number: Requireable; 472 | object: Requireable; 473 | string: Requireable; 474 | node: Requireable; 475 | element: Requireable; 476 | instanceOf(expectedClass: {}): Requireable; 477 | oneOf(types: any[]): Requireable; 478 | oneOfType(types: Validator[]): Requireable; 479 | arrayOf(type: Validator): Requireable; 480 | objectOf(type: Validator): Requireable; 481 | shape(type: ValidationMap): Requireable; 482 | } 483 | 484 | // 485 | // React.Children 486 | // ---------------------------------------------------------------------- 487 | 488 | type ReactChild = string | ReactElement; 489 | type ReactChildList = string | ReactElement | ReactElementArray 490 | 491 | interface ReactChildren { 492 | map(children: ReactElementArray, fn: (child: ReactChild) => T): Array; 493 | forEach(children: ReactElementArray, fn: (child: ReactChild) => any): void; 494 | count(children: ReactElementArray): number; 495 | only(children: ReactElementArray): ReactChild; 496 | } 497 | 498 | 499 | function render(element: ReactElement, container: Element, callback?: () => void): Component; 500 | function renderToString(element: ReactElement): string; 501 | function renderToStaticMarkup(element: ReactElement): string; 502 | function isValidElement(element: any): boolean; 503 | function unmountComponentAtNode(container: Element): boolean; 504 | function initializeTouchEvents(shouldUseTouch: boolean): void; 505 | 506 | } 507 | --------------------------------------------------------------------------------