primitives icon indicating copy to clipboard operation
primitives copied to clipboard

[ContextMenu] Add ability to control

Open benoitgrelard opened this issue 2 years ago • 18 comments

A few users have needed to control the ContextMenu in some scenarios (typically canvas based apps) so it would be good if we could provide a way to do so.

benoitgrelard avatar Apr 05 '22 12:04 benoitgrelard

i think this approach is very handy - just a anchorPoint to work with onShapeContextMenu

mrudowski avatar May 04 '22 23:05 mrudowski

What kind of control are we talking about here? Just the open state? Or something else too? My use case is that I'd like to conditionally disable the context menu - which could be faked through open={isDisabled ? false : undefined} (as long as the component would handle switching between controlled and uncontrolled "modes" gracefully)

Andarist avatar Oct 28 '22 07:10 Andarist

What kind of control are we talking about here? Just the open state?

This issue is about controlling the open state/position.

My use case is that I'd like to conditionally disable the context menu - which could be faked through open={isDisabled ? false : undefined} (as long as the component would handle switching between controlled and uncontrolled "modes" gracefully)

This sounds like a good case for adding a disabled prop maybe? That should be fairly trivial I think. Wanna create a separate issue @Andarist?

benoitgrelard avatar Oct 28 '22 09:10 benoitgrelard

Sure, I've created a feature request here: https://github.com/radix-ui/primitives/issues/1745

Andarist avatar Oct 28 '22 12:10 Andarist

Made a PR which adds a disabled prop to <ContextMenu /> trigger and data-disabled prop which could be helpful in case of custom styling of the trigger area. @Andarist, I hope that this will help in your case!

paperstick avatar Oct 29 '22 09:10 paperstick

Our case may be a little odd, but still wanting to put it out there.

We have a table where users can open a context menu on each row, but we also still have an actions column for our "less technical" users to allow them to open that same menu by clicking an icon. This is currently not possible without some workarounds / reuse of code between these two things

dbrxnds avatar Apr 24 '23 19:04 dbrxnds

I want to open context menu by keyboard shortcut in my case, and as mentioned by @dbrxnds , It would be nice if I can reuse the same menu between a dropdown menu and a context menu. Maybe the Menu component inside this project can be exposed as a separated component?

c8se avatar Aug 04 '23 09:08 c8se

Same here. We want to support opening context menu on right click but also when user taps a "..." button on the item in case they can't figure out the right click

siyao-polarr avatar Aug 21 '23 23:08 siyao-polarr

A short-term hack:

document.dispatchEvent(new KeyboardEvent("keydown", { key: "Escape" }))

This will trigger the DismissibleLayer's onDismiss function, which will close the ContextMenu.

canadaduane avatar Aug 22 '23 16:08 canadaduane

As a way to open the context menu when clicking on another element I came up with something similar:

trigger.current.dispatchEvent(
  new MouseEvent("contextmenu", {
    bubbles: true,
    clientX: button.current.getBoundingClientRect().x,
    clientY: button.current.getBoundingClientRect().y,
  }),
);

where trigger is the ContextMenu.Trigger element, and button is the element that should trigger the context menu on click.

AlbaOngaro avatar Aug 23 '23 10:08 AlbaOngaro

Our case may be a little odd, but still wanting to put it out there.

We have a table where users can open a context menu on each row, but we also still have an actions column for our "less technical" users to allow them to open that same menu by clicking an icon. This is currently not possible without some workarounds / reuse of code between these two things

no literally same issue here, we can't have this table rendering a menu for each cell. should be on a row level...

flbn avatar Nov 09 '23 12:11 flbn

no literally same issue here, we can't have this table rendering a menu for each cell. should be on a row level...

Just wrap the whole table with ContextMenu and render content depending on a selected cell. This sandbox might give you an idea how to achieve that

misha-erm avatar Nov 09 '23 12:11 misha-erm

Just wrap the whole table with ContextMenu and render content depending on a selected cell. This sandbox might give you an idea how to achieve that

the issue isn't really that, cells will be rendering different context menus depending on the content inside. it's... weird, but i think alba's comment might be the workaround i reach for. or maybe moving the root of the menu up and keeping each cell as a trigger. would be much easier with a primitive like open in <Popover/>

edit: ahh yeah, i've seen you've done what i was thinking of and moved the root up. cheers :)

flbn avatar Nov 09 '23 12:11 flbn

Would be nice to can use event to customize or prevent showing custom context menu

piszczu4 avatar Feb 18 '24 22:02 piszczu4

Solution for canvas-based apps

TLDR: Just wrap your map in ContextMenuTrigger

I thought toggling open/close state imperatively was an instrumental feature. But after getting stuck with issues styling @szhsin/react-menu consistently with other shadcn/ui components using tailwind, I tried the shadcn/radix-ui Context Menu again.

I was dealing with a canvas/map based application. I use maplibre-gl-js, deck.gl and nebula.gl for mapping, and shadcn/ui/radix-ui for components. Turns out, I just needed to wrap the entire map in a <ContextMenuTrigger>. Everything else works really nicely.

Much simpler than @szhsin/react-menu

This is a lot simpler than my original code using @szhsin/react-menu, which involved using the boundingBoxRef and ``anchorPoint props, storing pointer positions, and calculating workarounds/offsets for wrong menu position. I needed to use .getCanvas()?.getBoundingClientRect() and change the anchor points. For example:

  const containerRef = useRef<HTMLDivElement | null>(null);
  const [contextMenuAnchorPoint, setContextMenuAnchorPoint] = useState<{
    x: number;
    y: number;
  }>({ x: 0, y: 0 });
  const deckGlRef = useRef<DeckGLRef>(null);
  const canvasPosition = deckGlRef.current?.deck
    ?.getCanvas()
    ?.getBoundingClientRect();
  const panelCorrectedContextMenuAnchorPoint: typeof contextMenuAnchorPoint = {
    x: contextMenuAnchorPoint.x + (canvasPosition?.x ?? 0),
    y: contextMenuAnchorPoint.y + (canvasPosition?.y ?? 0),
  };

// In a callback e.g. onClick from the map library, I call `setContextMenuAnchorPoint`

return (
	<div
    	ref={containerRef}
		...

In both radix and szhsin/react-menu, I still have a contextMenuMode state which tells what context menu to show. e.g. If the user right clicks a cat, ill setContextMenuMode("cat"), and my ContextMenu component will conditionally render (e.g. disable/hide options).

Just sharing my thoughts with anyone who might need it after a good few hours spent on this :).

ben-xD avatar May 03 '24 17:05 ben-xD

UP! 🆙

Extremely necessary! I'm building a context-menu with options and I want it to work on mobile and just by controlling the opening state I can make it work.

image

lui7henrique avatar May 08 '24 22:05 lui7henrique