Pen Settings

HTML

CSS

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. You can use the CSS from another Pen by using it's URL and the proper URL extention.

+ 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

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.

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="bckg"> -->
    <div id="root" class="content">

    </div>
    <footer class="footer">
        <span>&copy; 2017 Developed with &nbsp;<i class="fa fa-heart" aria-hidden="true"></i> &nbsp;by Manish Giri using React</span>
    </footer>
<!-- </div> -->
              
            
!

CSS

              
                @import url('https://fonts.googleapis.com/css?family=Open+Sans|Spirax');
@import url('https://fonts.googleapis.com/css?family=Macondo');
@import url('https://fonts.googleapis.com/css?family=Open+Sans|Playfair+Display+SC');
@import url('https://fonts.googleapis.com/css?family=Macondo');


/*
  ---------------------------
    STICKY FOOTER
  ---------------------------

*/

html {
  height: 100%;
}
body {
  min-height: 100%;
  display: flex;
  flex-direction: column;
  //background: black;
   background: url(https://dl.dropbox.com/s/jcse0n5pcrrrer2/recipe-bg.jpg?dl=0);
  background-size: cover;
}
.content {
  flex: 1;

}
.footer {
  padding: 20px 20px 10px 20px;
}


/*
     SEARCH BAR
 */

.box{
  margin: 50px auto;
  width: 350px;
  height: 50px;
  background: #343d46;
  opacity:0.85;

  /*
       container-1 styles
   */
  .container-1{
    width: 300px;
    vertical-align: middle;
    white-space: nowrap;
    position: relative;


    /*
      input search
     */

    input#search{
        width: 350px;
        height: 50px;
        background: #2b303b;
        border: none;
        font-size: 10pt;
        float: left;
        color: #63717f;
        padding-left: 45px;
        -webkit-border-radius: 5px;
        -moz-border-radius: 5px;
        border-radius: 5px;

      &:hover, &:focus, &:active {
        outline:none;
        background: #ffffff;
      }

      -webkit-transition: background .55s ease;
      -moz-transition: background .55s ease;
      -ms-transition: background .55s ease;
      -o-transition: background .55s ease;
      transition: background .55s ease;
    }

    input#search::-webkit-input-placeholder {
      color: white;
    }

    input#search:-moz-placeholder { /* Firefox 18- */
      color: white;
    }

    input#search::-moz-placeholder {  /* Firefox 19+ */
      color: white;
    }

    input#search:-ms-input-placeholder {
      color: white;
    }

    /*
        placeholder icon
     */
    .icon{
      position: absolute;
      top: 50%;
      margin-left: 17px;
      margin-top: 17px;
      z-index: 1;
      color: #4f5b66;
    }


  }

}


/*
     FLEX-CONTAINER
 */
.flex-container {
  display: flex;
  flex-wrap: wrap;

}




/*
     FLEX ITEMS - Recipe card
 */
.recipe-item {
  padding: 10px;
  width: 33.3333%;
}














/*
  ---------------------------
    FOOTER
  ---------------------------

*/

footer {
  text-align: center;
  color: whitesmoke;
  font-family: 'Macondo', cursive;

  span {
    font-size: 16px;
    i {
      color: red;
      margin: -2px;
    }
  }

}




              
            
!

