background-shapes

PreviousNext
Docs API Reference
uicapsuleblock

Preview

Loading preview…
/background-shapes.tsx
import React, { useCallback, useEffect, useState } from "react";

// Cell shape functions that return JSX instead of SVG strings
const Cell1 = ({ colors }: { colors: string[]; strokeWidth: number }) => (
  <circle cx="50" cy="50" r="9.44" fill={colors[0]} fillRule="evenodd" />
);

const Cell2 = ({
  colors,
  strokeWidth,
}: {
  colors: string[];
  strokeWidth: number;
}) => (
  <>
    <line
      x1="25"
      x2="75"
      y1="25"
      y2="25"
      stroke={colors[0]}
      strokeWidth={strokeWidth}
    />
    <line
      x1="25"
      x2="75"
      y1="50"
      y2="50"
      stroke={colors[0]}
      strokeWidth={strokeWidth}
    />
    <line
      x1="25"
      x2="75"
      y1="75"
      y2="75"
      stroke={colors[0]}
      strokeWidth={strokeWidth}
    />
  </>
);

const Cell3 = ({
  colors,
  strokeWidth,
}: {
  colors: string[];
  strokeWidth: number;
}) => (
  <>
    <line
      x1="25"
      x2="75"
      y1="25"
      y2="75"
      stroke={colors[0]}
      strokeWidth={strokeWidth}
    />
    <line
      x1="25"
      x2="75"
      y1="75"
      y2="25"
      stroke={colors[0]}
      strokeWidth={strokeWidth}
    />
  </>
);

const Cell4 = ({
  colors,
  strokeWidth,
}: {
  colors: string[];
  strokeWidth: number;
}) => (
  <rect
    width="50"
    height="50"
    x="25"
    y="25"
    fill="none"
    stroke={colors[0]}
    strokeWidth={strokeWidth}
  />
);

const Cell5 = ({
  colors,
  strokeWidth,
}: {
  colors: string[];
  strokeWidth: number;
}) => (
  <line
    x1="25"
    x2="75"
    y1="75"
    y2="25"
    fill="none"
    stroke={colors[0]}
    strokeWidth={strokeWidth}
  />
);

const Cell6 = () => null;

const Cell7 = () => (
  <rect width="75" height="75" x="12.5" y="12.5" fill="rgba(255,255,255,0.1)" />
);

// Simple seeded random number generator
const seedPRNG = (seed: number) => {
  // Simple implementation - in production you might want a more robust seeded RNG
  // This is a basic seeded RNG implementation
  let seedValue = seed;
  return () => {
    seedValue = (seedValue * 9301 + 49297) % 233280;
    return seedValue / 233280;
  };
};

interface ShapeConfig {
  shape: ({
    colors,
    strokeWidth,
  }: {
    colors: string[];
    strokeWidth: number;
  }) => React.ReactElement | null;
  weight: number;
}

const shapesConfig: ShapeConfig[] = [
  { shape: Cell1, weight: 1 },
  { shape: Cell2, weight: 1 },
  { shape: Cell3, weight: 1 },
  { shape: Cell4, weight: 1 },
  { shape: Cell5, weight: 1 },
  { shape: Cell6, weight: 5 },
  { shape: Cell7, weight: 3 },
];

// Create weighted selector
const createWeightedSelector = (
  items: ShapeConfig[],
  seededRandom: () => number,
) => {
  const weightedArray: ShapeConfig[] = [];

  for (const item of items) {
    for (let i = 0; i < item.weight; i++) {
      weightedArray.push(item);
    }
  }

  return () => weightedArray[Math.floor(seededRandom() * weightedArray.length)];
};

// Individual shape component that manages its own interval
interface ShapeProps {
  x: number;
  y: number;
  colors: string[];
  strokeWidth: number;
  scale: number;
  shapeId: string;
  minInterval?: number;
  maxInterval?: number;
}

const Shape = ({
  x,
  y,
  colors,
  strokeWidth,
  scale,
  shapeId,
  minInterval = 0,
  maxInterval = 5000,
}: ShapeProps) => {
  const [currentShape, setCurrentShape] = useState<ShapeConfig>(() => {
    // Initialize with a random shape
    const seededRandom = seedPRNG(Math.random() * 1000);
    const pickShape = createWeightedSelector(shapesConfig, seededRandom);
    return pickShape();
  });

  useEffect(() => {
    const getRandomInterval = () =>
      Math.random() * (maxInterval - minInterval) + minInterval;

    const updateShape = () => {
      const seededRandom = seedPRNG(Math.random() * 1000);
      const pickShape = createWeightedSelector(shapesConfig, seededRandom);
      setCurrentShape(pickShape());
    };

    // Set initial random interval
    let timeoutId = setTimeout(() => {
      updateShape();

      // Set up recurring random intervals
      const setNextTimeout = () => {
        timeoutId = setTimeout(() => {
          updateShape();
          setNextTimeout();
        }, getRandomInterval());
      };

      setNextTimeout();
    }, getRandomInterval());

    return () => clearTimeout(timeoutId);
  }, [minInterval, maxInterval]);

  const ShapeComponent = currentShape.shape;

  return (
    <g transform={`translate(${x} ${y})`}>
      <g transform={`scale(${scale})`}>
        <ShapeComponent colors={colors} strokeWidth={strokeWidth} />
      </g>
    </g>
  );
};

interface BackgroundShapesProps {
  width?: number;
  height?: number;
  cellSize?: number;
  strokeWidth?: number;
  colors?: string[];
  className?: string;
  minInterval?: number;
  maxInterval?: number;
}

export const BackgroundShapes = ({
  width = 500,
  height = 500,
  cellSize = 20,
  strokeWidth = 10,
  colors = ["white"],
  className = "",
  minInterval = 1000,
  maxInterval = 5000,
}: BackgroundShapesProps) => {
  const [shapes, setShapes] = useState<React.ReactNode[]>([]);
  const borderSize = cellSize * 2;
  const scale = 0.2;

  useEffect(() => {
    const newShapes: React.ReactNode[] = [];

    // Generate shapes for each cell
    for (let x = borderSize; x < width / 2; x += cellSize) {
      for (let y = borderSize; y < height - borderSize; y += cellSize) {
        // Left side
        newShapes.push(
          <Shape
            key={`left-${x}-${y}`}
            x={x}
            y={y}
            colors={colors}
            strokeWidth={strokeWidth}
            scale={scale}
            shapeId={`left-${x}-${y}`}
            minInterval={minInterval}
            maxInterval={maxInterval}
          />,
        );

        // Right side (mirrored)
        newShapes.push(
          <Shape
            key={`right-${x}-${y}`}
            x={width - cellSize - x}
            y={y}
            colors={colors}
            strokeWidth={strokeWidth}
            scale={scale}
            shapeId={`right-${x}-${y}`}
            minInterval={minInterval}
            maxInterval={maxInterval}
          />,
        );
      }
    }

    setShapes(newShapes);
  }, [
    width,
    height,
    cellSize,
    strokeWidth,
    colors,
    borderSize,
    scale,
    minInterval,
    maxInterval,
  ]);

  return (
    <svg
      width={width}
      height={height}
      viewBox={`0 0 ${width} ${height}`}
      className={className}
    >
      {shapes}
    </svg>
  );
};

Installation

npx shadcn@latest add @uicapsule/background-shapes

Usage

import { BackgroundShapes } from "@/components/background-shapes"
<BackgroundShapes />