ui icon indicating copy to clipboard operation
ui copied to clipboard

Navigation Menu, Menu is not centered to parent

Open AlejandroSanchez90 opened this issue 2 years ago • 11 comments

Im triying to use navigaton menu, but the menu is not centered to parent image of problem

    <div className='hidden lg:flex h-full'>
      <div className='flex'>
        <NavigationMenu>
          <NavigationMenuList>
            {routes.map((route) => {
              return (
                <NavigationMenuItem key={route.path}>
                  {route.subRoutes ? (
                    <Link href={route.path}>
                      <NavigationMenuTrigger>{route.label}</NavigationMenuTrigger>
                      <NavigationMenuContent>
                        <ul>
                          {route.subRoutes.map((subRoute) => (
                            <li key={subRoute.path} className='whitespace-nowrap uppercase'>
                              <Link href={subRoute.path}>{subRoute.label}</Link>
                            </li>
                          ))}
                        </ul>
                      </NavigationMenuContent>
                    </Link>
                  ) : (
                    <Link href={route.path}>
                      <NavigationMenuLink className={navigationMenuTriggerStyle()}>
                        {route.label}
                      </NavigationMenuLink>
                    </Link>
                  )}
                </NavigationMenuItem>
              );
            })}
          </NavigationMenuList>
        </NavigationMenu>

      </div>
    </div>

AlejandroSanchez90 avatar Sep 12 '23 17:09 AlejandroSanchez90

reading the code seems like this is the correct behavior o.o

AlejandroSanchez90 avatar Sep 12 '23 19:09 AlejandroSanchez90

try this: https://codesandbox.io/s/simple-navigation-menu-em1qrc?file=/src/App.js this is a simple example.

Josevictor2 avatar Sep 13 '23 01:09 Josevictor2

@AlejandroSanchez90 any solution? I copied the demo code into my project same behaviour.

krunalinfynno avatar Oct 26 '23 11:10 krunalinfynno

Hi, @krunalinfynno @Josevictor2 @AlejandroSanchez90

Refer to this answer https://github.com/shadcn-ui/ui/issues/94#issuecomment-1763020702 I try reproduce and found the solution, seems like this:

  1. Add submenu-viewport class on NavigationMenuViewport
// components/ui/navigation-menu.tsx

const NavigationMenuViewport = React.forwardRef<
  React.ElementRef<typeof NavigationMenuPrimitive.Viewport>,
  React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Viewport>
>(({ className, ...props }, ref) => (
  <div className={cn("absolute left-0 top-full flex justify-center")}>
    <NavigationMenuPrimitive.Viewport
      className={cn(
        "submenu-viewport ....",
        className
      )}
      ref={ref}
      {...props}
    />
  </div>
))
NavigationMenuViewport.displayName =
  NavigationMenuPrimitive.Viewport.displayName
  1. Add this function on your component:
  function onNavChange() {
    setTimeout(() => {
      const triggers = document.querySelectorAll(
        '.submenu-trigger[data-state="open"]'
      )
      if (triggers.length === 0) return

      const firstTrigger = triggers[0] as HTMLElement
      const viewports = document.getElementsByClassName("submenu-viewport")

      if (viewports.length > 0) {
        const viewport = viewports[0] as HTMLElement
        viewport.style.left = `${firstTrigger.offsetLeft}px`
      }
    })
  }
  1. Add onValueChange inside <NavigationMenu>
<NavigationMenu onValueChange={onNavChange}>
  1. Add class submenu-trigger on <NavigationMenuTrigger>
 <NavigationMenuTrigger className="submenu-trigger">
  1. Last one, make sure you didn't add <NavigationMenuViewport>

It's work on my app

akbarsaputrait avatar Nov 24 '23 10:11 akbarsaputrait

@akbarsaputrait This solution work for me, however, is there any way to center the NavigationMenuContent below the trigger instead of left-aligning? Many thanks!

zardooohasselfrau avatar Feb 01 '24 00:02 zardooohasselfrau

@akbarsaputrait This solution work for me, however, is there any way to center the NavigationMenuContent below the trigger instead of left-aligning? Many thanks!

Not beautiful but working solution that I used for aligning on right instead of left was this:

<NavigationMenu className="[&>.absolute>.relative]:mt-0 [&>.absolute>.relative]:rounded-t-none [&>.absolute>.relative]:border-t-0 [&>.absolute]:-right-3 [&>.absolute]:left-auto [&>.absolute]:mt-5">

<NavigationMenuItem className="left-[unset] right-0">

You tweak and get to your solution from this I think.

u2ru avatar Feb 16 '24 12:02 u2ru

Hi, @krunalinfynno @Josevictor2 @AlejandroSanchez90

Refer to this answer #94 (comment) I try reproduce and found the solution, seems like this:

  1. Add submenu-viewport class on NavigationMenuViewport
// components/ui/navigation-menu.tsx

const NavigationMenuViewport = React.forwardRef<
  React.ElementRef<typeof NavigationMenuPrimitive.Viewport>,
  React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Viewport>