JS

              
                const recipes = [
    {
        name:"Baked Teriyaki Chicken", ingredients: "1 tablespoon cornstarch,12 skinless chicken thighs,1/2 teaspoon ground ginger,1 clove garlic minced,1/4 cup cider vinegar",
        image: 'https://dl.dropbox.com/s/38tda6uvphzxz0z/bct.jpg?dl=0',
        tags: "japanese,entree"
    },
    {
        name:"Roasted Garlic Cauliflower", ingredients: "1 large head cauliflower,2 tablespoons minced garlic,1/2 teaspoon ground ginger,salt and black pepper to taste,3 tablespoons olive oil",
        image: 'https://dl.dropbox.com/s/bnacmp7opv6xjat/rgc.jpg?dl=0',
        tags: "side dish,bake,prep-fast"
    },
      {
        name: "Banana Cupcakes", ingredients: "2 large eggs, 3/4 cup sugar, 1 teaspoon salt, 1 teaspoon baking powder, 1/2 cup melted butter, 1/2 teaspoon vanilla extract",
        image: 'https://dl.dropbox.com/s/9lrffte4ndn19p0/bc.jpg?dl=0',
        tags: "dessert, prep-moderate"

    },
    {
        name: "Indian Eggplant - Bhurtha", ingredients: "1 eggplant, 2 tablespoons vegetable oil, 1/2 teaspoon cumin seeds, 1 medium onion sliced, 1/2 teaspoon ground turmeric",
        image: 'https://dl.dropbox.com/s/j24zjshj5sry8c9/ieb.jpg?dl=0',
        tags: "indian, entree, prep-moderate"

    }

];

//setup local storage
localStorage.setItem("nixxRecipes", JSON.stringify(recipes));

class RecipeBoxApp extends React.Component {
    constructor(props) {
        super(props);
        let localData = localStorage.getItem("nixxRecipes");
        let recipes = (localData === "null") ? [] : JSON.parse(localData);
        this.state = {
            recipes: recipes,
            userSearch: "",

            //test new recipe
            newRecipeName: "",
            newRecipeIng: "",
            newRecipeTags: "",
            //test
            editedRecObj: {},

            //store props individually
            editedRecipeName: "",
            editedRecipeIng: "",
            editedRecipeTags: ""

        };
        this.newRecipeObj = {};
        this.editedRecipeObj = {};

        //bind event handlers
        this.handleRecipeNameInput = this.handleRecipeNameInput.bind(this);
        this.handleRecipeIngredientsInput = this.handleRecipeIngredientsInput.bind(this);
        this.handleRecipeTagsInput = this.handleRecipeTagsInput.bind(this);
        this.handleSubmit = this.handleSubmit.bind(this);

        // searchbar event handlers
        this.handleUserSearchInput = this.handleUserSearchInput.bind(this);


        //recipe card event handlers
        this.handleRecipeNameEdit = this.handleRecipeNameEdit.bind(this);
        this.handleRecipeIngredientsEdit = this.handleRecipeIngredientsEdit.bind(this);
        this.handleRecipeTagsEdit = this.handleRecipeTagsEdit.bind(this);
        this.handleRecipeEditSubmit = this.handleRecipeEditSubmit.bind(this);
        this.getRecipeToEdit = this.getRecipeToEdit.bind(this);

        this.handleDelete = this.handleDelete.bind(this);






    }

    componentWillReceiveProps() {
        this.newRecipeObj = {};
        this.setState({newRecipeName: "", newRecipeTags: "", newRecipeIng: ""});

    }

    // event handlers

    //--------------------------------------------------
    // for NavBar -- add new recipe
    //--------------------------------------------------
    handleRecipeNameInput(recipeName) {
        //this.newRecipeObj.name = recipeName;
        this.setState({newRecipeName: recipeName});

    }

    handleRecipeIngredientsInput(recipeIngredients) {
        //let ings = recipeIngredients.split(",");
        //this.newRecipeObj.ingredients = recipeIngredients;
        this.setState({newRecipeIng: recipeIngredients});
    }

    handleRecipeTagsInput(recipeTags) {
        //let tags = recipeTags.split(",");
        //this.newRecipeObj.tags = recipeTags;
        this.setState({newRecipeTags: recipeTags});
    }

    handleSubmit() {
        //let newRecipeArr = [this.newRecipeObj];
        let newRecipeArr = [{name: this.state.newRecipeName, ingredients: this.state.newRecipeIng, tags: this.state.newRecipeTags}];
        let updatedRecipes = this.state.recipes.concat(newRecipeArr);
        //update local storage
        localStorage.setItem("nixxRecipes", JSON.stringify(updatedRecipes));
        this.setState({recipes: updatedRecipes});
    }




