aboutsummaryrefslogtreecommitdiff
path: root/jb-ui/src/components/ui/dot-pattern.tsx
diff options
context:
space:
mode:
authorMax Nanis2026-02-18 20:42:03 -0500
committerMax Nanis2026-02-18 20:42:03 -0500
commit3eaa56f0306ead818f64c3d99fc6d230d9b970a4 (patch)
tree9fecc2f1456e6321572e0e65f57106916df173e2 /jb-ui/src/components/ui/dot-pattern.tsx
downloadamt-jb-3eaa56f0306ead818f64c3d99fc6d230d9b970a4.tar.gz
amt-jb-3eaa56f0306ead818f64c3d99fc6d230d9b970a4.zip
HERE WE GO, HERE WE GO, HERE WE GO
Diffstat (limited to 'jb-ui/src/components/ui/dot-pattern.tsx')
-rw-r--r--jb-ui/src/components/ui/dot-pattern.tsx156
1 files changed, 156 insertions, 0 deletions
diff --git a/jb-ui/src/components/ui/dot-pattern.tsx b/jb-ui/src/components/ui/dot-pattern.tsx
new file mode 100644
index 0000000..9522b7f
--- /dev/null
+++ b/jb-ui/src/components/ui/dot-pattern.tsx
@@ -0,0 +1,156 @@
+import React, { useEffect, useId, useRef, useState } from "react"
+import { motion } from "motion/react"
+
+import { cn } from "@/lib/utils"
+
+/**
+ * DotPattern Component Props
+ *
+ * @param {number} [width=16] - The horizontal spacing between dots
+ * @param {number} [height=16] - The vertical spacing between dots
+ * @param {number} [x=0] - The x-offset of the entire pattern
+ * @param {number} [y=0] - The y-offset of the entire pattern
+ * @param {number} [cx=1] - The x-offset of individual dots
+ * @param {number} [cy=1] - The y-offset of individual dots
+ * @param {number} [cr=1] - The radius of each dot
+ * @param {string} [className] - Additional CSS classes to apply to the SVG container
+ * @param {boolean} [glow=false] - Whether dots should have a glowing animation effect
+ */
+interface DotPatternProps extends React.SVGProps<SVGSVGElement> {
+ width?: number
+ height?: number
+ x?: number
+ y?: number
+ cx?: number
+ cy?: number
+ cr?: number
+ className?: string
+ glow?: boolean
+ [key: string]: unknown
+}
+
+/**
+ * DotPattern Component
+ *
+ * A React component that creates an animated or static dot pattern background using SVG.
+ * The pattern automatically adjusts to fill its container and can optionally display glowing dots.
+ *
+ * @component
+ *
+ * @see DotPatternProps for the props interface.
+ *
+ * @example
+ * // Basic usage
+ * <DotPattern />
+ *
+ * // With glowing effect and custom spacing
+ * <DotPattern
+ * width={20}
+ * height={20}
+ * glow={true}
+ * className="opacity-50"
+ * />
+ *
+ * @notes
+ * - The component is client-side only ("use client")
+ * - Automatically responds to container size changes
+ * - When glow is enabled, dots will animate with random delays and durations
+ * - Uses Motion for animations
+ * - Dots color can be controlled via the text color utility classes
+ */
+
+export function DotPattern({
+ width = 16,
+ height = 16,
+ x = 0,
+ y = 0,
+ cx = 1,
+ cy = 1,
+ cr = 1,
+ className,
+ glow = false,
+ ...props
+}: DotPatternProps) {
+ const id = useId()
+ const containerRef = useRef<SVGSVGElement>(null)
+ const [dimensions, setDimensions] = useState({ width: 0, height: 0 })
+
+ useEffect(() => {
+ const updateDimensions = () => {
+ if (containerRef.current) {
+ const { width, height } = containerRef.current.getBoundingClientRect()
+ setDimensions({ width, height })
+ }
+ }
+
+ updateDimensions()
+ window.addEventListener("resize", updateDimensions)
+ return () => window.removeEventListener("resize", updateDimensions)
+ }, [])
+
+ const dots = Array.from(
+ {
+ length:
+ Math.ceil(dimensions.width / width) *
+ Math.ceil(dimensions.height / height),
+ },
+ (_, i) => {
+ const col = i % Math.ceil(dimensions.width / width)
+ const row = Math.floor(i / Math.ceil(dimensions.width / width))
+ return {
+ x: col * width + cx,
+ y: row * height + cy,
+ delay: Math.random() * 5,
+ duration: Math.random() * 3 + 2,
+ }
+ }
+ )
+
+ return (
+ <svg
+ ref={containerRef}
+ aria-hidden="true"
+ className={cn(
+ "pointer-events-none absolute inset-0 h-full w-full text-neutral-400/80",
+ className
+ )}
+ {...props}
+ >
+ <defs>
+ <radialGradient id={`${id}-gradient`}>
+ <stop offset="0%" stopColor="currentColor" stopOpacity="1" />
+ <stop offset="100%" stopColor="currentColor" stopOpacity="0" />
+ </radialGradient>
+ </defs>
+ {dots.map((dot) => (
+ <motion.circle
+ key={`${dot.x}-${dot.y}`}
+ cx={dot.x}
+ cy={dot.y}
+ r={cr}
+ fill={glow ? `url(#${id}-gradient)` : "currentColor"}
+ initial={glow ? { opacity: 0.4, scale: 1 } : {}}
+ animate={
+ glow
+ ? {
+ opacity: [0.4, 1, 0.4],
+ scale: [1, 1.5, 1],
+ }
+ : {}
+ }
+ transition={
+ glow
+ ? {
+ duration: dot.duration,
+ repeat: Infinity,
+ repeatType: "reverse",
+ delay: dot.delay,
+ ease: "easeInOut",
+ }
+ : {}
+ }
+ />
+ ))}
+ </svg>
+ )
+}