>(({ className, ...props }, ref) => (
  <div className={cn("absolute left-0 top-full flex justify-center")}>
    <NavigationMenuPrimitive.Viewport
      className={cn(
        "submenu-viewport ....",
        className
      )}
      ref={ref}
      {...props}
    />
  </div>
))
NavigationMenuViewport.displayName =
  NavigationMenuPrimitive.Viewport.displayName
  1. Add this function on your component:
  function onNavChange() {
    setTimeout(() => {
      const triggers = document.querySelectorAll(
        '.submenu-trigger[data-state="open"]'
      )
      if (triggers.length === 0) return

      const firstTrigger = triggers[0] as HTMLElement
      const viewports = document.getElementsByClassName("submenu-viewport")

      if (viewports.length > 0) {
        const viewport = viewports[0] as HTMLElement
        viewport.style.left = `${firstTrigger.offsetLeft}px`
      }
    })
  }
  1. Add onValueChange inside <NavigationMenu>
<NavigationMenu onValueChange={onNavChange}>
  1. Add class submenu-trigger on <NavigationMenuTrigger>
 <NavigationMenuTrigger className="submenu-trigger">
  1. Last one, make sure you didn't add <NavigationMenuViewport>

It's work on my app

I will add a little improvement.

NO IDEA WHY BUT...

Problem:

  1. When onNavChange Triggered first time, .submenu-viewport is could not be found in DOM
  2. On the second trigger everything works.

Solution:

Instead of adding submenu-viewport class add left-[var(--menu-left-position)] to NavigationMenuViewport and just set global css variable with position

onNavChange:

function onNavChange() {
    setTimeout(() => {
      const triggers = document.querySelectorAll(
        '.submenu-trigger[data-state="open"]'
      )
      if (triggers.length === 0) return

      const firstTrigger = triggers[0] as HTMLElement

      document.documentElement.style.setProperty(
        "--menu-left-position",
        `${firstTrigger.offsetLeft}px`
      )
    })
  }

NavigationMenuViewport:

// components/ui/navigation-menu.tsx

const NavigationMenuViewport = React.forwardRef<
  React.ElementRef<typeof NavigationMenuPrimitive.Viewport>,
  React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Viewport>
>(({ className, ...props }, ref) => (
  <div className={cn("absolute left-0 top-full flex justify-center")}>
    <NavigationMenuPrimitive.Viewport
      className={cn(
        "left-[var(--menu-left-position)] ...",
        className,
      )}
      ref={ref}
      {...props}
    />
  </div>
));

NavigationMenu as before

<NavigationMenu onValueChange={onNavChange}>

NavigationMenuTrigger as before

<NavigationMenuTrigger className="submenu-trigger">

u2ru avatar Feb 16 '24 13:02 u2ru

@akbarsaputrait Any questions ? )))

u2ru avatar Feb 16 '24 17:02 u2ru

After a few long hours of trying to make this work for me too, I'll add my variant as this seems to have fixed my issue and allowed me to center the dropdown under the button.

onNavChange

function onNavChange() {
  setTimeout(() => {
    // Select elements with the state "open"
    const triggers = document.querySelectorAll(
      '.submenu-trigger[data-state="open"]'
    );
    const dropdowns = document.querySelectorAll(
      '.nav-viewport[data-state="open"]'
    );

    // Check if both triggers and dropdowns are present
    if (!triggers.length || !dropdowns.length) return;

    // Simplify the calculation by extracting it into a variable
    const { offsetLeft, offsetWidth } = triggers[0] as HTMLElement;
    const menuWidth = dropdowns[0].children[0].clientWidth;
    const menuLeftPosition = offsetLeft + offsetWidth / 2 - menuWidth / 2;

    // Apply the calculated position
    document.documentElement.style.setProperty(
      "--menu-left-position",
      `${menuLeftPosition}px`
    );
  });
}

NavigationMenuViewport (Additional nav-viewport)

const NavigationMenuViewport = React.forwardRef<
  React.ElementRef<typeof NavigationMenuPrimitive.Viewport>,
  React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Viewport>
>(({ className, ...props }, ref) => (
  <div className={cn("absolute left-0 top-full flex justify-center")}>
    <NavigationMenuPrimitive.Viewport
      className={cn(
        "left-[var(--menu-left-position)] nav-viewport ...",
        className
      )}
      ref={ref}
      {...props}
    />
  </div>
));

NavigationMenu as before

<NavigationMenu onValueChange={onNavChange}>

NavigationMenuTrigger as before

<NavigationMenuTrigger className="submenu-trigger">

ConnorC18 avatar Feb 17 '24 04:02 ConnorC18

@ConnorC18 Perfect!

Still having multiple NavMenus and moving Viewport by changing position property (left) is not a good practice in animation as it is being drawn on a specific layer when rendering a page.

Anyways, GJ

u2ru avatar Feb 17 '24 04:02 u2ru

Is it fixed i am facing same issue.?

Shead1980 avatar Mar 08 '24 19:03 Shead1980

This issue has been automatically closed because it received no activity for a while. If you think it was closed by accident, please leave a comment. Thank you.

shadcn avatar Jun 28 '24 23:06 shadcn

https://codesandbox.io/s/simple-navigation-menu-em1qrc?file=/src/App.js

Not fixed lol, there's quite a few issues around this

maxpaleo avatar Jul 20 '24 05:07 maxpaleo