feat: add Ripple component and its demo example to MagicuiPage

This commit is contained in:
javayhu 2025-04-26 10:19:44 +08:00
parent fd6126f0c1
commit 625bee14ef
4 changed files with 86 additions and 4 deletions

View File

@ -4,6 +4,7 @@ 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 { MarqueeDemoVertical } from '@/components/magicui/example/marquee-example';
import { RippleDemo } from '@/components/magicui/example/ripple-example';
/**
* Magic UI Components Showcase Page
@ -15,9 +16,12 @@ export default async function MagicuiPage() {
<div className="mx-auto space-y-8">
<DotPatternDemo />
<GridPatternDemo />
<RippleDemo />
<BentoDemo />
<MarqueeDemoVertical />
<AnimatedListDemo />
<div className="grid md:grid-cols-2 gap-4">
<MarqueeDemoVertical />
<AnimatedListDemo />
</div>
<HeroVideoDialogDemoTopInBottomOut />
</div>
);

View File

@ -0,0 +1,12 @@
import { Ripple } from "@/components/magicui/ripple";
export function RippleDemo() {
return (
<div className="relative flex h-[500px] w-full flex-col items-center justify-center overflow-hidden rounded-lg border bg-background">
<p className="z-10 whitespace-pre-wrap text-center text-5xl font-medium tracking-tighter text-white">
Ripple
</p>
<Ripple />
</div>
);
}

View File

@ -0,0 +1,59 @@
import React, { ComponentPropsWithoutRef, CSSProperties } from "react";
import { cn } from "@/lib/utils";
interface RippleProps extends ComponentPropsWithoutRef<"div"> {
mainCircleSize?: number;
mainCircleOpacity?: number;
numCircles?: number;
}
export const Ripple = React.memo(function Ripple({
mainCircleSize = 210,
mainCircleOpacity = 0.24,
numCircles = 8,
className,
...props
}: RippleProps) {
return (
<div
className={cn(
"pointer-events-none absolute inset-0 select-none [mask-image:linear-gradient(to_bottom,white,transparent)]",
className,
)}
{...props}
>
{Array.from({ length: numCircles }, (_, i) => {
const size = mainCircleSize + i * 70;
const opacity = mainCircleOpacity - i * 0.03;
const animationDelay = `${i * 0.06}s`;
const borderStyle = i === numCircles - 1 ? "dashed" : "solid";
const borderOpacity = 5 + i * 5;
return (
<div
key={i}
className={`absolute animate-ripple rounded-full border bg-foreground/25 shadow-xl`}
style={
{
"--i": i,
width: `${size}px`,
height: `${size}px`,
opacity,
animationDelay,
borderStyle,
borderWidth: "1px",
borderColor: `hsl(var(--foreground), ${borderOpacity / 100})`,
top: "50%",
left: "50%",
transform: "translate(-50%, -50%) scale(1)",
} as CSSProperties
}
/>
);
})}
</div>
);
});
Ripple.displayName = "Ripple";

View File

@ -130,7 +130,14 @@
transform: translateY(calc(-100% - var(--gap)));
}
}
}
--animate-ripple: ripple var(--duration,2s) ease calc(var(--i, 0)*.2s) infinite
;
@keyframes ripple {
0%, 100% {
transform: translate(-50%, -50%) scale(1);}
50% {
transform: translate(-50%, -50%) scale(0.9);}}}
:root {
--radius: 0.5rem;
@ -283,4 +290,4 @@ body {
--primary: var(--color-amber-500);
--primary-foreground: var(--color-amber-50);
}
}
}