-
Notifications
You must be signed in to change notification settings - Fork 767
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[NavigationMenu] Expose position of active <NavigationMenu.Item /> #1462
Comments
I realize another solution is to wrap One significant downside of this approach is that you get a short window of time where the CSS custom property indicating the position of the indicator is not set yet, so the ViewportPosition jumps around. const StyledIndicatorWithArrow = React.forwardRef((props, forwardedRef) => (
<StyledIndicator {...props} ref={forwardedRef}>
<StyledArrow />
<ViewportPosition>
<NavigationMenuViewport />
</ViewportPosition>
</StyledIndicator>
)); Example: https://codesandbox.io/s/cocky-heisenberg-biwho8?file=/App.js |
Hey @janhoogeveen , I agree this is a bit of a pain point at the moment, providing some way to track the content against the active trigger would be very useful for folks 👍
As you noted, the indicator wasn’t designed for this purpose. In addition to the visual glitch there is also an accessibility problem as Thanks for raising, I'm going to mark it for improvement. In the meantime it is possible to implement your own tracking logic fairly easily depending on your design requirements, here is a (really) rough example of how it could work with a centrally aligned navigation: https://codesandbox.io/s/navigation-menu-track-position-fhh9so |
Hey @andy-hook, great to hear! Curious to see what kind of API you're going to end up! 👍🏻 I really appreciate your example, it looks solid and easy enough to use in the meantime! |
@andy-hook Thanks for the example! I'll be looking forward to this feature as well. Would you mind explaining why you set the list ref with |
@elisehein It ensures the ref callbacks are updated with the list node when it’s available, there are likely more efficient ways to build this but I wanted to provide a quick example to show that this is possible in user code with some additional measurement. Another approach might be to get and sync the currently active trigger via effect and update the positioning accordingly: https://codesandbox.io/s/navigation-menu-track-position-effect-sync-q20tjd?file=/src/App.js It depends on your design and the types of animation effects you’re trying to achieve, this example is dependent on central alignment so a different approach or more generic implementation might be necessary depending on the need. It also doesn't account for responsive changes in element dimensions, for that you'd need to observe for element resize etc. Hope this helps! |
any news in this issue? |
What's the best way to center the content below the item, but keeping it "contained" in the viewport, so the content will stick to the left or right screen edge, instead of going off-screen? I'm trying to build a Main navigation menu, it is aligned to the right inside the header. For the right-most navigation items the content goes off-screen currently. Thanks for any hints |
Hello 👋 Is it OK to use a Dropdown in a NavigationMenu? Or can we use a Menubar as Navigation? Because what I want is a submenu displayed like a dropdown just below the NavigationMenu.Item (see screenshot below). I don't want a common view port to display links on the middle of the Navigation ... the example for NavigationMenu looks nice, but it does not look like the most common design for a Navigation. In my opinion, most navigations look like the MenuBar actually or a NavigationMenu + Dropdowns. And sticking to NavigationMenu seems to add a lot of complexity to position it below the NavigationMenu.Item and handle screen edges... It is the kind of things I am expecting Radix to handle for me. What Dropdown seems to do already by the way. Moving from Reakit v1 to RadixUI was easy until I started migrating the last component of my todo list (MainNav), and now I am hard stuck on my migration Pull Request 😅 Thanks for any help! 🙏 Screenshot of desired design |
Nope, as you say, Menubar shouldn't be used as navigation @heymath how many levels deep is your nav? is it only one level per your screenshot? |
Yes it is only one level 😉 Thanks! |
@heymath I don't think it's much complexity to use the Website navigation typically sits at the head of a page by convention so collisions are unlikely, and as horizontal width becomes limited it's typical to switch to a separate collapsed drawer / accordion style anyway. Here's a simple example: Using Hope this helps. |
@andy-hook well actually you can see on the screenshot that the "Plus" dropdown is at the end of the nav, and the nav is large (very often it that takes the whole screen width or more on small screens with a horizontal scroll). Anyway, I have to say that I like very much migrating other components from Reakit to Radix. It was clear and simple. By the way I am very new to this, I only spent one day exploring this migration, maybe I don't see how things can be done yet and it is not that difficult to achieve what we want... Thanks again for your answers! |
@andy-hook maybe there is another way to achieve it, but staying close to the current Reakit implementation, and my work in progress of the migration, I am stuck at this: https://codesandbox.io/s/simple-navigation-menu-forked-v5d6qp?file=/src/App.js My problem here is that Radix does not auto position the Content to be aligned with the Trigger or does not Provide any thing to help to do it (unless I missed it). And I do not see how to achieve it 🤔 Is it normal to not have this kind of feature included in NavigationMenu while it is in DropdownMenu & Menubar components? |
Any updates on this? |
up |
Ah facing the same issue, thought initially it was a bug in the shadcn/ui package. |
still not resolved? |
I was going to implement this navigation menu into our website, but ran into this very issue. Seems like it would be a nice feature to include? The example conveniently uses only 3 menu items to hide this issue. 🤔 |
UP |
Would really appreciate an easy prop to achieve this |
I've been using shadcn/ui alot recently, and I have to say this navigation menu seems like a poorly created component. Very little customizability, and no prop to get rid of the open on hover. Seems like a lot of functionality that is pretty standard in well developed ui libraries are not present in this radix nav menu comp. I'm currently using the sheet component now to create a my nav menu since it allows me to do what I need. |
+1 |
1 similar comment
+1 |
Any updates on this? It is shocking to me that a component library that is designed to be style-less has something this unorthodox built into it by default without a simple way of disabling it. Basically every website has a navbar and most of them are wider than their example in the docs. |
How to make the menubar active color when clicked |
Any updates on this? Would love to be able to override and choose the position of the dropdowns :) |
Ran into the same issue as everyone else, would love to have some props to control some of the behaviour. |
+1 |
+1 |
Up! |
Any update on this? NavigationMenu doesn't work at all with smaller menus because of this issue. Thanks! Screen.Recording.2024-02-16.at.1.52.24.AM.mov |
This issue has been open for almost 2 years. Dev team, please look into it. We keep facing this issue. |
Please implement this |
Stuck with the same issue. I'm going to write a workaround for now. |
can you share the workaround? Did people here open a discussion on the discord server? |
I ended up just replacing the menu items that need a dropdown with a Popover component. And it worked for me. |
Also having the same positioning issue - not all menu bars are short, or even center aligned... |
could you make a tutorial? |
This solution is great for quickly solve the problem; however, it still has an issue: the first time it opens, it will render the I found a simple solution that is working for my case: <NavigationMenu.Root>
<NavigationMenu.List>
{...}
<NavigationMenu.Indicator aria-hidden={false}>
<NavigationMenu.Viewport />
</NavigationMenu.Indicator>
</NavigationMenu.List>
<NavigationMenu.Viewport className="hidden" /> // display: none
</NavigationMenu.Root> or <NavigationMenu.Root>
<NavigationMenu.List>
{...}
</NavigationMenu.List>
<NavigationMenu.Indicator aria-hidden={false}>
<NavigationMenu.Viewport />
</NavigationMenu.Indicator>
<NavigationMenu.Viewport className="hidden" /> // display: none
</NavigationMenu.Root> Initially, the This is a crazy hack for a quick solution. Ideally, we would have an exported variable at a higher level so that we have manual control of the viewport position, but until then, this works. |
Here is my solution using import { forwardRef, useEffect, useRef } from 'react';
import * as NavigationMenuPrimitive from '@radix-ui/react-navigation-menu';
import { cn } from '~/lib/utils';
import { NavigationMenuViewport } from './NavigationMenuViewport';
export function mergeReferences<T = unknown>(
references: Array<React.MutableRefObject<T> | React.LegacyRef<T>>,
): React.RefCallback<T> {
return (value) => {
for (const reference of references) {
if (typeof reference === 'function') {
reference(value);
} else if (reference != null) {
(reference as React.MutableRefObject<T | null>).current = value;
}
}
};
}
export const NavigationMenu = forwardRef<
React.ElementRef<typeof NavigationMenuPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Root>
>(({ className, children, ...props }, reference) => {
const containerReference = useRef<HTMLElement>(null);
useEffect(() => {
const container = containerReference.current;
if (!container) return;
const updatePosition = (item: HTMLElement) => {
const menuItemRect = item.getBoundingClientRect();
const containerRect = container.getBoundingClientRect();
const position = {
top: menuItemRect.top - containerRect.top,
left: menuItemRect.left - containerRect.left,
};
container.style.setProperty('--radix-navigation-menu-item-active-top', `${position.top}px`);
container.style.setProperty('--radix-navigation-menu-item-active-left', `${position.left}px`);
};
const mutationCallback = (mutationsList: MutationRecord[]) => {
for (const mutation of mutationsList) {
if (
mutation.type === 'attributes' &&
mutation.attributeName === 'data-state' &&
mutation.target instanceof HTMLElement &&
mutation.target.hasAttribute('aria-expanded') &&
mutation.target.dataset.state === 'open'
) {
updatePosition(mutation.target);
}
}
};
const observer = new MutationObserver(mutationCallback);
observer.observe(container, {
childList: true,
attributes: true,
subtree: true,
});
return () => {
observer.disconnect();
};
}, []);
return (
<NavigationMenuPrimitive.Root
ref={mergeReferences([reference, containerReference])}
className={cn('relative z-10 flex max-w-max flex-1 items-center justify-center', className)}
{...props}
>
{children}
<NavigationMenuViewport />
</NavigationMenuPrimitive.Root>
);
});
NavigationMenu.displayName = NavigationMenuPrimitive.Root.displayName; And we can access export const NavigationMenuViewport = forwardRef<
React.ElementRef<typeof NavigationMenuPrimitive.Viewport>,
React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Viewport>
>(({ className, ...props }, reference) => (
<div
className={cn(
'absolute left-0 top-full flex translate-x-[var(--radix-navigation-menu-item-active-left)] transform justify-center transition-transform duration-100',
)}
>
<NavigationMenuPrimitive.Viewport
className={cn(
'origin-top-center bg-popover text-popover-foreground relative mt-1.5 h-[var(--radix-navigation-menu-viewport-height)] w-full overflow-hidden rounded-md border shadow-lg data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-90 md:w-[var(--radix-navigation-menu-viewport-width)]',
className,
)}
ref={reference}
{...props}
/>
</div>
));
NavigationMenuViewport.displayName = NavigationMenuPrimitive.Viewport.displayName; |
Feature request
Can we expose the position of the active
<NavigationMenu.Item />
, and maybe it's bounding box so we can position the<NavigationMenu.Viewport />
directly below the active item?It could be exposed through CSS custom properties as
--radix-navigation-menu-indicator-position
, which should work for both horizontal and vertical menu's.Even better would be a hook or render prop so we can wait until the indicator knows its position before we show the
<NavigationMenu.Viewport />
Overview
The NavigationMenu works really well if your foldout content is wider than your list of navigation menu items.
However, if you add (much) more
NavigationMenu.Item
components the layout will start to look awkward.Example on codesandbox: https://02xv7f.csb.app/
Who does this impact? Who is this for?
Everyone who wants to use NavigationMenu with a wide list of items.
The text was updated successfully, but these errors were encountered: