DEV Community

Cover image for GridLookout: Building Viewport-Aware Multi-Layer Grid Positioning Of React Components
pronab pal
pronab pal

Posted on

GridLookout: Building Viewport-Aware Multi-Layer Grid Positioning Of React Components

Ever struggled with building complex, interactive grid layouts in React? Does CSS feel too flexible ,making it hard to keep your intent clear? Meet GridLookout—a layout system that brings the structured power of design software layers to React development. With its unique viewport-based approach, GridLookout helps you design layouts to place content where it belongs.

What Makes GridLookout Different?

Most grid systems work with fixed grid sizes and absolute positioning. GridLookout takes a more flexible approach by:

  1. Using viewport-relative measurements for each layer
  2. Supporting relative positioning within cells (using values from 0 to 1)
  3. Enabling named cell addressing for semantic layouts
  4. Allowing independent layer configurations with their own viewports
  5. A cell in GridLookout hosts a ReactJS component enclosed in a fragment so that cell content is not influenced by any css properties outside the content.

Core Concepts

Viewport-Based Layer Architecture

Each layer in GridLookout defines its own viewport and contains cells with relative positioning:

const gridSchema = {
  layers: [
    {
      name: "MainLayer",
      viewport: { width: 600, height: 800 },
      cells: {
        header: {
          startX: 0,    // Starts at 0% of viewport width
          startY: 0,    // Starts at 0% of viewport height
          width: 1,     // Takes 100% of viewport width
          height: 0.1,  // Takes 10% of viewport height
          content: "HeaderComponent"
        },
        sidebar: {
          startX: 0,    // Starts at 0% of viewport width
          startY: 0.1,  // Starts at 10% of viewport height
          width: 0.2,   // Takes 20% of viewport width
          height: 0.9,  // Takes 90% of viewport height
          content: "SidebarComponent"
        }
      }
    }
  ]
};
Enter fullscreen mode Exit fullscreen mode

Semantic Cell Addressing

Instead of using numeric indices, cells are addressed by meaningful names:

// Traditional grid systems
grid[0][0] // Top-left cell

// GridLookout
layer.cells.header    // Header cell
layer.cells.sidebar   // Sidebar cell
layer.cells.main      // Main content cell
Enter fullscreen mode Exit fullscreen mode

The Advantage of Unique Addresses

GridLookout’s approach to assigning unique addresses to each cell provides significant benefits, including:

Precise State Management:

Each cell's unique address allows developers to manage application state at a granular level. This makes it easier to track, update, and control specific areas of the layout in real time.

Dynamic Content Updates:

With unique addresses, updating a cell's content or properties dynamically becomes straightforward, without affecting other parts of the layout.

Improved Debugging:

Debugging and testing individual cells is simplified, as each cell can be identified and isolated using its unique address.

Enhanced Flexibility:

Developers can implement complex features, such as animations, conditional rendering, or interactivity, tied directly to specific cells without ambiguity.

Interoperability with External Systems:

Unique addresses make it easier to integrate with other systems, such as APIs or backend services, that may need to target specific parts of the UI.

Building a Viewport-Aware GridLookout Component

Here's how to implement the viewport-based system in React:

import React from 'react';

const GridLayer = ({ layer }) => {
  const { viewport, cells } = layer;

  return (
    <div 
      className="grid-layer"
      style={{
        position: 'relative',
        width: viewport.width,
        height: viewport.height
      }}
    >
      {Object.entries(cells).map(([cellName, cell]) => (
        <div
          key={cellName}
          className="grid-cell"
          style={{
            position: 'absolute',
            left: `${cell.startX * 100}%`,
            top: `${cell.startY * 100}%`,
            width: `${cell.width * 100}%`,
            height: `${cell.height * 100}%`,
            border: '1px dashed #ccc'
          }}
        >
          {renderContent(cell.content)}
        </div>
      ))}
    </div>
  );
};

const GridLookout = ({ schema }) => {
  return (
    <div className="grid-container">
      {schema.layers.map((layer) => (
        <GridLayer
          key={layer.name}
          layer={layer}
        />
      ))}
    </div>
  );
};

