{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "buttons-text-animated",
  "title": "Text-animated buttons",
  "description": "Styled buttons: the Text-animated set. 5 decorative buttons variations (BounceTextButton, WaveTextButton, RevealTextButton, FlipTextButton, SlideTextButton) on the ai2 token system, powered by framer-motion, reduced-motion aware and sized sm to xl. Part of the free styled layer.",
  "dependencies": [
    "motion@^12.42.2"
  ],
  "registryDependencies": [
    "@ai2/tokens"
  ],
  "files": [
    {
      "path": "registry/ai2/styled/buttons-text-animated.tsx",
      "content": "\"use client\"\n\nimport type * as React from \"react\"\nimport { motion, useReducedMotion, type HTMLMotionProps, type Variants } from \"motion/react\"\n\nimport { cn } from \"@/lib/utils\"\n\n/* Text-animated button family: 5 variations on kinetic text. Letter splitting happens only for string children; otherwise it renders plainly. framer-motion parent/child variants plus stagger. All are sized, take colour ONLY from tokens and are static under reduced-motion. */\n\nexport type StyledSize = \"sm\" | \"md\" | \"lg\" | \"xl\"\n\nconst sizes: Record<StyledSize, string> = {\n  sm: \"h-8 gap-1.5 rounded-md px-3 text-sm\",\n  md: \"h-9 gap-2 rounded-lg px-4 text-sm\",\n  lg: \"h-10 gap-2 rounded-lg px-5 text-sm\",\n  xl: \"h-12 gap-2.5 rounded-xl px-6 text-base\",\n}\n\nconst base =\n  \"relative inline-flex shrink-0 select-none items-center justify-center overflow-hidden bg-brand font-medium whitespace-nowrap text-brand-foreground outline-none focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 [&_i]:text-base [&_i]:leading-none\"\n\ntype Props = HTMLMotionProps<\"button\"> & { size?: StyledSize }\n\n/* Shared: splits string children into per-letter spans that take a variant. */\nfunction Letters({ text, variants }: { text: string; variants: Variants }) {\n  return (\n    <>\n      {text.split(\"\").map((char, i) => (\n        <motion.span\n          key={`${char}-${i}`}\n          variants={variants}\n          className=\"inline-block\"\n          style={{ whiteSpace: \"pre\" }}\n        >\n          {char}\n        </motion.span>\n      ))}\n    </>\n  )\n}\n\n/* Bounce: the letters bounce upwards in sequence on hover. */\nexport function BounceTextButton({ className, size = \"md\", children, ...props }: Props) {\n  const reduce = useReducedMotion()\n  const text = typeof children === \"string\" ? children : null\n  return (\n    <motion.button\n      data-slot=\"styled-button\"\n      initial=\"rest\"\n      animate=\"rest\"\n      whileHover={reduce || !text ? undefined : \"hover\"}\n      whileTap={reduce ? undefined : { scale: 0.97 }}\n      variants={text ? { rest: {}, hover: { transition: { staggerChildren: 0.03 } } } : undefined}\n      className={cn(base, sizes[size], \"gap-0.5\", className)}\n      {...props}\n    >\n      {text ? <Letters text={text} variants={{ rest: { y: 0 }, hover: { y: -3 } }} /> : (children as React.ReactNode)}\n    </motion.button>\n  )\n}\n\n/* Wave: the letters rise and fall in sequence like a wave on hover. */\nexport function WaveTextButton({ className, size = \"md\", children, ...props }: Props) {\n  const reduce = useReducedMotion()\n  const text = typeof children === \"string\" ? children : null\n  return (\n    <motion.button\n      data-slot=\"styled-button\"\n      initial=\"rest\"\n      animate=\"rest\"\n      whileHover={reduce || !text ? undefined : \"hover\"}\n      whileTap={reduce ? undefined : { scale: 0.97 }}\n      variants={text ? { rest: {}, hover: { transition: { staggerChildren: 0.06 } } } : undefined}\n      className={cn(base, sizes[size], \"gap-0.5\", className)}\n      {...props}\n    >\n      {text ? (\n        <Letters\n          text={text}\n          variants={{ rest: { y: 0 }, hover: { y: [0, -4, 0], transition: { duration: 0.5, ease: \"easeInOut\" } } }}\n        />\n      ) : (\n        children as React.ReactNode\n      )}\n    </motion.button>\n  )\n}\n\n/* Reveal: the letters slide up into place on hover. */\nexport function RevealTextButton({ className, size = \"md\", children, ...props }: Props) {\n  const reduce = useReducedMotion()\n  const text = typeof children === \"string\" ? children : null\n  return (\n    <motion.button\n      data-slot=\"styled-button\"\n      initial=\"rest\"\n      animate=\"rest\"\n      whileHover={reduce || !text ? undefined : \"hover\"}\n      whileTap={reduce ? undefined : { scale: 0.97 }}\n      variants={text ? { rest: {}, hover: { transition: { staggerChildren: 0.03 } } } : undefined}\n      className={cn(base, sizes[size], \"gap-0.5\", className)}\n      {...props}\n    >\n      {text ? (\n        <Letters text={text} variants={{ rest: { y: 0, opacity: 1 }, hover: { y: [8, 0], opacity: [0.3, 1] } }} />\n      ) : (\n        children as React.ReactNode\n      )}\n    </motion.button>\n  )\n}\n\n/* Flip: the letters rotate on the X axis in sequence on hover. */\nexport function FlipTextButton({ className, size = \"md\", children, ...props }: Props) {\n  const reduce = useReducedMotion()\n  const text = typeof children === \"string\" ? children : null\n  return (\n    <motion.button\n      data-slot=\"styled-button\"\n      initial=\"rest\"\n      animate=\"rest\"\n      whileHover={reduce || !text ? undefined : \"hover\"}\n      whileTap={reduce ? undefined : { scale: 0.97 }}\n      variants={text ? { rest: {}, hover: { transition: { staggerChildren: 0.04 } } } : undefined}\n      className={cn(base, sizes[size], \"gap-0.5\", className)}\n      style={{ transformPerspective: 400 }}\n      {...props}\n    >\n      {text ? (\n        <Letters text={text} variants={{ rest: { rotateX: 0 }, hover: { rotateX: [0, 360] } }} />\n      ) : (\n        children as React.ReactNode\n      )}\n    </motion.button>\n  )\n}\n\n/* Slide: the label slides up on hover and a copy of the same label comes from below. */\nexport function SlideTextButton({ className, size = \"md\", children, ...props }: Props) {\n  const reduce = useReducedMotion()\n  return (\n    <motion.button\n      data-slot=\"styled-button\"\n      whileTap={reduce ? undefined : { scale: 0.97 }}\n      transition={{ type: \"spring\", stiffness: 400, damping: 20 }}\n      className={cn(base, sizes[size], \"group\", className)}\n      {...props}\n    >\n      <span className=\"pointer-events-none flex items-center gap-2 opacity-0\">{children as React.ReactNode}</span>\n      <span className=\"absolute inset-0 flex items-center justify-center gap-2 transition-transform duration-(--motion-base) ease-(--motion-ease) group-hover:-translate-y-full motion-reduce:transition-none\">\n        {children as React.ReactNode}\n      </span>\n      <span className=\"absolute inset-0 flex translate-y-full items-center justify-center gap-2 transition-transform duration-(--motion-base) ease-(--motion-ease) group-hover:translate-y-0 motion-reduce:hidden\">\n        {children as React.ReactNode}\n      </span>\n    </motion.button>\n  )\n}\n",
      "type": "registry:component",
      "target": "components/ui/buttons-text-animated.tsx"
    }
  ],
  "categories": [
    "styled",
    "buttons"
  ],
  "type": "registry:component"
}