Pen Settings

HTML

CSS

CSS Base

Vendor Prefixing

Add External Stylesheets/Pens

Any URLs added here will be added as <link>s in order, and before the CSS in the editor. You can use the CSS from another Pen by using its URL and the proper URL extension.

+ add another resource

JavaScript

Babel includes JSX processing.

Add External Scripts/Pens

Any URL's added here will be added as <script>s in order, and run before the JavaScript in the editor. You can use the URL of any other Pen and it will include the JavaScript from that Pen.

+ add another resource

Packages

Add Packages

Search for and use JavaScript packages from npm here. By selecting a package, an import statement will be added to the top of the JavaScript editor for this package.

Behavior

Auto Save

If active, Pens will autosave every 30 seconds after being saved once.

Auto-Updating Preview

If enabled, the preview panel updates automatically as you code. If disabled, use the "Run" button to update.

Format on Save

If enabled, your code will be formatted when you actively save your Pen. Note: your code becomes un-folded during formatting.

Editor Settings

Code Indentation

Want to change your Syntax Highlighting theme, Fonts and more?

Visit your global Editor Settings.

HTML

              
                <div id="content"></div>
              
            
!

CSS

              
                @import url(https://fonts.googleapis.com/css?family=Ubuntu:400,700)
@import url(https://fonts.googleapis.com/css?family=Abril+Fatface)

*
  font-family: 'Ubuntu', sans-serif
  color: #212121
  list-style: none
  text-decoration: none

h1, h2, h3, h4, h5, h6
  font-family: 'Abril Fatface', cursive
  letter-spacing: 1px

html, body, #content, .main, .wrapper
  width: 100%
  height: 100%
  box-sizing: border-box
  overflow: hidden
  text-decoration: none
    
  @media screen and ( max-width: 700px )
    overflow: auto

body
  background-color: #Ffffff
  
.wrapper
  max-width: 1280px
  margin: 0 auto
  background-color: #FFFfff
  box-shadow: 0px 0px 7px 0px #9E9E9E

.clearfix
  content: ''
  height: 0
  line-height: 0
  clear: both

.none
  display: none
  
.not-visible
  opacity: 0

  
button
  width: 90px
  height: 40px
  line-height: 40px
  vertical-align: middle
  border-radius: 2px
  outline: none
  border: none
  cursor: pointer
  color: #ffffff
  box-shadow: 0px 1px 7px -2px #9E9E9E
  transition: 150ms all ease-out

  i
    color: #ffffff
  
button.new
  position: absolute
  z-index: 9999
  top: 100px
  right: 20px
  width: 50px
  height: 50px
  line-height: 50px
  border-radius: 50%
  background-color: #ef5350
  box-shadow: 0px 3px 20px -3px #212121
  
  &:hover
    background-color: #ef9a9a
  &:active
    background-color: #ffcdd2
  
button.edit, button.save
  background-color: #26A69A
  margin-top: 45px
  margin-bottom: 25px
  
  &:hover
    background-color: #80CBC4
  &:active
    background-color: #B2DFDB
  &:disabled
    background-color: #757575
    
button.cancel, button.delete
  background-color: #FF7043
  margin-left: 20px
  margin-top: 45px
  margin-bottom: 25px
  
  &:hover
    background-color: #FFAB91
  &:active
    background-color: #FFCCBC
     
button.close
  position: relative
  float: right
  margin-top: 20px
  margin-right: 10px
  background-color: #EEEEEE
  color: #616161
  
  &:hover
    background-color: #E0E0E0
  &:active
    background-color: #F5F5F5


  
  
.wrapper

  .sidebar
    position: relative
    width: 34%
    float: left
    height: 100%
    overflow: auto
    background-color: #FFF8E1
    box-shadow: 0px 0px 7px 0px #9E9E9E
    
    @media screen and ( max-width: 700px )
      width: 100%
      float: none
      height: auto
    
    h2
      margin-top: 25px
      margin-left: 25px
      
    ul
      padding: 25px
      width: 95%
      box-sizing: border-box
      
    li
      margin-bottom: 10px
      width: 100%
      
      a
        text-decoration: none
        width: 100%
        
        &:after
          content: ''
          display: block
          width: 0
          height: 2px
          background-color: none
          transition: 300ms all ease-out
          background-color: #2196F3
        
        &:hover:after
          width: 100%
          

  .details-box
    position: relative
    width: 64%
    padding: 0 20px 0 20px
    box-sizing: border-box
    background-color: #E1F5FE
    float: right
    height: 100%
    overflow: auto
    box-shadow: 0px 0px 7px 0px #9E9E9E
    
    @media screen and ( max-width: 700px )
      width: 100%
      float: none
      height: auto
    
      
    ul
      padding: 0
    
    div
      width: 100%
    
      form
        margin: 25px 0
        width: 100%

        label
          
          input
            width: 100%
            font-size: 1em
            outline: none
            border: 1px solid #212121
            border-radius: 2px
            padding: 5px
            margin: 5px 0 15px
            box-sizing: border-box
            
            &:focus
              border-color: #2196F3
            
          textarea
            width: 100%
            min-height: 100px
            font-size: 1em
            outline: none
            border: 1px solid #212121
            border-radius: 2px
            padding: 5px
            box-sizing: border-box
            resize: vertical
            margin: 5px 0 15px
            
            
            &:focus
              border-color: #2196F3

        button
          margin-top: 15px
    
    
footer
  position: relative
  width: 100%
  padding: 20px 0 10px
  
  p
    font-size: .75em
      
    a
      color: #2196F3
              
            
!

JS

              
                window.ee = new EventEmitter();

var recipes = (typeof localStorage['localRecipes'] != 'undefined') ? JSON.parse(localStorage['localRecipes']) : [
      {
  id: 1,
  name: 'Ice Cubes',
  ingredients: 'Water',
  instructions: 'Mix 2 cups of water with 2 tablespoons of water.\n\nAdd more water to taste.\n\nPlace the water filled ice trays in the freezer.\n\nShut the door to the freezer.'
}, {
  id: 2,
  name: 'Boiled water',
  ingredients: 'Water',
  instructions: "The perfect recipe for any new cook. Professional results in just seven minutes. And it's gluten free, too!\n\nOpen your cupboard or wherever it is you store your cookware. You may use whatever size pot you have.\n\nTurn the cold-water knob to the 'ON' position.\n\nFill stockpot to within a couple inches of the rim.\n\nLift stockpot from sink and transfer to stove.\n\nFind knob on stove that corresponds to the 'burner' you have placed your pot on.\n\nTurn knob to 'High' and wait until water boils.\n\nServe hot but do not drink."
}, {
  id: 3,
  name: 'Toast',
  ingredients: 'Sliced bread, Butter',
  instructions: "Take piece of bread in right or left hand.\n\nWith alternate hand, open toaster.\n\nNOTE: This step may vary depending on your toaster oven.\n\nPlace piece of bread in toaster oven.\n\nTurn dial/knob to determine the level of darkness to your desire.\n\n[Assuming your toaster oven is plugged into a working electric socket] Turn on toaster.\n\nWait.\n\nRemove toast (no longer bread) from toaster oven when the toasting of said toast is no longer toasting.\n\nPlace toast on surface of object of your choice.\n\nTake knife in hand.\n\nUsing knife, cut 4-7 tablespoons of butter.\n\nSpread said butter on toast.\n\nEat."
}, {
  id: 4,
  name: 'Ham steaks',
  ingredients: 'Olive oil, Ham steaks',
  instructions: "Heat a large skillet, with olive oil.\n\nAdd the ham steaks and heat through, 1 to 2 minutes on each side.\n\nEat."
}, {
  id: 5,
  name: 'Chineese dumplings',
  ingredients: 'Egg, Water, Salt, Flour, Minced meat, Onion, Garlic',
  instructions: "You can cook it any way you know, but you have to cook 1.5 billion of it."
}];

var ids = (typeof localStorage['localIds'] != 'undefined') ? localStorage.getItem('localIds') : 6;

var emptyObj = {
  name: '',
  ingredients: '',
  instructions: ''
};

var obj = {};



var App = React.createClass({
  getInitialState: function() {
    return {
      recipes: recipes,
      adding: false,
      recipeToShow: ''
    };
  },
  componentDidMount: function() {
    var self = this;
    window.ee.addListener('Details.show', function(item) {
      obj = item;
      self.setState({recipeToShow: item, adding: false});
    });
    window.ee.addListener('Recipe.add', function() {
      self.setState({recipes: recipes, adding: false});
      updateLocal();
    });
    window.ee.addListener('Recipe.edit', function() {
      self.setState({recipes: recipes});
      updateLocal();
    });
    window.ee.addListener('Recipe.deleted', function() {
      self.setState({recipes: recipes, recipeToShow: ''});
      updateLocal();
    });
    window.ee.addListener('Cancel', function() {
      self.setState({adding: false});
    });
  },
  componentWillUnmount: function() {
    window.ee.removeListener('Details.show');
    window.ee.removeListener('Recipe.add');
    window.ee.removeListener('Recipe.edit');
    window.ee.removeListener('Recipe.deleted');
    window.ee.removeListener('Cancel');
  },
  add: function(e) {
    e.preventDefault();
    this.setState({recipeToShow: '', adding: true});
  },
  render: function() {
    return (
      <div className="main">
        <div className="wrapper">
          <div className="sidebar">
            <h2>Recipes</h2>
            <button className="new" onClick={this.add}><i className="fa fa-plus fa-lg" aria-hidden="true"></i></button>
            <RecipesList list={this.state.recipes} />
          </div>
          <div className="details-box">
            <div className={this.state.adding ? '' : 'none'}>  
              <RecipeAddEdit toBeEdited={emptyObj} operation="add" />
            </div>
            <div className={this.state.adding ? 'none' : ''}>
              <RecipeDetails details={this.state.recipeToShow} />
            </div>
          </div>
        </div>
        <div className="clearfix"></div>
      </div>
    );
  }
});



var RecipesList = React.createClass({
  propTypes: {
    list: React.PropTypes.array
  },
  showDetails: function(id, e) {
    e.preventDefault();
    for (var i = 0; i < recipes.length; i++) {
      if (recipes[i].id === id) {
        window.ee.emit('Details.show', recipes[i]);
      }
    };
  },
  render: function() {
    var list = this.props.list;
    var self = this;
    var listTemplate = list.map(function(item, index) {
      return (
        <li key={index}>
          <a href onClick={self.showDetails.bind(self, item.id)}>{item.name}</a>
        </li>
      );
    });
    return (
      <ul>
        {listTemplate}
      </ul>
    );
  }
});



var RecipeDetails = React.createClass({
  propTypes: {
    details: React.PropTypes.oneOfType([React.PropTypes.string, React.PropTypes.shape({
      id: React.PropTypes.number,
      name: React.PropTypes.string,
      ingredients: React.PropTypes.string,
      instructions: React.PropTypes.string
    })])
  },
  getInitialState: function() {
    return {
      editing: false,
      details: false,
      name: '',
      ingredients: '',
      instructions: ''
    }
  },
  componentDidMount: function() {
    var self = this;
    window.ee.addListener('Cancel', function() {
      self.setState({editing: false});
    });
    window.ee.addListener('Recipe.edit', function() {
      self.setState({editing: false});
    });
    window.ee.addListener('Adding', function() {
      self.setState({details: false});
    });
  },
  componentWillUnmount: function() {
    window.ee.removeListener('Cancel');
    window.ee.removeListener('Recipe.addedit');
    window.ee.removeListener('Adding');
  },
  componentWillReceiveProps: function(nextProps) {
    this.setState({
      details: nextProps.details !== '' ? true : false,
      editing: false,
      name: nextProps.details.name,
      ingredients: nextProps.details.ingredients,
      instructions: nextProps.details.instructions
    });
  },
  edit: function(e) {
    e.preventDefault();
    this.setState({editing: true});
  },
  delete: function(e) {
    e.preventDefault();
    for (var i = 0; i< recipes.length; i++) {
      if (recipes[i].id === this.props.details.id) {
        recipes.splice(i, 1);
        window.ee.emit('Recipe.deleted');
      }
    };
  },
  close: function(e) {
    e.preventDefault();
    this.setState({details: false});
  },
  createMarkup: function(str) {
    var markup = marked(str);
    return {__html: markup};
  },
  render: function() {
    var self = this;
    var details = this.props.details;
    if (this.state.details) {
      var ingredients = details.ingredients.split(',');
      var ingredientsTemplate = ingredients.map(function(ing, index) {
        return <li key={index}>{ing.trim()}</li>
      });
      return (
        <div className="details">
          <div className={this.state.editing ? 'none' : ''}>
            <button  className={"close " + ((this.state.details !== '' && !this.state.editing) ? '' : 'not-visible')} onClick={this.close}>CLOSE</button>
            <div className="clearfix"></div>
            <h1>{details.name}</h1>
            <div>
              <h3>Ingredients:</h3>
              <ul>{ingredientsTemplate}</ul>
            </div>
            <h3>Preparation instructions:</h3>
            <div dangerouslySetInnerHTML={self.createMarkup(details.instructions)}></div>

            <button className="edit" onClick={this.edit}><i className="fa fa-pencil fa-lg" aria-hidden="true"></i>  EDIT</button>
            <button className="delete" onClick={this.delete}><i className="fa fa-trash-o fa-lg" aria-hidden="true"></i>  DELETE</button>
          </div>
          <div className={this.state.editing ? '' : 'none'}>  
            <RecipeAddEdit toBeEdited={this.props.details} operation="edit" />
          </div>
        </div>
             
      );
    } else {
      return (
        <div className={"placeholder " + (this.state.editing ? 'none' : '')}>
          <h1>Recipe Box</h1>
          <div className="clearfix"></div>
          <p>Click recipe name in "Recipes" section to inspect it's details.</p>
          <p>You can also edit, delete recipes and add new ones.</p>
          <footer><p>Made by <a href="https://saintgeo23.github.io/">saintgeo23</a> for <a href="https://www.freecodecamp.com/">FreeCodeCamp</a> project</p></footer>
        </div>
      );
    };
  }
});



var RecipeAddEdit = React.createClass({
  propTypes: {
    toBeEdited: React.PropTypes.shape({
      id: React.PropTypes.number,
      name: React.PropTypes.string,
      ingredients: React.PropTypes.string,
      instructions: React.PropTypes.string
    })
  },
  getInitialState: function() {
    return {
      id: '',
      name: '',
      ingredients: '',
      instructions: '',
      nameEmpty: true,
      ingredientsEmpty: true,
      instructionsEmpty: true
    }
  },
  componentWillReceiveProps: function(nextProps) {
    this.setState({
      id: nextProps.toBeEdited.id ? nextProps.toBeEdited.id : ids,
      name: nextProps.toBeEdited.name,
      ingredients: nextProps.toBeEdited.ingredients,
      instructions: nextProps.toBeEdited.instructions,
      nameEmpty: nextProps.toBeEdited.name ? false : true,
      ingredientsEmpty: nextProps.toBeEdited.ingredients ? false : true,
      instructionsEmpty: nextProps.toBeEdited.instructions ? false : true
    });
  },
  onInputChange: function(fieldName, e) {
    this.setState({
      [''+fieldName]: e.target.value
    });
    if (e.target.value.trim() !== '') {
      this.setState({[''+fieldName+'Empty']: false});
    } else this.setState({[''+fieldName+'Empty']: true});
  },
  edit: function(e) {
    e.preventDefault();
    for (var i = 0; i < recipes.length; i++) {
      if (recipes[i].id === obj.id) {
        recipes[i].name = this.state.name;
        recipes[i].ingredients = this.state.ingredients;
        recipes[i].instructions = this.state.instructions;
        break;
      }
    }
    window.ee.emit('Recipe.edit');
  },
  add: function(e) {
    e.preventDefault();
    var item = {
      id: this.state.id,
      name: this.state.name,
      ingredients: this.state.ingredients,
      instructions: this.state.instructions
    };
    recipes.push(item);
    ++ids;
    window.ee.emit('Recipe.add');
  },
  cancel: function(e) {
    e.preventDefault();
    window.ee.emit('Cancel');
  },
  render: function() {
    return (
      <form>
        <label>Recipe name:<br />
          <input type="text"
            value={this.state.name}
            onChange={this.onInputChange.bind(this, 'name')} />
        </label><br />
        <label>Ingredients (separated by comma):<br />
          <input type="text"
            value={this.state.ingredients}
            onChange={this.onInputChange.bind(this, 'ingredients')} />
        </label><br />
        <label>Preparation instructions:<br />
          <textarea value={this.state.instructions}
            onChange={this.onInputChange.bind(this, 'instructions')}></textarea>
        </label><br />
        <button className="save" onClick={this.props.operation === 'add' ? this.add : this.edit} disabled={this.state.nameEmpty || this.state.ingredientsEmpty || this.state.instructionsEmpty}><i className="fa fa-floppy-o fa-lg" aria-hidden="true"></i>  SAVE</button>
        <button className="cancel" onClick={this.cancel}><i className="fa fa-ban fa-lg" aria-hidden="true"></i>  CANCEL</button>
      </form>
    );
  }
});



ReactDOM.render(
  <App />,
  document.getElementById('content')
);
 
function updateLocal() {
  localStorage.setItem('localRecipes', JSON.stringify(recipes));
  localStorage.setItem('localIds', ids);
};

updateLocal();
              
            
!
999px

Console