    //--------------------------------------------------
    // For Recipe Card
    //--------------------------------------------------

    handleRecipeNameEdit(modifiedName) {
        this.editedRecipeObj.name = modifiedName;
        this.setState({editedRecipeName: modifiedName});
    }

    handleRecipeIngredientsEdit(modifiedIngredients) {
        this.editedRecipeObj.ingredients = modifiedIngredients;
        this.setState({editedRecipeIng: modifiedIngredients});
    }

    handleRecipeTagsEdit(modifiedTags) {
        this.editedRecipeObj.tags = modifiedTags;
        this.setState({editedRecipeTags: modifiedTags});
    }


    // get position/recipe details of selected recipe
    getRecipeToEdit(key) {
        //let recs = this.state.recipes;
        let currentRecipeObj = {};
        this.state.recipes.forEach((element, index) => {
            if(index === key) {
                currentRecipeObj = element;
            }
        });

        this.editedRecipeObj = currentRecipeObj;
        //test
        this.setState({editedRecObj: this.editedRecipeObj, editedRecipeName: this.editedRecipeObj.name,
            editedRecipeIng: this.editedRecipeObj.ingredients, editedRecipeTags: this.editedRecipeObj.tags
        });

    }

    // BUG
    handleRecipeEditSubmit(key) {
        //get position of recipe that was edited
        let updatedRecipes = this.state.recipes;

        //update the recipe that was edited
        updatedRecipes.forEach((element, index) => {
            if(index === key) {
                //element = this.editedRecipeObj;
                updatedRecipes.splice(index, 1, this.editedRecipeObj);
            }
        });

        //update local storage
        localStorage.setItem("nixxRecipes", JSON.stringify(updatedRecipes));

        //set state
        this.setState({recipes: updatedRecipes});
    }

    // when recipe is deleted
    handleDelete(key) {
        //use filter to filter out the deleted recipe
        let editedRecipes = this.state.recipes.filter((element, index) => index !== key);
        //set localstorage to editedRecipes
        localStorage.setItem("nixxRecipes", JSON.stringify(editedRecipes));
        //call this.setState to set recipes to editedRecipes
        this.setState({recipes: editedRecipes});
    }

    //--------------------------------------------------
        // for NavBar -- add new recipe
    //--------------------------------------------------
    handleUserSearchInput(searchItem) {
        this.setState({userSearch: searchItem});
    }


    render() {
        return (
            <div>
                <NavBar onRecipeNameChange={this.handleRecipeNameInput}
                        onRecipeIngredientsChange={this.handleRecipeIngredientsInput}
                        onRecipeTagsChange={this.handleRecipeTagsInput}
                        onSubmit={this.handleSubmit}

                        filterNameText={this.state.newRecipeName}
                        filterIngredientsText={this.state.newRecipeIng}
                        filterTagsText={this.state.newRecipeTags}

                />
                <SearchBar onSearch={this.handleUserSearchInput} filterSearch={this.state.userSearch}/>


                <RecipeFlexContainer recipes={this.state.recipes} onRecipeDelete={this.handleDelete}
                                     onRecipeNameEdit={this.handleRecipeNameEdit}
                                     onRecipeIngredientsEdit={this.handleRecipeIngredientsEdit}
                                     onRecipeTagsEdit={this.handleRecipeTagsEdit}
                                     onRecipeEditSubmit={this.handleRecipeEditSubmit}
                                     filterNameEdit={this.state.editedRecipeName}
                                     filterIngredientsEdit={this.state.editedRecipeIng}
                                     filterTagsEdit={this.state.editedRecipeTags}
                                     onRecipeEditSelect={this.getRecipeToEdit}
                                     searchItem={this.state.userSearch}

                />

            </div>
        );
    }
}


class NavBar extends React.Component{

