"use client"; import {motion} from "motion/react"; import React, {useEffect, useId, useRef, useState} from "react"; import {cn} from "@/lib/utilities"; import styles from "./dot-background.module.css"; /** Props accepted by {@link DotBackground}. */ export interface DotBackgroundProps extends React.SVGProps { /** Horizontal spacing between generated dots. @default 16 */ width?: number; /** Vertical spacing between generated dots. @default 16 */ height?: number; /** Horizontal offset applied to the dot grid. @default 0 */ x?: number; /** Vertical offset applied to the dot grid. @default 0 */ y?: number; /** Horizontal center offset for each generated circle. @default 1 */ cx?: number; /** Vertical center offset for each generated circle. @default 1 */ cy?: number; /** Radius used for each generated dot. @default 1 */ cr?: number; /** Additional CSS classes merged with the root SVG element. @default undefined */ className?: string; /** Enables pulsing radial glow animation for every dot. @default false */ glow?: boolean; } /** * Renders an animated dot grid with an optional glowing pulse effect. * * @remarks * - Animated component using the `motion` library * - Renders an `` element * - Styling via CSS Modules with `--ac-*` custom properties * - Client-side only (`"use client"` directive) * * @example * ```tsx * * ``` * * @see {@link DotBackgroundProps} for available props */ const DotBackground = React.forwardRef( ( {width = 16, height = 16, x = 0, y = 0, cx = 1, cy = 1, cr = 1, className, glow = false, ...props}: Readonly, ref, ): React.JSX.Element => { const id = useId(); const containerRef = useRef(null); const [dimensions, setDimensions] = useState({width: 0, height: 0}); const setRefs = React.useCallback( (node: SVGSVGElement | null): void => { containerRef.current = node; if (typeof ref === "function") { ref(node); return; } if (ref) { ref.current = node; } }, [ref], ); useEffect(() => { const updateDimensions = (): void => { if (!containerRef.current) { return; } const rect = containerRef.current.getBoundingClientRect(); // eslint-disable-next-line react-x/set-state-in-effect -- resize observer style update is the hook's purpose setDimensions({width: rect.width, height: rect.height}); }; updateDimensions(); globalThis.window.addEventListener("resize", updateDimensions); return () => globalThis.window.removeEventListener("resize", updateDimensions); }, []); const totalColumns = Math.ceil(dimensions.width / width); const dots = Array.from( { length: totalColumns * Math.ceil(dimensions.height / height), }, (_, index) => { const column = index % Math.max(totalColumns, 1); const row = Math.floor(index / Math.max(totalColumns, 1)); return { x: x + column * width + cx, y: y + row * height + cy, delay: Math.random() * 5, duration: Math.random() * 3 + 2, }; }, ); return ( ); }, ); DotBackground.displayName = "DotBackground"; export {DotBackground};