cssAudio - Activefile-genericCSS - ActiveGeneric - ActiveHTML - ActiveImage - ActiveJS - ActiveSVG - ActiveText - Activefile-genericVideo - ActiveLovehtmlicon-new-collectionicon-personicon-teamlog-outoctocatpop-outspinnerstartv

Pen Settings

CSS Base

Vendor Prefixing

Add External Stylesheets/Pens

Any URL's added here will be added as <link>s in order, and before the CSS in the editor. If you link to another Pen, it will include the CSS from that Pen. If the preprocessor matches, it will attempt to combine them before processing.

Quick-add: + add another resource

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.

Quick-add: + add another resource

Code Indentation

     

Save Automatically?

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.

            
              $bRad: 5px
$fColour1: #111
$fColour4: #444
$bgColour: #0079B0
$m: 20px

body
  background: /*#457E86*/white
  font-family: helvetica
  line-height: 150%
  width: 100%
  margin: 0
  display: flex
  justify-content: space-around
  color: $fColour1

.recipe-box
  display: flex
  flex-wrap: wrap

#container
  width: 100%
  
.row-break
  width: 100%
  
h3, h4
  margin-bottom: 0
  + p, + ul
    margin-top: 0
a
  text-decoration: none
  color: $fColour4
  
button
  padding: 10px
  font-size: 0.9em
  border-radius: $bRad
  .fa
    font-size: 1.2em
    margin-right: 10px
  .fa-cart-plus
    font-size: 1.5em
    margin-top: -0.3em

.recipe-list
  width: 30vw
  min-width: 200px
  max-height: 400px
  max-height: 90vh
  overflow-y: auto
  margin: $m 0 0 $m
  background-color: $bgColour
  border-radius: $bRad
  
  h2
    margin: 20px
  ul
    display: flex
    flex-direction: column
    justify-content: flex-start
    list-style: none
    padding: 0
  li
    background-color: white
    padding: 0 20px
    margin: 10px 20px
    display: flex
    justify-content: space-between
    align-items: center
    
.recipe-details, .shopping
  display: flex
  flex-flow: column wrap
  //justify-content: flex-start 
  justify-content: space-between
  width: 500px
  width: 50vw
  min-width: 200px
  background-color: $bgColour
  margin: 20px 20px 0 20px
  padding: 10px 20px 30px 20px
  border-radius: $bRad

  #recipe-details-title
    display: flex
    justify-content: space-between
    button
      margin-top: 10px
    
  #recipe-details-content
    display: block
    &.Save
      display: none
      
  .recipe-btns
    display: flex
    justify-content: flex-end
    button
      margin: 10px 0 0 10px

  #recipe-editor
    opacity: 1.0
    display: none
    flex-direction: column
    transition: display 1s
    backround-color: #ddd
    &.Save
      display: flex
    label
      margin-top: 10px

    input
      margin: 5px 0
      padding: 5px
      border-radius: $bRad
      
.shopping
  width: 60%
  align-self: flex-start
  margin-bottom: $m
      
@media (max-width: 680px)
  .recipe-box
    justify-content: center
  .recipe-list, .recipe-details, .shopping
    width: 80%
    margin: $m 0 0 0
  .recipe-list
    height: 60vh
            
          