    constructor(props) {
        super(props);
        this.handleRecipeNameChange = this.handleRecipeNameChange.bind(this);
        this.handleRecipeIngredientsChange = this.handleRecipeIngredientsChange.bind(this);
        this.handleRecipeTagsChange = this.handleRecipeTagsChange.bind(this);
    }

    handleRecipeNameChange(e) {
        this.props.onRecipeNameChange(e.target.value);
    }

    handleRecipeIngredientsChange(e) {
        this.props.onRecipeIngredientsChange(e.target.value);
    }

    handleRecipeTagsChange(e) {
        this.props.onRecipeTagsChange(e.target.value);
    }

    render() {

        let style = {
            brand:  {
                fontFamily: "'Spirax', cursive",
                fontSize: "34px"
            },

            modal: {

                header: {
                    fontFamily: "'Macondo', cursive",
                    color: "#00897b"
                }

            }
        };

        return (
            <div>
                <div className="navbar-fixed">
                    <nav>
                        <div className="nav-wrapper">
                            <a href="#!" className="brand-logo center" style={style.brand}>Recipe-Box</a>

                        </div>
                        <div className="nav-content">
                            <span className="nav-title"></span>
                            <a className="btn-floating btn-large halfway-fab waves-effect waves-light teal" href="#modal1">
                                <i className="material-icons">add</i>
                            </a>
                        </div>
                    </nav>
                </div>
                <div id="modal1" className="modal">
                    <div className="modal-content">
                        <h2 style={style.modal.header}>Add A New Recipe</h2>
                        <div className="row">
                            <form className="col s12">
                                <div className="row">
                                    <div className="input-field col s6">
                                        <input id="input_text" type="text" data-length="30" value={this.props.filterName} onChange={this.handleRecipeNameChange}/>
                                            <label htmlFor="input_text">Name</label>
                                    </div>
                                </div>
                                <div className="row">
                                    <div className="input-field col s12">
                                        <textarea id="textarea1" className="materialize-textarea" data-length="80" value={this.props.filterIngredientsText} onChange={this.handleRecipeIngredientsChange}></textarea>
                                        <label htmlFor="textarea1">Ingredients (separate with commas)</label>
                                    </div>
                                </div>
                                <div className="row">
                                    <div className="input-field col s12">
                                        <textarea id="textarea2" className="materialize-textarea" data-length="30" value={this.props.filterTagsText} onChange={this.handleRecipeTagsChange}></textarea>
                                        <label htmlFor="textarea2">Tags (separate with commas)</label>
                                    </div>
                                </div>
                            </form>
                        </div>

                        {/*<h4>Modal Header</h4>*/}
                        {/*<p>A bunch of text</p>*/}
                    </div>
                    <div className="modal-footer">
                        <a href="#!" className="modal-action modal-close waves-effect waves-green btn btn-floating btn-large green darken-4 pulse" onClick={this.props.onSubmit}><i className="material-icons">done</i></a>
                    </div>
                </div>
            </div>
        )
    }
}

class SearchBar extends React.Component {
    constructor(props) {
        super(props);
        this.handleSearch = this.handleSearch.bind(this);

    }

    handleSearch(e) {
        let searchItem = e.target.value;
        this.props.onSearch(searchItem);
    }
    render() {
        let style = {
            marginTop:"20px"
        };
        return (
            <div className="box searchbox">
                <div className="container-1">
                    <span className="icon"><i className="fa fa-search"></i></span>
                    <input type="search" id="search" placeholder="Search..." value={this.props.filterSearch} onChange={this.handleSearch}/>
                </div>
            </div>

        )
    }
}

class RecipeFlexContainer extends React.Component {
    constructor(props) {
        super(props);
    }

