Saturday, July 27, 2024
HomeWeb developmentHigher Context Menus With Protected Triangles — Smashing Journal

Higher Context Menus With Protected Triangles — Smashing Journal


You’ve little question wrestled with menus which have nested menus earlier than. I can’t depend what number of instances I’ve hovered over a menu merchandise that reveals one other listing of menu objects, then tried to hover over that nested menu solely to have your complete menu shut on me.

That’s the setup for what I believe is a reasonably widespread concern when making menus — stopping nested menus from closing inadvertently. It’s not the customers’ fault; leaving hover between menu ranges is simple. It’s additionally not precisely the online’s fault; the menu is meant to shut if the pointer leaves the interactive space.

Protecting the nested menu open is tougher than it appears.

Earlier than we dig deeper into the difficulty, let’s acknowledge that counting on hover interactions for displaying and hiding menu objects is already considerably problematic. Not all units have a mouse, or a pointer, for that matter. You may argue that click on (or faucet) interactions are higher. Take Amazon’s major navigation for example. It requires a click on to open the principle menu and one other click on to open the nested menus.

Amazon employs a click on (or faucet) to open and shut nested menus.

Wanting previous a “hover versus faucet” debate, the attraction of hover interactions is apparent. It might be good to save lots of the consumer additional clicks, proper?

That’s what we’re aiming for on this article. The explanation I wish to sort out this in any respect is that I noticed Notion displaying off its new menu hover interactions.

Whereas I don’t have inside details about Notion’s method, I’ve what I believe is a sensible method to go about it based mostly on different examples I’ve seen, and I wish to present you the way I received there.

The Answer: Protected Triangles

The answer is protected triangles. It’s not precisely a brand new thought, both. Amazon popularized the thought, and Ben Kamens blogged it again in 2013 whereas introducing a jQuery plugin to perform it.

The essential thought is properly illustrated on this video that Michael Villar contains in a submit on the Top app weblog.

The protected triangle represents the interactive space that permits the nested menu to stay open.

There needs to be a “fashionable” method for including protected triangles to nested menus. I sought out extra examples.

Instance 1: VS Code

VS Code pulls off a pleasant hover interplay in its internet app.

VS Code additionally helps a protected triangle.

The VS Code method makes use of a delayed set off of a mouseover occasion callback in JavaScript. However earlier than the occasion fires, CSS kinds are utilized to the :hover state of menu objects.

macOS

I’m on a Mac and seen that macOS additionally implements some type of protected triangle in its menus.

A protected triangle in macOS menus cancels the protected space when returning hover from the nested menu to the top-level menu merchandise.

I can’t crack macOS open and examine its code, however this is a superb instance. Discover how the protected triangle works when transferring from a top-level menu to a nested menu however not when coming back from the nested menu to the top-level menu. It’s the type of delicate, polished distinction we would anticipate from Apple.

Radix Navigation Element

The open-source Radix library gives a Dropdown Menu element that pulls it off.

The Radix dropdown element implementation of a protected triangle.

What an incredible job! I discovered this instance from a chat Vercel posted to YouTube the place Radix co-creator Pedro Duarte provides a grasp class on the design challenges of dropdown menus.

The performance is just about the identical because the macOS method, the place :hover just isn’t triggered within the sibling components, making the expertise actually easy. Not like macOS, although, that is code that I can entry and examine.

The Radix method was massively useful so far as informing my very own work. So, let’s break down the method to point out you the way I carried out this into the principle navigation of the venture I work on, Neo4j.

A protected triangle implementation in Neo4j.

Demo

I put collectively a simplified model of my implementation which you could strive. The code is written in React, however the resolution just isn’t React-specific and might be paired with any UI framework you want.

Demonstrating a protected triangle.

See that? The second menu exposes the “protected triangle” on hover, displaying the hoverable space that permits the nested menu to remain open, even after the pointer has left the hover state.

The way it Works

The 2 key components for this method are SVG and the CSS pointer-events property.

Mouse Enter and Depart

First issues first. When hovering over a menu merchandise that features nested components, we set off an onMouseEnter callback that opens the nested menu, with onMouseEnter={() => setOpen(true)}.

Then, an onMouseLeave callback is chargeable for closing the submenu when the pointer leaves the aspect and its youngsters by way of onMouseLeave={() => setOpen(false)}. This half is equivalent to a easy nested menu, however discover <SafeArea /> as a result of that is the place the magic occurs.

const SafeAreaNestedOption = () => {
  const [open, setOpen] = useState<boolean>(false);
  const dad or mum = useRef<HTMLLIElement>(null);
  const youngster = useRef<HTMLDivElement>(null);
  const getTop = useCallback(() => {
    const peak = youngster.present?.offsetHeight;
    return peak ? `-${peak / 2 - 15}px` : 0;
  }, [child]);

  return (
    <li
      ref={dad or mum}
      model={{ place: "relative" }}
      onMouseEnter={() => setOpen(true)}
      onMouseLeave={() => setOpen(false)}
    >
      <NestedPlaceholder />
      {/* Protected mouse space */}
      {/* That is the place the magic will occur */}
      {open && dad or mum.present && youngster.present && (
        <SafeArea anchor={dad or mum.present} submenu={youngster.present} />
      )}
      {/* Nested components as youngsters */}
      <div
        model={ 0,
          high: getTop()
        }
        ref={youngster}
      >
        <ul>
          <li>Nested Possibility 1</li>
          <li>Nested Possibility 2</li>
          <li>Nested Possibility 3</li>
          <li>Nested Possibility 4</li>
        </ul>
      </div>
    </li>
  );
};

SVG

