{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "native-morphing-button-baseui",
  "type": "registry:component",
  "title": "Native Morphing Button",
  "description": "Floating action button that morphs into a menu of actions. (Base UI)",
  "registryDependencies": [
    "button"
  ],
  "dependencies": [
    "framer-motion",
    "react"
  ],
  "files": [
    {
      "path": "@uitripled/react-baseui/src/components/native/native-morphing-button-baseui.tsx",
      "content": "\"use client\";\n\nimport { cn } from \"@/lib/utils\";\nimport {\n  AnimatePresence,\n  LayoutGroup,\n  motion,\n  Transition,\n  useReducedMotion,\n} from \"framer-motion\";\nimport { Plus, X } from \"lucide-react\";\nimport { useEffect, useRef, useState } from \"react\";\n\nexport interface MorphingButtonAction {\n  /**\n   * Display label for the action.\n   */\n  label: string;\n  /**\n   * Icon to display alongside the label.\n   */\n  icon: React.ReactNode;\n  /**\n   * Callback when action is clicked.\n   */\n  onClick: () => void;\n}\n\nexport interface NativeMorphingButtonProps {\n  /**\n   * Array of actions to display in the expanded menu.\n   */\n  actions: MorphingButtonAction[];\n  /**\n   * Position of the FAB.\n   * Default: 'bottom-right'\n   */\n  position?: \"bottom-right\" | \"bottom-left\" | \"top-right\" | \"top-left\";\n  /**\n   * Whether to use fixed positioning.\n   * Default: false (relative to container)\n   */\n  fixed?: boolean;\n  /**\n   * Custom icon when collapsed.\n   */\n  icon?: React.ReactNode;\n  /**\n   * Custom close icon when expanded.\n   */\n  closeIcon?: React.ReactNode;\n  className?: string;\n}\n\nconst positionClasses = {\n  \"bottom-right\": \"bottom-4 right-4\",\n  \"bottom-left\": \"bottom-4 left-4\",\n  \"top-right\": \"top-4 right-4\",\n  \"top-left\": \"top-4 left-4\",\n};\n\nconst springTransition: Transition = {\n  type: \"spring\",\n  stiffness: 300,\n  damping: 30,\n};\nconst reducedTransition: Transition = { duration: 0.1 };\n\nexport function NativeMorphingButton({\n  actions,\n  position = \"bottom-right\",\n  fixed = false,\n  icon,\n  closeIcon,\n  className,\n}: NativeMorphingButtonProps) {\n  const [isExpanded, setIsExpanded] = useState(false);\n  const shouldReduceMotion = useReducedMotion();\n  const fabRef = useRef<HTMLButtonElement>(null);\n  const menuRef = useRef<HTMLDivElement>(null);\n  const wasExpanded = useRef(false);\n\n  const transition = shouldReduceMotion ? reducedTransition : springTransition;\n\n  // Focus the first action on open, return focus to the FAB on close.\n  useEffect(() => {\n    if (isExpanded) {\n      wasExpanded.current = true;\n      menuRef.current\n        ?.querySelector<HTMLButtonElement>('[role=\"menuitem\"]')\n        ?.focus();\n    } else if (wasExpanded.current) {\n      fabRef.current?.focus();\n    }\n  }, [isExpanded]);\n\n  const handleMenuKeyDown = (e: React.KeyboardEvent) => {\n    const items = Array.from(\n      menuRef.current?.querySelectorAll<HTMLButtonElement>(\n        '[role=\"menuitem\"]'\n      ) ?? []\n    );\n    if (!items.length) return;\n    const currentIndex = items.indexOf(\n      document.activeElement as HTMLButtonElement\n    );\n    if (e.key === \"ArrowDown\") {\n      e.preventDefault();\n      items[(currentIndex + 1) % items.length]?.focus();\n    }\n    if (e.key === \"ArrowUp\") {\n      e.preventDefault();\n      items[(currentIndex - 1 + items.length) % items.length]?.focus();\n    }\n  };\n\n  return (\n    <div\n      className={cn(\n        fixed ? \"fixed\" : \"absolute\",\n        positionClasses[position],\n        \"z-50\",\n        className\n      )}\n      onKeyDown={(e) => {\n        if (e.key === \"Escape\" && isExpanded) {\n          setIsExpanded(false);\n        }\n      }}\n    >\n      <LayoutGroup>\n        <motion.div\n          layout\n          className=\"relative\"\n          initial={false}\n          animate={{\n            width: isExpanded ? 280 : 56,\n            height: isExpanded ? \"auto\" : 56,\n            borderRadius: isExpanded ? 16 : 28,\n          }}\n          transition={transition}\n        >\n          {/* Main FAB Button */}\n          <motion.button\n            ref={fabRef}\n            type=\"button\"\n            onClick={() => setIsExpanded(!isExpanded)}\n            className=\"absolute right-0 bottom-0 z-10 flex h-14 w-14 items-center justify-center rounded-full bg-primary text-primary-foreground shadow-lg transition-shadow hover:shadow-xl focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2\"\n            whileHover={shouldReduceMotion ? undefined : { scale: 1.05 }}\n            whileTap={shouldReduceMotion ? undefined : { scale: 0.95 }}\n            aria-label={isExpanded ? \"Close menu\" : \"Open menu\"}\n            aria-expanded={isExpanded}\n            aria-haspopup=\"menu\"\n          >\n            <AnimatePresence mode=\"wait\">\n              {isExpanded ? (\n                <motion.div\n                  key=\"close\"\n                  aria-hidden=\"true\"\n                  initial={\n                    shouldReduceMotion ? false : { rotate: -90, opacity: 0 }\n                  }\n                  animate={{ rotate: 0, opacity: 1 }}\n                  exit={\n                    shouldReduceMotion\n                      ? { opacity: 0 }\n                      : { rotate: 90, opacity: 0 }\n                  }\n                  transition={{ duration: 0.2 }}\n                >\n                  {closeIcon ?? <X className=\"h-5 w-5\" />}\n                </motion.div>\n              ) : (\n                <motion.div\n                  key=\"open\"\n                  aria-hidden=\"true\"\n                  initial={\n                    shouldReduceMotion ? false : { rotate: 90, opacity: 0 }\n                  }\n                  animate={{ rotate: 0, opacity: 1 }}\n                  exit={\n                    shouldReduceMotion\n                      ? { opacity: 0 }\n                      : { rotate: -90, opacity: 0 }\n                  }\n                  transition={{ duration: 0.2 }}\n                >\n                  {icon ?? <Plus className=\"h-5 w-5\" />}\n                </motion.div>\n              )}\n            </AnimatePresence>\n          </motion.button>\n\n          {/* Expanded Menu */}\n          <AnimatePresence>\n            {isExpanded && (\n              <>\n                <button\n                  type=\"button\"\n                  aria-label=\"Close menu\"\n                  tabIndex={-1}\n                  className=\"fixed inset-0 z-[-1] cursor-default bg-transparent\"\n                  onClick={() => setIsExpanded(false)}\n                />\n                <motion.div\n                  ref={menuRef}\n                  initial={\n                    shouldReduceMotion\n                      ? { opacity: 0 }\n                      : { opacity: 0, scale: 0.8 }\n                  }\n                  animate={{ opacity: 1, scale: 1 }}\n                  exit={\n                    shouldReduceMotion\n                      ? { opacity: 0 }\n                      : { opacity: 0, scale: 0.8 }\n                  }\n                  transition={{ duration: 0.2, delay: 0.1 }}\n                  style={{ transformOrigin: \"bottom right\" }}\n                  className=\"absolute bottom-0 right-0 w-64 rounded-2xl rounded-br-[28px] border border-border bg-card p-4 shadow-2xl\"\n                  role=\"menu\"\n                  onKeyDown={handleMenuKeyDown}\n                >\n                  <div className=\"mb-2 space-y-2\">\n                    {actions.map((action, index) => (\n                      <motion.button\n                        key={action.label}\n                        type=\"button\"\n                        initial={\n                          shouldReduceMotion ? false : { opacity: 0, x: -20 }\n                        }\n                        animate={{ opacity: 1, x: 0 }}\n                        transition={{ delay: index * 0.05 + 0.2 }}\n                        onClick={() => {\n                          action.onClick();\n                          setIsExpanded(false);\n                        }}\n                        className=\"flex w-full items-center gap-3 rounded-lg px-4 py-3 text-left text-sm transition-colors hover:bg-muted focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring\"\n                        role=\"menuitem\"\n                      >\n                        <span aria-hidden=\"true\" className=\"text-muted-foreground\">\n                          {action.icon}\n                        </span>\n                        <span className=\"font-medium\">{action.label}</span>\n                      </motion.button>\n                    ))}\n                  </div>\n                </motion.div>\n              </>\n            )}\n          </AnimatePresence>\n        </motion.div>\n      </LayoutGroup>\n    </div>\n  );\n}\n",
      "type": "registry:component",
      "target": "components/uitripled/native-morphing-button-baseui.tsx"
    }
  ]
}