<script src="https://cdn.jsdelivr.net/npm/baseline-status@1.0.8/baseline-status.min.js" type="module"></script>
<div class="grid-wrapper" id="grid">
  <div class="grid"></div>
  <div class="actions">
    <div class="button">
      <label for="add">Add new Element</label>
      <button id="add" onclick="add()">+</button>
    </div>
    <div class="button">
      <label for="clear">Clear Grid</label>
      <button id="clear" onclick="clearGrid()">!</button>
    </div>
    <div class="grid-size">
      <input type="number" value="" min="1" placeholder="rows" onchange="changeRows(this)" />
      <input type="number" value="" min="1" placeholder="columns" onchange="changeCols(this)" />
    </div>
  </div>
</div>

<baseline-status featureId="registered-custom-properties"></baseline-status>
<baseline-status featureId="cascade-layers"></baseline-status>
<baseline-status featureId="starting-style"></baseline-status>
<baseline-status featureId="relative-color"></baseline-status>
<baseline-status featureId="logical-properties"></baseline-status>
<baseline-status featureId="container-queries"></baseline-status>
<baseline-status featureId="currentcolor"></baseline-status>
<baseline-status featureId="line-clamp"></baseline-status>
<baseline-status featureId="transition-behavior"></baseline-status>
@layer reset, overrides;

@property --color {
  syntax: "<color>";
  inherits: true;
  initial-value: white;
}

@property --accent-color {
  syntax: "<color>";
  inherits: true;
  initial-value: skyblue;
}

@property --primary-bg-color {
  syntax: "<color>";
  inherits: true;
  initial-value: #141422;
}

@property --grid-cols {
  syntax: "<integer>";
  inherits: false;
  initial-value: 12;
}

@property --grid-rows {
  syntax: "<integer>";
  inherits: false;
  initial-value: 12;
}

@property --grid-gap {
  syntax: "<length>";
  inherits: true;
  initial-value: 4px;
}

@property --row-span {
  syntax: "<integer>";
  inherits: false;
  initial-value: 2;
}

@property --col-span {
  syntax: "<integer>";
  inherits: false;
  initial-value: 2;
}

@layer reset {
  * {
    margin: 0;
    padding: 0;
    box-sizing: border-box;
  }
}

:root {
  font-family: "Roboto", sans-serif;
}

body {
  background-color: var(--primary-bg-color);
  color: var(--color);
  padding-block-end: 5rem;
}

.actions {
  display: flex;
  justify-content: center;
  align-items: center;
  width: fit-content;
  margin-inline: auto;
  gap: 2rem;
}

.button {
  margin-inline: auto;
  margin-block: 2rem;
  width: fit-content;

  button {
    appearance: none;
    background-color: transparent;
    color: var(--color);
    border: solid 2px var(--color);
    padding: 0.25rem 1rem;
    cursor: pointer;
    border-radius: 5px;

    &:hover {
      background-color: lch(from var(--color) calc(l - 25) c h / 25%);
    }

    &:active {
      background-color: lch(from var(--color) calc(l - 5) c h / 34%);
    }
  }
}

.grid {
  --grid-gap: 1.25rem;
  width: 95cqw;
  height: 90cqh;
  padding: 1.5rem;
  border: 3px solid lch(from var(--color) calc(l - 5) c h / 34%);
  border-radius: 15px;
  margin-block-end: 1rem;
  margin-inline: auto;

  display: grid;
  grid-template-columns: repeat(var(--grid-cols), 1fr);
  grid-template-rows: repeat(var(--grid-rows), 1fr);
  gap: var(--grid-gap);

  .grid-element {
    width: 100%;
    height: 100%;
    background-color: lch(from var(--color) calc(l - 5) c h / 10%);
    border-radius: 1rem;
    display: block;
    text-align: left;
    align-content: flex-end;
    padding: 1rem;
    position: relative;
    transition: background 0.5s ease, display 0.5s ease allow-discrete;

    grid-row: span var(--row-span);
    grid-column: span var(--col-span);

    @starting-style {
      background-color: transparent;
    }

    .drag-handle {
      position: absolute;
      right: 0;
      bottom: 0;
      width: 20px;
      height: 20px;
      background-color: var(--accent-color);
      border-radius: 55px;
      cursor: nwse-resize;
    }

    .content {
      overflow: hidden;
      display: -webkit-box;
      -webkit-line-clamp: 1;
      -webkit-box-orient: vertical;
      text-overflow: ellipsis;
    }
  }
}

