JSFiddle - React, Tailwind, and code Playground

by mehulkar

HTML

<div class="page">
  <div class="shelf-grid-body">
    <button id="previous" class="shelf-grid-nav">&lt;</button>
    <ul id="grid" class="shelf-grid-list">
      <li><img src="//placecage.com/64/36" width="64" height="36"></li>
      <li><img src="//placecage.com/64/36" width="64" height="36"></li>
      <li><img src="//placecage.com/64/36" width="64" height="36"></li>
      <li><img src="//placecage.com/64/36" width="64" height="36"></li>
      <li><img src="//placecage.com/64/36" width="64" height="36"></li>
      <li><img src="//placecage.com/64/36" width="64" height="36"></li>
      <li><img src="//placecage.com/64/36" width="64" height="36"></li>
      <li><img src="//placecage.com/64/36" width="64" height="36"></li>
      <li><img src="//placecage.com/64/36" width="64" height="36"></li>
    </ul>
    <button id="next" class="shelf-grid-nav">&gt;</button>
  </div>

<p><code>overflow:hidden</code> on <code>.shelf-grid-list</code> causes the scrolled items to not be visible. If we remove the overflow-hidden, then the auto-columns don't get created with the right size.</p>
</div>

CSS

body { margin: 0; padding: 0; }
h2 { margin: 0; }
* { box-sizing: border-box; }
.page { width: 90vw; margin: 10px auto 0; }

:root {
  --item-gap: 20px;
  --items-per-page: 3;
  --paddle-nav-width: 20px;
  
  /**
    offset value is the grid gap, times the number of times
    the grid-gap appears on the page, and then divided by the
    number of iems on the page.
  */
  --offset: calc(var(--item-gap) * (var(--items-per-page) - 1) / var(--items-per-page));
}

.shelf-grid-body {
  display: flex;
  width: 100%;
  overflow: hidden;
}

.shelf-grid-list {
  padding: 0;
  margin: 0;
  flex-grow: 1;
  display: grid;
  grid-template-rows: max-content;
  grid-auto-columns: calc(100% / var(--items-per-page) - var(--offset));
  grid-auto-flow: column;
  grid-column-gap: var(--item-gap);
  transition-duration: 0.2s;
  transition-timing-function: ease-out;
  overflow: hidden;
}

li {
  list-style: none;
  background: bisque;
  text-align: center;
  border: solid 1px #ccc;
}

.shelf-grid-nav {
  background: none;
  flex-shrink: 0;
  flex-basis: var(--paddle-nav-width);
  padding: 0;
  cursor: pointer;
  border: solid 1px #ccc;
  z-index: 1;
  background-color: #fff;
}

.shelf-grid-nav:hover { background: #eee; }
.shelf-grid-nav:active { background: #ddd; }

JavaScript

let offset = 0;
let offsetValue = 400;

const prevFn = element => {
	offset = offset - offsetValue;
  let newValue;
  if (offset >= 0) {
  	newValue = `translateX(-${offset}px);`;
	} else {
  	newValue = 0;
  }
  element.setAttribute('style', `transform: ${newValue};`);
};

const nextFn = element => {
	offset = offset + offsetValue;
  const newValue = `translateX(-${offset}px);`;
  element.setAttribute('style', `transform: ${newValue};`);
};

const grid = document.querySelector('#grid');
document.querySelector('#previous')
	.addEventListener('click', prevFn.bind(null, grid));
document.querySelector('#next')
	.addEventListener('click', nextFn.bind(null, grid));