{"$schema":"https://ui.shadcn.com/schema/registry-item.json","author":"Eduardo Calvo <educlopez93@gmail.com>","css":{},"dependencies":["motion"],"description":"3x3 grid-based loading animation with preset patterns","devDependencies":[],"files":[{"content":"\"use client\";\n\nimport { cn } from \"@/lib/utils\";\nimport { motion, useReducedMotion } from \"motion/react\";\nimport { useEffect, useMemo, useState } from \"react\";\n\n// Grid matrix type: 3x3 array of 0s and 1s\nexport type GridMatrix = [\n  [0 | 1, 0 | 1, 0 | 1],\n  [0 | 1, 0 | 1, 0 | 1],\n  [0 | 1, 0 | 1, 0 | 1],\n];\n\n// All preset pattern names\nexport type PresetPattern =\n  // Solo (single cell)\n  | \"solo-center\"\n  | \"solo-tl\"\n  | \"solo-tr\"\n  | \"solo-bl\"\n  | \"solo-br\"\n  // Horizontal lines\n  | \"line-h-top\"\n  | \"line-h-mid\"\n  | \"line-h-bot\"\n  // Vertical lines\n  | \"line-v-left\"\n  | \"line-v-mid\"\n  | \"line-v-right\"\n  // Diagonals\n  | \"line-diag-1\"\n  | \"line-diag-2\"\n  // Corners\n  | \"corners-only\"\n  | \"corners-sync\"\n  | \"corners\"\n  // Plus\n  | \"plus-hollow\"\n  | \"plus-full\"\n  // L-shapes\n  | \"L-tl\"\n  | \"L-tr\"\n  | \"L-bl\"\n  | \"L-br\"\n  // T-shapes\n  | \"T-top\"\n  | \"T-bot\"\n  | \"T-left\"\n  | \"T-right\"\n  // Duo\n  | \"duo-h\"\n  | \"duo-v\"\n  | \"duo-diag\"\n  // Frame\n  | \"frame\"\n  | \"frame-sync\"\n  // Sparse\n  | \"sparse-1\"\n  | \"sparse-2\"\n  | \"sparse-3\"\n  // Waves\n  | \"wave-lr\"\n  | \"wave-rl\"\n  | \"wave-tb\"\n  | \"wave-bt\"\n  // Diagonal quadrants\n  | \"diagonal-tl\"\n  | \"diagonal-tr\"\n  | \"diagonal-bl\"\n  | \"diagonal-br\"\n  // Ripple\n  | \"ripple-out\"\n  | \"ripple-in\"\n  // Shapes\n  | \"cross\"\n  | \"x-shape\"\n  | \"diamond\"\n  // Stripes\n  | \"stripes-h\"\n  | \"stripes-v\"\n  // Patterns\n  | \"checkerboard\"\n  | \"rows-alt\"\n  // Spirals\n  | \"spiral-cw\"\n  | \"spiral-ccw\"\n  // Snake\n  | \"snake\"\n  | \"snake-rev\"\n  // Rain\n  | \"rain\"\n  | \"rain-rev\"\n  // Effects\n  | \"waterfall\"\n  | \"breathing\"\n  | \"heartbeat\"\n  | \"twinkle\"\n  | \"sparkle\"\n  | \"chaos\"\n  // Edge\n  | \"edge-cw\"\n  | \"border\";\n\nexport interface GridLoaderProps {\n  /** Blur amount in pixels — creates a soft glow effect */\n  blur?: number;\n  /** Additional CSS classes */\n  className?: string;\n  /** Color preset or custom CSS color */\n  color?: \"white\" | \"red\" | \"blue\" | \"green\" | \"amber\" | string;\n  /** Gap between cells in pixels */\n  gap?: number;\n  /** Animation mode */\n  mode?: \"pulse\" | \"sequence\" | \"stagger\";\n  /** Pattern to display - preset name or custom 3x3 matrix */\n  pattern?: PresetPattern | GridMatrix;\n  /** Use rounded (circular) cells instead of square */\n  rounded?: boolean;\n  /** Array of patterns for sequence mode */\n  sequence?: Array<PresetPattern | GridMatrix>;\n  /** Size preset or pixel value */\n  size?: \"sm\" | \"md\" | \"lg\" | \"xl\" | number;\n  /** Animation speed */\n  speed?: \"slow\" | \"normal\" | \"fast\";\n  /** Disable animation and show static pattern */\n  static?: boolean;\n}\n\n// Preset patterns as 3x3 matrices\nconst PATTERNS: Record<PresetPattern, GridMatrix> = {\n  border: [\n    [1, 1, 1],\n    [1, 0, 1],\n    [1, 1, 1],\n  ],\n  breathing: [\n    [0, 0, 0],\n    [0, 1, 0],\n    [0, 0, 0],\n  ],\n  chaos: [\n    [1, 0, 1],\n    [0, 1, 0],\n    [1, 0, 0],\n  ],\n\n  // Patterns\n  checkerboard: [\n    [1, 0, 1],\n    [0, 1, 0],\n    [1, 0, 1],\n  ],\n  corners: [\n    [1, 0, 1],\n    [0, 0, 0],\n    [1, 0, 1],\n  ],\n\n  // Corners\n  \"corners-only\": [\n    [1, 0, 1],\n    [0, 0, 0],\n    [1, 0, 1],\n  ],\n  \"corners-sync\": [\n    [1, 0, 1],\n    [0, 0, 0],\n    [1, 0, 1],\n  ],\n\n  // Shapes\n  cross: [\n    [0, 1, 0],\n    [1, 1, 1],\n    [0, 1, 0],\n  ],\n  \"diagonal-bl\": [\n    [0, 0, 0],\n    [1, 1, 0],\n    [1, 1, 0],\n  ],\n  \"diagonal-br\": [\n    [0, 0, 0],\n    [0, 1, 1],\n    [0, 1, 1],\n  ],\n\n  // Diagonal quadrants\n  \"diagonal-tl\": [\n    [1, 1, 0],\n    [1, 1, 0],\n    [0, 0, 0],\n  ],\n  \"diagonal-tr\": [\n    [0, 1, 1],\n    [0, 1, 1],\n    [0, 0, 0],\n  ],\n  diamond: [\n    [0, 1, 0],\n    [1, 0, 1],\n    [0, 1, 0],\n  ],\n  \"duo-diag\": [\n    [1, 0, 0],\n    [0, 1, 0],\n    [0, 0, 0],\n  ],\n\n  // Duo (two cells)\n  \"duo-h\": [\n    [0, 0, 0],\n    [1, 1, 0],\n    [0, 0, 0],\n  ],\n  \"duo-v\": [\n    [0, 1, 0],\n    [0, 1, 0],\n    [0, 0, 0],\n  ],\n\n  // Edge\n  \"edge-cw\": [\n    [1, 1, 1],\n    [0, 0, 0],\n    [0, 0, 0],\n  ],\n\n  // Frame\n  frame: [\n    [1, 1, 1],\n    [1, 0, 1],\n    [1, 1, 1],\n  ],\n  \"frame-sync\": [\n    [1, 1, 1],\n    [1, 0, 1],\n    [1, 1, 1],\n  ],\n  heartbeat: [\n    [0, 1, 0],\n    [1, 1, 1],\n    [0, 1, 0],\n  ],\n  \"L-bl\": [\n    [0, 0, 0],\n    [1, 0, 0],\n    [1, 1, 0],\n  ],\n  \"L-br\": [\n    [0, 0, 0],\n    [0, 0, 1],\n    [0, 1, 1],\n  ],\n\n  // L-shapes\n  \"L-tl\": [\n    [1, 1, 0],\n    [1, 0, 0],\n    [0, 0, 0],\n  ],\n  \"L-tr\": [\n    [0, 1, 1],\n    [0, 0, 1],\n    [0, 0, 0],\n  ],\n\n  // Diagonals\n  \"line-diag-1\": [\n    [1, 0, 0],\n    [0, 1, 0],\n    [0, 0, 1],\n  ],\n  \"line-diag-2\": [\n    [0, 0, 1],\n    [0, 1, 0],\n    [1, 0, 0],\n  ],\n  \"line-h-bot\": [\n    [0, 0, 0],\n    [0, 0, 0],\n    [1, 1, 1],\n  ],\n  \"line-h-mid\": [\n    [0, 0, 0],\n    [1, 1, 1],\n    [0, 0, 0],\n  ],\n\n  // Horizontal lines\n  \"line-h-top\": [\n    [1, 1, 1],\n    [0, 0, 0],\n    [0, 0, 0],\n  ],\n\n  // Vertical lines\n  \"line-v-left\": [\n    [1, 0, 0],\n    [1, 0, 0],\n    [1, 0, 0],\n  ],\n  \"line-v-mid\": [\n    [0, 1, 0],\n    [0, 1, 0],\n    [0, 1, 0],\n  ],\n  \"line-v-right\": [\n    [0, 0, 1],\n    [0, 0, 1],\n    [0, 0, 1],\n  ],\n  \"plus-full\": [\n    [0, 1, 0],\n    [1, 1, 1],\n    [0, 1, 0],\n  ],\n\n  // Plus\n  \"plus-hollow\": [\n    [0, 1, 0],\n    [1, 0, 1],\n    [0, 1, 0],\n  ],\n\n  // Rain\n  rain: [\n    [0, 1, 0],\n    [0, 1, 0],\n    [0, 0, 0],\n  ],\n  \"rain-rev\": [\n    [0, 0, 0],\n    [0, 1, 0],\n    [0, 1, 0],\n  ],\n  \"ripple-in\": [\n    [0, 0, 0],\n    [0, 1, 0],\n    [0, 0, 0],\n  ],\n\n  // Ripple\n  \"ripple-out\": [\n    [1, 1, 1],\n    [1, 0, 1],\n    [1, 1, 1],\n  ],\n  \"rows-alt\": [\n    [1, 1, 1],\n    [0, 0, 0],\n    [1, 1, 1],\n  ],\n\n  // Snake\n  snake: [\n    [1, 1, 0],\n    [0, 1, 0],\n    [0, 0, 0],\n  ],\n  \"snake-rev\": [\n    [0, 0, 0],\n    [0, 1, 0],\n    [0, 1, 1],\n  ],\n  \"solo-bl\": [\n    [0, 0, 0],\n    [0, 0, 0],\n    [1, 0, 0],\n  ],\n  \"solo-br\": [\n    [0, 0, 0],\n    [0, 0, 0],\n    [0, 0, 1],\n  ],\n  // Solo (single cell)\n  \"solo-center\": [\n    [0, 0, 0],\n    [0, 1, 0],\n    [0, 0, 0],\n  ],\n  \"solo-tl\": [\n    [1, 0, 0],\n    [0, 0, 0],\n    [0, 0, 0],\n  ],\n  \"solo-tr\": [\n    [0, 0, 1],\n    [0, 0, 0],\n    [0, 0, 0],\n  ],\n  sparkle: [\n    [1, 0, 1],\n    [0, 1, 0],\n    [1, 0, 1],\n  ],\n\n  // Sparse\n  \"sparse-1\": [\n    [1, 0, 0],\n    [0, 0, 1],\n    [0, 1, 0],\n  ],\n  \"sparse-2\": [\n    [0, 1, 0],\n    [1, 0, 0],\n    [0, 0, 1],\n  ],\n  \"sparse-3\": [\n    [0, 0, 1],\n    [0, 1, 0],\n    [1, 0, 0],\n  ],\n  \"spiral-ccw\": [\n    [1, 1, 1],\n    [1, 0, 0],\n    [1, 0, 0],\n  ],\n\n  // Spirals\n  \"spiral-cw\": [\n    [1, 1, 1],\n    [0, 0, 1],\n    [0, 0, 1],\n  ],\n\n  // Stripes\n  \"stripes-h\": [\n    [1, 1, 1],\n    [0, 0, 0],\n    [1, 1, 1],\n  ],\n  \"stripes-v\": [\n    [1, 0, 1],\n    [1, 0, 1],\n    [1, 0, 1],\n  ],\n  \"T-bot\": [\n    [0, 0, 0],\n    [0, 1, 0],\n    [1, 1, 1],\n  ],\n  \"T-left\": [\n    [1, 0, 0],\n    [1, 1, 0],\n    [1, 0, 0],\n  ],\n  \"T-right\": [\n    [0, 0, 1],\n    [0, 1, 1],\n    [0, 0, 1],\n  ],\n\n  // T-shapes\n  \"T-top\": [\n    [1, 1, 1],\n    [0, 1, 0],\n    [0, 0, 0],\n  ],\n  twinkle: [\n    [1, 0, 0],\n    [0, 0, 1],\n    [0, 1, 0],\n  ],\n\n  // Effects\n  waterfall: [\n    [1, 1, 0],\n    [0, 0, 0],\n    [0, 0, 0],\n  ],\n  \"wave-bt\": [\n    [0, 0, 0],\n    [1, 1, 1],\n    [1, 1, 1],\n  ],\n\n  // Waves\n  \"wave-lr\": [\n    [1, 1, 0],\n    [1, 1, 0],\n    [1, 1, 0],\n  ],\n  \"wave-rl\": [\n    [0, 1, 1],\n    [0, 1, 1],\n    [0, 1, 1],\n  ],\n  \"wave-tb\": [\n    [1, 1, 1],\n    [1, 1, 1],\n    [0, 0, 0],\n  ],\n  \"x-shape\": [\n    [1, 0, 1],\n    [0, 1, 0],\n    [1, 0, 1],\n  ],\n};\n\n// Color presets\nconst COLORS: Record<string, string> = {\n  amber: \"#fbbf24\",\n  blue: \"#38bdf8\",\n  green: \"#4ade80\",\n  red: \"#f87171\",\n  white: \"#f5f5f4\",\n};\n\n// Size presets in pixels\nconst SIZES: Record<string, number> = {\n  lg: 48,\n  md: 32,\n  sm: 24,\n  xl: 64,\n};\n\n// Speed presets in milliseconds\nconst SPEEDS: Record<string, number> = {\n  fast: 400,\n  normal: 800,\n  slow: 1500,\n};\n\n// Helper to resolve pattern to GridMatrix\nconst resolvePattern = (\n  pattern: PresetPattern | GridMatrix | undefined\n): GridMatrix => {\n  if (!pattern) {\n    return PATTERNS[\"plus-hollow\"];\n  }\n  if (typeof pattern === \"string\") {\n    return PATTERNS[pattern] ?? PATTERNS[\"plus-hollow\"];\n  }\n  return pattern;\n};\n\n// Helper to resolve color\nconst resolveColor = (color: string | undefined): string => {\n  if (!color) {\n    return COLORS.white;\n  }\n  return COLORS[color] ?? color;\n};\n\n// Helper to resolve size\nconst resolveSize = (\n  size: \"sm\" | \"md\" | \"lg\" | \"xl\" | number | undefined\n): number => {\n  if (size === undefined) {\n    return SIZES.md;\n  }\n  if (typeof size === \"number\") {\n    return size;\n  }\n  return SIZES[size] ?? SIZES.md;\n};\n\n// Cell component for individual grid cells\nconst GridCell = ({\n  active,\n  color,\n  cellSize,\n  animationDelay,\n  mode,\n  cycleDuration,\n  shouldReduceMotion,\n  rounded,\n}: {\n  active: boolean;\n  color: string;\n  cellSize: number;\n  animationDelay: number;\n  mode: \"pulse\" | \"sequence\" | \"stagger\";\n  cycleDuration: number;\n  shouldReduceMotion: boolean | null;\n  rounded?: boolean;\n}) => {\n  const glowSize = cellSize * 0.8;\n  const borderRadius = rounded ? \"50%\" : undefined;\n\n  if (!active) {\n    return (\n      <div\n        style={{\n          height: cellSize,\n          width: cellSize,\n        }}\n      />\n    );\n  }\n\n  // For reduced motion, show static cells\n  if (shouldReduceMotion) {\n    return (\n      <div\n        style={{\n          backgroundColor: color,\n          borderRadius,\n          boxShadow: `\n            0 0 ${glowSize * 0.3}px ${color},\n            0 0 ${glowSize * 0.6}px ${color}40,\n            0 0 ${glowSize * 1.2}px ${color}20\n          `,\n          height: cellSize,\n          width: cellSize,\n        }}\n      />\n    );\n  }\n\n  const pulseAnimation = {\n    opacity: [0.4, 1, 0.4],\n    scale: [0.95, 1, 0.95],\n  };\n\n  const staggerAnimation = {\n    opacity: [0, 1, 1, 0],\n    scale: [0.8, 1, 1, 0.8],\n  };\n\n  return (\n    <motion.div\n      animate={mode === \"stagger\" ? staggerAnimation : pulseAnimation}\n      style={{\n        backgroundColor: color,\n        borderRadius,\n        boxShadow: `\n          0 0 ${glowSize * 0.3}px ${color},\n          0 0 ${glowSize * 0.6}px ${color}40,\n          0 0 ${glowSize * 1.2}px ${color}20\n        `,\n        height: cellSize,\n        width: cellSize,\n      }}\n      transition={{\n        delay: mode === \"stagger\" ? animationDelay : 0,\n        duration: cycleDuration / 1000,\n        ease: [0.645, 0.045, 0.355, 1],\n        repeat: Number.POSITIVE_INFINITY,\n        times: mode === \"stagger\" ? [0, 0.2, 0.8, 1] : undefined,\n      }}\n    />\n  );\n};\n\nconst GridLoader = ({\n  pattern,\n  mode = \"pulse\",\n  sequence,\n  speed = \"normal\",\n  color,\n  size,\n  blur,\n  gap: gapProp,\n  rounded,\n  static: isStatic,\n  className,\n}: GridLoaderProps) => {\n  const shouldReduceMotion = useReducedMotion();\n  const [sequenceIndex, setSequenceIndex] = useState(0);\n\n  const sizeInPx = resolveSize(size);\n  const gapSize = gapProp ?? 0;\n  const cellSize = (sizeInPx - gapSize * 2) / 3;\n  const resolvedColor = resolveColor(color);\n  const cycleDuration = SPEEDS[speed] ?? SPEEDS.normal;\n  const disableAnimation = isStatic || shouldReduceMotion;\n\n  // Handle sequence mode\n  useEffect(() => {\n    if (mode !== \"sequence\" || !sequence || sequence.length === 0) {\n      return;\n    }\n    if (disableAnimation) {\n      return;\n    }\n\n    const interval = setInterval(() => {\n      setSequenceIndex((prev) => (prev + 1) % sequence.length);\n    }, cycleDuration);\n\n    return () => clearInterval(interval);\n  }, [mode, sequence, cycleDuration, disableAnimation]);\n\n  // Determine current grid to display\n  const currentGrid = useMemo(() => {\n    if (mode === \"sequence\" && sequence && sequence.length > 0) {\n      return resolvePattern(sequence[sequenceIndex]);\n    }\n    return resolvePattern(pattern);\n  }, [mode, sequence, sequenceIndex, pattern]);\n\n  // Flatten grid for rendering\n  const cells = currentGrid.flat();\n\n  // Calculate stagger delays based on active cells\n  const staggerDelays = useMemo(() => {\n    if (mode !== \"stagger\") {\n      return cells.map(() => 0);\n    }\n\n    const activeCells = cells.reduce<number[]>((acc, cell, idx) => {\n      if (cell === 1) {\n        acc.push(idx);\n      }\n      return acc;\n    }, []);\n\n    const delayPerCell = cycleDuration / 1000 / (activeCells.length + 2);\n\n    return cells.map((cell, idx) => {\n      if (cell === 0) {\n        return 0;\n      }\n      const activeIndex = activeCells.indexOf(idx);\n      return activeIndex * delayPerCell;\n    });\n  }, [cells, mode, cycleDuration]);\n\n  // Generate stable keys for grid positions (0-8 in a 3x3 grid)\n  const cellKeys = [\"tl\", \"tm\", \"tr\", \"ml\", \"mm\", \"mr\", \"bl\", \"bm\", \"br\"];\n\n  return (\n    <output\n      aria-label=\"Loading\"\n      className={cn(\"grid grid-cols-3\", className)}\n      style={{\n        filter: blur ? `blur(${blur}px)` : undefined,\n        gap: gapSize,\n        height: sizeInPx,\n        width: sizeInPx,\n      }}\n    >\n      {cells.map((active, idx) => (\n        <GridCell\n          active={active === 1}\n          animationDelay={staggerDelays[idx]}\n          cellSize={cellSize}\n          color={resolvedColor}\n          cycleDuration={cycleDuration}\n          key={cellKeys[idx]}\n          mode={mode}\n          rounded={rounded}\n          shouldReduceMotion={disableAnimation}\n        />\n      ))}\n    </output>\n  );\n};\n\nexport default GridLoader;\n\n// Export patterns for advanced usage\nexport { COLORS, PATTERNS, SIZES };\n","path":"index.tsx","target":"components/smoothui/grid-loader/index.tsx","type":"registry:ui"}],"name":"grid-loader","registryDependencies":[],"title":"Grid Loader","type":"registry:ui"}