!
            
              /** USER STORIES **
User Story: I can create recipes that have names and ingredients.
User Story: I can see an index view where the names of all the recipes are visible.
User Story: I can click into any of those recipes to view it.
User Story: I can edit these recipes.
User Story: I can delete these recipes.
User Story: All new recipes I add are saved in my browser's local storage. If I refresh the page, these recipes will still be there.
**/
var recipeData = [
  {
    "title": "Hi there",
    "ingredients":"React.js, Sass, JSX, Babel, Font-awesome, jQuery",
    "method":"This is a project created for FreeCodeCamp.com\'s Data Visualisation Certification. It is a simple recipe box built using React.js. Any changes you make are saved into your browsers local storage. The UI clearly needs some work including animating changes and fixing the CSS but I think it is something I will revisit to polish up. I have been playing with flex-box vs more traditional css which has been informative but needs to be made consistent. There is also currently a single jQuery function to add a new recipe which seems absurd to pull in a whole library for that. So that is also on the chopping block (pun entirely intended). Who knows, maybe I will come back add more styling, recipe metadata, ingredient quantities and units of measure. In the meantime, to sweeten the deal, I have dropped in a few of my favourite vegan recipes that generally confuse people as to how they are even possible. Enjoy!"
  },
  {
    "title": "Quiche",
    "ingredients":"extra-firm tofu - 450g, onion powder - 1 tsp, garlic powder - 1 tsp, turmeric - ½ tsp, salt - ½ tsp, nutritional yeast - ¼ cup, cornstarch - ¼ cup, dijon mustard - 1½ tbsp, lemon juice - 1 tbsp, fresh greens (any) - 4 cups chopped, red pepper flakes - ¼ tsp",
    "method":"Serves 6. Preheat oven to 180 C. Grease a shallow 9\" pie dish and set aside. Combine all ingredients, except greens, in a food processor or strong blender and whiz until smooth and creamy, stopping to break up chunks and scrape the sides as necessary. Mix in the greens and transfer batter to pie dish. Using a spatula, spread the mixture around so it\'s even and tight. Bake 30-40 minutes, until golden and the center is not still mushy. Allow to cool at least 10 minutes before slicing (luke warm or room temperature is best for slicing). Recipe courtesy of HappyHerbivore.com"
  },
  {
    "title": "Mud cake",
    "ingredients":"beetroot - 180g (about 1 largeish beetroot), very dark chocolate - 200g, brown sugar - 170g, vanilla extract - 1 tsp, canola oil - 1/3 cup, apple sauce - 1/3 cup, maple syrup - 2 tablespoons, plain flour - 1/2 cup, baking powder - 2 tsp, bicarb of soda - 1/2 tsp, salt - just a pinch, cocoa - 85g - as dark as you can get, hot water - 1/4 cup",
    "method":"Line a 22 cm square tin with baking paper, and pre-heat the oven to 160°C. Grate the beetroot on the fine part of the grater.  I regret deeply that I didn’t take a photo of this, because it was bright pink. Melt the chocolate.  The best way I’ve found to do this is to break it in pieces and put it in a glass bowl in the microwave at 50% for 1 minute at a time.  Stir after every minute, and expect that the whole thing will melt in about 5 minutes.  You do want to check it every minute, because chocolate can burn quite nastily if you allow it. Congratulations, everything from here is ridiculously easy. In a large bowl, beat together the sugar, vanilla, oil, apple sauce and maple syrup until everything is nicely combined. If you want to be super clever - use the 1/3 cup measure for the oil - then the apple sauce - then the maple syrup – 2 tablespoons is 1/6 cup - so just eyeball it and fill it about halfway.  The oil will stop the sauce and syrup from sticking. In a separate bowl, combine the flour, baking powder and bicarb, pinch of salt and the cocoa, then sift them in to the wet mix.  Or, if you are lazy like me, skip the separate bowl, and just bung everything in directly, ideally putting it through a sieve, because cocoa will clump rather if you let it.  Mix everything together until smooth. Throw in the beetroot and use a spatula to scrape all the chocolate into the mix, and stir everything together well.  You should have a lovely mud-pie sort of batter at this point. Oh, and if you were adding pecans, now would be the time. Scrape it into the prepared tin and spread it about a bit – this isn’t going to move around much, so if you leave it with a bumpy top, you will get a bumpy top. Bake for half an hour, then cover with foil and bake for another half hour.  Your cake will be very soft but should, just barely, pass the skewer test.  Let it cool in the tin to be on the safe side."
  }
];
if (!localStorage["recipeData"]) { localStorage.setItem('recipeData', JSON.stringify(recipeData)); }

var RecipeList = React.createClass({
  render: function render() {
    return (
      <div className="recipe-list">
      <h2>Recipes</h2>
        <ul>
          {this.props.list.map(function(recipe, i) {
            return (
              <li><p><a href="#" onClick={this.props.changeRecipe.bind(this, i)} key={i}>{recipe.title}</a></p><a href="#"><i className="fa fa-trash-o" onClick={this.props.deleteRecipe.bind(this, i)} key={i}></i></a></li>
            );
          }, this)}
        </ul>
      </div>
    );
  }
});

var TextInput = React.createClass({
  render: function() {
    return (
      <input type="text" className={this.props.classes} value={this.props.value} onChange={this.props.inputUpdate} />
    )
  }
});