We use SVG to “draw” the protected triangle contained in the SafeArea element. When a nested menu is open, we create the SVG as a toddler aspect that isn’t seen however is there. The concept is that customers work together with it when it’s uncovered, even when they don’t understand it.

The trick is to be sure that the SVG is rectangular with a peak equal to the peak of the nested menu and a width equal to the space between the cursor and the nested menu.

The SVG element draws a “safe” area that users interact with, even if they cannot see it
The SVG aspect attracts a “protected” space that customers work together with, even when they can not see it. (Massive preview)

So long as the pointer is hovering over the SVG aspect, we have now one thing we will use to take care of the nested menu’s open state.

Pointer Occasions

There are two steps we have to take to attain this. First, we’ll create a “desired” path that connects our cursor to the submenu.

A triangular form is probably the most easy path we will assemble between a menu merchandise and a nested menu. You may visualize what this triangle may seem like within the picture beneath. The inexperienced represents the protected space, indicating that it received’t set off any onMouseLeave occasions. Conversely, the purple space signifies that it’s going to begin the onMouseLeave occasion since we’re possible transferring towards a sibling menu merchandise.

The bounding SVG forms a safe area in a triangular shape
The bounding SVG kinds a protected space in a triangular form. (Massive preview)

I approached this by making a SafeArea element in React that accommodates the SVG markup:

<svg
  model={{
    place: "fastened",
    width: svgWidth,
    peak: submenuHeight,
    pointerEvents: "none",
    zIndex: 2,
    high: submenuY,
    left: mouseX - 2
  }}
  id="svg-safe-area"
>
  {/* Protected Space */}
  <path
    pointerEvents="auto"
    stroke="purple"
    strokeWidth="0.4"
    fill="rgb(114 140 89 / 0.3)"
    d={
      `M 0, ${mouseY-submenuY} 
        L ${svgWidth},${svgHeight}
        L ${svgWidth},0 
        z`
    }
  />
</svg>

Additionally, to consistently replace our protected triangle and place it appropriately, we’d like a mouse listener, particularly onmousemove. I relied on a React hook from Josh Comeau known as useMousePosition in a useMousePosition.tsx file that gives the protected triangle element, designating the mouse place with mouseX and mouseY.

The Protected Triangle

The triangle is the SVG’s solely path aspect. For this to work accurately, we should set the CSS pointer-events property to none, which we will do inline straight within the SVG. Then we set pointer-events to auto inline within the path aspect. This fashion, we cease propagating occasions when they’re coming from the path aspect — the protected triangle — however not when occasions come from the SVG’s purple space.

Let’s break down the trail we’re drawing, because it’s far more easy than it appears:

<path
  pointerEvents="auto"
  stroke="purple"
  strokeWidth="0.4"
  fill="rgb(114 140 89 / 0.3)"
  d={
    `M 0, ${mouseY-submenuY} 
      L ${svgWidth},${svgHeight}
      L ${svgWidth},0 
      z`
  }
/>

We set the pointer-events property to auto to seize all mouse occasions, and it doesn’t set off the onMouseLeave occasion so long as the cursor is inside the trail.

Subsequent, we offer the trail with some fundamental CSS kinds for debugging functions. This fashion, we will see the protected space whereas testing interactions.

The 0, ${mouseY-submenuY} half is the trail’s place to begin, designating the middle of the SVG’s space.

Then we proceed our path drawing with two strains: L ${svgWidth},${svgHeight} and L ${svgWidth},0. The previous represents the primary line (L) based mostly on the SVG’s width and peak, whereas the latter attracts the second line (L) based mostly on the SVG’s width.

The z a part of the trail is what makes the whole lot work. z is what closes the trail, making a straight line to the trail’s place to begin, stopping the necessity to attract a 3rd line.

Safe triangle path points
Exhibiting the factors of the protected triangle SVG path. (Massive preview)

You may discover the trail in additional element or regulate it utilizing this SVG path editor.

There Are Some Gotchas

This can be a comparatively easy resolution on goal. There are some conditions the place this method could also be too easy, and you will have one other artistic resolution, notably for those who’re not working in React like me.

For instance, what if the consumer’s pointer strikes diagonally and touches a unique menu merchandise? This method doesn’t seize that interplay to stop the present nested menu from closing, however which may not be what you need it to do. Maybe you need the nested menu to shut and wish to regulate the SVG with a unique form. An “straightforward” method to resolve that is to debounce a cleanup perform in order that, on each mouse motion, you name the cleanup perform. And after some variety of milliseconds have handed and not using a mouse motion, you’ll take away the SVG aspect, and the sibling listeners would set off as anticipated.

One other instance is the navigation paths. A triangle is terrific however won’t be the best form on your menu and the way it’s designed. After doing a few of my very own exams, I’ve discovered {that a} curved path tends to be simpler, nearer to Needle’s method for a protected space:

Needle’s Context Menu Safe Area paths
Needle’s Context Menu Protected Space paths. (Massive preview)

Wrapping Up

As you now know, arising with an answer for nested menus that reveal on hover is extra of a problem than it appears on the floor. Whether or not a hover-based method and the clicks it saves are definitely worth the further issues that make a greater consumer expertise versus a click-based method is completely as much as you. Should you go along with a menu that depends on a mouse hover to disclose a nested menu, you now have a useful resource that enhances its usability.

What about you? Is that this a UX problem you’ve struggled with? Have you ever tried to resolve it in another way? Is the “protected triangle” idea efficient on your specific use case? I’d like to know within the feedback!

Smashing Editorial
(gg, yk)



RELATED ARTICLES

LEAVE A REPLY

Please enter your comment!
Please enter your name here

Most Popular

Recent Comments