DEV Community

Biniam kiros
Biniam kiros

Posted on

ሰንጠረዥ (Sentereige): A Game-Changer for Staggered Grids in React with Next.js Tutorial

Struggling to implement a responsive, performant staggered grid (Masonry layout) in React? I’ve been there, scouring libraries for a solution that supports dynamic content, drag-and-drop, and seamless responsiveness. Most options fell short—until So I made Sentereige. This React component library simplifies creating interactive layouts like staggered grids, lists, and Kanban boards. In this tutorial, I’ll guide you through using Sentereige in a Next.js application to build a Kanban board with a staggered grid layout, complete with drag-and-drop functionality and toast notifications.

Why Sentereige?

Sentereige is a versatile, open-source library that tackles complex layout challenges. Its standout features include:

  • Staggered Grid (Masonry Layout): Automatically arranges items left-to-right in the shortest column, handling dynamic sizes.
  • Drag-and-Drop Sorting: Reorder items within a grid/list or across multiple grids with shared groupIds.
  • Responsive Design: Adapts to any screen size for a polished user experience.
  • Virtual Scrolling: Optimizes performance for large datasets.
  • Customizable Drag Handles: Define specific drag areas for intuitive interactions.
  • Nested Structures: Supports complex layouts with nested grids and lists.

Below are placeholder demo GIFs showcasing Sentereige’s capabilities (replace with actual GIFs from the Sentereige GitHub repository):

  • Grid Mode
    Grid Mode

  • List Mode
    List Mode

  • Kanban Mode
    Kanban Mode

These demos highlight Sentereige’s flexibility. Check the GitHub repo for real examples and detailed documentation.

Tutorial: Building a Kanban Board with Staggered Grid in Next.js

Let’s build a Kanban board with draggable columns (in grid mode) and cards (in list mode) using Sentereige in a Next.js app. The example includes a toast notification system to display interactions like item clicks and drags. Below, I’ll break down the code and explain each step.

Prerequisites

  • Node.js and npm/yarn installed.
  • A Next.js project (create one with npx create-next-app@latest).
  • Basic knowledge of React and Next.js.

Step 1: Install Sentereige

Install Sentereige in your Next.js project:

npm install sentereige
Enter fullscreen mode Exit fullscreen mode

Step 2: Set Up the Toast Notification Utility

To provide feedback when users interact with the Kanban board (e.g., dragging or clicking items), we’ll create a showToast function. This function dynamically creates a styled toast notification that slides in and fades out after 3 seconds.

Create a file at src/utils/toast.ts:

"use client";

export const showToast = (content: string): void => {
  // Remove any existing toast
  const existingToast = document.querySelector(".toast");
  if (existingToast) {
    existingToast.remove();
  }

  const toast = document.createElement("div");
  toast.className = "toast";
  toast.style.position = "fixed";
  toast.style.top = "10px";
  toast.style.right = "10px";
  toast.style.backgroundColor = "#1f2937";
  toast.style.color = "#ffffff";
  toast.style.paddingLeft = "1rem";
  toast.style.paddingRight = "1rem";
  toast.style.paddingTop = "0.5rem";
  toast.style.paddingBottom = "0.5rem";
  toast.style.borderRadius = "0.5rem";
  toast.style.boxShadow =
    "0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05)";
  toast.style.display = "flex";
  toast.style.alignItems = "center";
  toast.style.gap = "0.5rem";
  toast.style.maxWidth = "20rem";
  toast.style.zIndex = "50";
  toast.style.animation = "slide-in 0.3s ease-out";
  toast.textContent = content;

  // Add CSS animation keyframes
  const style = document.createElement("style");
  style.textContent = `
    @keyframes slide-in {
      from { transform: translateX(100%); opacity: 0; }
      to { transform: translateX(0); opacity: 1; }
    }
  `;
  document.head.appendChild(style);

  document.body.appendChild(toast);

  // Auto-remove after 3 seconds with fade-out
  setTimeout(() => {
    toast.style.transition = "opacity 0.3s ease-out";
    toast.style.opacity = "0";
    setTimeout(() => toast.remove(), 300);
  }, 3000);
};
Enter fullscreen mode Exit fullscreen mode