@layer overrides {
  baseline-status {
    margin-inline: auto;
    padding: 1rem 1rem 0 1rem;
  }
}
function load() {
  for (let i = 0; i < 12; i++) add();
}


function randomSize(min, max) {
  return Math.round(Math.random(max) * (max - min) + min);
}

function add() {
  const grid = document.querySelector("#grid .grid");
  const div = document.createElement("div");
  div.classList.add("grid-element");
  div.classList.add("resizable");
  div.style.setProperty("--col-span", randomSize(1, 4));
  div.style.setProperty("--row-span", randomSize(1, 4));

  const handle = document.createElement("div");
  handle.classList.add("drag-handle");

  const content = document.createElement("span");
  content.classList.add("content");
  content.textContent = (grid.childNodes.length + 1) + " - Lorem Ipsum";

  addResizeCallback(handle, div, grid);

  div.append(content, handle);

  grid.appendChild(div);
}

function clearGrid() {
  const grid = document.querySelector("#grid .grid");
  [...grid.childNodes].forEach((x) => x.remove());
}

function changeRows(e) {
  const grid = document.querySelector("#grid .grid");
  grid.style.setProperty("--grid-rows", e.value);
}

function changeCols(e) {
  const grid = document.querySelector("#grid .grid");
  grid.style.setProperty("--grid-cols", e.value);
}

function addResizeCallback(dragHandle, resizableItem, gridContainer) {
  let startX, startY, startWidth, startHeight;
  const columnNum = 0;
  const colNum = gridContainer.style.getPropertyValue("--grid-cols");
  const rowNum = gridContainer.style.getPropertyValue("--grid-rows");
  const gridWidth = gridContainer.getBoundingClientRect();
  const columnWidth = gridWidth.width / (colNum.length ? colNum : 12); // Assuming 12 columns
  const rowHeight = gridWidth.height / (rowNum.length ? rowNum : 12); // Assuming 12 Rows

  dragHandle.addEventListener("mousedown", (e) => {
    e.preventDefault();
    startX = e.clientX;
    startY = e.clientY;
    const itemRect = resizableItem.getBoundingClientRect();
    const styles = window.getComputedStyle(resizableItem);
    startWidth = Math.round(itemRect.width / columnWidth);
    startHeight = Math.round(itemRect.height / rowHeight);
    document.addEventListener("mousemove", handleMouseMove);
    document.addEventListener("mouseup", handleMouseUp);
  });

  function handleMouseMove(e) {
    const deltaX = e.clientX - startX;
    const deltaY = e.clientY - startY;
    const newSpanX = Math.max(
      1,
      Math.round((startWidth * columnWidth + deltaX) / columnWidth)
    );
    const newSpanY = Math.max(
      1,
      Math.round((startHeight * rowHeight + deltaY) / rowHeight)
    );
    resizeElement(dragHandle.parentElement, newSpanX, newSpanY);
  }

  function handleMouseUp() {
    document.removeEventListener("mousemove", handleMouseMove);
    document.removeEventListener("mouseup", handleMouseUp);
  }
}

function resizeElement(elem, newSpanX, newSpanY) {
    elem.style.setProperty("--col-span", newSpanX);
    elem.style.setProperty("--row-span", newSpanY);
}

load();

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

This Pen doesn't use any external JavaScript resources.