Context Menu
trovecn/context-menuFreeComponent
Right-click menu — Base UI's Menu popup anchored at the pointer.
Install it
npx shadcn@latest add https://trovecn.dev/r/context-menu.jsonSource
1"use client";23import {4 Children,5 cloneElement,6 createContext,7 isValidElement,8 useContext,9 useEffect,10 useMemo,11 useRef,12 useState,13 type ComponentProps,14 type ReactElement,15 type ReactNode,16} from "react";17import { ContextMenu as ContextMenuPrimitive } from "@base-ui/react/context-menu";18import { motion, AnimatePresence, useReducedMotion } from "motion/react";19import { CheckIcon, ChevronRightIcon } from "lucide-react";2021import { cn } from "@/lib/utils";22import { spring } from "@/lib/springs";23import { useProximityHover, proximityHoverWashClassName } from "@/hooks/use-proximity-hover";2425interface ContextMenuProximityContextValue {26 registerItem: (index: number, element: HTMLElement | null) => void;27}2829const ContextMenuProximityContext = createContext<ContextMenuProximityContextValue | null>(null);3031type ContextMenuIndexProp = { _index?: number };3233function useContextMenuItemRegistration(ref: React.RefObject<HTMLElement | null>, index?: number) {34 const ctx = useContext(ContextMenuProximityContext);35 useEffect(() => {36 if (index === undefined || !ctx) return;37 ctx.registerItem(index, ref.current);38 return () => ctx.registerItem(index, null);39 // eslint-disable-next-line react-hooks/exhaustive-deps40 }, [index, ctx]);41}4243function indexContextMenuChildren(children: ReactNode, counter: { current: number }): ReactNode {44 return Children.map(children, (child) => {45 if (!isValidElement(child)) return child;46 if (child.type === ContextMenuGroup || child.type === ContextMenuRadioGroup) {47 const groupProps = child.props as { children?: ReactNode };48 return cloneElement(child as ReactElement<{ children?: ReactNode }>, {49 children: indexContextMenuChildren(groupProps.children, counter),50 });51 }52 // ContextMenuSub's trigger is indexed as a row in *this* popup, but Base53 // UI requires it nested one level inside ContextMenuSub (alongside the54 // portaled SubContent), so that level is unwrapped here rather than55 // recursed into — SubContent is a separate popup with its own56 // independent index space and must be left alone.57 if (child.type === ContextMenuSub) {58 const subProps = child.props as { children?: ReactNode };59 return cloneElement(child as ReactElement<{ children?: ReactNode }>, {60 children: Children.map(subProps.children, (subChild) => {61 if (isValidElement(subChild) && subChild.type === ContextMenuSubTrigger) {62// … truncated
What it pulls in
npm packages
Other registry items
Files it writes
More from trovecn
All 26 items| Component | Registry | Kind | Access | Installs | Command |
|---|---|---|---|---|---|
| Accordionaccordion | trovecn | Components | Free | 3 deps | |
| Agent Activityagent-activity | trovecn | Components | Free | 2 deps | |
| Approval Requestapproval-request | trovecn | Components | Free | 2 deps | |
| Buttonbutton | trovecn | Components | Free | 2 deps | |
| Checkboxcheckbox | trovecn | Components | Free | 2 deps | |
| Checkbox Groupcheckbox-group | trovecn | Components | Free | 2 deps | |
| Comboboxcombobox | trovecn | Components | Free | 3 deps | |
| Comparison Slidercomparison-slider | trovecn | Components | Free | 1 dep |
