LISN.js

Edit on StackBlitz

import { SortableComponent, SortableItemComponent } from "@lisn.js/react";
import "lisn.js/sortable.css";

import styles from "./demo.module.css";

export default function Page() {
  return (
    <>
      <div className={styles.wrapper}>
        <SortableComponent className={styles.demo} config={{ mode: "swap" }}>
          <SortableItemComponent
            className={[styles.box, styles.r1, styles.c4].join(" ")}
          >
            <span className={styles.letter}>N</span>
          </SortableItemComponent>
          <SortableItemComponent
            className={[styles.box, styles.r2, styles.c2].join(" ")}
          >
            <span className={styles.letter}>I</span>
          </SortableItemComponent>
          <SortableItemComponent
            className={[styles.box, styles.r1, styles.c2].join(" ")}
          >
            <span className={styles.letter}>I</span>
          </SortableItemComponent>
          <SortableItemComponent
            className={[styles.box, styles.r2, styles.c1].join(" ")}
          >
            <span className={styles.letter}>L</span>
          </SortableItemComponent>

          <div>{/* dummy */}</div>

          <SortableItemComponent
            className={[styles.box, styles.r2, styles.c4].join(" ")}
          >
            <span className={styles.letter}>N</span>
          </SortableItemComponent>
          <SortableItemComponent
            className={[styles.box, styles.r1, styles.c1].join(" ")}
          >
            <span className={styles.letter}>L</span>
          </SortableItemComponent>
          <SortableItemComponent
            className={[styles.box, styles.r2, styles.c3].join(" ")}
          >
            <span className={styles.letter}>S</span>
          </SortableItemComponent>
          <SortableItemComponent
            className={[styles.box, styles.r1, styles.c3].join(" ")}
          >
            <span className={styles.letter}>S</span>
          </SortableItemComponent>

          <div className={[styles.line, styles.lineV].join(" ")}></div>
          <div className={[styles.line, styles.lineH].join(" ")}></div>
        </SortableComponent>
      </div>
    </>
  );
}
/* Container */
.wrapper {
  position: fixed;
  top: 0;
  left: 0;
  width: 100vw;
  width: 100dvw;
  height: 100vh;
  height: 100dvh;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  overflow: hidden;
}

/* Grid */
.demo {
  display: grid;
  justify-items: center;
  align-items: center;
  grid-template-rows: repeat(2, 1fr) 0px;
  grid-template-columns: repeat(4, 1fr) 0px;
  background: var(--text-color-lighter);
  box-shadow: var(--text-color) 0px 0px 14px -3px;
}

/* Grid box and content */
.box {
  background: var(--bg-color-lighter);
  width: 75px;
  height: 75px;
  text-align: center;
  color: var(--text-color-lighter);
  font-family: "Roboto Mono", ui-monospace, monospace, sans-serif;
  font-size: 100px;
  line-height: 1.5em;
  position: relative;
  overflow: hidden;
  margin: 0 -1px;
}

.box .letter {
  position: absolute;
  top: 0;
  left: 50%;
  transform: translate(-50%, 0%);
}

.box.r2 .letter {
  transform: translate(-50%, -50%);
}

/* Dummies to create a gap */
.line {
  transition-property: width, height;
  transition-duration: 1.5s;
}

.lineH {
  width: 95px;
}

.lineV {
  height: 95px;
}

/* When complete (ordered) */
.box.r1.c1
  + .box.r1.c2
  + .box.r1.c3
  + .box.r1.c4
  ~ .box.r2.c1
  + .box.r2.c2
  + .box.r2.c3
  + .box.r2.c4
  + .lineV {
  height: 75px;
}

.box.r1.c1
  + .box.r1.c2
  + .box.r1.c3
  + .box.r1.c4
  ~ .box.r2.c1
  + .box.r2.c2
  + .box.r2.c3
  + .box.r2.c4
  ~ .lineH {
  width: 73px;
}

Edit on CodePen

document.addEventListener("DOMContentLoaded", () => {
  const sortable = document.getElementById("demo");
  const items = sortable.querySelectorAll(".box");
  new LISN.widgets.Sortable(sortable, {
    items,
    mode: "swap",
  });
});
<div id="demo">
  <div class="box r1 c4"><span class="letter">N</span></div>
  <div class="box r2 c2"><span class="letter">I</span></div>
  <div class="box r1 c2"><span class="letter">I</span></div>
  <div class="box r2 c1"><span class="letter">L</span></div>

  <div></div>

  <div class="box r2 c4"><span class="letter">N</span></div>
  <div class="box r1 c1"><span class="letter">L</span></div>
  <div class="box r2 c3"><span class="letter">S</span></div>
  <div class="box r1 c3"><span class="letter">S</span></div>

  <div class="line line-v"></div>
  <div class="line line-h"></div>