This function uses Tailwind-inspired CSS for styling and adds a slide-in animation. It’s marked with "use client" since it manipulates the DOM, which is necessary for Next.js client-side components.

Step 3: Create the Kanban Board Component

Now, let’s implement the main component in your Next.js page (e.g., src/app/page.tsx). This component creates a Kanban board with three columns (“To Do,” “In Progress,” “Done”), each containing draggable cards. The columns are arranged in a staggered grid using Sentereige’s grid mode, while the cards within each column use list mode.

Replace the contents of src/app/page.tsx with the following:

"use client";
import Image from "next/image";
import { Sentereige } from "sentereige";
import { showToast } from "../utils/toast";

export default function Home() {
  // Define initial data for Kanban columns
  const initialColumns = [
    {
      id: "col1",
      title: "To Do",
      cards: Array.from({ length: 10 }, (_, i) => ({
        id: `todo-${i + 1}`,
        content: `Task ${i + 1}`,
        color: `hsl(${Math.floor(Math.random() * 360)}, 65%, 60%)`,
      })),
    },
    {
      id: "col2",
      title: "In Progress",
      cards: Array.from({ length: 5 }, (_, i) => ({
        id: `progress-${i + 1}`,
        content: `Task ${i + 1}`,
        color: `hsl(${Math.floor(Math.random() * 360)}, 70%, 65%)`,
      })),
    },
    {
      id: "col3",
      title: "Done",
      cards: Array.from({ length: 3 }, (_, i) => ({
        id: `done-${i + 1}`,
        content: `Task ${i + 1}`,
        color: `hsl(${Math.floor(Math.random() * 360)}, 70%, 65%)`,
      })),
    },
  ];

  return (
    <div className="grid grid-rows-[20px_1fr_20px] items-center justify-items-center min-h-screen p-8 pb-20 gap-16 sm:p-20 font-[family-name:var(--font-geist-sans)]">
      <main className="flex flex-col gap-[32px] row-start-2 items-center sm:items-start">
        {/* Next.js logo */}
        <Image
          className="dark:invert"
          src="/next.svg"
          alt="Next.js logo"
          width={180}
          height={38}
          priority
        />
        {/* Outer Sentereige for column grid */}
        <Sentereige
          mode="grid"
          dragHandleSelector=".drag-handle"
          isSortable
          options={{ gutter: 16, scrollSpeed: 10, scrollThreshold: 100 }}
          style={{ minHeight: "60vh", width: "1200px" }}
        >
          {initialColumns.map((column) => (
            <div
              key={column.id}
              style={{
                width: "300px",
              }}
            >
              {/* Column header with drag handle */}
              <h2
                className="drag-handle"
                style={{
                  border: "8px solid rgba(0, 0, 0, 0.1)",
                  borderRadius: "8px",
                  background: "#4a90e2",
                  color: "#ffffff",
                  fontSize: "1.1rem",
                  fontWeight: "500",
                  padding: "12px",
                  margin: 0,
                  textAlign: "center",
                  borderBottom: "1px solid #d0d0d0",
                  cursor: "grab",
                }}
              >
                {column.title}
              </h2>
              {/* Inner Sentereige for card list */}
              <Sentereige
                id={column.id}
                groupId="g"
                mode="list"
                isSortable
                onMovedEvent={(
                  key,
                  fromGroupId,
                  fromPosition,
                  toGroupId,
                  toPosition
                ) => {
                  showToast(
                    `Item moved: ${key} from group ${fromGroupId} index ${fromPosition} to group ${toGroupId} index ${toPosition}`
                  );
                }}
                onItemClick={(key: string) => showToast(`Item clicked! ${key}`)}
              >
                {column.cards.map((card) => (
                  <div
                    key={card.id}
                    style={{
                      border: "8px solid rgba(0, 0, 0, 0.1)",
                      borderRadius: "8px",
                      cursor: "pointer",
                      gap: "6px",
                      padding: "16px",
                      background: card.color,
                      display: "flex",
                      flexDirection: "column",
                      justifyContent: "center",
                      alignItems: "center",
                      overflow: "hidden",
                      transition: "background 0.2s ease",
                      margin: "4px",
                      userSelect: "none",
                    }}
                  >
                    <h1
                      style={{
                        margin: 0,
                        fontSize: "1rem",
                        fontWeight: "500",
                        color: "#ffffff",
                        textAlign: "center",
                        lineHeight: "1.3",
                      }}
                    >
                      {card.id}
                    </h1>
                  </div>
                ))}
              </Sentereige>
            </div>
          ))}
        </Sentereige>
      </main>
      <footer className="row-start-3 flex gap-[24px] flex-wrap items-center justify-center">
        <a
          className="flex items-center gap-2 hover:underline hover:underline-offset-4"
          href="https://nextjs.org/learn?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
          target="_blank"
          rel="noopener noreferrer"
        >
          <Image
            aria-hidden
            src="/file.svg"
            alt="File icon"
            width={16}
            height={16}
          />
          Learn
        </a>
        <a
          className="flex items-center gap-2 hover:underline hover:underline-offset-4"
          href="https://vercel.com/templates?framework=next.js&utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
          target="_blank"
          rel="noopener noreferrer"
        >
          <Image
            aria-hidden
            src="/window.svg"
            alt="Window icon"
            width={16}
            height={16}
          />
          Examples
        </a>
        <a
          className="flex items-center gap-2 hover:underline hover:underline-offset-4"
          href="https://nextjs.org?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
          target="_blank"
          rel="noopener noreferrer"
        >
          <Image
            aria-hidden
            src="/globe.svg"
            alt="Globe icon"
            width={16}
            height={16}
          />
          Go to nextjs.org 
        </a>
      </footer>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

Step 4: Understanding the Code

Let’s break down the key parts of the page.tsx code:

  1. Data Structure:

    • The initialColumns array defines three columns: “To Do” (10 cards), “In Progress” (5 cards), and “Done” (3 cards).
    • Each card has an id, content, and a random color for visual distinction.
  2. Outer Sentereige (Grid Mode):

    • The outer <Sentereige> component is set to mode="grid" to create a staggered grid of columns.
    • dragHandleSelector=".drag-handle" ensures only the column headers (with class drag-handle) are draggable.
    • isSortable enables drag-and-drop for columns.
    • options={{ gutter: 16, scrollSpeed: 10, scrollThreshold: 100 }} configures the grid’s spacing and scrolling behavior.
    • style={{ minHeight: "60vh", width: "1200px" }} sets the grid’s dimensions.
  3. Inner Sentereige (List Mode):

    • Each column contains an inner <Sentereige> component set to mode="list" for vertical card arrangement.
    • id={column.id} and groupId="g" allow cards to be dragged across columns within the same group.
    • onMovedEvent triggers a toast notification when a card is moved, showing details like the item’s key and position.
    • onItemClick triggers a toast when a card is clicked.
  4. Styling:

    • Columns and cards use inline styles for borders, padding, and colors, with Tailwind-inspired classes for the layout.
    • The drag-handle class on the column header (<h2>) makes it draggable, with a cursor: grab for user feedback.

Step 5: Run the Application

  1. Run your Next.js app:
npm run dev
Enter fullscreen mode Exit fullscreen mode
  1. Open http://localhost:3000 in your browser. You’ll see a Kanban board with three columns, where you can:
    • Drag columns to reorder them (via the header).
    • Drag cards within a column or across columns.
    • Click cards to see toast notifications.

Result

Step 6: Customize and Extend

  • Add Real Data: Replace initialColumns with data from an API or state management (e.g., Redux, Zustand).
  • Enhance Styling: Use Tailwind CSS or a CSS-in-JS library like Emotion for more complex styles.
  • Add Features: Implement card creation/deletion or persist the board state to a backend.

Conclusion

Sentereige solved my struggles with staggered grids in React, offering a flexible, performant solution for layouts like this Kanban board. Its drag-and-drop, responsive design, and nested structure support make it a go-to for complex UIs. Try it out in your Next.js project, and explore the Sentereige GitHub repo for more examples and documentation.

If you’ve been wrestling with layout challenges, Sentereige could be your game-changer. Let me know in the comments how you’re using it or if you have questions!

Tags: react, nextjs, ui, layout, opensource

Top comments (0)