var RecipeDetails = React.createClass({
  getInitialState: function() {
    return { editBtn: 'Edit'}
  },
  toggleEdit: function(e) {
    this.setState({editBtn: (e.target.className == 'Edit') ? 'Save': 'Edit'});
  },
  newRecipe: function(e) {
    
    if (this.state.editBtn == 'Edit') {
      $('button.Edit').click();
    }
    this.props.createNewRecipe();
  },
  render: function() {
    return (
      <div className="recipe-details">
        <div id="recipe-details-info">
        <div id="recipe-details-title">
          <h3>{this.props.title}</h3> 
          <button className={this.state.editBtn} onClick={this.toggleEdit} ><i className="fa fa-pencil"></i>{this.state.editBtn}</button>
        </div>
        <div id="recipe-details-content" className={this.state.editBtn}>
          <h4>Ingredients:</h4>
          <ul>{this.props.listifyIngredients(this.props.ingredients)}</ul>
          <h4>Method:</h4>
          <p>{this.props.method}</p>
        </div>
        <div id="recipe-editor" className={this.state.editBtn} >
            <label for="title">Title</label>
            <input name="title" type="text" className="title" value={this.props.title} onChange={this.props.inputUpdate} />
            <label for="ingredients">Ingredients</label>
            <input name="ingredients" type="text" className="ingredients" value={this.props.ingredients} onChange={this.props.inputUpdate} />
            <label for="method">Method</label>
            <input name="method" type="text" className="method" value={this.props.method}  onChange={this.props.inputUpdate} />
          </div>
          </div>
          <div className="recipe-btns">
            
            <button onClick={this.props.addToShoppingList} ><i className="fa fa-cart-plus"></i>Add to shopping list</button>
            <button onClick={this.newRecipe}><i className="fa fa-plus"></i>Add new recipe</button>
          </div>
          
        </div>
    );
  }  
});

var RowBreak = React.createClass({
  render: function() {
    return (
      <div className="row-break">
      </div>
    );
  }
});

var ShoppingList = React.createClass({
  render: function() {
    return (
      <div className="shopping">
          <h3>Shopping list</h3>
          <ul>{this.props.listifyIngredients(this.props.list)}</ul>
      </div>
    );
  }  
});

var RecipeBox = React.createClass({
  getInitialState: function() {
    this.storedData = JSON.parse(localStorage["recipeData"]);
    this.setState({shopping: []});
    return {shopping: []};
  },
  componentDidMount: function() {
    this.setState({index: 0, title: this.storedData[0].title, ingredients: this.storedData[0].ingredients, method: this.storedData[0].method, ingList: this.listifyIngredients(this.storedData[0].ingredients) });
  },
  listifyIngredients: function(ing) {
    
    if (typeof(ing) == 'object') {
      var list = ing.join('').split(',')
      for (var i = 0; i < list.length; i++)
        list[i] = list[i].trim().toLowerCase();
      list = list.sort().filter(function(e,i,arr) {
        return (e !== arr[i-1]);
      });
    }
    if (typeof(ing) == 'string') {
      var list = ing.split(',');
    }
    if (list) {
      return (
        list.map(function(item, i) {
          return (
            <li>{item}</li>
          )
        })
      );
    }
  },
  changeRecipe: function changeRecipe(i) {
    this.setState({index: i, title: this.storedData[i].title, ingredients: this.storedData[i].ingredients, method: this.storedData[i].method })
  },
  createNewRecipe: function() {
    var i = this.storedData.length;
    this.storedData[i] = {'ingredients':'','method':'','title':''};
    this.changeRecipe(i);
  },
  deleteRecipe: function(i) {
    var j = (i>0) ? i-1 : i+1;
    j = (this.state.index != i) ? this.state.index : j;
    this.changeRecipe(j); // changeRecipe to trigger state update
    var deleted = this.storedData.splice(i,1);
    this.saveToStorage();
  },
  addToShoppingList: function() {
    var newList = this.state.shopping;
    if (newList.length > 0) {
      newList.push(', ');
    }
    newList.push(this.state.ingredients);
    this.setState({shopping: newList});
    
  },
  inputUpdate: function inputUpdate(e) {
    var update = {};
    update[e.target.name] = e.target.value;
    this.setState(update);
    this.storedData[this.state.index][e.target.name] = e.target.value;
    this.saveToStorage();
  },
  saveToStorage: function() {
    localStorage.setItem('recipeData', JSON.stringify(this.storedData));
  },
  render: function() {
    return (
      <div className="recipe-box">
        <RecipeList list={this.storedData} changeRecipe={this.changeRecipe} deleteRecipe={this.deleteRecipe} />
        <RecipeDetails title={this.state.title} ingredients={this.state.ingredients} method={this.state.method} inputUpdate={this.inputUpdate} createNewRecipe={this.createNewRecipe} addToShoppingList={this.addToShoppingList} listifyIngredients={this.listifyIngredients} />
        <RowBreak />
        <ShoppingList list={this.state.shopping} listifyIngredients={this.listifyIngredients} />
      </div>
    );
  }
});

React.render(<RecipeBox />, document.getElementById('container'));
            
          
!
999px
Loading ..................

Console