{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "text-rotate",
  "type": "registry:block",
  "title": "Text rotate",
  "description": "Text rotate",
  "files": [
    {
      "path": "components/usages/textrotateusage.tsx",
      "content": "\"use client\";\r\n\r\nimport { useEffect, useRef } from \"react\";\r\n\r\nimport TextRotate, { TextRotateRef } from \"@/registry/open-source/text-rotate\";\r\nimport { LayoutGroup, motion, useInView } from \"motion/react\";\r\n\r\nfunction Item({\r\n\tindex,\r\n\timage,\r\n\tlink,\r\n\tonInView,\r\n}: {\r\n\tindex: number;\r\n\timage: string;\r\n\tlink: string;\r\n\tonInView: (inView: boolean) => void;\r\n}) {\r\n\tconst ref = useRef<HTMLDivElement>(null);\r\n\tconst isInView = useInView(ref, {\r\n\t\tmargin: \"-45% 0px -45% 0px\",\r\n\t});\r\n\r\n\tuseEffect(() => {\r\n\t\tonInView(isInView);\r\n\t}, [isInView, onInView]);\r\n\r\n\treturn (\r\n\t\t<section\r\n\t\t\tref={ref}\r\n\t\t\tkey={index + 1}\r\n\t\t\tclassName=\"h-full w-1/2 flex justify-center items-center snap-center\"\r\n\t\t>\r\n\t\t\t<div className=\"w-16 h-16 sm:w-36 sm:h-36 md:w-40 md:h-40\">\r\n\t\t\t\t<a href={link} target=\"_blank\" rel=\"noreferrer\">\r\n\t\t\t\t\t<img\r\n\t\t\t\t\t\tsrc={image}\r\n\t\t\t\t\t\talt={`Example ${index + 2}`}\r\n\t\t\t\t\t\tclassName=\"w-full h-full object-cover\"\r\n\t\t\t\t\t/>\r\n\t\t\t\t</a>\r\n\t\t\t</div>\r\n\t\t</section>\r\n\t);\r\n}\r\n\r\nexport default function TextRotateUsage() {\r\n\tconst textRotateRef = useRef<TextRotateRef>(null);\r\n\r\n\tconst handleInView = (index: number, inView: boolean) => {\r\n\t\tconsole.log(index, inView);\r\n\t\tif (inView && textRotateRef.current) {\r\n\t\t\ttextRotateRef.current.jumpTo(index);\r\n\t\t}\r\n\t};\r\n\r\n\treturn (\r\n\t\t<div className=\"w-dvw h-dvh text-2xl sm:text-3xl md:text-5xl flex flex-row items-center justify-center font-overused-grotesk bg-background dark:text-muted text-foreground font-light overflow-hidden p-12 sm:p-20 md:p-24\">\r\n\t\t\t<LayoutGroup>\r\n\t\t\t\t<motion.div className=\"flex whitespace-pre\" layout>\r\n\t\t\t\t\t<motion.span\r\n\t\t\t\t\t\tclassName=\"pt-0.5 sm:pt-1 md:pt-2\"\r\n\t\t\t\t\t\tlayout\r\n\t\t\t\t\t\ttransition={{ type: \"spring\", damping: 30, stiffness: 400 }}\r\n\t\t\t\t\t>\r\n\t\t\t\t\t\tMake it{\" \"}\r\n\t\t\t\t\t</motion.span>\r\n\t\t\t\t\t<TextRotate\r\n\t\t\t\t\t\ttexts={[\r\n\t\t\t\t\t\t\t\"work!\",\r\n\t\t\t\t\t\t\t\"fancy ✽\",\r\n\t\t\t\t\t\t\t\"right\",\r\n\t\t\t\t\t\t\t\"fast\",\r\n\t\t\t\t\t\t\t\"fun\",\r\n\t\t\t\t\t\t\t\"rock\",\r\n\t\t\t\t\t\t\t\"🕶️🕶️🕶️\",\r\n\t\t\t\t\t\t]}\r\n\t\t\t\t\t\tmainClassName=\"text-secondary px-2 sm:px-2 md:px-3 bg-background overflow-hidden py-0.5 sm:py-1 md:py-2 justify-center rounded-lg\"\r\n\t\t\t\t\t\tstaggerFrom={\"last\"}\r\n\t\t\t\t\t\tinitial={{ y: \"100%\" }}\r\n\t\t\t\t\t\tanimate={{ y: 0 }}\r\n\t\t\t\t\t\texit={{ y: \"-120%\" }}\r\n\t\t\t\t\t\tstaggerDuration={0.025}\r\n\t\t\t\t\t\tsplitLevelClassName=\"overflow-hidden pb-0.5 sm:pb-1 md:pb-1\"\r\n\t\t\t\t\t\ttransition={{ type: \"spring\", damping: 30, stiffness: 400 }}\r\n\t\t\t\t\t\trotationInterval={2000}\r\n\t\t\t\t\t/>\r\n\t\t\t\t\t<TextRotate\r\n\t\t\t\t\t\ttexts={[\r\n\t\t\t\t\t\t\t\"The problem isn't how to make the world more technological. It's about how to make the world more humane again.\",\r\n\t\t\t\t\t\t\t\"When you use other people's software you live in somebody else's dream.\",\r\n\t\t\t\t\t\t]}\r\n\t\t\t\t\t\tmainClassName=\" md:leading-10 flex whitespace-pre text-lg sm:text-xl md:text-5xl max-w-xl text-center\"\r\n\t\t\t\t\t\tstaggerFrom={\"random\"}\r\n\t\t\t\t\t\tanimatePresenceMode=\"wait\"\r\n\t\t\t\t\t\tsplitBy=\"characters\"\r\n\t\t\t\t\t\tinitial={[{ filter: \"blur(20px)\", opacity: 0 }]}\r\n\t\t\t\t\t\tanimate={[{ filter: \"blur(0px)\", opacity: 1 }]}\r\n\t\t\t\t\t\texit={[{ filter: \"blur(20px)\", opacity: 0 }]}\r\n\t\t\t\t\t\tloop\r\n\t\t\t\t\t\tstaggerDuration={0.01}\r\n\t\t\t\t\t\tsplitLevelClassName=\"\"\r\n\t\t\t\t\t\telementLevelClassName=\"md:py-[4px]\"\r\n\t\t\t\t\t\ttransition={{\r\n\t\t\t\t\t\t\tease: [0.909, 0.151, 0.153, 0.86],\r\n\t\t\t\t\t\t\tduration: 1,\r\n\t\t\t\t\t\t}}\r\n\t\t\t\t\t\trotationInterval={4000}\r\n\t\t\t\t\t/>\r\n\t\t\t\t</motion.div>\r\n\t\t\t</LayoutGroup>\r\n\t\t\t{/* <div className=\"w-dvw h-dvh overflow-auto absolute snap-y snap-mandatory\">\r\n\t\t\t\t<div className=\"sticky inset-0 h-full w-full flex items-center justify-end bg-background dark:text-muted text-foreground\">\r\n\t\t\t\t\t<div className=\"w-2/3\">\r\n\t\t\t\t\t\t<TextRotate\r\n\t\t\t\t\t\t\tref={textRotateRef}\r\n\t\t\t\t\t\t\ttexts={[\r\n\t\t\t\t\t\t\t\t[\"/itjustworks.jpg\", \"/itjustworks.jpg\"].map(\r\n\t\t\t\t\t\t\t\t\t(image) => \"dan\"\r\n\t\t\t\t\t\t\t\t),\r\n\t\t\t\t\t\t\t]}\r\n\t\t\t\t\t\t\tmainClassName=\"text-sm sm:text-3xl md:text-4xl w-full justify-center flex pt-2\"\r\n\t\t\t\t\t\t\tsplitLevelClassName=\"overflow-hidden pb-2\"\r\n\t\t\t\t\t\t\tstaggerFrom={\"first\"}\r\n\t\t\t\t\t\t\tanimatePresenceMode=\"wait\"\r\n\t\t\t\t\t\t\tloop={false}\r\n\t\t\t\t\t\t\tauto={false}\r\n\t\t\t\t\t\t\tstaggerDuration={0.005}\r\n\t\t\t\t\t\t\tinitial={{ opacity: 0, y: 50 }}\r\n\t\t\t\t\t\t\tanimate={{ opacity: 1, y: 0 }}\r\n\t\t\t\t\t\t\texit={{ opacity: 0, y: -50 }}\r\n\t\t\t\t\t\t\ttransition={{ type: \"spring\", duration: 0.6, bounce: 0 }}\r\n\t\t\t\t\t\t/>\r\n\t\t\t\t\t</div>\r\n\t\t\t\t</div>\r\n\t\t\t\t<div className=\"absolute inset-0\">\r\n\t\t\t\t\t{[\"/itjustworks.jpg\", \"/itjustworks.jpg\"]\r\n\t\t\t\t\t\t.slice(1)\r\n\t\t\t\t\t\t.map((image, index) => (\r\n\t\t\t\t\t\t\t<Item\r\n\t\t\t\t\t\t\t\tkey={index}\r\n\t\t\t\t\t\t\t\tindex={index}\r\n\t\t\t\t\t\t\t\timage={image.url}\r\n\t\t\t\t\t\t\t\tlink={image.link}\r\n\t\t\t\t\t\t\t\tonInView={(inView) => handleInView(index, inView)}\r\n\t\t\t\t\t\t\t/>\r\n\t\t\t\t\t\t))}\r\n\t\t\t\t</div>\r\n\t\t\t</div> */}\r\n\t\t</div>\r\n\t);\r\n}\r\n",
      "type": "registry:block",
      "target": "~/example.tsx"
    },
    {
      "path": "components/usages/textrotateusage.tsx",
      "content": "\"use client\";\r\n\r\nimport { useEffect, useRef } from \"react\";\r\n\r\nimport TextRotate, { TextRotateRef } from \"@/registry/open-source/text-rotate\";\r\nimport { LayoutGroup, motion, useInView } from \"motion/react\";\r\n\r\nfunction Item({\r\n\tindex,\r\n\timage,\r\n\tlink,\r\n\tonInView,\r\n}: {\r\n\tindex: number;\r\n\timage: string;\r\n\tlink: string;\r\n\tonInView: (inView: boolean) => void;\r\n}) {\r\n\tconst ref = useRef<HTMLDivElement>(null);\r\n\tconst isInView = useInView(ref, {\r\n\t\tmargin: \"-45% 0px -45% 0px\",\r\n\t});\r\n\r\n\tuseEffect(() => {\r\n\t\tonInView(isInView);\r\n\t}, [isInView, onInView]);\r\n\r\n\treturn (\r\n\t\t<section\r\n\t\t\tref={ref}\r\n\t\t\tkey={index + 1}\r\n\t\t\tclassName=\"h-full w-1/2 flex justify-center items-center snap-center\"\r\n\t\t>\r\n\t\t\t<div className=\"w-16 h-16 sm:w-36 sm:h-36 md:w-40 md:h-40\">\r\n\t\t\t\t<a href={link} target=\"_blank\" rel=\"noreferrer\">\r\n\t\t\t\t\t<img\r\n\t\t\t\t\t\tsrc={image}\r\n\t\t\t\t\t\talt={`Example ${index + 2}`}\r\n\t\t\t\t\t\tclassName=\"w-full h-full object-cover\"\r\n\t\t\t\t\t/>\r\n\t\t\t\t</a>\r\n\t\t\t</div>\r\n\t\t</section>\r\n\t);\r\n}\r\n\r\nexport default function TextRotateUsage() {\r\n\tconst textRotateRef = useRef<TextRotateRef>(null);\r\n\r\n\tconst handleInView = (index: number, inView: boolean) => {\r\n\t\tconsole.log(index, inView);\r\n\t\tif (inView && textRotateRef.current) {\r\n\t\t\ttextRotateRef.current.jumpTo(index);\r\n\t\t}\r\n\t};\r\n\r\n\treturn (\r\n\t\t<div className=\"w-dvw h-dvh text-2xl sm:text-3xl md:text-5xl flex flex-row items-center justify-center font-overused-grotesk bg-background dark:text-muted text-foreground font-light overflow-hidden p-12 sm:p-20 md:p-24\">\r\n\t\t\t<LayoutGroup>\r\n\t\t\t\t<motion.div className=\"flex whitespace-pre\" layout>\r\n\t\t\t\t\t<motion.span\r\n\t\t\t\t\t\tclassName=\"pt-0.5 sm:pt-1 md:pt-2\"\r\n\t\t\t\t\t\tlayout\r\n\t\t\t\t\t\ttransition={{ type: \"spring\", damping: 30, stiffness: 400 }}\r\n\t\t\t\t\t>\r\n\t\t\t\t\t\tMake it{\" \"}\r\n\t\t\t\t\t</motion.span>\r\n\t\t\t\t\t<TextRotate\r\n\t\t\t\t\t\ttexts={[\r\n\t\t\t\t\t\t\t\"work!\",\r\n\t\t\t\t\t\t\t\"fancy ✽\",\r\n\t\t\t\t\t\t\t\"right\",\r\n\t\t\t\t\t\t\t\"fast\",\r\n\t\t\t\t\t\t\t\"fun\",\r\n\t\t\t\t\t\t\t\"rock\",\r\n\t\t\t\t\t\t\t\"🕶️🕶️🕶️\",\r\n\t\t\t\t\t\t]}\r\n\t\t\t\t\t\tmainClassName=\"text-secondary px-2 sm:px-2 md:px-3 bg-background overflow-hidden py-0.5 sm:py-1 md:py-2 justify-center rounded-lg\"\r\n\t\t\t\t\t\tstaggerFrom={\"last\"}\r\n\t\t\t\t\t\tinitial={{ y: \"100%\" }}\r\n\t\t\t\t\t\tanimate={{ y: 0 }}\r\n\t\t\t\t\t\texit={{ y: \"-120%\" }}\r\n\t\t\t\t\t\tstaggerDuration={0.025}\r\n\t\t\t\t\t\tsplitLevelClassName=\"overflow-hidden pb-0.5 sm:pb-1 md:pb-1\"\r\n\t\t\t\t\t\ttransition={{ type: \"spring\", damping: 30, stiffness: 400 }}\r\n\t\t\t\t\t\trotationInterval={2000}\r\n\t\t\t\t\t/>\r\n\t\t\t\t\t<TextRotate\r\n\t\t\t\t\t\ttexts={[\r\n\t\t\t\t\t\t\t\"The problem isn't how to make the world more technological. It's about how to make the world more humane again.\",\r\n\t\t\t\t\t\t\t\"When you use other people's software you live in somebody else's dream.\",\r\n\t\t\t\t\t\t]}\r\n\t\t\t\t\t\tmainClassName=\" md:leading-10 flex whitespace-pre text-lg sm:text-xl md:text-5xl max-w-xl text-center\"\r\n\t\t\t\t\t\tstaggerFrom={\"random\"}\r\n\t\t\t\t\t\tanimatePresenceMode=\"wait\"\r\n\t\t\t\t\t\tsplitBy=\"characters\"\r\n\t\t\t\t\t\tinitial={[{ filter: \"blur(20px)\", opacity: 0 }]}\r\n\t\t\t\t\t\tanimate={[{ filter: \"blur(0px)\", opacity: 1 }]}\r\n\t\t\t\t\t\texit={[{ filter: \"blur(20px)\", opacity: 0 }]}\r\n\t\t\t\t\t\tloop\r\n\t\t\t\t\t\tstaggerDuration={0.01}\r\n\t\t\t\t\t\tsplitLevelClassName=\"\"\r\n\t\t\t\t\t\telementLevelClassName=\"md:py-[4px]\"\r\n\t\t\t\t\t\ttransition={{\r\n\t\t\t\t\t\t\tease: [0.909, 0.151, 0.153, 0.86],\r\n\t\t\t\t\t\t\tduration: 1,\r\n\t\t\t\t\t\t}}\r\n\t\t\t\t\t\trotationInterval={4000}\r\n\t\t\t\t\t/>\r\n\t\t\t\t</motion.div>\r\n\t\t\t</LayoutGroup>\r\n\t\t\t{/* <div className=\"w-dvw h-dvh overflow-auto absolute snap-y snap-mandatory\">\r\n\t\t\t\t<div className=\"sticky inset-0 h-full w-full flex items-center justify-end bg-background dark:text-muted text-foreground\">\r\n\t\t\t\t\t<div className=\"w-2/3\">\r\n\t\t\t\t\t\t<TextRotate\r\n\t\t\t\t\t\t\tref={textRotateRef}\r\n\t\t\t\t\t\t\ttexts={[\r\n\t\t\t\t\t\t\t\t[\"/itjustworks.jpg\", \"/itjustworks.jpg\"].map(\r\n\t\t\t\t\t\t\t\t\t(image) => \"dan\"\r\n\t\t\t\t\t\t\t\t),\r\n\t\t\t\t\t\t\t]}\r\n\t\t\t\t\t\t\tmainClassName=\"text-sm sm:text-3xl md:text-4xl w-full justify-center flex pt-2\"\r\n\t\t\t\t\t\t\tsplitLevelClassName=\"overflow-hidden pb-2\"\r\n\t\t\t\t\t\t\tstaggerFrom={\"first\"}\r\n\t\t\t\t\t\t\tanimatePresenceMode=\"wait\"\r\n\t\t\t\t\t\t\tloop={false}\r\n\t\t\t\t\t\t\tauto={false}\r\n\t\t\t\t\t\t\tstaggerDuration={0.005}\r\n\t\t\t\t\t\t\tinitial={{ opacity: 0, y: 50 }}\r\n\t\t\t\t\t\t\tanimate={{ opacity: 1, y: 0 }}\r\n\t\t\t\t\t\t\texit={{ opacity: 0, y: -50 }}\r\n\t\t\t\t\t\t\ttransition={{ type: \"spring\", duration: 0.6, bounce: 0 }}\r\n\t\t\t\t\t\t/>\r\n\t\t\t\t\t</div>\r\n\t\t\t\t</div>\r\n\t\t\t\t<div className=\"absolute inset-0\">\r\n\t\t\t\t\t{[\"/itjustworks.jpg\", \"/itjustworks.jpg\"]\r\n\t\t\t\t\t\t.slice(1)\r\n\t\t\t\t\t\t.map((image, index) => (\r\n\t\t\t\t\t\t\t<Item\r\n\t\t\t\t\t\t\t\tkey={index}\r\n\t\t\t\t\t\t\t\tindex={index}\r\n\t\t\t\t\t\t\t\timage={image.url}\r\n\t\t\t\t\t\t\t\tlink={image.link}\r\n\t\t\t\t\t\t\t\tonInView={(inView) => handleInView(index, inView)}\r\n\t\t\t\t\t\t\t/>\r\n\t\t\t\t\t\t))}\r\n\t\t\t\t</div>\r\n\t\t\t</div> */}\r\n\t\t</div>\r\n\t);\r\n}\r\n",
      "type": "registry:ui"
    },
    {
      "path": "registry/open-source/text-rotate.tsx",
      "content": "\"use client\";\r\n\r\nimport {\r\n\tElementType,\r\n\tforwardRef,\r\n\tuseCallback,\r\n\tuseEffect,\r\n\tuseImperativeHandle,\r\n\tuseMemo,\r\n\tuseState,\r\n} from \"react\";\r\n\r\nimport { cn } from \"@/registry/utilities/cn\";\r\nimport {\r\n\tAnimatePresence,\r\n\tAnimatePresenceProps,\r\n\tmotion,\r\n\tMotionProps,\r\n\tTransition,\r\n} from \"motion/react\";\r\n\r\n// Credit:\r\n// https://www.fancycomponents.dev/docs/components/text/text-rotate\r\n\r\n// handy function to split text into characters with support for unicode and emojis\r\nconst splitIntoCharacters = (text: string): string[] => {\r\n\tif (typeof Intl !== \"undefined\" && \"Segmenter\" in Intl) {\r\n\t\tconst segmenter = new Intl.Segmenter(\"en\", { granularity: \"grapheme\" });\r\n\t\treturn Array.from(segmenter.segment(text), ({ segment }) => segment);\r\n\t}\r\n\t// Fallback for browsers that don't support Intl.Segmenter\r\n\treturn Array.from(text);\r\n};\r\n\r\ninterface TextRotateProps {\r\n\t/**\r\n\t * Array of text strings to rotate through.\r\n\t * Required prop with no default value.\r\n\t */\r\n\ttexts: string[];\r\n\r\n\t/**\r\n\t * render as HTML Tag\r\n\t */\r\n\tas?: ElementType;\r\n\r\n\t/**\r\n\t * Time in milliseconds between text rotations.\r\n\t * @default 2000\r\n\t */\r\n\trotationInterval?: number;\r\n\r\n\t/**\r\n\t * Initial animation state or array of states.\r\n\t * @default { y: \"100%\", opacity: 0 }\r\n\t */\r\n\tinitial?: MotionProps[\"initial\"] | MotionProps[\"initial\"][];\r\n\r\n\t/**\r\n\t * Animation state to animate to or array of states.\r\n\t * @default { y: 0, opacity: 1 }\r\n\t */\r\n\tanimate?: MotionProps[\"animate\"] | MotionProps[\"animate\"][];\r\n\r\n\t/**\r\n\t * Animation state when exiting or array of states.\r\n\t * @default { y: \"-120%\", opacity: 0 }\r\n\t */\r\n\texit?: MotionProps[\"exit\"] | MotionProps[\"exit\"][];\r\n\r\n\t/**\r\n\t * AnimatePresence mode\r\n\t * @default \"wait\"\r\n\t */\r\n\tanimatePresenceMode?: AnimatePresenceProps[\"mode\"];\r\n\r\n\t/**\r\n\t * Whether to run initial animation on first render.\r\n\t * @default false\r\n\t */\r\n\tanimatePresenceInitial?: boolean;\r\n\r\n\t/**\r\n\t * Duration of stagger delay between elements in seconds.\r\n\t * @default 0\r\n\t */\r\n\tstaggerDuration?: number;\r\n\r\n\t/**\r\n\t * Direction to stagger animations from.\r\n\t * @default \"first\"\r\n\t */\r\n\tstaggerFrom?: \"first\" | \"last\" | \"center\" | number | \"random\";\r\n\r\n\t/**\r\n\t * Animation transition configuration.\r\n\t * @default { type: \"spring\", damping: 25, stiffness: 300 }\r\n\t */\r\n\ttransition?: Transition;\r\n\r\n\t/**\r\n\t * Whether to loop through texts continuously.\r\n\t * @default true\r\n\t */\r\n\tloop?: boolean;\r\n\r\n\t/**\r\n\t * Whether to auto-rotate texts.\r\n\t * @default true\r\n\t */\r\n\tauto?: boolean;\r\n\r\n\t/**\r\n\t * How to split the text for animation.\r\n\t * @default \"characters\"\r\n\t */\r\n\tsplitBy?: \"words\" | \"characters\" | \"lines\" | string;\r\n\r\n\t/**\r\n\t * Callback function triggered when rotating to next text.\r\n\t * @default undefined\r\n\t */\r\n\tonNext?: (index: number) => void;\r\n\r\n\t/**\r\n\t * Class name for the main container element.\r\n\t * @default undefined\r\n\t */\r\n\tmainClassName?: string;\r\n\r\n\t/**\r\n\t * Class name for the split level wrapper elements.\r\n\t * @default undefined\r\n\t */\r\n\tsplitLevelClassName?: string;\r\n\r\n\t/**\r\n\t * Class name for individual animated elements.\r\n\t * @default undefined\r\n\t */\r\n\telementLevelClassName?: string;\r\n}\r\n\r\n/**\r\n * Interface for the ref object exposed by TextRotate component.\r\n * Provides methods to control text rotation programmatically.\r\n * This allows external components to trigger text changes\r\n * without relying on the automatic rotation.\r\n */\r\nexport interface TextRotateRef {\r\n\t/**\r\n\t * Advance to next text in sequence.\r\n\t * If at the end, will loop to beginning if loop prop is true.\r\n\t */\r\n\tnext: () => void;\r\n\r\n\t/**\r\n\t * Go back to previous text in sequence.\r\n\t * If at the start, will loop to end if loop prop is true.\r\n\t */\r\n\tprevious: () => void;\r\n\r\n\t/**\r\n\t * Jump to specific text by index.\r\n\t * Will clamp index between 0 and texts.length - 1.\r\n\t */\r\n\tjumpTo: (index: number) => void;\r\n\r\n\t/**\r\n\t * Reset back to first text.\r\n\t * Equivalent to jumpTo(0).\r\n\t */\r\n\treset: () => void;\r\n}\r\n\r\n/**\r\n * Internal interface for representing words when splitting text by characters.\r\n * Used to maintain proper word spacing and line breaks while allowing\r\n * character-by-character animation. This prevents words from breaking\r\n * across lines during animation.\r\n */\r\ninterface WordObject {\r\n\t/**\r\n\t * Array of individual characters in the word.\r\n\t * Uses Intl.Segmenter when available for proper Unicode handling.\r\n\t */\r\n\tcharacters: string[];\r\n\r\n\t/**\r\n\t * Whether this word needs a space after it.\r\n\t * True for all words except the last one in a sequence.\r\n\t */\r\n\tneedsSpace: boolean;\r\n}\r\n\r\nconst TextRotate = forwardRef<TextRotateRef, TextRotateProps>(\r\n\t(\r\n\t\t{\r\n\t\t\ttexts,\r\n\t\t\tas = \"p\",\r\n\t\t\ttransition = { type: \"spring\", damping: 25, stiffness: 300 },\r\n\t\t\tinitial = { y: \"100%\", opacity: 0 },\r\n\t\t\tanimate = { y: 0, opacity: 1 },\r\n\t\t\texit = { y: \"-120%\", opacity: 0 },\r\n\t\t\tanimatePresenceMode = \"wait\",\r\n\t\t\tanimatePresenceInitial = false,\r\n\t\t\trotationInterval = 2000,\r\n\t\t\tstaggerDuration = 0,\r\n\t\t\tstaggerFrom = \"first\",\r\n\t\t\tloop = true,\r\n\t\t\tauto = true,\r\n\t\t\tsplitBy = \"characters\",\r\n\t\t\tonNext,\r\n\t\t\tmainClassName,\r\n\t\t\tsplitLevelClassName,\r\n\t\t\telementLevelClassName,\r\n\t\t\t...props\r\n\t\t},\r\n\t\tref\r\n\t) => {\r\n\t\tconst [currentTextIndex, setCurrentTextIndex] = useState(0);\r\n\r\n\t\t// Splitting the text into animation segments\r\n\t\tconst elements = useMemo(() => {\r\n\t\t\tconst currentText = texts[currentTextIndex];\r\n\t\t\tif (splitBy === \"characters\") {\r\n\t\t\t\tconst text = currentText?.split(\" \");\r\n\t\t\t\treturn text.map((word, i) => ({\r\n\t\t\t\t\tcharacters: splitIntoCharacters(word),\r\n\t\t\t\t\tneedsSpace: i !== text.length - 1,\r\n\t\t\t\t}));\r\n\t\t\t}\r\n\t\t\treturn splitBy === \"words\"\r\n\t\t\t\t? currentText?.split(\" \")\r\n\t\t\t\t: splitBy === \"lines\"\r\n\t\t\t\t\t? currentText?.split(\"\\n\")\r\n\t\t\t\t\t: currentText?.split(splitBy);\r\n\t\t}, [texts, currentTextIndex, splitBy]);\r\n\r\n\t\t// Helper function to calculate stagger delay for each text segment\r\n\t\tconst getStaggerDelay = useCallback(\r\n\t\t\t(index: number, totalChars: number) => {\r\n\t\t\t\tconst total = totalChars;\r\n\t\t\t\tif (staggerFrom === \"first\") return index * staggerDuration;\r\n\t\t\t\tif (staggerFrom === \"last\")\r\n\t\t\t\t\treturn (total - 1 - index) * staggerDuration;\r\n\t\t\t\tif (staggerFrom === \"center\") {\r\n\t\t\t\t\tconst center = Math.floor(total / 2);\r\n\t\t\t\t\treturn Math.abs(center - index) * staggerDuration;\r\n\t\t\t\t}\r\n\t\t\t\tif (staggerFrom === \"random\") {\r\n\t\t\t\t\tconst randomIndex = Math.floor(Math.random() * total);\r\n\t\t\t\t\treturn Math.abs(randomIndex - index) * staggerDuration;\r\n\t\t\t\t}\r\n\t\t\t\treturn Math.abs(staggerFrom - index) * staggerDuration;\r\n\t\t\t},\r\n\t\t\t[staggerFrom, staggerDuration]\r\n\t\t);\r\n\r\n\t\t// Helper function to handle index changes and trigger callback\r\n\t\tconst handleIndexChange = useCallback(\r\n\t\t\t(newIndex: number) => {\r\n\t\t\t\tsetCurrentTextIndex(newIndex);\r\n\t\t\t\tonNext?.(newIndex);\r\n\t\t\t},\r\n\t\t\t[onNext]\r\n\t\t);\r\n\r\n\t\t// Go to next text\r\n\t\tconst next = useCallback(() => {\r\n\t\t\tconst nextIndex =\r\n\t\t\t\tcurrentTextIndex === texts.length - 1\r\n\t\t\t\t\t? loop\r\n\t\t\t\t\t\t? 0\r\n\t\t\t\t\t\t: currentTextIndex\r\n\t\t\t\t\t: currentTextIndex + 1;\r\n\r\n\t\t\tif (nextIndex !== currentTextIndex) {\r\n\t\t\t\thandleIndexChange(nextIndex);\r\n\t\t\t}\r\n\t\t}, [currentTextIndex, texts.length, loop, handleIndexChange]);\r\n\r\n\t\t// Go back to previous text\r\n\t\tconst previous = useCallback(() => {\r\n\t\t\tconst prevIndex =\r\n\t\t\t\tcurrentTextIndex === 0\r\n\t\t\t\t\t? loop\r\n\t\t\t\t\t\t? texts.length - 1\r\n\t\t\t\t\t\t: currentTextIndex\r\n\t\t\t\t\t: currentTextIndex - 1;\r\n\r\n\t\t\tif (prevIndex !== currentTextIndex) {\r\n\t\t\t\thandleIndexChange(prevIndex);\r\n\t\t\t}\r\n\t\t}, [currentTextIndex, texts.length, loop, handleIndexChange]);\r\n\r\n\t\t// Jump to specific text by index\r\n\t\tconst jumpTo = useCallback(\r\n\t\t\t(index: number) => {\r\n\t\t\t\tconst validIndex = Math.max(0, Math.min(index, texts.length - 1));\r\n\t\t\t\tif (validIndex !== currentTextIndex) {\r\n\t\t\t\t\thandleIndexChange(validIndex);\r\n\t\t\t\t}\r\n\t\t\t},\r\n\t\t\t[texts.length, currentTextIndex, handleIndexChange]\r\n\t\t);\r\n\r\n\t\t// Reset back to first text\r\n\t\tconst reset = useCallback(() => {\r\n\t\t\tif (currentTextIndex !== 0) {\r\n\t\t\t\thandleIndexChange(0);\r\n\t\t\t}\r\n\t\t}, [currentTextIndex, handleIndexChange]);\r\n\r\n\t\t// Get animation props for each text segment. If array is provided, states will be mapped to text segments cyclically.\r\n\t\tconst getAnimationProps = useCallback(\r\n\t\t\t(index: number) => {\r\n\t\t\t\tconst getProp = (\r\n\t\t\t\t\tprop:\r\n\t\t\t\t\t\t| MotionProps[\"initial\"]\r\n\t\t\t\t\t\t| MotionProps[\"initial\"][]\r\n\t\t\t\t\t\t| MotionProps[\"animate\"]\r\n\t\t\t\t\t\t| MotionProps[\"animate\"][]\r\n\t\t\t\t\t\t| MotionProps[\"exit\"]\r\n\t\t\t\t\t\t| MotionProps[\"exit\"][]\r\n\t\t\t\t) => {\r\n\t\t\t\t\tif (Array.isArray(prop)) {\r\n\t\t\t\t\t\treturn prop[index % prop.length];\r\n\t\t\t\t\t}\r\n\t\t\t\t\treturn prop;\r\n\t\t\t\t};\r\n\r\n\t\t\t\treturn {\r\n\t\t\t\t\tinitial: getProp(initial) as MotionProps[\"initial\"],\r\n\t\t\t\t\tanimate: getProp(animate) as MotionProps[\"animate\"],\r\n\t\t\t\t\texit: getProp(exit) as MotionProps[\"exit\"],\r\n\t\t\t\t};\r\n\t\t\t},\r\n\t\t\t[initial, animate, exit]\r\n\t\t);\r\n\r\n\t\t// Expose all navigation functions via ref\r\n\t\tuseImperativeHandle(\r\n\t\t\tref,\r\n\t\t\t() => ({\r\n\t\t\t\tnext,\r\n\t\t\t\tprevious,\r\n\t\t\t\tjumpTo,\r\n\t\t\t\treset,\r\n\t\t\t}),\r\n\t\t\t[next, previous, jumpTo, reset]\r\n\t\t);\r\n\r\n\t\t// Auto-rotate text\r\n\t\tuseEffect(() => {\r\n\t\t\tif (!auto) return;\r\n\t\t\tconst intervalId = setInterval(next, rotationInterval);\r\n\t\t\treturn () => clearInterval(intervalId);\r\n\t\t}, [next, rotationInterval, auto]);\r\n\r\n\t\t// Custom motion component to render the text as a custom HTML tag provided via prop\r\n\t\tconst MotionComponent = useMemo(() => motion.create(as ?? \"p\"), [as]);\r\n\r\n\t\treturn (\r\n\t\t\t<MotionComponent\r\n\t\t\t\tclassName={cn(\"flex flex-wrap whitespace-pre-wrap\", mainClassName)}\r\n\t\t\t\ttransition={transition}\r\n\t\t\t\tlayout\r\n\t\t\t\t{...props}\r\n\t\t\t>\r\n\t\t\t\t<span className=\"sr-only\">{texts[currentTextIndex]}</span>\r\n\r\n\t\t\t\t<AnimatePresence\r\n\t\t\t\t\tmode={animatePresenceMode}\r\n\t\t\t\t\tinitial={animatePresenceInitial}\r\n\t\t\t\t>\r\n\t\t\t\t\t<motion.span\r\n\t\t\t\t\t\tkey={currentTextIndex}\r\n\t\t\t\t\t\tclassName={cn(\r\n\t\t\t\t\t\t\t\"flex flex-wrap\",\r\n\t\t\t\t\t\t\tsplitBy === \"lines\" && \"flex-col w-full\"\r\n\t\t\t\t\t\t)}\r\n\t\t\t\t\t\taria-hidden\r\n\t\t\t\t\t\tlayout\r\n\t\t\t\t\t>\r\n\t\t\t\t\t\t{(splitBy === \"characters\"\r\n\t\t\t\t\t\t\t? (elements as WordObject[])\r\n\t\t\t\t\t\t\t: (elements as string[]).map((el, i) => ({\r\n\t\t\t\t\t\t\t\t\tcharacters: [el],\r\n\t\t\t\t\t\t\t\t\tneedsSpace: i !== elements.length - 1,\r\n\t\t\t\t\t\t\t\t}))\r\n\t\t\t\t\t\t).map((wordObj, wordIndex, array) => {\r\n\t\t\t\t\t\t\tconst previousCharsCount = array\r\n\t\t\t\t\t\t\t\t.slice(0, wordIndex)\r\n\t\t\t\t\t\t\t\t.reduce((sum, word) => sum + word.characters.length, 0);\r\n\r\n\t\t\t\t\t\t\treturn (\r\n\t\t\t\t\t\t\t\t<span\r\n\t\t\t\t\t\t\t\t\tkey={wordIndex}\r\n\t\t\t\t\t\t\t\t\tclassName={cn(\"inline-flex\", splitLevelClassName)}\r\n\t\t\t\t\t\t\t\t>\r\n\t\t\t\t\t\t\t\t\t{wordObj.characters.map((char, charIndex) => {\r\n\t\t\t\t\t\t\t\t\t\tconst totalIndex = previousCharsCount + charIndex;\r\n\t\t\t\t\t\t\t\t\t\tconst animationProps =\r\n\t\t\t\t\t\t\t\t\t\t\tgetAnimationProps(totalIndex);\r\n\t\t\t\t\t\t\t\t\t\treturn (\r\n\t\t\t\t\t\t\t\t\t\t\t<span\r\n\t\t\t\t\t\t\t\t\t\t\t\tclassName={cn(elementLevelClassName)}\r\n\t\t\t\t\t\t\t\t\t\t\t\tkey={char + \"-\" + charIndex}\r\n\t\t\t\t\t\t\t\t\t\t\t>\r\n\t\t\t\t\t\t\t\t\t\t\t\t<motion.span\r\n\t\t\t\t\t\t\t\t\t\t\t\t\t{...animationProps}\r\n\t\t\t\t\t\t\t\t\t\t\t\t\tkey={charIndex}\r\n\t\t\t\t\t\t\t\t\t\t\t\t\ttransition={{\r\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t...transition,\r\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tdelay: getStaggerDelay(\r\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tpreviousCharsCount + charIndex,\r\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tarray.reduce(\r\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t(sum, word) =>\r\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tsum + word.characters.length,\r\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t0\r\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t)\r\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t),\r\n\t\t\t\t\t\t\t\t\t\t\t\t\t}}\r\n\t\t\t\t\t\t\t\t\t\t\t\t\tclassName={\"inline-block\"}\r\n\t\t\t\t\t\t\t\t\t\t\t\t>\r\n\t\t\t\t\t\t\t\t\t\t\t\t\t{char}\r\n\t\t\t\t\t\t\t\t\t\t\t\t</motion.span>\r\n\t\t\t\t\t\t\t\t\t\t\t</span>\r\n\t\t\t\t\t\t\t\t\t\t);\r\n\t\t\t\t\t\t\t\t\t})}\r\n\t\t\t\t\t\t\t\t\t{wordObj.needsSpace && (\r\n\t\t\t\t\t\t\t\t\t\t<span className=\"whitespace-pre\"> </span>\r\n\t\t\t\t\t\t\t\t\t)}\r\n\t\t\t\t\t\t\t\t</span>\r\n\t\t\t\t\t\t\t);\r\n\t\t\t\t\t\t})}\r\n\t\t\t\t\t</motion.span>\r\n\t\t\t\t</AnimatePresence>\r\n\t\t\t</MotionComponent>\r\n\t\t);\r\n\t}\r\n);\r\n\r\nTextRotate.displayName = \"TextRotate\";\r\n\r\nexport default TextRotate;\r\n",
      "type": "registry:ui"
    },
    {
      "path": "registry/utilities/cn.ts",
      "content": "import { ClassValue, clsx } from \"clsx\";\r\nimport { twMerge } from \"tailwind-merge\";\r\n\r\nexport function cn(...inputs: ClassValue[]) {\r\n\treturn twMerge(clsx(inputs));\r\n}\r\n",
      "type": "registry:ui"
    }
  ]
}