{"$schema":"https://ui.shadcn.com/schema/registry-item.json","name":"motion-text-morph","type":"registry:lib","title":"Motion text-morph","description":"Text that morphs into its next state letter by letter: the letters both have glide to their new places, the new ones","dependencies":["motion"],"registryDependencies":["https://dashral.com/r/utils.json","https://dashral.com/r/motion-reduced.json","https://dashral.com/r/motion-tokens.json"],"files":[{"path":"registry/dashral/lib/motion-text-morph.tsx","type":"registry:file","target":"components/dashral/lib/motion-text-morph.tsx","content":"\"use client\"\n\n// Text that morphs into its next state letter by letter: the letters both have glide to their new places, the new ones\n// rise in out of a soft blur, and the ones it no longer has fade away (\"Follow\" to \"Following\" keeps \"Follow\" where it\n// is and grows \"ing\"). The motion Button uses it for a label that's only text; use it for any short text that changes.\n\nimport * as React from \"react\"\nimport { AnimatePresence, LayoutGroup, motion } from \"motion/react\"\nimport { cn } from \"./utils\"\n\nimport { useReducedMotion } from \"./motion-reduced\"\nimport { duration, ease, spring } from \"./motion-tokens\"\n\n/** The letters, each keyed by itself and how many of it came before (\"l-0\", \"l-1\"), so a shared letter is the same one. */\nfunction lettersOf(text: string) {\n  const seen = new Map<string, number>()\n  return [...text].map((char) => {\n    const n = seen.get(char) ?? 0\n    seen.set(char, n + 1)\n    return { char, key: `${char}-${n}` }\n  })\n}\n\n/**\n * A short text that morphs into the next one letter by letter. Screen readers hear the text as a whole; the letters\n * are only for the eye. With reduced motion the new text fades in.\n */\nexport function TextMorph({ children, className }: { children: string; className?: string }) {\n  const reduced = useReducedMotion()\n  const group = React.useId()\n  return (\n    <span className={cn(\"relative inline-flex whitespace-pre\", className)}>\n      <span className=\"sr-only\">{children}</span>\n      <LayoutGroup id={group}>\n        {/* popLayout lifts a leaving letter out of the line at once, so the others glide to their places straight away. */}\n        <AnimatePresence mode=\"popLayout\" initial={false}>\n          {lettersOf(children).map(({ char, key }) => (\n            <motion.span\n              key={key}\n              aria-hidden\n              className=\"inline-block\"\n              layout={reduced ? false : \"position\"}\n              initial={reduced ? { opacity: 0 } : { opacity: 0, y: \"0.3em\", filter: \"blur(3px)\" }}\n              animate={{ opacity: 1, y: 0, filter: \"blur(0px)\" }}\n              exit={reduced ? { opacity: 0, transition: { duration: 0 } } : { opacity: 0, y: \"-0.3em\", filter: \"blur(3px)\", transition: { duration: duration.fast, ease: ease.exit } }}\n              transition={{ layout: spring.glide, default: { duration: duration.standard, ease: ease.enter } }}\n            >\n              {char === \" \" ? \" \" : char}\n            </motion.span>\n          ))}\n        </AnimatePresence>\n      </LayoutGroup>\n    </span>\n  )\n}\n"}]}