"use client"; import { useEffect, useRef, useState } from "react"; import { createPortal } from "react-dom"; import { X } from "lucide-react"; import { cn } from "@/lib/utils"; export interface RadialMenuItem { id: string; icon: React.ReactNode; label: string; onClick: () => void; disabled?: boolean; destructive?: boolean; } interface RadialMenuProps { items: RadialMenuItem[]; isOpen: boolean; position: { x: number; y: number }; onClose: () => void; size?: number; } export function RadialMenu({ items, isOpen, position, onClose, size = 200, }: RadialMenuProps) { const [mounted, setMounted] = useState(false); const [activeIndex, setActiveIndex] = useState(-1); const [animatingIn, setAnimatingIn] = useState(false); const menuRef = useRef(null); const activeIndexRef = useRef(activeIndex); const itemsRef = useRef(items); const onCloseRef = useRef(onClose); activeIndexRef.current = activeIndex; itemsRef.current = items; onCloseRef.current = onClose; useEffect(() => { setMounted(true); }, []); useEffect(() => { if (isOpen) { requestAnimationFrame(() => requestAnimationFrame(() => setAnimatingIn(true))); } else { setAnimatingIn(false); } }, [isOpen]); useEffect(() => { if (!isOpen) return; setActiveIndex(-1); const handleKeyDown = (e: KeyboardEvent) => { const items = itemsRef.current; const currentIndex = activeIndexRef.current; if (e.key === "Escape") { e.preventDefault(); onCloseRef.current(); return; } if (e.key === "Enter") { if (currentIndex >= 0 && currentIndex < items.length) { e.preventDefault(); const item = items[currentIndex]; if (!item.disabled) { item.onClick(); onCloseRef.current(); } } return; } if (e.key === "ArrowRight" || e.key === "ArrowDown") { e.preventDefault(); setActiveIndex((prev) => { const hasEnabledItem = items.some((item) => !item.disabled); if (!hasEnabledItem) return -1; let next = prev; let loops = 0; do { next = next + 1 >= items.length ? 0 : next + 1; loops++; } while (items[next]?.disabled && loops < items.length); return items[next]?.disabled ? -1 : next; }); return; } if (e.key === "ArrowLeft" || e.key === "ArrowUp") { e.preventDefault(); setActiveIndex((prev) => { const hasEnabledItem = items.some((item) => !item.disabled); if (!hasEnabledItem) return -1; let next = prev; let loops = 0; do { next = next - 1 < 0 ? items.length - 1 : next - 1; loops++; } while (items[next]?.disabled && loops < items.length); return items[next]?.disabled ? -1 : next; }); return; } }; document.addEventListener("keydown", handleKeyDown); return () => document.removeEventListener("keydown", handleKeyDown); }, [isOpen]); const radius = size / 2 - 28; const center = size / 2; if (!mounted) return null; return createPortal( <>
{items.map((item, index) => { const angle = (index / items.length) * 2 * Math.PI - Math.PI / 2; const x = center + radius * Math.cos(angle); const y = center + radius * Math.sin(angle); const itemSize = 40; return (
); })}
, document.body ); }