{ "$schema": "https://ui.shadcn.com/schema/registry-item.json", "name": "native-typewriter", "type": "registry:component", "title": "Native Typewriter", "description": "Typewriter effect with speed control, looping, and blinking cursor.", "dependencies": [ "framer-motion", "react" ], "files": [ { "path": "@uitripled/react-carbon/src/components/native/native-typewriter-carbon.tsx", "content": "\"use client\";\n\nimport { cn } from \"@/lib/utils\";\nimport { motion, useReducedMotion } from \"framer-motion\";\nimport { useEffect, useState } from \"react\";\n\ninterface NativeTypewriterProps {\n /**\n * The text content to type. Can be a string or an array of strings.\n * If an array, it will type each string in sequence (or loop if loop=true).\n */\n content: string | string[];\n /**\n * Typing speed.\n * 'slow' = 100ms, 'medium' = 50ms, 'fast' = 30ms.\n * Or pass a number in ms.\n * Default: 'medium'\n */\n speed?: \"slow\" | \"medium\" | \"fast\" | number;\n /**\n * Whether to show a blinking cursor.\n * Default: true\n */\n cursor?: boolean;\n /**\n * Whether to loop the typing animation.\n * Default: false\n */\n loop?: boolean;\n /**\n * Delay before starting animation in ms.\n * Default: 0\n */\n startDelay?: number;\n /**\n * Whether to apply a blur/morph effect to each character.\n * Default: true\n */\n morph?: boolean;\n className?: string;\n}\n\nexport function NativeTypewriter({\n content,\n speed = \"medium\",\n cursor = true,\n loop = false,\n startDelay = 0,\n morph = true,\n className,\n}: NativeTypewriterProps) {\n const shouldReduceMotion = useReducedMotion();\n const [displayedText, setDisplayedText] = useState(\"\");\n const [isStarted, setIsStarted] = useState(false);\n\n // Calculate delay calculation\n const speedMap = {\n slow: 100,\n medium: 50,\n fast: 30,\n };\n const typingSpeed = typeof speed === \"number\" ? speed : speedMap[speed];\n\n useEffect(() => {\n // If reduced motion is enabled, just show the full text immediately\n if (shouldReduceMotion) {\n setDisplayedText(Array.isArray(content) ? content.join(\" \") : content);\n return;\n }\n\n let timeoutId: NodeJS.Timeout;\n let currentIndex = 0;\n let currentStringIndex = 0;\n\n // Normalize content to array\n const textArray = Array.isArray(content) ? content : [content];\n\n // Actually, simpler logic for a first pass is often better.\n // Let's implement a robust \"Type -> Wait -> (Delete if multi/loop) -> Next\" cycle.\n\n let isDeleting = false;\n\n // Unified typing loop logic\n const runLoop = () => {\n const currentString = textArray[currentStringIndex];\n\n if (isDeleting) {\n setDisplayedText(currentString.substring(0, currentIndex));\n currentIndex--;\n if (currentIndex < 0) {\n isDeleting = false;\n currentIndex = 0;\n currentStringIndex = (currentStringIndex + 1) % textArray.length;\n if (!loop && currentStringIndex === 0) {\n return;\n }\n timeoutId = setTimeout(runLoop, 500);\n } else {\n timeoutId = setTimeout(runLoop, typingSpeed / 2);\n }\n } else {\n setDisplayedText(currentString.substring(0, currentIndex + 1));\n currentIndex++;\n if (currentIndex > currentString.length) {\n if (\n (textArray.length > 1 &&\n (loop || currentStringIndex < textArray.length - 1)) ||\n (textArray.length === 1 && loop)\n ) {\n isDeleting = true;\n currentIndex = currentString.length;\n timeoutId = setTimeout(runLoop, 2000);\n }\n } else {\n timeoutId = setTimeout(runLoop, typingSpeed);\n }\n }\n };\n\n const initialTimer = setTimeout(() => {\n setIsStarted(true);\n runLoop();\n }, startDelay);\n\n return () => {\n clearTimeout(initialTimer);\n clearTimeout(timeoutId);\n };\n }, [content, typingSpeed, loop, startDelay, shouldReduceMotion]);\n\n return (\n