// Component registry for dynamic content rendering
const componentRegistry = {
  HeaderComponent: () => <header>Header Content</header>,
  SidebarComponent: () => <nav>Sidebar Content</nav>,
  MainComponent: () => <main>Main Content</main>
};

const renderContent = (componentName) => {
  const Component = componentRegistry[componentName];
  return Component ? <Component /> : null;
};

export default GridLookout;
Enter fullscreen mode Exit fullscreen mode

Managing Layer State with Viewport Awareness

Here's how to handle viewport-aware state management:

import { useState, useCallback } from 'react';

const useGridState = (initialSchema) => {
  const [schema, setSchema] = useState(initialSchema);

  const updateViewport = useCallback((layerName, newViewport) => {
    setSchema(prev => ({
      ...prev,
      layers: prev.layers.map(layer => 
        layer.name === layerName 
          ? { ...layer, viewport: newViewport }
          : layer
      )
    }));
  }, []);

  const updateCell = useCallback((layerName, cellName, updates) => {
    setSchema(prev => ({
      ...prev,
      layers: prev.layers.map(layer => 
        layer.name === layerName 
          ? {
              ...layer,
              cells: {
                ...layer.cells,
                [cellName]: { ...layer.cells[cellName], ...updates }
              }
            }
          : layer
      )
    }));
  }, []);

  return { schema, updateViewport, updateCell };
};
Enter fullscreen mode Exit fullscreen mode

Example Usage

Here's how to use GridLookout in a real application:

const App = () => {
  const initialSchema = {
    layers: [
      {
        name: "MainLayer",
        viewport: { width: 600, height: 800 },
        cells: {
          header: {
            startX: 0,
            startY: 0,
            width: 1,
            height: 0.1,
            content: "HeaderComponent"
          },
          sidebar: {
            startX: 0,
            startY: 0.1,
            width: 0.2,
            height: 0.9,
            content: "SidebarComponent"
          },
          main: {
            startX: 0.2,
            startY: 0.1,
            width: 0.8,
            height: 0.9,
            content: "MainComponent"
          }
        }
      }
    ]
  };

  const { schema, updateViewport } = useGridState(initialSchema);

  // Handle viewport updates on window resize
  useEffect(() => {
    const handleResize = () => {
      updateViewport("MainLayer", {
        width: window.innerWidth,
        height: window.innerHeight
      });
    };

    window.addEventListener('resize', handleResize);
    return () => window.removeEventListener('resize', handleResize);
  }, [updateViewport]);

  return <GridLookout schema={schema} />;
};
Enter fullscreen mode Exit fullscreen mode

here is the look

Image description

Practical Use Cases

GridLookout's viewport-based approach is perfect for:

  • Responsive dashboards
  • Complex application layouts
  • Multi-pane interfaces
  • Responsive data visualization
  • Split-screen layouts
  • Adaptive content organization

Performance Tips

  1. Memoize cell components based on viewport changes
  2. Use ResizeObserver for efficient viewport updates
  3. Implement virtualization for layers with many cells
  4. Batch viewport-related state updates
  5. Use CSS transforms for animations to avoid layout recalculations

Conclusion

GridLookout's viewport-based approach provides a powerful way to build complex, responsive layouts in React. By combining semantic cell addressing with relative positioning, it makes it easier to create maintainable and flexible grid layouts.

A Note on GridLookoutEditor

Calculating startX, startY, width, and height for all cells in a particular layer can be challenging without the right tools, as these values are interrelated and refer to portions of the viewport. To address this, we have developed GridLookoutEditor, a powerful tool that allows designers to visually adjust grid and cell boundaries for each layer,see the net effect from multiple layers, ensuring accurate sizing and positioning. Stay tuned for my next article, where I will introduce GridLookoutEditor and its code.

The complete source code is available on GitHub: [https://github.com/spicecoder/gridlookout-devdemo]

Have you built something interesting with GridLookout? Share your experience in the comments below!

Top comments (0)