</div>
/* Grid */
#demo {
  display: grid;
  justify-items: center;
  align-items: center;
  grid-template-rows: repeat(2, 1fr) 0px;
  grid-template-columns: repeat(4, 1fr) 0px;
  background: var(--text-color-lighter);
  box-shadow: var(--text-color) 0px 0px 14px -3px;
}

/* Grid box and content */
#demo .box {
  background: var(--bg-color-lighter);
  width: 75px;
  height: 75px;
  text-align: center;
  color: var(--text-color-lighter);
  font-family: "Roboto Mono", ui-monospace, monospace, sans-serif;
  font-size: 100px;
  line-height: 1.5em;
  position: relative;
  overflow: hidden;
  margin: 0 -1px;
}

#demo .box .letter {
  position: absolute;
  top: 0;
  left: 50%;
  transform: translate(-50%, 0%);
}

#demo .box.r2 .letter {
  transform: translate(-50%, -50%);
}

/* Dummies to create a gap */
#demo .line {
  transition-property: width, height;
  transition-duration: 1.5s;
}

#demo .line-h {
  width: 95px;
}

#demo .line-v {
  height: 95px;
}

/* When complete (ordered) */
#demo
  .box.r1.c1
  + .box.r1.c2
  + .box.r1.c3
  + .box.r1.c4
  ~ .box.r2.c1
  + .box.r2.c2
  + .box.r2.c3
  + .box.r2.c4
  + .line-v {
  height: 75px;
}

#demo
  .box.r1.c1
  + .box.r1.c2
  + .box.r1.c3
  + .box.r1.c4
  ~ .box.r2.c1
  + .box.r2.c2
  + .box.r2.c3
  + .box.r2.c4
  ~ .line-h {
  width: 73px;
}

Edit on CodePen

<div id="demo" data-lisn-sortable="mode=swap">
  <div class="box r1 c4" data-lisn-sortable-item>
    <span class="letter">N</span>
  </div>
  <div class="box r2 c2" data-lisn-sortable-item>
    <span class="letter">I</span>
  </div>
  <div class="box r1 c2" data-lisn-sortable-item>
    <span class="letter">I</span>
  </div>
  <div class="box r2 c1" data-lisn-sortable-item>
    <span class="letter">L</span>
  </div>

  <div></div>

  <div class="box r2 c4" data-lisn-sortable-item>
    <span class="letter">N</span>
  </div>
  <div class="box r1 c1" data-lisn-sortable-item>
    <span class="letter">L</span>
  </div>
  <div class="box r2 c3" data-lisn-sortable-item>
    <span class="letter">S</span>
  </div>
  <div class="box r1 c3" data-lisn-sortable-item>
    <span class="letter">S</span>
  </div>

  <div class="line line-v"></div>
  <div class="line line-h"></div>
</div>
/* Grid */
#demo {
  display: grid;
  justify-items: center;
  align-items: center;
  grid-template-rows: repeat(2, 1fr) 0px;
  grid-template-columns: repeat(4, 1fr) 0px;
  background: var(--text-color-lighter);
  box-shadow: var(--text-color) 0px 0px 14px -3px;
}

/* Grid box and content */
#demo .box {
  background: var(--bg-color-lighter);
  width: 75px;
  height: 75px;
  text-align: center;
  color: var(--text-color-lighter);
  font-family: "Roboto Mono", ui-monospace, monospace, sans-serif;
  font-size: 100px;
  line-height: 1.5em;
  position: relative;
  overflow: hidden;
  margin: 0 -1px;
}

#demo .box .letter {
  position: absolute;
  top: 0;
  left: 50%;
  transform: translate(-50%, 0%);
}

#demo .box.r2 .letter {
  transform: translate(-50%, -50%);
}

/* Dummies to create a gap */
#demo .line {
  transition-property: width, height;
  transition-duration: 1.5s;
}

#demo .line-h {
  width: 95px;
}

#demo .line-v {
  height: 95px;
}

/* When complete (ordered) */
#demo
  .box.r1.c1
  + .box.r1.c2
  + .box.r1.c3
  + .box.r1.c4
  ~ .box.r2.c1
  + .box.r2.c2
  + .box.r2.c3
  + .box.r2.c4
  + .line-v {
  height: 75px;
}

#demo
  .box.r1.c1
  + .box.r1.c2
  + .box.r1.c3
  + .box.r1.c4
  ~ .box.r2.c1
  + .box.r2.c2
  + .box.r2.c3
  + .box.r2.c4
  ~ .line-h {
  width: 73px;
}
N
I
I
L
N
L
S
S