feat: add AnimatedGrid and InteractiveGridPattern components and their demo examples to MagicuiPage
This commit is contained in:
parent
625bee14ef
commit
3fff508728
@ -1,8 +1,10 @@
|
||||
import { AnimatedGridPatternDemo } from '@/components/magicui/example/animated-grid-pattern-example';
|
||||
import { AnimatedListDemo } from '@/components/magicui/example/animated-list-example';
|
||||
import { BentoDemo } from '@/components/magicui/example/bento-grid-example';
|
||||
import { DotPatternDemo } from '@/components/magicui/example/dot-pattern-example';
|
||||
import { GridPatternDemo } from '@/components/magicui/example/grid-pattern-example';
|
||||
import { HeroVideoDialogDemoTopInBottomOut } from '@/components/magicui/example/hero-video-dialog-example';
|
||||
import { InteractiveGridPatternDemo } from '@/components/magicui/example/interactive-grid-pattern-example';
|
||||
import { MarqueeDemoVertical } from '@/components/magicui/example/marquee-example';
|
||||
import { RippleDemo } from '@/components/magicui/example/ripple-example';
|
||||
|
||||
@ -16,6 +18,8 @@ export default async function MagicuiPage() {
|
||||
<div className="mx-auto space-y-8">
|
||||
<DotPatternDemo />
|
||||
<GridPatternDemo />
|
||||
<AnimatedGridPatternDemo />
|
||||
<InteractiveGridPatternDemo />
|
||||
<RippleDemo />
|
||||
<BentoDemo />
|
||||
<div className="grid md:grid-cols-2 gap-4">
|
||||
|
154
src/components/magicui/animated-grid-pattern.tsx
Normal file
154
src/components/magicui/animated-grid-pattern.tsx
Normal file
@ -0,0 +1,154 @@
|
||||
"use client";
|
||||
|
||||
import { motion } from "motion/react";
|
||||
import {
|
||||
ComponentPropsWithoutRef,
|
||||
useEffect,
|
||||
useId,
|
||||
useRef,
|
||||
useState,
|
||||
} from "react";
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
export interface AnimatedGridPatternProps
|
||||
extends ComponentPropsWithoutRef<"svg"> {
|
||||
width?: number;
|
||||
height?: number;
|
||||
x?: number;
|
||||
y?: number;
|
||||
strokeDasharray?: any;
|
||||
numSquares?: number;
|
||||
maxOpacity?: number;
|
||||
duration?: number;
|
||||
repeatDelay?: number;
|
||||
}
|
||||
|
||||
export function AnimatedGridPattern({
|
||||
width = 40,
|
||||
height = 40,
|
||||
x = -1,
|
||||
y = -1,
|
||||
strokeDasharray = 0,
|
||||
numSquares = 50,
|
||||
className,
|
||||
maxOpacity = 0.5,
|
||||
duration = 4,
|
||||
repeatDelay = 0.5,
|
||||
...props
|
||||
}: AnimatedGridPatternProps) {
|
||||
const id = useId();
|
||||
const containerRef = useRef(null);
|
||||
const [dimensions, setDimensions] = useState({ width: 0, height: 0 });
|
||||
const [squares, setSquares] = useState(() => generateSquares(numSquares));
|
||||
|
||||
function getPos() {
|
||||
return [
|
||||
Math.floor((Math.random() * dimensions.width) / width),
|
||||
Math.floor((Math.random() * dimensions.height) / height),
|
||||
];
|
||||
}
|
||||
|
||||
// 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(),
|
||||
}));
|
||||
}
|
||||
|
||||
// Function to update a single square's position
|
||||
const updateSquarePosition = (id: number) => {
|
||||
setSquares((currentSquares) =>
|
||||
currentSquares.map((sq) =>
|
||||
sq.id === id
|
||||
? {
|
||||
...sq,
|
||||
pos: getPos(),
|
||||
}
|
||||
: sq,
|
||||
),
|
||||
);
|
||||
};
|
||||
|
||||
// Update squares to animate in
|
||||
useEffect(() => {
|
||||
if (dimensions.width && dimensions.height) {
|
||||
setSquares(generateSquares(numSquares));
|
||||
}
|
||||
}, [dimensions, numSquares]);
|
||||
|
||||
// Resize observer to update container dimensions
|
||||
useEffect(() => {
|
||||
const resizeObserver = new ResizeObserver((entries) => {
|
||||
for (let entry of entries) {
|
||||
setDimensions({
|
||||
width: entry.contentRect.width,
|
||||
height: entry.contentRect.height,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
if (containerRef.current) {
|
||||
resizeObserver.observe(containerRef.current);
|
||||
}
|
||||
|
||||
return () => {
|
||||
if (containerRef.current) {
|
||||
resizeObserver.unobserve(containerRef.current);
|
||||
}
|
||||
};
|
||||
}, [containerRef]);
|
||||
|
||||
return (
|
||||
<svg
|
||||
ref={containerRef}
|
||||
aria-hidden="true"
|
||||
className={cn(
|
||||
"pointer-events-none absolute inset-0 h-full w-full fill-gray-400/30 stroke-gray-400/30",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<defs>
|
||||
<pattern
|
||||
id={id}
|
||||
width={width}
|
||||
height={height}
|
||||
patternUnits="userSpaceOnUse"
|
||||
x={x}
|
||||
y={y}
|
||||
>
|
||||
<path
|
||||
d={`M.5 ${height}V.5H${width}`}
|
||||
fill="none"
|
||||
strokeDasharray={strokeDasharray}
|
||||
/>
|
||||
</pattern>
|
||||
</defs>
|
||||
<rect width="100%" height="100%" fill={`url(#${id})`} />
|
||||
<svg x={x} y={y} className="overflow-visible">
|
||||
{squares.map(({ pos: [x, y], id }, index) => (
|
||||
<motion.rect
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: maxOpacity }}
|
||||
transition={{
|
||||
duration,
|
||||
repeat: 1,
|
||||
delay: index * 0.1,
|
||||
repeatType: "reverse",
|
||||
}}
|
||||
onAnimationComplete={() => updateSquarePosition(id)}
|
||||
key={`${x}-${y}-${index}`}
|
||||
width={width - 1}
|
||||
height={height - 1}
|
||||
x={x * width + 1}
|
||||
y={y * height + 1}
|
||||
fill="currentColor"
|
||||
strokeWidth="0"
|
||||
/>
|
||||
))}
|
||||
</svg>
|
||||
</svg>
|
||||
);
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
import { cn } from "@/lib/utils";
|
||||
import { AnimatedGridPattern } from "@/components/magicui/animated-grid-pattern";
|
||||
|
||||
export function AnimatedGridPatternDemo() {
|
||||
return (
|
||||
<div className="relative flex h-[500px] w-full items-center justify-center overflow-hidden rounded-lg border bg-background p-20">
|
||||
<AnimatedGridPattern
|
||||
numSquares={30}
|
||||
maxOpacity={0.1}
|
||||
duration={3}
|
||||
repeatDelay={1}
|
||||
className={cn(
|
||||
"[mask-image:radial-gradient(500px_circle_at_center,white,transparent)]",
|
||||
"inset-x-0 inset-y-[-30%] h-[200%] skew-y-12",
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
@ -0,0 +1,17 @@
|
||||
"use client";
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
import { InteractiveGridPattern } from "@/components/magicui/interactive-grid-pattern";
|
||||
|
||||
export function InteractiveGridPatternDemo() {
|
||||
return (
|
||||
<div className="relative flex h-[500px] w-full flex-col items-center justify-center overflow-hidden rounded-lg border bg-background">
|
||||
<InteractiveGridPattern
|
||||
className={cn(
|
||||
"[mask-image:radial-gradient(400px_circle_at_center,white,transparent)]",
|
||||
"inset-x-0 inset-y-[-30%] h-[200%] skew-y-12",
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
72
src/components/magicui/interactive-grid-pattern.tsx
Normal file
72
src/components/magicui/interactive-grid-pattern.tsx
Normal file
@ -0,0 +1,72 @@
|
||||
"use client";
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
import React, { useState } from "react";
|
||||
|
||||
/**
|
||||
* InteractiveGridPattern is a component that renders a grid pattern with interactive squares.
|
||||
*
|
||||
* @param width - The width of each square.
|
||||
* @param height - The height of each square.
|
||||
* @param squares - The number of squares in the grid. The first element is the number of horizontal squares, and the second element is the number of vertical squares.
|
||||
* @param className - The class name of the grid.
|
||||
* @param squaresClassName - The class name of the squares.
|
||||
*/
|
||||
interface InteractiveGridPatternProps extends React.SVGProps<SVGSVGElement> {
|
||||
width?: number;
|
||||
height?: number;
|
||||
squares?: [number, number]; // [horizontal, vertical]
|
||||
className?: string;
|
||||
squaresClassName?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* The InteractiveGridPattern component.
|
||||
*
|
||||
* @see InteractiveGridPatternProps for the props interface.
|
||||
* @returns A React component.
|
||||
*/
|
||||
export function InteractiveGridPattern({
|
||||
width = 40,
|
||||
height = 40,
|
||||
squares = [24, 24],
|
||||
className,
|
||||
squaresClassName,
|
||||
...props
|
||||
}: InteractiveGridPatternProps) {
|
||||
const [horizontal, vertical] = squares;
|
||||
const [hoveredSquare, setHoveredSquare] = useState<number | null>(null);
|
||||
|
||||
return (
|
||||
<svg
|
||||
width={width * horizontal}
|
||||
height={height * vertical}
|
||||
className={cn(
|
||||
"absolute inset-0 h-full w-full border border-gray-400/30",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
{Array.from({ length: horizontal * vertical }).map((_, index) => {
|
||||
const x = (index % horizontal) * width;
|
||||
const y = Math.floor(index / horizontal) * height;
|
||||
return (
|
||||
<rect
|
||||
key={index}
|
||||
x={x}
|
||||
y={y}
|
||||
width={width}
|
||||
height={height}
|
||||
className={cn(
|
||||
"stroke-gray-400/30 transition-all duration-100 ease-in-out [&:not(:hover)]:duration-1000",
|
||||
hoveredSquare === index ? "fill-gray-300/30" : "fill-transparent",
|
||||
squaresClassName,
|
||||
)}
|
||||
onMouseEnter={() => setHoveredSquare(index)}
|
||||
onMouseLeave={() => setHoveredSquare(null)}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</svg>
|
||||
);
|
||||
}
|
Loading…
Reference in New Issue
Block a user