JSFiddle - React, Tailwind, and code Playground

HTML

<!-- The API key below only works in JSFiddle. If you don't have an API key, go to [https://console.cloud.google.com/google/maps-apis/start] and then replace the key parameter below with your API key from your project. -->

<!DOCTYPE html>
<html lang="en">
  <head>
    <title>Commutes and Destinations Map</title>
    <meta charset="utf-8"/>
    <meta name="viewport" content="width=device-width,initial-scale=1">
  </head>
  <body>
    <!-- Defined commutes SVGs -->
    <svg class="hide">
      <defs>
        <symbol id="commutes-initial-icon">
          <path d="M41 20H18.6c-9.5 0-10.8 13.5 0 13.5h14.5C41 33.5 41 45 33 45H17.7" stroke="#D2E3FC" stroke-width="5"></path>
          <path d="M41 22c.2 0 .4 0 .6-.2l.4-.5c.3-1 .7-1.7 1.1-2.5l2-3c.8-1 1.5-2 2-3 .6-1 .9-2.3.9-3.8 0-2-.7-3.6-2-5-1.4-1.3-3-2-5-2s-3.6.7-5 2c-1.3 1.4-2 3-2 5 0 1.4.3 2.6.8 3.6s1.2 2 2 3.2c.9 1 1.6 2 2 2.8.5.9 1 1.7 1.2 2.7l.4.5.6.2Zm0-10.5c-.7 0-1.3-.2-1.8-.7-.5-.5-.7-1.1-.7-1.8s.2-1.3.7-1.8c.5-.5 1.1-.7 1.8-.7s1.3.2 1.8.7c.5.5.7 1.1.7 1.8s-.2 1.3-.7 1.8c-.5.5-1.1.7-1.8.7Z" fill="#185ABC"></path>
          <path d="m12 32-8 6v12h5v-7h6v7h5V38l-8-6Z" fill="#4285F4"></path>
        </symbol>
      </defs>
      <use href="#commutes-initial-icon"/>
    </svg>
    <svg class="hide">
      <defs>
        <symbol id="commutes-add-icon">
          <path d="M0 0h24v24H0V0z" fill="none"/>
          <path d="M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z"/>
        </symbol>
      </defs>
      <use href="#commutes-add-icon"/>
    </svg>
    <svg class="hide">
      <defs>
        <symbol id="commutes-driving-icon">
          <path d="M0 0h24v24H0V0z" fill="none"/>
          <path d="M18.92 6.01C18.72 5.42 18.16 5 17.5 5h-11c-.66 0-1.21.42-1.42 1.01L3 12v8c0 .55.45 1 1 1h1c.55 0 1-.45 1-1v-1h12v1c0 .55.45 1 1 1h1c.55 0 1-.45 1-1v-8l-2.08-5.99zM6.85 7h10.29l1.08 3.11H5.77L6.85 7zM19 17H5v-5h14v5z"/>
          <circle cx="7.5" cy="14.5" r="1.5"/>
          <circle cx="16.5" cy="14.5" r="1.5"/>
        </symbol>
    ...

CSS

html,
body {
  height: 100%;
  margin: 0;
  padding: 0;
}

.commutes {
  align-content: stretch;
  color: #202124;
  display: flex;
  flex-direction: column;
  flex-wrap: wrap;
  font-family: Arial, sans-serif;
  height: 100%;
  min-height: 256px;
  min-width: 360px;
  overflow: auto;
  width: 100%;
}

.commutes-info {
  flex: 0 0 110px;
  max-width: 100%;
  overflow: hidden;
}

.commutes-initial-state {
  border-radius: 8px;
  border: 1px solid #dadce0;
  display: flex;
  height: 98px;
  margin-top: 8px;
  padding: 0 16px;
}

.commutes-initial-state svg {
  align-self: center;
}

.commutes-initial-state .description {
  align-self: center;
  flex-grow: 1;
  padding: 0 16px;
}

.commutes-initial-state .description .heading {
  font: 22px/28px Arial, sans-serif;
  margin: 0;
}

.commutes-initial-state .description p {
  color: #5f6368;
  font: 13px/20px Arial, sans-serif;
  margin: 0;
}

.commutes-initial-state .add-button {
  align-self: center;
  background-color: #4285f4;
  border-color: #4285f4;
  border-radius: 4px;
  border-style: solid;
  color: #fff;
  cursor: pointer;
  display: inline-flex;
  fill: #fff;
  padding: 8px 16px 8px 8px;
  white-space: nowrap;
}

.commutes-initial-state .add-button .label {
  font: normal 600 15px/24px Arial, sans-serif;
  padding-left: 8px;
}

@media (max-width: 535px) {
  .commutes-initial-state svg {
    display: none;
  }

  .commutes-initial-state .description {
    padding-left: 0;
  }

  .commutes-initial-state .description .heading {
    font-weight: bold;
    font-size: 15px;
    line-height: 24px;
  }
}

.commutes-destinations {
  display: none;
  position: relative;
  width: 100%;
}

.commutes-destinations:hover .visible {
  display: block;
}

.commutes-destinations .destinations-container {
  display: flex;
  overflow-x: auto;
  padding: 8px 8px 4px 8px;
  white-space: nowrap;
  width: 100%;
}

.commutes-destinations .destinations-container::-webkit-scrollbar {
  display: none;
}

.commutes-destinations...

JavaScript

'use strict';

/**
 * Element selectors for commutes widget.
 */
const commutesEl = {
  map: document.querySelector('.map-view'),
  initialStatePanel: document.querySelector('.commutes-initial-state'),
  destinationPanel: document.querySelector('.commutes-destinations'),
  modal: document.querySelector('.commutes-modal-container'),
};

/**
 * Element selectors for commutes destination panel.
 */
const destinationPanelEl = {
  addButton: commutesEl.destinationPanel.querySelector('.add-button'),
  container: commutesEl.destinationPanel.querySelector('.destinations-container'),
  list: commutesEl.destinationPanel.querySelector('.destination-list'),
  scrollLeftButton: commutesEl.destinationPanel.querySelector('.left-control'),
  scrollRightButton: commutesEl.destinationPanel.querySelector('.right-control'),
  getActiveDestination: () => commutesEl.destinationPanel.querySelector('.destination.active'),
};

/**
 * Element selectors for commutes modal popup.
 */
const destinationModalEl = {
  title: commutesEl.modal.querySelector('h2'),
  form: commutesEl.modal.querySelector('form'),
  destinationInput: commutesEl.modal.querySelector('input[name="destination-address"]'),
  errorMessage: commutesEl.modal.querySelector('.error-message'),
  addButton: commutesEl.modal.querySelector('.add-destination-button'),
  deleteButton: commutesEl.modal.querySelector('.delete-destination-button'),
  editButton: commutesEl.modal.querySelector('.edit-destination-button'),
  cancelButton: commutesEl.modal.querySelector('.cancel-button'),
  getTravelModeInput: () => commutesEl.modal.querySelector('input[name="travel-mode"]:checked'),
};

/**
 * Max number of destination allowed to be added to commutes panel.
 */
const MAX_NUM_DESTINATIONS = 10;

/**
 * Bounds to bias search within ~50km distance.
 */
const BIAS_BOUND_DISTANCE = 0.5;

/**
 * Hour in seconds.
 */
const HOUR_IN_SECONDS = 3600;

/**
 * Minutes in seconds.
 */
const MIN_IN_SECONDS = 60;

/**
 * Stroke colors for destination...