Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
105 changes: 63 additions & 42 deletions apps/www/public/llms-full.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2337,6 +2337,7 @@ Description: A animated background grid pattern made with SVGs, fully customizab

import {
ComponentPropsWithoutRef,
useCallback,
useEffect,
useId,
useRef,
Expand All @@ -2358,6 +2359,12 @@ export interface AnimatedGridPatternProps extends ComponentPropsWithoutRef<"svg"
repeatDelay?: number
}

type Square = {
id: number
pos: [number, number]
iteration: number
}

export function AnimatedGridPattern({
width = 40,
height = 40,
Expand All @@ -2368,70 +2375,83 @@ export function AnimatedGridPattern({
className,
maxOpacity = 0.5,
duration = 4,
repeatDelay = 0.5,
...props
}: AnimatedGridPatternProps) {
const id = useId()
const containerRef = useRef(null)
const containerRef = useRef<SVGSVGElement | null>(null)
const [dimensions, setDimensions] = useState({ width: 0, height: 0 })
const [squares, setSquares] = useState(() => generateSquares(numSquares))
const [squares, setSquares] = useState<Array<Square>>([])

function getPos() {
const getPos = useCallback((): [number, number] => {
return [
Math.floor((Math.random() * dimensions.width) / width),
Math.floor((Math.random() * dimensions.height) / height),
]
}
}, [dimensions.height, dimensions.width, height, width])

// Adjust the generateSquares function to return objects with an id, x, and y
function generateSquares(count: number) {
return Array.from({ length: count }, (_, i) => ({
id: i,
pos: getPos(),
}))
}
const generateSquares = useCallback(
(count: number) => {
return Array.from({ length: count }, (_, i) => ({
id: i,
pos: getPos(),
iteration: 0,
}))
},
[getPos]
)

// Function to update a single square's position
const updateSquarePosition = (id: number) => {
setSquares((currentSquares) =>
currentSquares.map((sq) =>
sq.id === id
? {
...sq,
pos: getPos(),
}
: sq
)
)
}
const updateSquarePosition = useCallback(
(squareId: number) => {
setSquares((currentSquares) => {
const current = currentSquares[squareId]
if (!current || current.id !== squareId) return currentSquares

const nextSquares = currentSquares.slice()
nextSquares[squareId] = {
...current,
pos: getPos(),
iteration: current.iteration + 1,
}

return nextSquares
})
},
[getPos]
)

// Update squares to animate in
useEffect(() => {
if (dimensions.width && dimensions.height) {
setSquares(generateSquares(numSquares))
}
}, [dimensions, numSquares, generateSquares])
}, [dimensions.width, dimensions.height, generateSquares, numSquares])

// Resize observer to update container dimensions
useEffect(() => {
const element = containerRef.current
if (!element) return

const resizeObserver = new ResizeObserver((entries) => {
for (const entry of entries) {
setDimensions({
width: entry.contentRect.width,
height: entry.contentRect.height,
setDimensions((currentDimensions) => {
const nextWidth = entry.contentRect.width
const nextHeight = entry.contentRect.height
if (
currentDimensions.width === nextWidth &&
currentDimensions.height === nextHeight
) {
return currentDimensions
}
return { width: nextWidth, height: nextHeight }
})
}
})

if (containerRef.current) {
resizeObserver.observe(containerRef.current)
}
resizeObserver.observe(element)

return () => {
if (containerRef.current) {
resizeObserver.unobserve(containerRef.current)
}
resizeObserver.disconnect()
}
}, [containerRef])
}, [])

return (
<svg
Expand Down Expand Up @@ -2461,7 +2481,7 @@ export function AnimatedGridPattern({
</defs>
<rect width="100%" height="100%" fill={`url(#${id})`} />
<svg x={x} y={y} className="overflow-visible">
{squares.map(({ pos: [x, y], id }, index) => (
{squares.map(({ pos: [squareX, squareY], id, iteration }, index) => (
<motion.rect
initial={{ opacity: 0 }}
animate={{ opacity: maxOpacity }}
Expand All @@ -2470,13 +2490,14 @@ export function AnimatedGridPattern({
repeat: 1,
delay: index * 0.1,
repeatType: "reverse",
repeatDelay,
}}
onAnimationComplete={() => updateSquarePosition(id)}
key={`${x}-${y}-${index}`}
key={`${id}-${iteration}`}
width={width - 1}
height={height - 1}
x={x * width + 1}
y={y * height + 1}
x={squareX * width + 1}
y={squareY * height + 1}
fill="currentColor"
strokeWidth="0"
/>
Expand All @@ -2503,7 +2524,7 @@ export default function AnimatedGridPatternDemo() {
duration={3}
repeatDelay={1}
className={cn(
"[mask-image:radial-gradient(500px_circle_at_center,white,transparent)]",
"mask-[radial-gradient(500px_circle_at_center,white,transparent)]",
"inset-x-0 inset-y-[-30%] h-[200%] skew-y-12"
)}
/>
Expand Down
2 changes: 1 addition & 1 deletion apps/www/public/r/animated-grid-pattern-demo.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
"files": [
{
"path": "registry/example/animated-grid-pattern-demo.tsx",
"content": "import { cn } from \"@/lib/utils\"\nimport { AnimatedGridPattern } from \"@/registry/magicui/animated-grid-pattern\"\n\nexport default function AnimatedGridPatternDemo() {\n return (\n <div className=\"bg-background relative flex h-[500px] w-full items-center justify-center overflow-hidden rounded-lg border p-20\">\n <AnimatedGridPattern\n numSquares={30}\n maxOpacity={0.1}\n duration={3}\n repeatDelay={1}\n className={cn(\n \"[mask-image:radial-gradient(500px_circle_at_center,white,transparent)]\",\n \"inset-x-0 inset-y-[-30%] h-[200%] skew-y-12\"\n )}\n />\n </div>\n )\n}\n",
"content": "import { cn } from \"@/lib/utils\"\nimport { AnimatedGridPattern } from \"@/registry/magicui/animated-grid-pattern\"\n\nexport default function AnimatedGridPatternDemo() {\n return (\n <div className=\"bg-background relative flex h-[500px] w-full items-center justify-center overflow-hidden rounded-lg border p-20\">\n <AnimatedGridPattern\n numSquares={30}\n maxOpacity={0.1}\n duration={3}\n repeatDelay={1}\n className={cn(\n \"mask-[radial-gradient(500px_circle_at_center,white,transparent)]\",\n \"inset-x-0 inset-y-[-30%] h-[200%] skew-y-12\"\n )}\n />\n </div>\n )\n}\n",
"type": "registry:example"
}
]
Expand Down
2 changes: 1 addition & 1 deletion apps/www/public/r/animated-grid-pattern.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
"files": [
{
"path": "registry/magicui/animated-grid-pattern.tsx",
"content": "\"use client\"\n\nimport {\n ComponentPropsWithoutRef,\n useEffect,\n useId,\n useRef,\n useState,\n} from \"react\"\nimport { motion } from \"motion/react\"\n\nimport { cn } from \"@/lib/utils\"\n\nexport interface AnimatedGridPatternProps extends ComponentPropsWithoutRef<\"svg\"> {\n width?: number\n height?: number\n x?: number\n y?: number\n strokeDasharray?: number\n numSquares?: number\n maxOpacity?: number\n duration?: number\n repeatDelay?: number\n}\n\nexport function AnimatedGridPattern({\n width = 40,\n height = 40,\n x = -1,\n y = -1,\n strokeDasharray = 0,\n numSquares = 50,\n className,\n maxOpacity = 0.5,\n duration = 4,\n ...props\n}: AnimatedGridPatternProps) {\n const id = useId()\n const containerRef = useRef(null)\n const [dimensions, setDimensions] = useState({ width: 0, height: 0 })\n const [squares, setSquares] = useState(() => generateSquares(numSquares))\n\n function getPos() {\n return [\n Math.floor((Math.random() * dimensions.width) / width),\n Math.floor((Math.random() * dimensions.height) / height),\n ]\n }\n\n // Adjust the generateSquares function to return objects with an id, x, and y\n function generateSquares(count: number) {\n return Array.from({ length: count }, (_, i) => ({\n id: i,\n pos: getPos(),\n }))\n }\n\n // Function to update a single square's position\n const updateSquarePosition = (id: number) => {\n setSquares((currentSquares) =>\n currentSquares.map((sq) =>\n sq.id === id\n ? {\n ...sq,\n pos: getPos(),\n }\n : sq\n )\n )\n }\n\n // Update squares to animate in\n useEffect(() => {\n if (dimensions.width && dimensions.height) {\n setSquares(generateSquares(numSquares))\n }\n }, [dimensions, numSquares, generateSquares])\n\n // Resize observer to update container dimensions\n useEffect(() => {\n const resizeObserver = new ResizeObserver((entries) => {\n for (const entry of entries) {\n setDimensions({\n width: entry.contentRect.width,\n height: entry.contentRect.height,\n })\n }\n })\n\n if (containerRef.current) {\n resizeObserver.observe(containerRef.current)\n }\n\n return () => {\n if (containerRef.current) {\n resizeObserver.unobserve(containerRef.current)\n }\n }\n }, [containerRef])\n\n return (\n <svg\n ref={containerRef}\n aria-hidden=\"true\"\n className={cn(\n \"pointer-events-none absolute inset-0 h-full w-full fill-gray-400/30 stroke-gray-400/30\",\n className\n )}\n {...props}\n >\n <defs>\n <pattern\n id={id}\n width={width}\n height={height}\n patternUnits=\"userSpaceOnUse\"\n x={x}\n y={y}\n >\n <path\n d={`M.5 ${height}V.5H${width}`}\n fill=\"none\"\n strokeDasharray={strokeDasharray}\n />\n </pattern>\n </defs>\n <rect width=\"100%\" height=\"100%\" fill={`url(#${id})`} />\n <svg x={x} y={y} className=\"overflow-visible\">\n {squares.map(({ pos: [x, y], id }, index) => (\n <motion.rect\n initial={{ opacity: 0 }}\n animate={{ opacity: maxOpacity }}\n transition={{\n duration,\n repeat: 1,\n delay: index * 0.1,\n repeatType: \"reverse\",\n }}\n onAnimationComplete={() => updateSquarePosition(id)}\n key={`${x}-${y}-${index}`}\n width={width - 1}\n height={height - 1}\n x={x * width + 1}\n y={y * height + 1}\n fill=\"currentColor\"\n strokeWidth=\"0\"\n />\n ))}\n </svg>\n </svg>\n )\n}\n",
"content": "\"use client\"\n\nimport {\n ComponentPropsWithoutRef,\n useCallback,\n useEffect,\n useId,\n useRef,\n useState,\n} from \"react\"\nimport { motion } from \"motion/react\"\n\nimport { cn } from \"@/lib/utils\"\n\nexport interface AnimatedGridPatternProps extends ComponentPropsWithoutRef<\"svg\"> {\n width?: number\n height?: number\n x?: number\n y?: number\n strokeDasharray?: number\n numSquares?: number\n maxOpacity?: number\n duration?: number\n repeatDelay?: number\n}\n\ntype Square = {\n id: number\n pos: [number, number]\n iteration: number\n}\n\nexport function AnimatedGridPattern({\n width = 40,\n height = 40,\n x = -1,\n y = -1,\n strokeDasharray = 0,\n numSquares = 50,\n className,\n maxOpacity = 0.5,\n duration = 4,\n repeatDelay = 0.5,\n ...props\n}: AnimatedGridPatternProps) {\n const id = useId()\n const containerRef = useRef<SVGSVGElement | null>(null)\n const [dimensions, setDimensions] = useState({ width: 0, height: 0 })\n const [squares, setSquares] = useState<Array<Square>>([])\n\n const getPos = useCallback((): [number, number] => {\n return [\n Math.floor((Math.random() * dimensions.width) / width),\n Math.floor((Math.random() * dimensions.height) / height),\n ]\n }, [dimensions.height, dimensions.width, height, width])\n\n const generateSquares = useCallback(\n (count: number) => {\n return Array.from({ length: count }, (_, i) => ({\n id: i,\n pos: getPos(),\n iteration: 0,\n }))\n },\n [getPos]\n )\n\n const updateSquarePosition = useCallback(\n (squareId: number) => {\n setSquares((currentSquares) => {\n const current = currentSquares[squareId]\n if (!current || current.id !== squareId) return currentSquares\n\n const nextSquares = currentSquares.slice()\n nextSquares[squareId] = {\n ...current,\n pos: getPos(),\n iteration: current.iteration + 1,\n }\n\n return nextSquares\n })\n },\n [getPos]\n )\n\n useEffect(() => {\n if (dimensions.width && dimensions.height) {\n setSquares(generateSquares(numSquares))\n }\n }, [dimensions.width, dimensions.height, generateSquares, numSquares])\n\n useEffect(() => {\n const element = containerRef.current\n if (!element) return\n\n const resizeObserver = new ResizeObserver((entries) => {\n for (const entry of entries) {\n setDimensions((currentDimensions) => {\n const nextWidth = entry.contentRect.width\n const nextHeight = entry.contentRect.height\n if (\n currentDimensions.width === nextWidth &&\n currentDimensions.height === nextHeight\n ) {\n return currentDimensions\n }\n return { width: nextWidth, height: nextHeight }\n })\n }\n })\n\n resizeObserver.observe(element)\n\n return () => {\n resizeObserver.disconnect()\n }\n }, [])\n\n return (\n <svg\n ref={containerRef}\n aria-hidden=\"true\"\n className={cn(\n \"pointer-events-none absolute inset-0 h-full w-full fill-gray-400/30 stroke-gray-400/30\",\n className\n )}\n {...props}\n >\n <defs>\n <pattern\n id={id}\n width={width}\n height={height}\n patternUnits=\"userSpaceOnUse\"\n x={x}\n y={y}\n >\n <path\n d={`M.5 ${height}V.5H${width}`}\n fill=\"none\"\n strokeDasharray={strokeDasharray}\n />\n </pattern>\n </defs>\n <rect width=\"100%\" height=\"100%\" fill={`url(#${id})`} />\n <svg x={x} y={y} className=\"overflow-visible\">\n {squares.map(({ pos: [squareX, squareY], id, iteration }, index) => (\n <motion.rect\n initial={{ opacity: 0 }}\n animate={{ opacity: maxOpacity }}\n transition={{\n duration,\n repeat: 1,\n delay: index * 0.1,\n repeatType: \"reverse\",\n repeatDelay,\n }}\n onAnimationComplete={() => updateSquarePosition(id)}\n key={`${id}-${iteration}`}\n width={width - 1}\n height={height - 1}\n x={squareX * width + 1}\n y={squareY * height + 1}\n fill=\"currentColor\"\n strokeWidth=\"0\"\n />\n ))}\n </svg>\n </svg>\n )\n}\n",
"type": "registry:ui"
}
]
Expand Down
2 changes: 1 addition & 1 deletion apps/www/registry/example/animated-grid-pattern-demo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ export default function AnimatedGridPatternDemo() {
duration={3}
repeatDelay={1}
className={cn(
"[mask-image:radial-gradient(500px_circle_at_center,white,transparent)]",
"mask-[radial-gradient(500px_circle_at_center,white,transparent)]",
"inset-x-0 inset-y-[-30%] h-[200%] skew-y-12"
)}
/>
Expand Down
Loading
Loading