feat: add FadeIn and TypingCursor utility components

This commit is contained in:
ashim-hq
2026-04-23 17:21:22 +08:00
parent 2260d696bc
commit d22e4c2596
2 changed files with 76 additions and 0 deletions
+26
View File
@@ -0,0 +1,26 @@
"use client";
import { motion } from "framer-motion";
import type { ReactNode } from "react";
export function FadeIn({
children,
className,
delay = 0,
}: {
children: ReactNode;
className?: string;
delay?: number;
}) {
return (
<motion.div
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-100px" }}
transition={{ duration: 0.5, delay, ease: "easeOut" }}
className={className}
>
{children}
</motion.div>
);
}
@@ -0,0 +1,50 @@
"use client";
import { AnimatePresence, motion } from "framer-motion";
import { useCallback, useEffect, useState } from "react";
const phrases = [
"100% local processing.",
"Zero data leaves your network.",
"50+ image tools.",
"14 AI models. Your hardware.",
"Air-gapped ready.",
"Enterprise-grade. Free forever.",
"One Docker container.",
"Open source. Always.",
];
export function TypingCursor() {
const [index, setIndex] = useState(0);
const advance = useCallback(() => {
setIndex((i) => (i + 1) % phrases.length);
}, []);
useEffect(() => {
const timer = setInterval(advance, 3000);
return () => clearInterval(timer);
}, [advance]);
return (
<span className="inline-flex items-center">
<AnimatePresence mode="wait">
<motion.span
key={phrases[index]}
initial={{ opacity: 0, y: 10 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: -10 }}
transition={{ duration: 0.3 }}
className="text-accent"
>
{phrases[index]}
</motion.span>
</AnimatePresence>
<motion.span
animate={{ opacity: [1, 0] }}
transition={{ duration: 0.8, repeat: Infinity, repeatType: "reverse" }}
className="ml-0.5 inline-block w-[3px] h-[1em] bg-accent align-middle"
/>
</span>
);
}