{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "button-group-motion",
  "title": "Group-motion button",
  "description": "Styled button: the Group-motion set. 5 decorative button variations (SlideButtonGroup, PopButtonGroup, FadeButtonGroup, SpringButtonGroup, MorphButtonGroup) 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/button-group-motion.tsx",
      "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { motion, useReducedMotion } from \"motion/react\"\n\nimport { cn } from \"@/lib/utils\"\n\n/* Motion button-group family: five animated action groups. The structure stays\n   the same (a container with buttons inside); the difference is in the\n   INTERACTION motion: a sliding indicator, a pop on press, a soft fade, a springy\n   return, a morphing highlight. When a moving button is pressed the active index\n   is held in state; the state lives in the root component and no subtree ever\n   unmounts. All motion is disabled through useReducedMotion(). Color comes ONLY\n   from semantic tokens, via alpha color-mix. Deterministic: no randomness or\n   source of time. */\n\nexport type StyledSize = \"sm\" | \"md\" | \"lg\" | \"xl\"\n\n/* Base Button olcegiyle hizali: sm=h-8, md=h-9, lg=h-10, xl=h-12. */\nconst sizes: Record<StyledSize, string> = {\n  sm: \"h-8 px-3 text-sm\",\n  md: \"h-9 px-4 text-sm\",\n  lg: \"h-10 px-5 text-sm\",\n  xl: \"h-12 px-6 text-base\",\n}\n\ntype Action = {\n  label?: React.ReactNode\n  icon?: React.ReactNode\n  onClick?: () => void\n}\n\ntype GroupProps = {\n  className?: string\n  size?: StyledSize\n  actions?: Action[]\n}\n\nconst defaultActions: Action[] = [\n  { label: \"Left\" },\n  { label: \"Center\" },\n  { label: \"Right\" },\n]\n\nconst item =\n  \"relative inline-flex shrink-0 select-none items-center justify-center gap-2 rounded-lg font-medium whitespace-nowrap outline-none transition-colors focus-visible:z-10 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\nconst shell =\n  \"inline-flex items-center gap-1 rounded-xl border border-field-border bg-background p-1 isolate\"\n\nconst spring = { type: \"spring\" as const, stiffness: 420, damping: 32 }\n\n/* Slide: basilan butonun arkasina kayan bir gosterge tasinir. */\nexport function SlideButtonGroup({ className, size = \"md\", actions = defaultActions }: GroupProps) {\n  const [active, setActive] = React.useState(0)\n  const reduce = useReducedMotion()\n  const uid = React.useId()\n\n  return (\n    <div\n      data-slot=\"styled-button-group\"\n      role=\"group\"\n      aria-label=\"Slide actions\"\n      className={cn(shell, className)}\n    >\n      {actions.map((action, i) => (\n        <button\n          key={i}\n          type=\"button\"\n          onClick={() => {\n            setActive(i)\n            action.onClick?.()\n          }}\n          data-slot=\"styled-button-group-item\"\n          className={cn(\n            item,\n            sizes[size],\n            active === i ? \"text-foreground\" : \"text-muted-foreground hover:text-foreground\"\n          )}\n        >\n          {active === i ? (\n            <motion.span\n              layoutId={reduce ? undefined : `${uid}-slide`}\n              aria-hidden=\"true\"\n              className=\"absolute inset-0 -z-10 rounded-lg bg-secondary\"\n              transition={reduce ? { duration: 0 } : spring}\n            />\n          ) : null}\n          <span className=\"relative inline-flex items-center gap-2\">\n            {action.icon}\n            {action.label}\n          </span>\n        </button>\n      ))}\n    </div>\n  )\n}\n\n/* Pop: basinca kisa bir olcek darbesi, birakinca geri doner. */\nexport function PopButtonGroup({ className, size = \"md\", actions = defaultActions }: GroupProps) {\n  const reduce = useReducedMotion()\n\n  return (\n    <div\n      data-slot=\"styled-button-group\"\n      role=\"group\"\n      aria-label=\"Pop actions\"\n      className={cn(shell, className)}\n    >\n      {actions.map((action, i) => (\n        <motion.button\n          key={i}\n          type=\"button\"\n          onClick={action.onClick}\n          data-slot=\"styled-button-group-item\"\n          whileTap={reduce ? undefined : { scale: 0.92 }}\n          whileHover={reduce ? undefined : { scale: 1.04 }}\n          transition={spring}\n          className={cn(\n            item,\n            sizes[size],\n            \"text-foreground hover:bg-secondary hover:text-secondary-foreground\"\n          )}\n        >\n          {action.icon}\n          {action.label}\n        </motion.button>\n      ))}\n    </div>\n  )\n}\n\n/* Fade: only the opacity changes on hover and press, no transform. */\nexport function FadeButtonGroup({ className, size = \"md\", actions = defaultActions }: GroupProps) {\n  const reduce = useReducedMotion()\n\n  return (\n    <div\n      data-slot=\"styled-button-group\"\n      role=\"group\"\n      aria-label=\"Fade actions\"\n      className={cn(shell, className)}\n    >\n      {actions.map((action, i) => (\n        <motion.button\n          key={i}\n          type=\"button\"\n          onClick={action.onClick}\n          data-slot=\"styled-button-group-item\"\n          initial={false}\n          whileHover={reduce ? undefined : { opacity: 1 }}\n          whileTap={reduce ? undefined : { opacity: 0.6 }}\n          transition={{ duration: reduce ? 0 : 0.18, ease: \"easeOut\" }}\n          className={cn(\n            item,\n            sizes[size],\n            \"text-foreground opacity-70 hover:bg-secondary hover:text-secondary-foreground\"\n          )}\n        >\n          {action.icon}\n          {action.label}\n        </motion.button>\n      ))}\n    </div>\n  )\n}\n\n/* Spring: it pulls down on press and settles back with a spring on release. */\nexport function SpringButtonGroup({ className, size = \"md\", actions = defaultActions }: GroupProps) {\n  const reduce = useReducedMotion()\n\n  return (\n    <div\n      data-slot=\"styled-button-group\"\n      role=\"group\"\n      aria-label=\"Spring actions\"\n      className={cn(shell, className)}\n    >\n      {actions.map((action, i) => (\n        <motion.button\n          key={i}\n          type=\"button\"\n          onClick={action.onClick}\n          data-slot=\"styled-button-group-item\"\n          whileTap={reduce ? undefined : { y: 2 }}\n          transition={reduce ? { duration: 0 } : { type: \"spring\" as const, stiffness: 600, damping: 18 }}\n          className={cn(\n            item,\n            sizes[size],\n            \"border border-field-border text-foreground hover:bg-secondary hover:text-secondary-foreground\"\n          )}\n        >\n          {action.icon}\n          {action.label}\n        </motion.button>\n      ))}\n    </div>\n  )\n}\n\n/* Morph: the active highlight moves from one button to another by changing shape. */\nexport function MorphButtonGroup({ className, size = \"md\", actions = defaultActions }: GroupProps) {\n  const [active, setActive] = React.useState(0)\n  const reduce = useReducedMotion()\n  const uid = React.useId()\n\n  return (\n    <div\n      data-slot=\"styled-button-group\"\n      role=\"group\"\n      aria-label=\"Morph actions\"\n      className={cn(\n        \"inline-flex items-center gap-1 rounded-full border border-field-border bg-secondary/40 p-1 isolate\",\n        className\n      )}\n    >\n      {actions.map((action, i) => (\n        <button\n          key={i}\n          type=\"button\"\n          onClick={() => {\n            setActive(i)\n            action.onClick?.()\n          }}\n          data-slot=\"styled-button-group-item\"\n          className={cn(\n            item,\n            sizes[size],\n            \"rounded-full\",\n            active === i\n              ? \"text-primary-foreground\"\n              : \"text-muted-foreground hover:text-foreground\"\n          )}\n        >\n          {active === i ? (\n            <motion.span\n              layoutId={reduce ? undefined : `${uid}-morph`}\n              aria-hidden=\"true\"\n              className=\"absolute inset-0 -z-10 rounded-full bg-primary\"\n              transition={reduce ? { duration: 0 } : { type: \"spring\" as const, stiffness: 380, damping: 30 }}\n            />\n          ) : null}\n          <span className=\"relative inline-flex items-center gap-2\">\n            {action.icon}\n            {action.label}\n          </span>\n        </button>\n      ))}\n    </div>\n  )\n}\n",
      "type": "registry:component",
      "target": "components/ui/button-group-motion.tsx"
    }
  ],
  "categories": [
    "styled",
    "button"
  ],
  "type": "registry:component"
}