JSFiddle - React, Tailwind, and code Playground
by lid0
HTML
<div id="app">
<div id="width-wrap">
<header>
<h1>Cocktail DB Browser</h1>
<!-- TODO: replace the select control with this custom multi-select control -->
<label>Pick some ingredients
<div class="ingredient-selector" @change="onIngredientSelected()">
<!-- TODO: this span must show all the selected ingredients -->
<span class="selected-ingredient">
<span v-for="selectedIngredient in selectedIngredients" class="remove" @click="deselectIngredient(selectedIngredient)">{{selectedIngredient}}</span> <!-- TODO: clicking this should deselected the ingredient -->
</span>
<!-- TODO: this input must add ingredients on chnage -->
<input type="text" list="ingredient-list" v-model="ingredientSelectorText">
<datalist id="ingredient-list">
<!-- TODO: add all the ingredients as options -->
<option v-for="ingredient in ingredients">{{ingredient}}</option>
</datalist>
</div>
</label>
<!-- TODO: Delete from here ... -->
<!-- TODO: ... to here -->
</header>
<div id="results">
<!-- TODO: This element should only appear if there are no results -->
<div>There are no drinks that use all of the selected ingredients</div>
<div v-if="isLoadingResults" id="loader" class="lds-ripple active"><div></div><div></div></div>
<div v-for="r in results" class="result" :class="{ expanded: isExpanded(r.id) }" @click="handleResultClick(r.id)">
<img class="photo" :src="r.thumbURL + ( ! isExpanded(r.id) ? '/preview' : '' )">
<div class="info">
<div class="summary">
<div class="prop"><span class="prop-value">{{ r.name }}</span></div>
...
CSS
body {
margin: 0;
font-family: "Dosis", sans-serif;
background: #000;
color: #ee4292;
}
* {
box-sizing: border-box;
transition: all 200ms ease-in-out;
}
#width-wrap {
max-width: 1024px;
min-height: 100vh;
margin-left: auto;
margin-right: auto;
background: #101;
border: 1px solid transparent;
padding-bottom: 100px;
}
header {
margin: 2em 2em;
}
#results {
display: flex;
flex-direction: column;
}
#results > div {
display: flex;
cursor: pointer;
}
#results > div:hover,
#results > div.expanded {
background: #ee429220;
}
#results > div img {
display: block;
width: 100px;
object-fit: cover;
filter: saturate(0.4);
}
#results > div:hover img,
#results > div.expanded img {
filter: saturate(1.1);
}
#results > div.expanded img {
width: 200px;
}
#results > div .info {
flex-grow: 1;
padding-bottom: 30px;
}
#results > div .name {
margin-top: 0.5em;
font-size: 32px;
letter-spacing: 6px;
line-height: 40px;
text-transform: uppercase;
}
#results > div:hover .name,
#results > div.expanded .name {
color: #ffb9d9;
}
#results > div .prop {
margin: 0.5em 30px;
display: flex;
align-items: baseline;
}
#results > div .prop-name {
text-transform: uppercase;
margin-right: 1em;
color:
rgba(255,255,255,0.3);
font-size: 10px;
letter-spacing: 2px;
width: 20%;
}
#results > div .prop-value {
width: 80%;
}
@media screen and ( max-width: 768px ) {
#results > div.expanded { flex-direction: column; }
#results > div.expanded img { width: 100%; height: 200px; }
#results > div .prop { flex-wrap: wrap; }
#results > div .prop-name { width: 100%; }
#results > div .prop-value { width: 100%; }
}
#results > div .ingredients ul {
list-style: none;
margin: 0;
padding-left: 0;
}
#results > div .details {
display: none;
}
#results > div.expanded .details {
display:...
Vue
import {createApp,defineComponent,provide, inject,ref} from "https://unpkg.com/[email protected]/dist/vue.esm-browser.js";
const Cocktails = {
data() {
return {
// TODO: add a property that stores the input text
ingredientSelectorText: "",
selectedIngredients: [], // TODO: make this an array of ingredients
expandedDrinkDetails: null,
_isLoadingDetailsFor: null,
ingredients: [],
isLoadingResults: false,
results: [],
cache: new Map(),
}
},
methods: {
onIngredientSelected: async function(){
if(this.ingredients.includes(this.ingredientSelectorText)){
if(!this.selectedIngredients.includes(this.ingredientSelectorText)){
this.selectedIngredients.push(this.ingredientSelectorText);
this.ingredientSelectorText ="";
}
}
console.log(this.selectedIngredients);
},
deselectIngredient: async function(selectedIngredient){
this.selectedIngredients = this.selectedIngredients.filter((v)=>v!=selectedIngredient)
console.log( this.selectedIngredients)
},
initIngredientList: async function() {
// Pull the ingredients list from the API and add it to the select element
const response = await fetch('https://www.thecocktaildb.com/api/json/v1/1/list.php?i=list');
if ( ! response.ok ) {
console.log(new Error("Something went wrong"));
return;
}
const data = await response.json();
this.ingredients = data.drinks // Grab the drinks array of the returned JSON
// Convert the drinks array to an array of ingredient names (the strIngredient1 property is the ingredient name)
.map( i =>...