Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion apps/www/public/registry/styles/default/typewriter.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"files": [
{
"name": "typewriter.tsx",
"content": "\"use client\"\n\nimport { useEffect, useState } from \"react\"\nimport { animate, motion, useMotionValue, useTransform } from \"framer-motion\"\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: \"easeInOut\",\n onComplete: () => setAnimationComplete(true),\n })\n return () => {\n controls.stop && controls.stop()\n }\n }, [count, baseText.length, delay])\n\n return (\n <span>\n <motion.span>{displayText}</motion.span>\n {animationComplete && (\n <RepeatedTextAnimation texts={texts} delay={delay + 1} />\n )}\n <BlinkingCursor />\n </span>\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: \"easeIn\",\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 <motion.span className=\"inline\">{displayText}</motion.span>\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: \"linear\",\n times: [0, 0.5, 0.5, 1],\n },\n },\n}\n\nfunction BlinkingCursor() {\n return (\n <motion.div\n variants={cursorVariants}\n animate=\"blinking\"\n className=\"inline-block h-5 w-[1px] translate-y-1 bg-neutral-900\"\n />\n )\n}\n"
"content": "\"use client\"\n\nimport { useEffect, useState } from \"react\"\nimport { animate, motion, useMotionValue, useTransform } from \"framer-motion\"\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: \"easeInOut\",\n onComplete: () => setAnimationComplete(true),\n })\n return () => {\n controls.stop && controls.stop()\n }\n }, [count, baseText.length, delay])\n\n return (\n <span>\n <motion.span>{displayText}</motion.span>\n {animationComplete && (\n <RepeatedTextAnimation texts={texts} delay={delay + 1} />\n )}\n <BlinkingCursor />\n </span>\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: \"easeIn\",\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 (\n <motion.span className=\"inline whitespace-pre-wrap hyphens-auto\">\n {displayText}\n </motion.span>\n )\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: \"linear\",\n times: [0, 0.5, 0.5, 1],\n },\n },\n}\n\nfunction BlinkingCursor() {\n return (\n <motion.div\n variants={cursorVariants}\n animate=\"blinking\"\n className=\"inline-block h-5 w-[1px] translate-y-1 bg-neutral-900\"\n />\n )\n}\n"
}
],
"type": "components:ui"
Expand Down
6 changes: 5 additions & 1 deletion apps/www/registry/default/ui/typewriter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,11 @@ function RepeatedTextAnimation({
}
}, [count, delay, textIndex, texts, updatedThisRound])

return <motion.span className="inline">{displayText}</motion.span>
return (
<motion.span className="inline whitespace-pre-wrap hyphens-auto">
{displayText}
</motion.span>
)
}

const cursorVariants = {
Expand Down