{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "grid-pattern",
  "type": "registry:ui",
  "title": "Grid Pattern",
  "description": "A decorative SVG line grid that fills its positioned parent, with optional accent cells (`squares`, addressed as `[column, row]`) that can pulse. Colour comes from `currentColor`, so a text-colour class on `className` sets the tone.",
  "author": "ofri-peretz <https://github.com/ofri-peretz>",
  "categories": [
    "decorative",
    "pattern"
  ],
  "dependencies": [
    "motion"
  ],
  "registryDependencies": [
    "https://ds.interlace.tools/r/theme.json",
    "https://ds.interlace.tools/r/cn.json",
    "https://ds.interlace.tools/r/use-reduced-motion.json"
  ],
  "files": [
    {
      "path": "registry/interlace-ui/patterns/grid-pattern.tsx",
      "target": "components/ui/patterns/grid-pattern.tsx",
      "type": "registry:ui",
      "content": "\"use client\";\n\nimport { ComponentPropsWithoutRef, forwardRef, useId } from \"react\";\n\n// @interlace/grid-pattern v1.2.0 — Interlace design system.\n// Docs, props and live preview: https://ds.interlace.tools/c/grid-pattern\n// What changed since: https://ds.interlace.tools/c/grid-pattern#history\n// Generated banner — keep it, the upgrade diff reads this version.\n\n/**\n * @interlace/ui — GridPattern\n *\n * A decorative SVG line grid that fills its positioned parent, with optional\n * accent cells (`squares`, addressed as `[column, row]`) that can pulse.\n * Colour comes from `currentColor`, so a text-colour class on `className` sets\n * the tone.\n *\n * The whole surface is `aria-hidden` and `pointer-events-none`.\n *\n * ## Anatomy\n *\n *   GridPattern                      (svg — data-slot=\"grid-pattern\")\n *     ├─ defs > pattern              (one cell: `M.5 {h}V.5H{w}`, id from useId)\n *     ├─ rect 100%×100%              (fills the parent with the tiled pattern)\n *     └─ svg                         (data-slot=\"grid-pattern-squares\")\n *         └─ rect | motion.rect      (data-slot=\"grid-pattern-square\")\n *\n * ## Motion\n *\n * JS-driven, not CSS. The pulse is a `motion.rect` from `motion/react`\n * animating `opacity: [0.5, 1, 0.5]` on `repeat: Infinity` — inline styles the\n * `prefers-reduced-motion` reset in `styles/preflight.css` cannot reach. It is\n * gated in JS instead: `shouldAnimate = animated && !reducedMotion`, and when\n * that is false the accent cells render as plain `<rect>` elements with no\n * animation object at all. The grid lines themselves never animate.\n *\n * | Rule | Concept                     | Where in this file                                     |\n * | ---- | --------------------------- | ------------------------------------------------------ |\n * | R4   | Extends native el           | `ComponentPropsWithoutRef<'svg'>` + `forwardRef`       |\n * | R5   | testid required, no default | `'data-testid': string`                                |\n * | R6   | data-slot per part          | `grid-pattern` / `-squares` / `-square`                |\n * | R7   | className merged + ...rest  | `cn(BASE, className)` + `{...props}`                   |\n * | R8   | No `isXxx`                  | `animated`, `squares`, `dashArray`                     |\n * | R19  | Tokens only                 | `fill-current` / `stroke-current`; no colour prop      |\n * | R25  | Client component            | `useId` + `useReducedMotion`                           |\n * | R26  | A11y                        | `aria-hidden=\"true\"` — decorative, never in the tree   |\n */\n\nimport { motion } from \"motion/react\";\n\nimport { cn } from \"@/lib/utils\";\nimport { useReducedMotion } from \"@/hooks/use-reduced-motion\";\n\n/**\n * A single highlighted cell, addressed by its column/row index in the grid\n * (not pixel coordinates). `[col, row]` — `[0, 0]` is the top-left cell.\n */\nexport type GridPatternSquare = [column: number, row: number];\n\ninterface GridPatternProps extends ComponentPropsWithoutRef<\"svg\"> {\n  /**\n   * Width of a single grid cell, in user-space units.\n   * @default 40\n   */\n  cellWidth?: number;\n  /**\n   * Height of a single grid cell, in user-space units.\n   * @default 40\n   */\n  cellHeight?: number;\n  /**\n   * Horizontal offset of the pattern origin, in user-space units. A small\n   * negative value (the default) clips the leading edge so the grid bleeds\n   * cleanly past the left edge of its container.\n   * @default -1\n   */\n  offsetX?: number;\n  /**\n   * Vertical offset of the pattern origin, in user-space units.\n   * @default -1\n   */\n  offsetY?: number;\n  /**\n   * Stroke width of the grid lines, in user-space units.\n   * @default 1\n   */\n  strokeWidth?: number;\n  /**\n   * SVG `stroke-dasharray` for the grid lines. `\"0\"` draws solid lines;\n   * e.g. `\"4 2\"` draws a dashed grid.\n   * @default \"0\"\n   */\n  dashArray?: string;\n  /**\n   * Cells to fill as accents, each `[column, row]`. Filled cells inherit the\n   * SVG's `fill`/`fill-opacity` (`currentColor` by default — set via a text\n   * color utility on `className`).\n   * @default undefined\n   */\n  squares?: GridPatternSquare[];\n  /**\n   * Pulse the accent `squares` with a soft opacity loop. Automatically forced\n   * off when the user prefers reduced motion. Has no effect unless `squares`\n   * is provided.\n   * @default false\n   */\n  animated?: boolean;\n  /**\n   * Seconds for one full pulse cycle of an animated accent cell.\n   * @default 4\n   */\n  animationDuration?: number;\n  /**\n   * Stable id for end-to-end selectors. Required at the type level — no runtime\n   * default, so an omission surfaces in review rather than silently masking it.\n   */\n  \"data-testid\": string;\n}\n\n/**\n * GridPattern — a decorative, consumer-agnostic SVG grid backdrop.\n *\n * Renders a tiled line grid that fills its positioned parent, with optional\n * highlighted accent cells that can softly pulse. Purely decorative: marked\n * `aria-hidden` and `pointer-events-none` so it never enters the a11y tree or\n * intercepts clicks (R23).\n *\n * Color is driven by `currentColor` and Tailwind opacity utilities rather than\n * baked-in literals — set the grid tone with a text-color class on `className`\n * (e.g. `className=\"text-border/40\"`). This keeps the component on design\n * tokens with zero raw color literals in source (R19).\n *\n * Motion respects `prefers-reduced-motion`: when `animated` is set but the user\n * opts out, accent cells render statically (R26, MOTION_PHILOSOPHY).\n *\n * @example\n * ```tsx\n * <div className=\"relative h-64 overflow-hidden text-border/30\">\n *   <GridPattern data-testid=\"hero-grid\" />\n * </div>\n * ```\n *\n * @example Animated accents\n * ```tsx\n * <GridPattern\n *   data-testid=\"features-grid\"\n *   squares={[[2, 1], [5, 3], [8, 2]]}\n *   animated\n *   className=\"text-brand-500/20\"\n * />\n * ```\n */\nexport const GridPattern = forwardRef<SVGSVGElement, GridPatternProps>(\n  function GridPattern(\n    {\n      cellWidth = 40,\n      cellHeight = 40,\n      offsetX = -1,\n      offsetY = -1,\n      strokeWidth = 1,\n      dashArray = \"0\",\n      squares,\n      animated = false,\n      animationDuration = 4,\n      className,\n      ...props\n    },\n    ref,\n  ) {\n    const patternId = useId();\n    const reducedMotion = useReducedMotion();\n    // Reduced-motion preference overrides the consumer's `animated` opt-in —\n    // re-animating against the user's wish would be the worse failure.\n    const shouldAnimate = animated && !reducedMotion;\n\n    return (\n      <svg\n        ref={ref}\n        aria-hidden=\"true\"\n        data-slot=\"grid-pattern\"\n        className={cn(\n          \"pointer-events-none absolute inset-0 size-full fill-current stroke-current opacity-30\",\n          className,\n        )}\n        {...props}\n      >\n        <defs>\n          <pattern\n            id={patternId}\n            width={cellWidth}\n            height={cellHeight}\n            patternUnits=\"userSpaceOnUse\"\n            x={offsetX}\n            y={offsetY}\n          >\n            <path\n              d={`M.5 ${cellHeight}V.5H${cellWidth}`}\n              fill=\"none\"\n              strokeWidth={strokeWidth}\n              strokeDasharray={dashArray}\n            />\n          </pattern>\n        </defs>\n\n        <rect\n          width=\"100%\"\n          height=\"100%\"\n          strokeWidth={0}\n          fill={`url(#${patternId})`}\n        />\n\n        {squares && squares.length > 0 ? (\n          <svg\n            x={offsetX}\n            y={offsetY}\n            className=\"overflow-visible\"\n            data-slot=\"grid-pattern-squares\"\n          >\n            {squares.map(([column, row]) => {\n              const key = `${column}-${row}`;\n              const x = column * cellWidth + 1;\n              const y = row * cellHeight + 1;\n              const width = cellWidth - 1;\n              const height = cellHeight - 1;\n\n              return shouldAnimate ? (\n                <motion.rect\n                  key={key}\n                  data-slot=\"grid-pattern-square\"\n                  strokeWidth={0}\n                  width={width}\n                  height={height}\n                  x={x}\n                  y={y}\n                  initial={{ opacity: 0.5 }}\n                  animate={{ opacity: [0.5, 1, 0.5] }}\n                  transition={{\n                    duration: animationDuration,\n                    repeat: Infinity,\n                    ease: \"easeInOut\",\n                  }}\n                />\n              ) : (\n                <rect\n                  key={key}\n                  data-slot=\"grid-pattern-square\"\n                  strokeWidth={0}\n                  width={width}\n                  height={height}\n                  x={x}\n                  y={y}\n                />\n              );\n            })}\n          </svg>\n        ) : null}\n      </svg>\n    );\n  },\n);\n"
    }
  ],
  "meta": {
    "tier": "pattern",
    "client": true,
    "minViewport": null,
    "loading": false,
    "version": "1.2.0",
    "since": "1.0.0"
  },
  "docs": "## @interlace/grid-pattern\n\nInstalled to `components/ui/patterns/grid-pattern.tsx`.\n\n```tsx\nimport { /* … */ } from '@/components/ui/patterns/grid-pattern';\n```\n\nProps, a11y contract, live preview and source: https://ds.interlace.tools/c/grid-pattern\n\nRequires the `@interlace/theme` CSS baseline (installed automatically as a registry dependency)."
}
