{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "context-menu-bubble-shadcnui",
  "type": "registry:component",
  "title": "Context Menu Bubble",
  "description": "Right-click reveals circular expanding radial menu with icons",
  "registryDependencies": [
    "button"
  ],
  "dependencies": [
    "framer-motion",
    "react"
  ],
  "files": [
    {
      "path": "@uitripled/react-shadcn/src/components/motion-core/context-menu-bubble.tsx",
      "content": "\"use client\";\n\nimport { AnimatePresence, motion } from \"framer-motion\";\nimport { Copy, Edit, MoreVertical, Share2, Trash2 } from \"lucide-react\";\nimport { MouseEvent, useRef, useState } from \"react\";\n\ntype MenuItem = {\n  label: string;\n  icon: React.ReactNode;\n  onClick: () => void;\n  danger?: boolean;\n};\n\ntype ContextMenuBubbleProps = {\n  items?: MenuItem[];\n};\n\nexport function ContextMenuBubble({\n  items = [\n    { label: \"Edit\", icon: <Edit className=\"h-4 w-4\" />, onClick: () => {} },\n    { label: \"Copy\", icon: <Copy className=\"h-4 w-4\" />, onClick: () => {} },\n    { label: \"Share\", icon: <Share2 className=\"h-4 w-4\" />, onClick: () => {} },\n    {\n      label: \"Delete\",\n      icon: <Trash2 className=\"h-4 w-4\" />,\n      onClick: () => {},\n      danger: true,\n    },\n  ],\n}: ContextMenuBubbleProps) {\n  const [isOpen, setIsOpen] = useState(false);\n  const [position, setPosition] = useState({ x: 0, y: 0 });\n  const triggerRef = useRef<HTMLButtonElement>(null);\n  const containerRef = useRef<HTMLDivElement>(null);\n\n  const updatePosition = () => {\n    if (triggerRef.current && containerRef.current) {\n      const buttonRect = triggerRef.current.getBoundingClientRect();\n      const containerRect = containerRef.current.getBoundingClientRect();\n      setPosition({\n        x: buttonRect.left + buttonRect.width / 2 - containerRect.left,\n        y: buttonRect.top + buttonRect.height / 2 - containerRect.top,\n      });\n    }\n  };\n\n  const handleContextMenu = (e: MouseEvent) => {\n    e.preventDefault();\n    updatePosition();\n    setIsOpen(true);\n  };\n\n  const handleClick = () => {\n    updatePosition();\n    setIsOpen(!isOpen);\n  };\n\n  const closeMenu = () => setIsOpen(false);\n\n  const itemCount = items.length;\n  const radius = 80;\n  const angleStep = (2 * Math.PI) / itemCount;\n\n  return (\n    <div\n      ref={containerRef}\n      className=\"relative flex h-64 w-full items-center justify-center overflow-hidden rounded-2xl\"\n    >\n      <motion.button\n        ref={triggerRef}\n        onContextMenu={handleContextMenu}\n        onClick={handleClick}\n        whileHover={{ scale: 1.1 }}\n        whileTap={{ scale: 0.9 }}\n        className=\"flex h-12 w-12 items-center justify-center rounded-full bg-primary text-primary-foreground\"\n      >\n        <MoreVertical className=\"h-5 w-5\" />\n      </motion.button>\n\n      <AnimatePresence>\n        {isOpen && (\n          <>\n            <motion.div\n              initial={{ opacity: 0 }}\n              animate={{ opacity: 1 }}\n              exit={{ opacity: 0 }}\n              onClick={closeMenu}\n              className=\"fixed inset-0 z-40\"\n            />\n\n            <motion.div\n              initial={{ scale: 0, opacity: 0 }}\n              animate={{ scale: 1, opacity: 1 }}\n              exit={{ scale: 0, opacity: 0 }}\n              transition={{\n                type: \"spring\",\n                stiffness: 300,\n                damping: 25,\n              }}\n              className=\"absolute z-50 flex h-40 w-40 items-center justify-center\"\n              style={{\n                left: position.x - 80,\n                top: position.y - 80,\n              }}\n            >\n              <div className=\"absolute inset-0 rounded-full bg-background/95 backdrop-blur-sm\" />\n\n              {items.map((item, index) => {\n                const angle = index * angleStep - Math.PI / 2;\n                const x = Math.cos(angle) * radius;\n                const y = Math.sin(angle) * radius;\n\n                return (\n                  <motion.button\n                    key={item.label}\n                    initial={{ scale: 0, opacity: 0, x: 0, y: 0 }}\n                    animate={{\n                      scale: 1,\n                      opacity: 1,\n                      x,\n                      y,\n                      rotate: 360,\n                    }}\n                    exit={{ scale: 0, opacity: 0, x: 0, y: 0 }}\n                    transition={{\n                      delay: index * 0.05,\n                      type: \"spring\",\n                      stiffness: 300,\n                      damping: 25,\n                    }}\n                    whileHover={{ scale: 1.2 }}\n                    whileTap={{ scale: 0.9 }}\n                    onClick={() => {\n                      item.onClick();\n                      closeMenu();\n                    }}\n                    className={`absolute flex h-10 w-10 items-center justify-center rounded-full border border-border bg-card shadow-lg transition-colors ${\n                      item.danger\n                        ? \"hover:bg-destructive hover:text-destructive-foreground\"\n                        : \"hover:bg-primary hover:text-primary-foreground\"\n                    }`}\n                  >\n                    {item.icon}\n                  </motion.button>\n                );\n              })}\n\n              <button\n                onClick={closeMenu}\n                className=\"absolute flex h-10 w-10 items-center justify-center rounded-full bg-card\"\n              >\n                <MoreVertical className=\"h-5 w-5\" />\n              </button>\n            </motion.div>\n          </>\n        )}\n      </AnimatePresence>\n    </div>\n  );\n}\n",
      "type": "registry:component",
      "target": "components/uitripled/context-menu-bubble-shadcnui.tsx"
    }
  ]
}