Skip to content

Instantly share code, notes, and snippets.

@carbide-public
Created July 21, 2018 20:15
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save carbide-public/fce1291124144e693597f4973312fac3 to your computer and use it in GitHub Desktop.
Save carbide-public/fce1291124144e693597f4973312fac3 to your computer and use it in GitHub Desktop.
TodoMVC React
html,
body {
margin: 0;
padding: 0;
}
button {
margin: 0;
padding: 0;
border: 0;
background: none;
font-size: 100%;
vertical-align: baseline;
font-family: inherit;
font-weight: inherit;
color: inherit;
-webkit-appearance: none;
appearance: none;
-webkit-font-smoothing: antialiased;
-moz-font-smoothing: antialiased;
font-smoothing: antialiased;
}
body {
font: 14px 'Helvetica Neue', Helvetica, Arial, sans-serif;
line-height: 1.4em;
background: #f5f5f5;
color: #4d4d4d;
min-width: 230px;
max-width: 550px;
margin: 0 auto;
-webkit-font-smoothing: antialiased;
-moz-font-smoothing: antialiased;
font-smoothing: antialiased;
font-weight: 300;
}
button,
input[type="checkbox"] {
outline: none;
}
.hidden {
display: none;
}
#root {
background: #fff;
margin: 130px 0 40px 0;
position: relative;
box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.2),
0 25px 50px 0 rgba(0, 0, 0, 0.1);
}
#root input::-webkit-input-placeholder {
font-style: italic;
font-weight: 300;
color: #e6e6e6;
}
#root input::-moz-placeholder {
font-style: italic;
font-weight: 300;
color: #e6e6e6;
}
#root input::input-placeholder {
font-style: italic;
font-weight: 300;
color: #e6e6e6;
}
#root h1 {
position: absolute;
top: -155px;
width: 100%;
font-size: 100px;
font-weight: 100;
text-align: center;
color: rgba(175, 47, 47, 0.15);
-webkit-text-rendering: optimizeLegibility;
-moz-text-rendering: optimizeLegibility;
text-rendering: optimizeLegibility;
}
.new-todo,
.edit {
position: relative;
margin: 0;
width: 100%;
font-size: 24px;
font-family: inherit;
font-weight: inherit;
line-height: 1.4em;
border: 0;
outline: none;
color: inherit;
padding: 6px;
border: 1px solid #999;
box-shadow: inset 0 -1px 5px 0 rgba(0, 0, 0, 0.2);
box-sizing: border-box;
-webkit-font-smoothing: antialiased;
-moz-font-smoothing: antialiased;
font-smoothing: antialiased;
}
.new-todo {
padding: 16px 16px 16px 60px;
border: none;
background: rgba(0, 0, 0, 0.003);
box-shadow: inset 0 -2px 1px rgba(0,0,0,0.03);
}
.main {
position: relative;
z-index: 2;
border-top: 1px solid #e6e6e6;
}
label[for='toggle-all'] {
display: none;
}
.toggle-all {
position: absolute;
top: -55px;
left: -12px;
width: 60px;
height: 34px;
text-align: center;
border: none; /* Mobile Safari */
}
.toggle-all:before {
content: '❯';
font-size: 22px;
color: #e6e6e6;
padding: 10px 27px 10px 27px;
}
.toggle-all:checked:before {
color: #737373;
}
.todo-list {
margin: 0;
padding: 0;
list-style: none;
}
.todo-list li {
position: relative;
font-size: 24px;
border-bottom: 1px solid #ededed;
}
.todo-list li:last-child {
border-bottom: none;
}
.todo-list li.editing {
border-bottom: none;
padding: 0;
}
.todo-list li.editing .edit {
display: block;
width: 506px;
padding: 13px 17px 12px 17px;
margin: 0 0 0 43px;
}
.todo-list li.editing .view {
display: none;
}
.todo-list li .toggle {
text-align: center;
width: 40px;
/* auto, since non-WebKit browsers doesn't support input styling */
height: auto;
position: absolute;
top: 0;
bottom: 0;
margin: auto 0;
border: none; /* Mobile Safari */
-webkit-appearance: none;
appearance: none;
}
.todo-list li .toggle:after {
content: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="40" height="40" viewBox="-10 -18 100 135"><circle cx="50" cy="50" r="50" fill="none" stroke="#ededed" stroke-width="3"/></svg>');
}
.todo-list li .toggle:checked:after {
content: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="40" height="40" viewBox="-10 -18 100 135"><circle cx="50" cy="50" r="50" fill="none" stroke="#bddad5" stroke-width="3"/><path fill="#5dc2af" d="M72 25L42 71 27 56l-4 4 20 20 34-52z"/></svg>');
}
.todo-list li label {
white-space: pre-line;
word-break: break-all;
padding: 15px 60px 15px 15px;
margin-left: 45px;
display: block;
line-height: 1.2;
transition: color 0.4s;
}
.todo-list li.completed label {
color: #d9d9d9;
text-decoration: line-through;
}
.todo-list li .destroy {
display: none;
position: absolute;
top: 0;
right: 10px;
bottom: 0;
width: 40px;
height: 40px;
margin: auto 0;
font-size: 30px;
color: #cc9a9a;
margin-bottom: 11px;
transition: color 0.2s ease-out;
}
.todo-list li .destroy:hover {
color: #af5b5e;
}
.todo-list li .destroy:after {
content: '×';
}
.todo-list li:hover .destroy {
display: block;
}
.todo-list li .edit {
display: none;
}
.todo-list li.editing:last-child {
margin-bottom: -1px;
}
.footer {
color: #777;
padding: 10px 15px;
height: 20px;
text-align: center;
border-top: 1px solid #e6e6e6;
}
.footer:before {
content: '';
position: absolute;
right: 0;
bottom: 0;
left: 0;
height: 50px;
overflow: hidden;
box-shadow: 0 1px 1px rgba(0, 0, 0, 0.2),
0 8px 0 -3px #f6f6f6,
0 9px 1px -3px rgba(0, 0, 0, 0.2),
0 16px 0 -6px #f6f6f6,
0 17px 2px -6px rgba(0, 0, 0, 0.2);
}
.todo-count {
float: left;
text-align: left;
}
.todo-count strong {
font-weight: 300;
}
.filters {
margin: 0;
padding: 0;
list-style: none;
position: absolute;
right: 0;
left: 0;
}
.filters li {
display: inline;
}
.filters li a {
color: inherit;
margin: 3px;
padding: 3px 7px;
text-decoration: none;
border: 1px solid transparent;
border-radius: 3px;
}
.filters li a.selected,
.filters li a:hover {
border-color: rgba(175, 47, 47, 0.1);
}
.filters li a.selected {
border-color: rgba(175, 47, 47, 0.2);
}
.clear-completed,
html .clear-completed:active {
float: right;
position: relative;
line-height: 20px;
text-decoration: none;
cursor: pointer;
position: relative;
}
.clear-completed:hover {
text-decoration: underline;
}
.info {
margin: 65px auto 0;
color: #bfbfbf;
font-size: 10px;
text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5);
text-align: center;
}
.info p {
line-height: 1;
}
.info a {
color: inherit;
text-decoration: none;
font-weight: 400;
}
.info a:hover {
text-decoration: underline;
}
/*
Hack to remove background from Mobile Safari.
Can't use it globally since it destroys checkboxes in Firefox
*/
@media screen and (-webkit-min-device-pixel-ratio:0) {
.toggle-all,
.todo-list li .toggle {
background: none;
}
.todo-list li .toggle {
height: 40px;
}
.toggle-all {
-webkit-transform: rotate(90deg);
transform: rotate(90deg);
-webkit-appearance: none;
appearance: none;
}
}
@media (max-width: 430px) {
.footer {
height: 50px;
}
.filters {
bottom: 10px;
}
}
import {ALL_TODOS, ACTIVE_TODOS, COMPLETED_TODOS} from './constants.js'
import React from 'react'
import {Router} from 'director'
import TodoFooter from './footer.js';
import TodoItem from './todoItem.js';
var ENTER_KEY = 13;
export default class TodoApp extends React.Component {
constructor(props){
super(props)
this.state = {
nowShowing: ALL_TODOS,
editing: null,
newTodo: ''
}
}
componentDidMount() {
var setState = this.setState;
var router = Router({
'/': setState.bind(this, {nowShowing: ALL_TODOS}),
'/active': setState.bind(this, {nowShowing: ACTIVE_TODOS}),
'/completed': setState.bind(this, {nowShowing: COMPLETED_TODOS})
});
router.init('/');
}
handleChange(event) {
this.setState({newTodo: event.target.value});
}
handleNewTodoKeyDown(event) {
if (event.keyCode !== ENTER_KEY) {
return;
}
event.preventDefault();
var val = this.state.newTodo.trim();
if (val) {
this.props.model.addTodo(val);
this.setState({newTodo: ''});
}
}
toggleAll(event) {
var checked = event.target.checked;
this.props.model.toggleAll(checked);
}
toggle(todoToToggle) {
this.props.model.toggle(todoToToggle);
}
destroy(todo) {
this.props.model.destroy(todo);
}
edit(todo) {
this.setState({editing: todo.id});
}
save(todoToSave, text) {
this.props.model.save(todoToSave, text);
this.setState({editing: null});
}
cancel() {
this.setState({editing: null});
}
clearCompleted() {
this.props.model.clearCompleted();
}
render() {
var footer;
var main;
var todos = this.props.model.todos;
var shownTodos = todos.filter(function (todo) {
switch (this.state.nowShowing) {
case ACTIVE_TODOS:
return !todo.completed;
case COMPLETED_TODOS:
return todo.completed;
default:
return true;
}
}, this);
var todoItems = shownTodos.map(function (todo) {
return (
<TodoItem
key={todo.id}
todo={todo}
onToggle={this.toggle.bind(this, todo)}
onDestroy={this.destroy.bind(this, todo)}
onEdit={this.edit.bind(this, todo)}
editing={this.state.editing === todo.id}
onSave={this.save.bind(this, todo)}
onCancel={this.cancel.bind(this)}
/>
);
}, this);
var activeTodoCount = todos.reduce(function (accum, todo) {
return todo.completed ? accum : accum + 1;
}, 0);
var completedCount = todos.length - activeTodoCount;
if (activeTodoCount || completedCount) {
footer =
<TodoFooter
count={activeTodoCount}
completedCount={completedCount}
nowShowing={this.state.nowShowing}
onClearCompleted={this.clearCompleted.bind(this)}
/>;
}
if (todos.length) {
main = (
<section className="main">
<input
className="toggle-all"
type="checkbox"
onChange={this.toggleAll.bind(this)}
checked={activeTodoCount === 0}
/>
<ul className="todo-list">
{todoItems}
</ul>
</section>
);
}
return (
<div>
<header className="header">
<h1>todos</h1>
<input
className="new-todo"
placeholder={"What needs to be done?"}
value={this.state.newTodo}
onKeyDown={this.handleNewTodoKeyDown.bind(this)}
onChange={this.handleChange.bind(this)}
autoFocus={true}
/>
</header>
{main}
{footer}
</div>
);
}
}
hr {
margin: 20px 0;
border: 0;
border-top: 1px dashed #c5c5c5;
border-bottom: 1px dashed #f7f7f7;
}
.learn a {
font-weight: normal;
text-decoration: none;
color: #b83f45;
}
.learn a:hover {
text-decoration: underline;
color: #787e7e;
}
.learn h3,
.learn h4,
.learn h5 {
margin: 10px 0;
font-weight: 500;
line-height: 1.2;
color: #000;
}
.learn h3 {
font-size: 24px;
}
.learn h4 {
font-size: 18px;
}
.learn h5 {
margin-bottom: 0;
font-size: 14px;
}
.learn ul {
padding: 0;
margin: 0 0 30px 25px;
}
.learn li {
line-height: 20px;
}
.learn p {
font-size: 15px;
font-weight: 300;
line-height: 1.3;
margin-top: 0;
margin-bottom: 0;
}
#issue-count {
display: none;
}
.quote {
border: none;
margin: 20px 0 60px 0;
}
.quote p {
font-style: italic;
}
.quote p:before {
content: '“';
font-size: 50px;
opacity: .15;
position: absolute;
top: -20px;
left: 3px;
}
.quote p:after {
content: '”';
font-size: 50px;
opacity: .15;
position: absolute;
bottom: -42px;
right: 3px;
}
.quote footer {
position: absolute;
bottom: -40px;
right: 0;
}
.quote footer img {
border-radius: 3px;
}
.quote footer a {
margin-left: 5px;
vertical-align: middle;
}
.speech-bubble {
position: relative;
padding: 10px;
background: rgba(0, 0, 0, .04);
border-radius: 5px;
}
.speech-bubble:after {
content: '';
position: absolute;
top: 100%;
right: 30px;
border: 13px solid transparent;
border-top-color: rgba(0, 0, 0, .04);
}
.learn-bar > .learn {
position: absolute;
width: 272px;
top: 8px;
left: -300px;
padding: 10px;
border-radius: 5px;
background-color: rgba(255, 255, 255, .6);
transition-property: left;
transition-duration: 500ms;
}
@media (min-width: 899px) {
.learn-bar {
width: auto;
padding-left: 300px;
}
.learn-bar > .learn {
left: 8px;
}
}
import {say} from 'cowsay'
say({text: "hello there! \n\nWelcome to the Carbide Alpha\nRelease TODOMVC Example Notebook!\n\nTo get started, open the external \nwindow, and press the run button\non index.js \n\n(that's the cell under this one)\n\nSend bugs to\nbugs@trycarbide.com!\n\nSend hellos to\nhello@trycarbide.com!" || ' '})
export const ALL_TODOS = 'all';
export const ACTIVE_TODOS = 'active';
export const COMPLETED_TODOS = 'completed';
import React from 'react' /// Open the bootloader, and run this code to build the todo app. (Run the code with Cmd-Enter, or by pressing the play button on the bottom right)
import ReactDOM from 'react-dom'
import TodoApp from './app.js'
import './base.css'
import './app.css'
import TodoModel from './todoModel.js'
var model = new TodoModel('react-todos');
function render() {
ReactDOM.render(
<TodoApp model={model}/>,
document.getElementById('root')
);
}
model.subscribe(render);
render()
// this tells the hot reloading engine to do its magic
export function __render(){
render()
}
import React from 'react'
var ESCAPE_KEY = 27;
var ENTER_KEY = 13;
export default class TodoItem extends React.Component {
constructor(props){
super(props)
this.state = {editText: this.props.todo.title}
}
handleSubmit (event) {
var val = this.state.editText.trim();
if (val) {
this.props.onSave(val);
this.setState({editText: val});
} else {
this.props.onDestroy();
}
}
handleEdit () {
this.props.onEdit();
this.setState({editText: this.props.todo.title});
}
handleKeyDown (event) {
if (event.which === ESCAPE_KEY) {
this.setState({editText: this.props.todo.title});
this.props.onCancel(event);
} else if (event.which === ENTER_KEY) {
this.handleSubmit(event);
}
}
handleChange (event) {
if (this.props.editing) {
this.setState({editText: event.target.value});
}
}
shouldComponentUpdate (nextProps, nextState) { /// This is a completely optional performance enhancement that you can implement on any React component. If you were to delete this method the app would still work correctly (and still be very performant!), we just use it as an example of how little code it takes to get an order of magnitude performance improvement.
return (
nextProps.todo !== this.props.todo ||
nextProps.editing !== this.props.editing ||
nextState.editText !== this.state.editText
);
}
componentDidUpdate (prevProps) { /// Safely manipulate the DOM after updating the state when invoking \`this.props.onEdit()\` in the \`handleEdit\` method above. For more info refer to notes at https://facebook.github.io/react/docs/component-api.html#setstate and https://facebook.github.io/react/docs/component-specs.html#updating-componentdidupdate
if (!prevProps.editing && this.props.editing) {
var node = React.findDOMNode(this.refs.editField);
node.focus();
node.setSelectionRange(node.value.length, node.value.length);
}
}
render () {
return (
<li className={
(this.props.todo.completed ? "completed " : '') +
(this.props.editing ? "editing" : '')
}>
<div className="view">
<input
className="toggle"
type="checkbox"
checked={this.props.todo.completed}
onChange={this.props.onToggle}
/>
<label onDoubleClick={this.handleEdit}>
{this.props.todo.title}
</label>
<button className="destroy" onClick={this.props.onDestroy} />
</div>
<input
ref="editField"
className="edit"
value={this.state.editText}
onBlur={this.handleSubmit.bind(this)}
onChange={this.handleChange.bind(this)}
onKeyDown={this.handleKeyDown.bind(this)}
/>
</li>
);
}
};
import * as Utils from './utils.js'
export default class TodoModel { /// Generic "model" object. You can use whatever framework you want. For this application it may not even be worth separating this logic out, but we do this to demonstrate one way to separate out parts of your application.
constructor(key) {
this.key = key
this.todos = Utils.store(key)
this.onChanges = []
}
subscribe(onChange) {
this.onChanges.push(onChange)
}
inform() {
Utils.store(this.key, this.todos)
this.onChanges.forEach(function (cb) { cb() })
}
addTodo(title) {
this.todos = this.todos.concat({
id: Utils.uuid(),
title: title,
completed: false
})
this.inform()
}
toggleAll(checked) {
this.todos = this.todos.map(function (todo) { /// Note: it's usually better to use immutable data structures since they're easier to reason about and React works very well with them. That's why we use map() and filter() everywhere instead of mutating the array or todo items themselves.
return Utils.extend({}, todo, {completed: checked})
})
this.inform()
}
toggle(todoToToggle) {
this.todos = this.todos.map(function (todo) {
return todo !== todoToToggle ?
todo :
Utils.extend({}, todo, {completed: !todo.completed})
})
this.inform()
}
destroy(todo) {
this.todos = this.todos.filter(function (candidate) {
return candidate !== todo
})
this.inform()
}
save(todoToSave, text) {
this.todos = this.todos.map(function (todo) {
return todo !== todoToSave ? todo : Utils.extend({}, todo, {title: text})
})
this.inform()
}
clearCompleted() {
this.todos = this.todos.filter(function (todo) {
return !todo.completed
})
this.inform()
}
}
export function uuid() {
var i, random;
var uuid = '';
for (i = 0; i < 32; i++) {
random = Math.random() * 16 | 0;
if (i === 8 || i === 12 || i === 16 || i === 20) {
uuid += '-';
}
uuid += (i === 12 ? 4 : (i === 16 ? (random & 3 | 8) : random))
.toString(16);
}
return uuid;
}
export function pluralize(count, word) {
return count === 1 ? word : word + 's';
}
export function store(namespace, data) {
if (data) {
return localStorage.setItem(namespace, JSON.stringify(data));
}
var store = localStorage.getItem(namespace);
return (store && JSON.parse(store)) || [];
}
export function extend() {
var newObj = {};
for (var i = 0; i < arguments.length; i++) {
var obj = arguments[i];
for (var key in obj) {
if (obj.hasOwnProperty(key)) {
newObj[key] = obj[key];
}
}
}
return newObj;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment