{ "$schema": "https://ui.shadcn.com/schema/registry-item.json", "name": "typewriter", "type": "registry:ui", "description": "Typewriter effect component with customizable typing speed and cursor animation", "dependencies": [ "motion" ], "files": [ { "path": "registry/default/ui/typewriter.tsx", "content": "\"use client\"\n\nimport { useEffect, useState } from \"react\"\nimport { animate, motion, useMotionValue, useTransform } from \"motion/react\"\n\nexport interface ITypewriterProps {\n delay: number\n texts: string[]\n baseText?: string\n}\n\nexport function Typewriter({ delay, texts, baseText = \"\" }: ITypewriterProps) {\n const [animationComplete, setAnimationComplete] = useState(false)\n const count = useMotionValue(0)\n const rounded = useTransform(count, (latest) => Math.round(latest))\n const displayText = useTransform(rounded, (latest) =>\n baseText.slice(0, latest)\n )\n\n useEffect(() => {\n const controls = animate(count, baseText.length, {\n type: \"tween\",\n delay,\n duration: 1,\n ease: [0.42, 0, 0.58, 1] as const,\n onComplete: () => setAnimationComplete(true),\n })\n return () => {\n controls.stop && controls.stop()\n }\n }, [count, baseText.length, delay])\n\n return (\n \n {displayText}\n {animationComplete && (\n \n )}\n \n \n )\n}\n\nexport interface IRepeatedTextAnimationProps {\n delay: number\n texts: string[]\n}\n\nconst defaultTexts = [\n \"quiz page with questions and answers\",\n \"blog Article Details Page Layout\",\n \"ecommerce dashboard with a sidebar\",\n \"ui like platform.openai.com....\",\n \"buttttton\",\n \"aop that tracks non-standard split sleep cycles\",\n \"transparent card to showcase achievements of a user\",\n]\nfunction RepeatedTextAnimation({\n delay,\n texts = defaultTexts,\n}: IRepeatedTextAnimationProps) {\n const textIndex = useMotionValue(0)\n\n const baseText = useTransform(textIndex, (latest) => texts[latest] || \"\")\n const count = useMotionValue(0)\n const rounded = useTransform(count, (latest) => Math.round(latest))\n const displayText = useTransform(rounded, (latest) =>\n baseText.get().slice(0, latest)\n )\n const updatedThisRound = useMotionValue(true)\n\n useEffect(() => {\n const animation = animate(count, 60, {\n type: \"tween\",\n delay,\n duration: 1,\n ease: [0.42, 0, 1, 1] as const,\n repeat: Infinity,\n repeatType: \"reverse\",\n repeatDelay: 1,\n onUpdate(latest) {\n if (updatedThisRound.get() && latest > 0) {\n updatedThisRound.set(false)\n } else if (!updatedThisRound.get() && latest === 0) {\n textIndex.set((textIndex.get() + 1) % texts.length)\n updatedThisRound.set(true)\n }\n },\n })\n return () => {\n animation.stop && animation.stop()\n }\n }, [count, delay, textIndex, texts, updatedThisRound])\n\n return {displayText}\n}\n\nconst cursorVariants = {\n blinking: {\n opacity: [0, 0, 1, 1],\n transition: {\n duration: 1,\n repeat: Infinity,\n repeatDelay: 0,\n ease: [0, 0, 1, 1] as const,\n times: [0, 0.5, 0.5, 1],\n },\n },\n}\n\nfunction BlinkingCursor() {\n return (\n \n )\n}\n", "type": "registry:ui" } ] }