{ "$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: , onClick: () => {} },\n { label: \"Copy\", icon: , onClick: () => {} },\n { label: \"Share\", icon: , onClick: () => {} },\n {\n label: \"Delete\",\n icon: ,\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(null);\n const containerRef = useRef(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 \n \n \n \n\n \n {isOpen && (\n <>\n \n\n \n
\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 {\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 \n );\n })}\n\n \n \n \n \n \n )}\n \n
\n );\n}\n", "type": "registry:component", "target": "components/uitripled/context-menu-bubble-shadcnui.tsx" } ] }