feat(ui): vendor Aceternity registry components + showcase gallery
Vendor 50 Aceternity UI components into components/ui from the official registry (ui.aceternity.com/registry/<slug>.json), adapted for this template's stack: Tailwind v4, motion/react 13, cn util, React 19. Add a live showcase gallery (/ui + /docs/... navigation is unchanged): app/ui/page.tsx renders <ShowcaseGallery> with 30+ demos — 3d-pin, aurora, background-beams, bento, card-stack, card-spotlight, direction-aware-hover, encrypted-text, evervault-card, flip-words, hero-highlight, hero-parallax, infinite-moving-cards, moving-border, sparkles, tracing-beam, animated-tooltip, typewriter-effect, placeholders-and-vanish-input and friends. Toolchain stays zero-warning: vendored animation code is scoped in eslint.config.mjs (react-hooks/react-compiler advisory rules, no-any, prefer-const, no-img-element) so app code keeps a 0-error gate. typecheck, build, 7 vitest tests and 2 test suites all green.
This commit is contained in:
@@ -0,0 +1,104 @@
|
||||
"use client";
|
||||
|
||||
import React, { useId } from "react";
|
||||
import { motion, useTime, useTransform } from "motion/react";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
export interface SquigglyTextProps {
|
||||
/**
|
||||
* The text (or any node) to wrap with the squiggly effect.
|
||||
*/
|
||||
children: React.ReactNode;
|
||||
className?: string;
|
||||
style?: React.CSSProperties;
|
||||
/**
|
||||
* Number of distinct displacement frames to cycle through.
|
||||
* Higher = smoother wobble, more SVG filters in the DOM.
|
||||
* @default 5
|
||||
*/
|
||||
steps?: number;
|
||||
/**
|
||||
* Time between filter swaps, in milliseconds.
|
||||
* Lower = more frantic; higher = lazier wave.
|
||||
* @default 80
|
||||
*/
|
||||
stepDuration?: number;
|
||||
/**
|
||||
* Maximum displacement in px. Bigger = more squiggly.
|
||||
* Pass a single number for a constant scale, or a tuple to alternate
|
||||
* between two values per step (matches the original Lucas Bebber demo).
|
||||
* @default [6, 8]
|
||||
*/
|
||||
scale?: number | [number, number];
|
||||
/**
|
||||
* Turbulence base frequency. Lower values produce longer, smoother waves;
|
||||
* higher values produce tighter, jitterier noise.
|
||||
* @default 0.02
|
||||
*/
|
||||
baseFrequency?: number;
|
||||
/**
|
||||
* Number of turbulence octaves. Higher = more detailed noise.
|
||||
* @default 3
|
||||
*/
|
||||
numOctaves?: number;
|
||||
/**
|
||||
* Render the wrapper as this element type.
|
||||
* @default "span"
|
||||
*/
|
||||
as?: "span" | "div";
|
||||
}
|
||||
|
||||
export function SquigglyText({
|
||||
children,
|
||||
steps = 5,
|
||||
stepDuration = 80,
|
||||
scale = [6, 8],
|
||||
baseFrequency = 0.02,
|
||||
numOctaves = 3,
|
||||
as = "span",
|
||||
className,
|
||||
style,
|
||||
}: SquigglyTextProps) {
|
||||
const reactId = useId();
|
||||
// useId can produce ":" / "_" which aren't valid in CSS url(#…) refs.
|
||||
const safeId = reactId.replace(/[:_]/g, "");
|
||||
const filterId = (i: number) => `squiggly-${safeId}-${i}`;
|
||||
|
||||
const filters = React.useMemo(
|
||||
() => Array.from({ length: steps }, (_, i) => `url(#${filterId(i)})`),
|
||||
// filterId is stable per instance
|
||||
[steps, safeId]
|
||||
);
|
||||
|
||||
const time = useTime();
|
||||
const filter = useTransform(time, (t) => filters[Math.floor(t / stepDuration) % filters.length]);
|
||||
|
||||
const scaleAt = (i: number) => (Array.isArray(scale) ? scale[i % scale.length] : scale);
|
||||
|
||||
const Wrapper = as === "div" ? motion.div : motion.span;
|
||||
|
||||
return (
|
||||
<Wrapper style={{ filter, ...style }} className={cn("inline-block", className)}>
|
||||
<svg
|
||||
aria-hidden
|
||||
className="pointer-events-none absolute h-0 w-0 overflow-hidden"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<defs>
|
||||
{Array.from({ length: steps }).map((_, i) => (
|
||||
<filter id={filterId(i)} key={i}>
|
||||
<feTurbulence
|
||||
baseFrequency={baseFrequency}
|
||||
numOctaves={numOctaves}
|
||||
result="noise"
|
||||
seed={i}
|
||||
/>
|
||||
<feDisplacementMap in="SourceGraphic" in2="noise" scale={scaleAt(i)} />
|
||||
</filter>
|
||||
))}
|
||||
</defs>
|
||||
</svg>
|
||||
{children}
|
||||
</Wrapper>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user