Context Menu
nexvyn/context-menuFreeComponent
A right-click menu with proximity highlight and keyboard navigation.
Live preview
live · rendered by the registry
Rendered by nexvyn on their own page — this is the component running, not a screenshot of it.Install it
npx shadcn@latest add https://ui.nexvyn.dev/r/context-menu.jsonSource
1'use client'23import {4 createContext,5 forwardRef,6 useCallback,7 useContext,8 useEffect,9 useId,10 useMemo,11 useRef,12 useState,13 type ReactNode,14} from 'react'15import { createPortal } from 'react-dom'16import { motion, useReducedMotion } from 'motion/react'17import { cn } from '@/lib/utils'18import { springs } from '@/lib/motion-tokens'19import { useProximityHighlight, ProximityHighlight } from '@/lib/hooks/use-proximity-highlight'2021export interface ContextMenuItem {22 label: string23 icon?: ReactNode24 shortcut?: string25 onSelect?: () => void26 destructive?: boolean27 disabled?: boolean28 separatorAfter?: boolean29}3031interface ContextMenuContextValue {32 open: boolean33 setOpen: (next: boolean) => void34 items: ContextMenuItem[]35 onOpenChange?: (open: boolean) => void36}3738const ContextMenuContext = createContext<ContextMenuContextValue | null>(null)3940function useContextMenuCtx(componentName: string) {41 const ctx = useContext(ContextMenuContext)42 if (!ctx) throw new Error(`${componentName} must be used within ContextMenu`)43 return ctx44}4546export interface ContextMenuProps {47 children: ReactNode48 items: ContextMenuItem[]49 disabled?: boolean50 onOpenChange?: (open: boolean) => void51 className?: string52 menuClassName?: string53}5455export const ContextMenu = forwardRef<HTMLDivElement, ContextMenuProps>(56 ({ children, items, disabled = false, onOpenChange, className, menuClassName }, ref) => {57 const [open, setOpen] = useState(false)58 const [position, setPosition] = useState({ x: 0, y: 0 })59 const triggerRef = useRef<HTMLDivElement>(null)60 const contentRef = useRef<HTMLDivElement>(null)61 const longPressTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null)62 const longPressMovedRef = useRef(false)63 const previousFocusRef = useRef<HTMLElement | null>(null)6465 const setOpenState = useCallback(66 (next: boolean) => {67 setOpen(next)68 onOpenChange?.(next)69 },70 [onOpenChange],71 )7273 const handleContextMenu = useCallback(74 (e: React.MouseEvent) => {75 if (disabled) return76 e.preventDefault()77 previousFocusRef.current = document.activeElement as HTMLElement | null78 setPosition({ x: e.clientX, y: e.clientY })79 setOpenState(true)80 },81 [disabled, setOpenState],82 )8384 const handleTouchStart = useCallback(85 (e: React.TouchEvent) => {86 if (disabled) return87 longPressMovedRef.current = false88 const touch = e.touches[0]89// … truncated
What it pulls in
npm packages
Files it writes
More from nexvyn
All 33 items| Component | Registry | Kind | Access | Installs | Command |
|---|---|---|---|---|---|
| Badgebadge | nexvyn | Components | Free | 2 deps · 2 files | |
| Bars Themebars-theme | nexvyn | Components | Free | 1 dep · 3 files | |
| Bounce Sidebarbounce-sidebar | nexvyn | Components | Free | 1 dep · 2 files | |
| Breadcrumbsbreadcrumbs | nexvyn | Components | Free | 1 dep · 2 files | |
| Checkboxcheckbox | nexvyn | Components | Free | 1 dep · 3 files | |
| Clipboard Fieldclipboard-field | nexvyn | Components | Free | no deps · 2 files | |
| Color Pickercolor-picker | nexvyn | Components | Free | no deps · 16 files | |
| Comboboxcombobox | nexvyn | Components | Free | 1 dep · 2 files |
