diff --git a/.babelrc b/.babelrc
new file mode 100644
index 0000000..86c445f
--- /dev/null
+++ b/.babelrc
@@ -0,0 +1,3 @@
+{
+ "presets": ["es2015", "react"]
+}
diff --git a/.eslintrc b/.eslintrc
new file mode 100644
index 0000000..8dc6807
--- /dev/null
+++ b/.eslintrc
@@ -0,0 +1,21 @@
+{
+ "rules": {
+ "no-console": "off",
+ "indent": [ "error", 2 ],
+ "quotes": [ "error", "single" ],
+ "semi": ["error", "always"],
+ "linebreak-style": [ "error", "unix" ]
+ },
+ "env": {
+ "es6": true,
+ "node": true,
+ "mocha": true,
+ "jasmine": true
+ },
+ "ecmaFeatures": {
+ "modules": true,
+ "experimentalObjectRestSpread": true,
+ "impliedStrict": true
+ },
+ "extends": "eslint:recommended"
+}
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..345130c
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,136 @@
+# Created by https://www.gitignore.io/api/osx,vim,node,macos,windows
+
+### macOS ###
+*.DS_Store
+.AppleDouble
+.LSOverride
+
+# Icon must end with two \r
+Icon
+
+# Thumbnails
+._*
+
+# Files that might appear in the root of a volume
+.DocumentRevisions-V100
+.fseventsd
+.Spotlight-V100
+.TemporaryItems
+.Trashes
+.VolumeIcon.icns
+.com.apple.timemachine.donotpresent
+
+# Directories potentially created on remote AFP share
+.AppleDB
+.AppleDesktop
+Network Trash Folder
+Temporary Items
+.apdisk
+
+### Node ###
+# Logs
+logs
+*.log
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+
+# Runtime data
+pids
+*.pid
+*.seed
+*.pid.lock
+
+# Directory for instrumented libs generated by jscoverage/JSCover
+lib-cov
+
+# Coverage directory used by tools like istanbul
+coverage
+
+# nyc test coverage
+.nyc_output
+
+# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
+.grunt
+
+# Bower dependency directory (https://bower.io/)
+bower_components
+
+# node-waf configuration
+.lock-wscript
+
+# Compiled binary addons (http://nodejs.org/api/addons.html)
+build/Release
+
+# Dependency directories
+node_modules/
+jspm_packages/
+
+# Typescript v1 declaration files
+typings/
+
+# Optional npm cache directory
+.npm
+
+# Optional eslint cache
+.eslintcache
+
+# Optional REPL history
+.node_repl_history
+
+# Output of 'npm pack'
+*.tgz
+
+# Yarn Integrity file
+.yarn-integrity
+
+# dotenv environment variables file
+.env
+
+
+### OSX ###
+
+# Icon must end with two \r
+
+# Thumbnails
+
+# Files that might appear in the root of a volume
+
+# Directories potentially created on remote AFP share
+
+### Vim ###
+# swap
+[._]*.s[a-v][a-z]
+[._]*.sw[a-p]
+[._]s[a-v][a-z]
+[._]sw[a-p]
+# session
+Session.vim
+# temporary
+.netrwhist
+*~
+# auto-generated tag files
+tags
+
+### Windows ###
+# Windows thumbnail cache files
+Thumbs.db
+ehthumbs.db
+ehthumbs_vista.db
+
+# Folder config file
+Desktop.ini
+
+# Recycle Bin used on file shares
+$RECYCLE.BIN/
+
+# Windows Installer files
+*.cab
+*.msi
+*.msm
+*.msp
+
+# Windows shortcuts
+*.lnk
+
+# End of https://www.gitignore.io/api/osx,vim,node,macos,windows
diff --git a/README.md b/README.md
index 872470e..e69de29 100644
--- a/README.md
+++ b/README.md
@@ -1,51 +0,0 @@
- 24: Component Composition
-======
-
-## Submission Instructions
-* continue working on the fork you created from lab 23
-* open a **new branch** for today's assignment
-* upon completion, create a **new pull request** in github
-* submit a link to your PR in canvas
-
-## Learning Objectives
-* students will be able to utilize proper component composition constructs
-* students will be able to compose react components through the use of props
-
-#### Configuration
-* `README.md`
-* `.babelrc`
-* `.gitignore`
-* `package.json`
-* `webpack.config.js`
-* `src/**`
-* `src/main.js`
-* `src/style`
-* `src/style/main.scss`
-
-## Requirements
-#### Feature Tasks
-refactor and add the following components:
-
-###### NoteUpdateForm
-* create a `NoteUpdateForm` component that inherits a note through props
- * on submit, this should update the App's state with an updated note
-
-###### Refactor the NoteItem Component
-* include the following behavior:
- * if the user double clicks on the notes content, it should switch to the edit view
-* default view
- * display the note content and a delete button
- * the delete button should remove the note from the application's state
-* edit view
- * show the `NoteUpdateForm` and a cancel button
- * `onNoteUpdateForm` submit (or click of the cancel button) should switch back to the default view
-
-###### App Component Tree
-your components should be nested in the following layout
-```
-App
- NoteCreateForm
- NoteList
- NoteItem
- NoteUpdateForm
-```
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..175238b
--- /dev/null
+++ b/package.json
@@ -0,0 +1,38 @@
+{
+ "name": "24-component-composition",
+ "version": "1.0.0",
+ "description": "",
+ "main": "webpack.config.js",
+ "scripts": {
+ "build": "webpack",
+ "watch": "webpack-dev-server --inline --hot"
+ },
+ "repository": {
+ "type": "git",
+ "url": "git+https://github.com/nickjaz/24-component-composition.git"
+ },
+ "keywords": [],
+ "author": "",
+ "license": "ISC",
+ "bugs": {
+ "url": "https://github.com/nickjaz/24-component-composition/issues"
+ },
+ "homepage": "https://github.com/nickjaz/24-component-composition#readme",
+ "dependencies": {
+ "babel-core": "^6.26.0",
+ "babel-loader": "^7.1.2",
+ "babel-preset-es2015": "^6.24.1",
+ "babel-preset-react": "^6.24.1",
+ "css-loader": "^0.28.5",
+ "extract-text-webpack-plugin": "^3.0.0",
+ "html-webpack-plugin": "^2.30.1",
+ "node-sass": "^4.5.3",
+ "react": "^15.6.1",
+ "react-dom": "^15.6.1",
+ "react-router-dom": "^4.2.0",
+ "sass-loader": "^6.0.6",
+ "uuid": "^3.1.0",
+ "webpack": "^3.5.5",
+ "webpack-dev-server": "^2.7.1"
+ }
+}
diff --git a/src/component/note-container/_note-container.scss b/src/component/note-container/_note-container.scss
new file mode 100644
index 0000000..abd8c04
--- /dev/null
+++ b/src/component/note-container/_note-container.scss
@@ -0,0 +1,12 @@
+@import '../../style/_vars.scss';
+
+.note-container {
+ padding: $lrg-gutter;
+
+ h1 {
+ display: inline-block;
+ margin-bottom: $lrg-gutter;
+ padding: $sml-gutter;
+ border-bottom: solid 2px $white;
+ }
+}
diff --git a/src/component/note-container/index.js b/src/component/note-container/index.js
new file mode 100644
index 0000000..0a7493b
--- /dev/null
+++ b/src/component/note-container/index.js
@@ -0,0 +1,61 @@
+import './_note-container.scss'
+
+import React from 'react';
+import uuid from 'uuid/v1';
+
+import NoteCreateForm from '../note-create-form';
+import NoteList from '../note-list';
+
+class NoteContainer extends React.Component {
+ constructor(props) {
+ super(props);
+
+ this.noteCreate = this.noteCreate.bind(this);
+ this.noteRemove = this.noteRemove.bind(this);
+ this.noteUpdate = this.noteUpdate.bind(this);
+ }
+
+ noteCreate(note) {
+ note.id = uuid();
+ let {app} = this.props;
+ app.setState(prevState => ({
+ notes: prevState.notes.concat([note])
+ }));
+ }
+
+ noteRemove(note) {
+ let {app} = this.props;
+ app.setState(prevState => ({
+ notes: prevState.notes.filter((item) => {
+ return item.id !== note.id;
+ })
+ }));
+ }
+
+ noteUpdate(note) {
+ let {app} = this.props;
+ app.setState(prevState => ({
+ notes: prevState.notes.map((item) => {
+ return item.id === note.id ? note : item;
+ })
+ }))
+ }
+
+ render() {
+ let {app} = this.props;
+
+ return (
+
+
Note Maker
+
+
+
+ )
+ }
+}
+
+export default NoteContainer;
diff --git a/src/component/note-create-form/_note-create-form.scss b/src/component/note-create-form/_note-create-form.scss
new file mode 100644
index 0000000..7ccb9a4
--- /dev/null
+++ b/src/component/note-create-form/_note-create-form.scss
@@ -0,0 +1,23 @@
+@import '../../style/_vars.scss';
+
+.note-create-form {
+ padding: $sml-gutter;
+ border-bottom: solid 2px $white;
+
+ input {
+ display: block;
+ margin-bottom: $sml-gutter;
+ }
+
+ textarea {
+ display: block;
+ width: 50%;
+ max-width: 50%;
+
+ }
+
+ button {
+ display: inline;
+ margin: $sml-gutter 0;
+ }
+}
diff --git a/src/component/note-create-form/index.js b/src/component/note-create-form/index.js
new file mode 100644
index 0000000..432890c
--- /dev/null
+++ b/src/component/note-create-form/index.js
@@ -0,0 +1,53 @@
+import './_note-create-form.scss'
+
+import React from 'react';
+
+class NoteCreateForm extends React.Component {
+ constructor(props) {
+ super(props);
+ this.state = {
+ title: '',
+ content: '',
+ completed: false
+ }
+
+ this.handleChange = this.handleChange.bind(this);
+ this.handleSubmit = this.handleSubmit.bind(this);
+ }
+
+ handleChange(e) {
+ this.setState({
+ [e.target.name]: e.target.value
+ })
+ }
+
+ handleSubmit(e) {
+ e.preventDefault();
+ this.props.handleSubmit(this.state);
+ }
+
+ render() {
+ return (
+
+ )
+ }
+}
+
+export default NoteCreateForm;
diff --git a/src/component/note-list/_note-list.scss b/src/component/note-list/_note-list.scss
new file mode 100644
index 0000000..a9fd6d8
--- /dev/null
+++ b/src/component/note-list/_note-list.scss
@@ -0,0 +1,33 @@
+@import '../../style/_vars.scss';
+
+.note-list {
+ padding: $lrg-gutter $sml-gutter;
+
+ li {
+ padding-top: $sml-gutter;
+ padding-bottom: $lrg-gutter;
+ border-bottom: dashed 2px $white;
+
+ h2 {
+ display: inline;
+ text-decoration: underline;
+ }
+
+ button {
+ display: inline;
+ float: right;
+ font-size: 10px;
+ font-family: courier;
+ padding: 2px 4px;
+ }
+
+ p {
+ margin-top: $sml-gutter;
+ font-size: 14px;
+ }
+ }
+
+ li:first {
+ border-top: dashed 2px $white;
+ }
+}
diff --git a/src/component/note-list/index.js b/src/component/note-list/index.js
new file mode 100644
index 0000000..72335c7
--- /dev/null
+++ b/src/component/note-list/index.js
@@ -0,0 +1,61 @@
+import './_note-list.scss'
+
+import React from 'react';
+import NoteUpdateForm from '../note-update-form';
+
+let renderIf = (test, component) => test ? component : undefined;
+
+class NoteList extends React.Component {
+ constructor(props) {
+ super(props);
+
+ this.state = {
+ editing: false
+ }
+
+ this.handleEditClick = this.handleEditClick.bind(this);
+ }
+
+ handleEditClick(e) {
+ this.setState({
+ editing: true
+ })
+ }
+
+ render() {
+ return (
+
+ { this.props.notes.length === 0 ?
+ :
+
+ {this.props.notes.map((item, i) => {
+ return (
+ -
+
+
{item.title}
+
+
{item.content}
+
+
+ {renderIf(this.state.editing,
+ {
+ note.id = item.id;
+ this.props.noteUpdate(note);
+ }}
+ />
+ )}
+
+ )
+ })}
+
+ }
+
+ )
+ }
+}
+
+export default NoteList;
diff --git a/src/component/note-update-form/_note-update-form.scss b/src/component/note-update-form/_note-update-form.scss
new file mode 100644
index 0000000..f42f796
--- /dev/null
+++ b/src/component/note-update-form/_note-update-form.scss
@@ -0,0 +1,17 @@
+@import '../../style/_vars.scss';
+
+.note-update-form {
+ padding: $sml-gutter 0;
+ position: relative;
+
+ textarea {
+ max-width: 40%;
+ width: 40%;
+ }
+
+ button {
+ position: absolute;
+ right: 0;
+ bottom: 4px;
+ }
+}
diff --git a/src/component/note-update-form/index.js b/src/component/note-update-form/index.js
new file mode 100644
index 0000000..fdf92d3
--- /dev/null
+++ b/src/component/note-update-form/index.js
@@ -0,0 +1,47 @@
+import './_note-update-form.scss'
+
+import React from 'react';
+
+
+class NoteUpdateForm extends React.Component {
+ constructor(props) {
+ super(props);
+
+ this.state = {
+ title: this.props.note.title,
+ content: '',
+ editing: false
+ }
+
+ this.handleChange = this.handleChange.bind(this);
+ this.handleSubmit = this.handleSubmit.bind(this);
+ }
+
+ handleChange(e) {
+ this.setState({
+ [e.target.name]: e.target.value
+ })
+ }
+
+ handleSubmit(e) {
+ e.preventDefault();
+ this.props.handleSubmit(this.state);
+ }
+
+ render() {
+ return (
+
+ )
+ }
+}
+
+export default NoteUpdateForm;
diff --git a/src/index.html b/src/index.html
new file mode 100644
index 0000000..3243af3
--- /dev/null
+++ b/src/index.html
@@ -0,0 +1,10 @@
+
+
+
+
+ Note Maker App
+
+
+
+
+
diff --git a/src/main.js b/src/main.js
new file mode 100644
index 0000000..e6982b9
--- /dev/null
+++ b/src/main.js
@@ -0,0 +1,43 @@
+import './style/main.scss';
+import React from 'react';
+import ReactDom from 'react-dom';
+import {BrowserRouter, Route} from 'react-router-dom';
+
+import NoteContainer from './component/note-container';
+
+class App extends React.Component {
+ constructor(props) {
+ super(props);
+ this.state = {
+ notes: []
+ }
+
+ this.getApp = this.getApp.bind(this);
+ }
+
+ componentDidUpdate() {
+ console.log('__STATE__', this.state);
+ }
+
+ getApp() {
+ return {
+ state: this.state,
+ setState: this.setState.bind(this)
+ }
+ }
+
+ render() {
+ return (
+
+
+
+
+
+ )
+ }
+}
+
+ReactDom.render(, document.getElementById('root'))
diff --git a/src/style/_base.scss b/src/style/_base.scss
new file mode 100644
index 0000000..a7228d9
--- /dev/null
+++ b/src/style/_base.scss
@@ -0,0 +1,35 @@
+body {
+ background: $gray;
+}
+
+h1 {
+ font-size: 30px;
+ font-family: arial;
+}
+
+h2 {
+ font-size: 20px;
+ font-family: courier;
+}
+
+p {
+ font-size: 10px;
+ font-family: courier;
+}
+
+button {
+ padding: 4px 8px;
+ border: none;
+ border-radius: 3px;
+ background: $black;
+ color: $white;
+}
+
+button:hover {
+ background: $white;
+ color: $black;
+}
+
+input:focus {
+ background: $gray;
+}
diff --git a/src/style/_reset.scss b/src/style/_reset.scss
new file mode 100644
index 0000000..ed11813
--- /dev/null
+++ b/src/style/_reset.scss
@@ -0,0 +1,48 @@
+/* http://meyerweb.com/eric/tools/css/reset/
+ v2.0 | 20110126
+ License: none (public domain)
+*/
+
+html, body, div, span, applet, object, iframe,
+h1, h2, h3, h4, h5, h6, p, blockquote, pre,
+a, abbr, acronym, address, big, cite, code,
+del, dfn, em, img, ins, kbd, q, s, samp,
+small, strike, strong, sub, sup, tt, var,
+b, u, i, center,
+dl, dt, dd, ol, ul, li,
+fieldset, form, label, legend,
+table, caption, tbody, tfoot, thead, tr, th, td,
+article, aside, canvas, details, embed,
+figure, figcaption, footer, header, hgroup,
+menu, nav, output, ruby, section, summary,
+time, mark, audio, video {
+ margin: 0;
+ padding: 0;
+ border: 0;
+ font-size: 100%;
+ font: inherit;
+ vertical-align: baseline;
+}
+/* HTML5 display-role reset for older browsers */
+article, aside, details, figcaption, figure,
+footer, header, hgroup, menu, nav, section {
+ display: block;
+}
+body {
+ line-height: 1;
+}
+ol, ul {
+ list-style: none;
+}
+blockquote, q {
+ quotes: none;
+}
+blockquote:before, blockquote:after,
+q:before, q:after {
+ content: '';
+ content: none;
+}
+table {
+ border-collapse: collapse;
+ border-spacing: 0;
+}
diff --git a/src/style/_vars.scss b/src/style/_vars.scss
new file mode 100644
index 0000000..1a24aa6
--- /dev/null
+++ b/src/style/_vars.scss
@@ -0,0 +1,5 @@
+$gray: #ccc;
+$white: #fff;
+$black: #111;
+$lrg-gutter: 30px;
+$sml-gutter: 10px;
diff --git a/src/style/main.scss b/src/style/main.scss
new file mode 100644
index 0000000..3c13688
--- /dev/null
+++ b/src/style/main.scss
@@ -0,0 +1,3 @@
+@import 'reset';
+@import 'vars';
+@import 'base';
diff --git a/webpack.config.js b/webpack.config.js
new file mode 100644
index 0000000..6a08720
--- /dev/null
+++ b/webpack.config.js
@@ -0,0 +1,34 @@
+'use strict';
+
+const HtmlPlugin = require('html-webpack-plugin');
+const ExtractPlugin = require('extract-text-webpack-plugin');
+
+module.exports = {
+ devtool: 'cheap-module-eval-source-map',
+ devServer: {
+ historyApiFallback: true
+ },
+ entry: `${__dirname}/src/main.js`,
+ output: {
+ filename: 'bundle-[hash].js',
+ path: `${__dirname}/build`,
+ publicPath: '/'
+ },
+ plugins: [
+ new HtmlPlugin({ template: `${__dirname}/src/index.html`}),
+ new ExtractPlugin('bundle-[hash].css')
+ ],
+ module: {
+ rules: [
+ {
+ test: /\.js$/,
+ exclude: /node_modules/,
+ loader: 'babel-loader'
+ },
+ {
+ test: /\.scss$/,
+ loader: ExtractPlugin.extract(['css-loader', 'sass-loader'])
+ }
+ ]
+ }
+};