    render() {
        var gridRecipes = this.props.recipes;
        let onDelete = this.props.onRecipeDelete;


        //second set of props
        let onRecipeNameEdit = this.props.onRecipeNameEdit;
        let onRecipeIngEdit = this.props.onRecipeIngredientsEdit;
        let onRecipeTagsEdit = this.props.onRecipeTagsEdit;
        let onRecipeEditSubmit = this.props.onRecipeEditSubmit;
        let onRecipeEditSelect = this.props.onRecipeEditSelect;

        let filterNameEdit = this.props.filterNameEdit;
        let filterIngredientsEdit = this.props.filterIngredientsEdit;
        let filterTagsEdit = this.props.filterTagsEdit;

        //props for search
        let searchedItem = this.props.searchItem;


        console.log(gridRecipes);
        var recipeCards = [];

        if(searchedItem === "") {
            recipeCards = gridRecipes.map(function (recipe, index) {
                return <div className="recipe-item" key={index}>
                    <RecipeCard recipe={recipe} key={index} index={index} onDelete={onDelete}
                                onRecNameEdit={onRecipeNameEdit}
                                onRecIngredientsEdit={onRecipeIngEdit}
                                onRecTagsEdit={onRecipeTagsEdit}
                                onRecEditSubmit={onRecipeEditSubmit}
                                filterNameEdit={filterNameEdit}
                                filterIngredientsEdit={filterIngredientsEdit}
                                filterTagsEdit={filterTagsEdit}
                                onRecipeEdit={onRecipeEditSelect}
                    />
                </div>
            });
        }

        else {
            console.log(`user searched for ${searchedItem}`);
            recipeCards = gridRecipes.filter(r => r.name.toLowerCase().indexOf(searchedItem) !== -1).map(function (recipe, index) {
                //console.log(recipe);
                return <div className="recipe-item" key={index}>
                    <RecipeCard recipe={recipe} key={index} index={index} onDelete={onDelete}
                                filterNameEdit={filterNameEdit}
                                filterIngredientsEdit={filterIngredientsEdit}
                                filterTagsEdit={filterTagsEdit}
                    />
                </div>

            })
            console.log(recipeCards);
            console.log(Array.isArray(recipeCards));
        }

        return (
            <div className="flex-container">
                {recipeCards}
            </div>
        )
    }

}

let styles = {
  cardContent: {
    title: {
        color: "#4e342e",
        fontFamily:"'Playfair Display SC', serif",
        fontSize: 26,
        marginTop: -10,
        marginBottom: 10
    },

    tags: {
      marginBottom: -5
    },

    deleteButton: {
        marginTop: -5
    }
  },

  cardReveal: {
      header: {
          color: "#212121",
          fontFamily:"'Playfair Display SC', serif",
          padding: 5,
          margin: 10
      },

      title: {

      },

      items: {

          fontFamily: "'Open Sans', sans-serif"
      }

  },

    modal: {

        header: {
            fontFamily: "'Macondo', cursive",
            color: "#00897b"
        }

    }
};

class RecipeCard extends React.Component {
    constructor(props) {
        super(props);
        this.handleRecipeDelete = this.handleRecipeDelete.bind(this);
        this.handleRecipeNameChange = this.handleRecipeNameChange.bind(this);
        this.handleRecipeIngredientsChange = this.handleRecipeIngredientsChange.bind(this);
        this.handleRecipeTagsChange = this.handleRecipeTagsChange.bind(this);
        this.handleEditRecipeSubmit = this.handleEditRecipeSubmit.bind(this);
        this.handleRecipeEditSelection = this.handleRecipeEditSelection.bind(this);

        //test key of index
        //this.cardKey = this.props.index;


    }

    handleRecipeNameChange(e) {
        this.props.onRecNameEdit(e.target.value);

    }

    handleRecipeIngredientsChange(e) {
        this.props.onRecIngredientsEdit(e.target.value);
    }

    handleRecipeTagsChange(e) {
        this.props.onRecTagsEdit(e.target.value);
    }

    handleRecipeEditSelection() {
        console.log(this.props.index);
        this.props.onRecipeEdit(this.props.index);
    }


