Created
December 5, 2016 01:21
-
-
Save jungchris/9fe08afa426c5cbc07a4c50c8f2de166 to your computer and use it in GitHub Desktop.
Scoreboard Application to Learn React
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
body { | |
background: #d5d5d5; | |
font-family: arial; | |
color: #FAFAFA; | |
text-transform: uppercase; | |
} | |
.scoreboard { | |
background: #333; | |
width: 700px; | |
margin: 70px auto; | |
box-shadow: 0 4px 0px #333; | |
border-radius: 15px; | |
} | |
.header { | |
padding: 5px 10px; | |
text-align: center; | |
display: flex; | |
align-items: center; | |
background-color: #222; | |
border-radius: 15px 15px 0 0; | |
border-bottom: solid 2px #444; | |
} | |
.header h1 { | |
flex-grow: 1; | |
font-size: 1.5em; | |
letter-spacing: 3px; | |
font-weight: normal; | |
} | |
.header .stats, | |
.header .stopwatch { | |
width: 170px; | |
} | |
.stats { | |
margin-top: 0; | |
font-weight: normal; | |
} | |
.stats td:first-child { | |
text-align: right; | |
font-weight: normal; | |
letter-spacing: 2px; | |
color: #666; | |
font-size: .7em; | |
} | |
.stats td:last-child { | |
text-align: left; | |
} | |
.stopwatch { | |
padding: 15px 10px 5px 10px; | |
margin: -5px -10px -5px 10px; | |
background: #2f2f2f; | |
border-radius: 0 15px 0 0; | |
} | |
.stopwatch-time { | |
font-family: monospace; | |
font-size: 2em; | |
} | |
.stopwatch button { | |
margin: 8px 5px; | |
background-color: #222; | |
border-radius: 5px; | |
padding: 7px 8px; | |
border: none; | |
color: #999; | |
letter-spacing: 2px; | |
font-weight: bold; | |
text-shadow: none; | |
text-transform: uppercase; | |
} | |
.stopwatch button:hover { | |
background: #4b71b5; | |
color: #fafafa; | |
cursor: pointer; | |
} | |
.stopwatch h2 { | |
font-size: .6em; | |
margin: 0; | |
font-weight: normal; | |
letter-spacing: 2px; | |
color: #666; | |
} | |
.player { | |
display: flex; | |
font-size: 1.2em; | |
border-bottom: solid 2px #444; | |
letter-spacing: 2px; | |
} | |
.remove-player { | |
visibility: hidden; | |
margin-right: 10px; | |
color: #e57373; | |
cursor: pointer; | |
} | |
.player-name:hover .remove-player { | |
visibility: visible; | |
} | |
.player-name { | |
flex-grow: 1; | |
padding: 20px 10px 10px 10px; | |
} | |
.player-score { | |
width: 190px; | |
background: blue; | |
} | |
.counter { | |
display: flex; | |
} | |
.counter-action { | |
border: none; | |
font-weight: bold; | |
color: #FAFAFA; | |
display: block; | |
padding: 20px 20px; | |
cursor: pointer; | |
-webkit-user-select: none; | |
-moz-user-select: none; | |
-ms-user-select: none; | |
user-select: none; | |
} | |
.counter-action.increment { | |
background: #66BB6A; | |
} | |
.counter-action.increment:hover { | |
background: #549d59; | |
cursor: pointer; | |
} | |
.counter-action.decrement { | |
background: #ef5350; | |
} | |
.counter-action.decrement:hover { | |
background: #c44442; | |
cursor: pointer; | |
} | |
.counter-score { | |
flex-grow: 1; | |
background: #2b2b2b; | |
color: #FAFAFA; | |
text-align: center; | |
font-family: monospace; | |
padding: 10px; | |
font-size: 2em; | |
} | |
.add-player-form form { | |
display: flex; | |
background-color: #222; | |
border-radius: 0 0 20px 20px | |
} | |
.add-player-form input[type=text] { | |
flex-grow: 1; | |
border-width: 0 0 1px 0; | |
margin: 15px 10px 15px 15px; | |
padding: 10px; | |
border-radius: 5px; | |
background-color: #333; | |
border-style: none; | |
text-shadow: none; | |
text-transform: uppercase; | |
color: #999; | |
letter-spacing: 2px; | |
outline: none; | |
} | |
.add-player-form input[type=text]::-webkit-input-placeholder{ | |
color: #666; | |
letter-spacing: 2px; | |
} | |
.add-player-form input[type=text]:focus{ | |
background-color: #444; | |
} | |
.add-player-form input[type=submit] { | |
display: block; | |
font-size: .6em; | |
margin: 15px 15px 15px 0; | |
padding: 10px; | |
background-color: #333; | |
border-radius: 5px; | |
border: none; | |
color: #999; | |
letter-spacing: 2px; | |
font-weight: bold; | |
text-shadow: none; | |
text-transform: uppercase; | |
} | |
.add-player-form input[type=submit]:hover{ | |
background: #4b71b5; | |
color: #fafafa; | |
cursor: pointer; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
var PLAYERS = [ | |
{ | |
name: "Jim Hoskins", | |
score: 31, | |
id: 1, | |
}, | |
{ | |
name: "Andrew Chalkley", | |
score: 35, | |
id: 2, | |
}, | |
{ | |
name: "Alena Holligan", | |
score: 42, | |
id: 3, | |
}, | |
]; | |
var Stopwatch = React.createClass({ | |
render: function() { | |
return ( | |
<div className="stopwatch"> | |
<h2>Stopwatch</h2> | |
<div className="stopwatch-time">0</div> | |
<button>Start</button> | |
<button>Reset</button> | |
</div> | |
); | |
} | |
}); | |
var nextId = 4; | |
// stateful class for taking user input | |
var AddPlayerForm = React.createClass({ | |
propTypes: { | |
onAdd: React.PropTypes.func.isRequired, | |
}, | |
// | |
// // this is used by the form render function this.state.name | |
getInitialState: function() { | |
return { | |
name: "", | |
}; | |
}, | |
// | |
// note that setState is a native function | |
onNameChange: function(e) { | |
//console.log('onNameChange', e.target.value); | |
this.setState({name: e.target.value}); | |
}, | |
// | |
onSubmit: function(e) { | |
e.preventDefault(); | |
// add name | |
this.props.onAdd(this.state.name); | |
// clear input field | |
this.setState({name: ""}); | |
}, | |
// <form onSubmit={this.onSubmit}> | |
// <input type="text" value={this.state.name} onChange={this.onNameChange} /> | |
render: function() { | |
return ( | |
<div className="add-player-form"> | |
<form onSubmit={this.onSubmit}> | |
<input type="text" value={this.state.name} onChange={this.onNameChange} /> | |
<input type="submit" value="Add Player" /> | |
</form> | |
</div> | |
); | |
}, | |
}); | |
function Stats(props) { | |
var totalPlayers = props.players.length; | |
var totalPoints = props.players.reduce(function(total, player){ | |
return total + player.score; | |
}, 0); | |
return ( | |
<table className="stats"> | |
<tbody> | |
<tr> | |
<td>Players: </td> | |
<td>{totalPlayers}</td> | |
</tr> | |
<tr> | |
<td>Total Points:</td> | |
<td>{totalPoints}</td> | |
</tr> | |
</tbody> | |
</table> | |
); | |
} | |
// making players a property of stats so that we can figure out the num of players in Stats | |
Stats.propTypes = { | |
players: React.PropTypes.array.isRequired, | |
}; | |
function Header(props) { | |
return ( | |
<div className="header"> | |
<Stats players={props.players}/> | |
<h1>{props.title}</h1> | |
<Stopwatch /> | |
</div> | |
); | |
} | |
Header.propTypes = { | |
title: React.PropTypes.string.isRequired, | |
players: React.PropTypes.array.isRequired, | |
}; | |
function Counter(props) { | |
return ( | |
<div className="counter"> | |
<button className="counter-action decrement" onClick={function() {props.onChange(-1);}} > - </button> | |
<div className="counter-score"> {props.score} </div> | |
<button className="counter-action increment" onClick={function() {props.onChange(1);}}> + </button> | |
</div> | |
); | |
} | |
Counter.propTypes = { | |
score: React.PropTypes.number.isRequired, | |
onChange: React.PropTypes.func.isRequired, | |
} | |
//<a className="remove-player" onClick={props.onRemove}>X</a> | |
function Player(props) { | |
return ( | |
<div className="player"> | |
<div className="player-name"> | |
<a className="remove-player" onClick={props.onRemove}>X</a> | |
{props.name} | |
</div> | |
<div className="player-score"> | |
<Counter score={props.score} onChange={props.onScoreChange} /> | |
</div> | |
</div> | |
); | |
} | |
Player.propTypes = { | |
name: React.PropTypes.string.isRequired, | |
score: React.PropTypes.number.isRequired, | |
onScoreChange: React.PropTypes.func.isRequired, | |
onRemove: React.PropTypes.func.isRequired, | |
}; | |
// Which component in the Application currently holds state? (not Player, Counter) | |
var Application = React.createClass({ | |
propTypes: { | |
title: React.PropTypes.string, | |
initialPlayers: React.PropTypes.arrayOf(React.PropTypes.shape({ | |
name: React.PropTypes.string.isRequired, | |
score: React.PropTypes.number.isRequired, | |
id: React.PropTypes.number.isRequired, | |
})).isRequired, | |
}, | |
getDefaultProps: function() { | |
return { | |
title: "Scoreboard", | |
} | |
}, | |
getInitialState: function() { | |
return { | |
players: this.props.initialPlayers, | |
}; | |
}, | |
onScoreChange: function(index, delta) { | |
console.log('onScoreChange', index, delta); | |
this.state.players[index].score += delta; | |
this.setState(this.state); | |
}, | |
onPlayerAdd: function(name) { | |
console.log('Player added:', name); | |
this.state.players.push({ | |
name: name, | |
score: 0, | |
id: nextId, | |
}); | |
this.setState(this.state); | |
nextId += 1; | |
}, | |
onRemovePlayer: function(index) { | |
console.log('onRemovePlayer', index); | |
this.state.players.splice(index, 1); | |
this.setState(this.state); | |
}, | |
render: function() { | |
return ( | |
<div className="scoreboard"> | |
<Header title={this.props.title} players={this.state.players} /> | |
<div className="players"> | |
{this.state.players.map(function(player, index) { | |
return ( | |
<Player | |
onScoreChange={function(delta) {this.onScoreChange(index ,delta)}.bind(this)} | |
onRemove={function() {this.onRemovePlayer(index)}.bind(this)} | |
name={player.name} | |
score={player.score} | |
key={player.id} /> | |
); | |
}.bind(this))} | |
</div> | |
<AddPlayerForm onAdd={this.onPlayerAdd} /> | |
</div> | |
); | |
} | |
}); | |
ReactDOM.render(<Application initialPlayers={PLAYERS}/>, document.getElementById('container')); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<html> | |
<head> | |
<meta charset="utf-8"> | |
<title>Scoreboard</title> | |
<link rel="stylesheet" href="./app.css" /> | |
</head> | |
<body> | |
<div id="container">Loading...</div> | |
<script src="./vendor/react.js"></script> | |
<script src="./vendor/react-dom.js"></script> | |
<script src="./vendor/babel-browser.min.js"></script> | |
<script type="text/babel" src="./app.jsx"></script> | |
</body> | |
</html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment