<div class="wrapper">
  <div class="container">
    <img class="logo" src="https://ravennainteractive.com/assets/img/ravenna.svg">
    <div class="typeahead-wrapper">
      <div id="typeahead-container">
        <label for="typeahed">Search by movie title or lead actor</label>
        <input id="typeahead-input" name="typeahead" required>
        <div id="typeahead-results" data-selector="results">
        </div>
        <div id="typeahead-selection">
          <p></p>
        </div>
      </div>
    </div>
  </div>
</div>
html, body {
  height: 100%;
  background: black;
  color: white;
  font-family: sans-serif;
}

.wrapper {
  height: 100%;
  padding: 1rem;
}

.container {
  display: flex;
  flex-direction: column;
  height: 100%;
  gap: 1rem;
}

.logo {
  max-width: 100%;
  width: 12rem;
}

.typeahead-wrapper {
  display: flex;
  flex: 1;
  gap: 1rem;
}

#typeahead-container {
  display: flex;
  flex: 1;
  flex-direction: column;
}

#typeahead-input {
  margin-top: .5rem;
  padding: .5rem 1rem;
  box-shadow: 1.5px 2px 0px 1px #5ce8a4;
  border-radius: 5px;
  border: none;
  max-width: 40rem;
}

#typeahead-results {
  opacity: 0;
  max-height: 0;
  margin-top: 1rem;
  transition: max-height ease-in-out .2s;
}

#typeahead-results button {
  display: block;
  width: 100%;
  text-align: left;
  background: #fff;
  border-color: #5ce8a4;
  padding: 1rem;
}

#typeahead-results button:not(:first-child) {
  margin-top: .5rem;
}

#typeahead-results.show {
  opacity: 1;
  max-height: 5rem;
}
function TypeAhead(rootId, handleSelect) {
  const root = document.getElementById(rootId);
  const input = root.querySelector('input');
  const resultsContainer = root.querySelector('[data-selector="results"]');
  const selectionContainer = root.querySelector('#typeahead-selection');
  
  // Placeholder for creating these els if they don't exist

  const search = async () => {
    selectionContainer.innerHTML = '';
    const val = input.value;
    
    if (val.length > 2) {
      const res = await fetch(`https://ravennainteractive.com/codepen/typeahead?query=${val}`);
      
      const data = await res.json();
      
      console.log(data)
      
      if (!res.ok) {
        console.log(data);
        return;
      }
      
      return data;
    }
  }

  const displayResults = (results) => {
    resultsContainer.innerHTML = "";
    
    if (!results || !results.length) {
      selectionContainer.innerHTML = "<p>No results found. Try searching for Tom</p>";
      return;
    } 
    
    const resultsFrag = document.createDocumentFragment();

    results.forEach((result, i) => {
      resultsFrag.appendChild(buildResult({result, index: i}));
    })
    
    resultsContainer.appendChild(resultsFrag);
  }

  const buildResult = ({result, index}) => {
    const button = document.createElement('button');
    button.dataset.index = index;
    button.dataset.value = result.id;
    button.innerHTML = `<div>Title: ${result.title}</div><div>Lead: ${result.starring_actor}</div>`
    button.dataset.identifier = 'result-item';
    
    button.addEventListener('click', e => {
      input.value = result.title;
      handleSelect(e);
      resultsContainer.classList.remove('show');
      selectionContainer.innerHTML = `<p>You picked ${result.title} starring ${result.starring_actor}! You are probably gonna do something really cool with the result.</p>`
    });
    /*
    div.addEventListener('keyup', e => {
      if (e.key === 'Enter') {
        input.value = result.label;
        handleSelect(e);
        resultsContainer.classList.remove('show')
      }
    })
    */

    return button;
  }

  const handleArrowUp = (e) => {
    e.stopPropagation();
    const resultItems = [...resultsContainer.children];

    if (!resultItems.length) return;

    const active = document.activeElement;
    const activeIndex = resultItems.indexOf(active);

    if (activeIndex < 1) {
      input.focus();
    } else {
      resultItems[activeIndex - 1].focus();
    }
  }

  const handleArrowDown = (e) => {
    e.stopPropagation();
    const resultItems = [...resultsContainer.children];

    if (!resultItems.length) return;

    const active = document.activeElement;
    const activeIndex = resultItems.indexOf(active);

    resultItems[Math.min(activeIndex + 1, resultItems.length - 1)].focus();
  }

  root.addEventListener('keyup', e => {
    switch (e.key) {
      case 'ArrowUp':
        handleArrowUp(e)
        break;
      case 'ArrowDown':
        handleArrowDown(e)
        break;
    }
  });
  
  root.addEventListener('click', e => e.stopPropagation())
  
  window.addEventListener('click', e => {
    resultsContainer.classList.remove('show');
  })
  
  input.addEventListener('focus', e => {
    resultsContainer.classList.add('show');
  })
  
  input.addEventListener('input', debounce(async () => {
    const items = await search();
    displayResults(items);
  }))
}

// Standard debounce function
const debounce = (func, delay = 300) => {
  let timer;
  return (...args) => {
    clearTimeout(timer);
    timer = setTimeout(() => {
      func.apply(this, args);
      clearTimeout(timer);
    }, delay)
  }
}

TypeAhead('typeahead-container', (result) => console.log(result))

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

This Pen doesn't use any external JavaScript resources.