    handleEditRecipeSubmit(e) {

        console.log(this.props.index);

        let pos = this.props.index;
        //let pos = this.cardKey;
        console.log(`Recipe to be edited = #${pos}`);
        this.props.onRecEditSubmit(pos);

    }

    handleRecipeDelete(e) {
        //console.log(e.target.value);
        console.log(this.props.index);
        //call onDelete event handler with number of recipecard to be deleted
        this.props.onDelete(this.props.index);
    }

    render() {
        var recipeName = this.props.recipe.name;
        var ingredients = this.props.recipe.ingredients.split(",").map(function (ingredient, index) {
            return <a href="#!" className="collection-item" key={index}>{ingredient}</a>
        });
        var tags = this.props.recipe.tags.split(",").map(function (tag, index) {
            return  <div className="chip" key={index}>{tag}<i className="close material-icons">close</i></div>
        });

        return (
            <div>
                <div className="card medium hoverable">
                    <div className="card-image waves-effect waves-block waves-light">
                        <img className="activator" src={this.props.recipe.image}/>
                    </div>
                    <div className="card-content">
                        <span className="card-title activator grey-text text-darken-2" onClick={this.handleRecipeEditSelection}><i className="material-icons brown-text text-darken-4 right">more_vert</i></span>
                        <h1 style={styles.cardContent.title}>{recipeName}</h1>
                        {/*<p><a href="#">This is a link</a></p>*/}
                        <div id="tags" style={styles.cardContent.tags}>
                            {tags}
                        </div>
                        <a className="btn-floating pulse waves-effect waves-light red right" style={styles.cardContent.deleteButton} onClick={this.handleRecipeDelete}><i className="material-icons right">delete</i></a>
                    </div>
                    <div className="card-reveal">
                        <span className="card-title grey-text text-darken-4"><i className="material-icons right">close</i></span>
                        <h4 style={styles.cardReveal.header}>{recipeName}</h4>
                        {/*<h5>Ingredients</h5>*/}
                        <div className="collection">
                            {ingredients}
                        </div>
                        <a className="btn-floating btn-large waves-effect waves-light cyan darken-4 right pulse" href="#modal2"><i className="material-icons right">mode_edit</i></a>
                    </div>
                </div>
                <div id="modal2" className="modal">
                    <div className="modal-content">
                        <h2 style={styles.modal.header}>Edit Recipe</h2>
                        <div className="row">
                            <form className="col s12">
                                <div className="row">
                                    <div className="input-field col s6">
                                        <input id="input_text2" type="text" data-length="10" value={this.props.filterNameEdit} onChange={this.handleRecipeNameChange}/>
                                        <label htmlFor="input_text2">Name</label>
                                    </div>
                                </div>
                                <div className="row">
                                    <div className="input-field col s12">
                                        <textarea id="textarea3" className="materialize-textarea" data-length="120" value={this.props.filterIngredientsEdit} onChange={this.handleRecipeIngredientsChange}></textarea>
                                        <label htmlFor="textarea3">Ingredients (separate with commas)</label>
                                    </div>
                                </div>
                                <div className="row">
                                    <div className="input-field col s12">
                                        <textarea id="textarea4" className="materialize-textarea" data-length="30" value={this.props.filterTagsEdit} onChange={this.handleRecipeTagsChange}></textarea>
                                        <label htmlFor="textarea4">Tags (separate with commas)</label>
                                    </div>
                                </div>
                            </form>
                        </div>
                    </div>
                    <div className="modal-footer">
                        <a href="#!" className="modal-action modal-close waves-effect waves-green btn btn-floating btn-large green darken-4 pulse"><i className="material-icons">done_all</i></a>
                    </div>
                </div>
            </div>
        )
    }
}

ReactDOM.render(<RecipeBoxApp recipes={recipes}/>, document.getElementById('root'));

$(document).ready(function() {
    $('#modal1').modal();
    $('#modal2').modal();
});
              
            
!
999px

Console