diff options
Diffstat (limited to 'jb-ui/src/components/ui/dot-pattern.tsx')
| -rw-r--r-- | jb-ui/src/components/ui/dot-pattern.tsx | 156 |
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> + ) +} |
