在React嵌套菜单上的悬停状态

huangapple go评论66阅读模式
英文:

Hover states on React nested menu

问题

在以下代码中,我有一个嵌套菜单。在悬停时,我希望将 selected 类名称添加到 Nav.Item,并且仅在我悬停到另一个 Nav.Item 上时将其移除。我可以通过 onMouseOver 来实现这一点。不幸的是,它不会向任何子级 Nav.Item 添加 selected 类。

因此,要求如下:

  • 对于悬停在任何 nav.item 上,应添加 selected 类
  • 如果鼠标离开项目,则不要删除 selected
  • 当出现子菜单时,在 Nav.Item 上保留 selected
  • 只有在悬停到另一个相同级别的 Nav.Item 上时才移除 selected
"use client"; 
 
import Nav from 'react-bootstrap/Nav'; 
import Link from 'next/link'; 
 
import { getMenu } from '@/lib/APIs/menu'; 
import { use, useEffect, useRef, useState } from 'react'; 
import { usePathname } from 'next/navigation'; 
import { Button, NavDropdown } from 'react-bootstrap'; 
 
const dataPromise = getMenu(); 
 
const GlobalNav = () => { 
    const MENU = use(dataPromise); 
    const [selectedItemId, setSelectedItemId] = useState<string | null>(null); 
 
    function handleItemHover(itemId: string) { 
        setSelectedItemId(itemId); 
    } 
 
    function renderMenuItems(menuItems: any[]) { 
        return menuItems.map((item: any) => ( 
            <Nav.Item 
                as='li' 
                key={item.id}    
                onMouseOver={() => handleItemHover(item.id)} 
                className={selectedItemId === item.id ? 'selected' : ''} 
            > 
                <Nav.Link 
                    as={Link} 
                    href={item.url} 
                > 
                    <span> 
                        {item.title} 
                    </span> 
                </Nav.Link> 
                {item.children && item.children.length > 0 && ( 
                    <Nav as='ul'> 
                        {renderMenuItems(item.children)} 
                    </Nav> 
                )} 
            </Nav.Item> 
        )); 
    } 
 
    return ( 
        <Nav as='ul'> 
            {renderMenuItems(MENU)} 
        </Nav> 
    ); 
} 
 
export default GlobalNav; 

希望有人能帮助您解决这个问题。

英文:

In the following code I have a nested menu. On hover I wanted to add selected class name to the Nav.Item and only remove it when I hover over another Nav.Item. I could achive that with onMouseOver. Unfortunately, it doesn't add selected class to any of the sub-level Nav.Item.

So the criterias would be:
for hover on any nav.item selected class should be added

  • don't remove selected class if mouse leaves the item
  • keep selected class on the Nav.Item when a child menu appears
  • remove selected class only, if hover over another Nav.Item but on the same level
&quot;use client&quot;; 
 
import Nav from &#39;react-bootstrap/Nav&#39;; 
import Link from &#39;next/link&#39;; 
 
import { getMenu } from &#39;@/lib/APIs/menu&#39;; 
import { use, useEffect, useRef, useState } from &#39;react&#39;; 
import { usePathname } from &#39;next/navigation&#39;; 
import { Button, NavDropdown } from &#39;react-bootstrap&#39;; 
 
const dataPromise = getMenu(); 
 
const GlobalNav = () =&gt; { 
    const MENU = use(dataPromise); 
    const [selectedItemId, setSelectedItemId] = useState&lt;string | null&gt;(null); 
 
    function handleItemHover(itemId: string) { 
        setSelectedItemId(itemId); 
    } 
 
    function renderMenuItems(menuItems: any[]) { 
        return menuItems.map((item: any) =&gt; ( 
            &lt;Nav.Item 
                as=&#39;li&#39; 
                key={item.id}    
                onMouseOver={() =&gt; handleItemHover(item.id)} 
                className={selectedItemId === item.id ? &#39;selected&#39; : &#39;&#39;} 
            &gt; 
                &lt;Nav.Link 
                    as={Link} 
                    href={item.url} 
                &gt; 
                    &lt;span&gt; 
                        {item.title} 
                    &lt;/span&gt; 
                &lt;/Nav.Link&gt; 
                {item.children &amp;&amp; item.children.length &gt; 0 &amp;&amp; ( 
                    &lt;Nav as=&#39;ul&#39;&gt; 
                        {renderMenuItems(item.children)} 
                    &lt;/Nav&gt; 
                )} 
            &lt;/Nav.Item&gt; 
        )); 
    } 
 
    return ( 
        &lt;Nav as=&#39;ul&#39;&gt; 
            {renderMenuItems(MENU)} 
        &lt;/Nav&gt; 
    ); 
} 
 
export default GlobalNav; 

I hope someone can help with it.

答案1

得分: 1

请尝试以下代码:

function hasSelectedChild(menuItem) {
   return menuItem.children && menuItem.children.length > 0 && menuItem.children.find(item => item.id === selectedItemId || hasSelectedChild(item)) 
}

function renderMenuItems(menuItems) { 
        return menuItems.map(item => ( 
            <Nav.Item 
                as='li' 
                key={item.id}    
                onMouseOver={(e) => {
                  e.stopPropagation();
                  handleItemHover(item.id)
                }} 
                className={(hasSelectedChild(item) || selectedItemId === item.id) ? 'selected' : ''}
            > 
                <Nav.Link 
                    as={Link} 
                    href={item.url} 
                > 
                    <span> 
                        {item.title} 
                    </span> 
                </Nav.Link> 
                {item.children && item.children.length > 0 && ( 
                    <Nav as='ul'> 
                        {renderMenuItems(item.children)} 
                    </Nav> 
                )} 
            </Nav.Item> 
        )); 
    }

希望这有帮助。

英文:

Try this:

function hasSelectedChild(menuItem) {
   return menuItem.children &amp;&amp; menuItem.children.length &gt; 0 &amp;&amp; menuItem.children.find(item =&gt; item.id === selectedItemId || hasSelectedChild(item)) 
}

function renderMenuItems(menuItems: any[]) { 
        return menuItems.map((item: any) =&gt; ( 
            &lt;Nav.Item 
                as=&#39;li&#39; 
                key={item.id}    
                onMouseOver={(e) =&gt; {
                  e.stopPropagation();
                  handleItemHover(item.id)
                }} 
                className={(hasSelectedChild(item) || selectedItemId === item.id) ? &#39;selected&#39; : &#39;&#39;}
            &gt; 
                &lt;Nav.Link 
                    as={Link} 
                    href={item.url} 
                &gt; 
                    &lt;span&gt; 
                        {item.title} 
                    &lt;/span&gt; 
                &lt;/Nav.Link&gt; 
                {item.children &amp;&amp; item.children.length &gt; 0 &amp;&amp; ( 
                    &lt;Nav as=&#39;ul&#39;&gt; 
                        {renderMenuItems(item.children)} 
                    &lt;/Nav&gt; 
                )} 
            &lt;/Nav.Item&gt; 
        )); 
    }

答案2

得分: 1

为了实现在鼠标悬停在Nav.Item元素上时添加"selected"类,并在出现子菜单时保持该类,您可以按照以下方式修改代码:

import Nav from 'react-bootstrap/Nav';
import Link from 'next/link';
import { getMenu } from '@/lib/APIs/menu';
import { usePathname } from 'next/navigation';
import { Button, NavDropdown } from 'react-bootstrap';

const dataPromise = getMenu();

const GlobalNav = () => {
  const MENU = use(dataPromise);
  const [selectedItemId, setSelectedItemId] = useState<string | null>(null);

  function handleItemHover(itemId: string) {
    setSelectedItemId(itemId);
  }

  function handleItemMouseLeave() {
    setSelectedItemId(null);
  }

  function renderMenuItems(menuItems: any[]) {
    return menuItems.map((item: any) => (
      <Nav.Item
        as='li'
        key={item.id}
        onMouseOver={() => handleItemHover(item.id)}
        onMouseLeave={handleItemMouseLeave}
        className={selectedItemId === item.id ? 'selected' : ''}
      >
        <Nav.Link as={Link} href={item.url}>
          <span>{item.title}</span>
        </Nav.Link>
        {item.children && item.children.length > 0 && (
          <Nav as='ul' className={selectedItemId === item.id ? 'selected' : ''}>
            {renderMenuItems(item.children)}
          </Nav>
        )}
      </Nav.Item>
    ));
  }

  return (
    <Nav as='ul'>
      {renderMenuItems(MENU)}
    </Nav>
  );
}

export default GlobalNav;

在嵌套的Nav组件上添加了className属性,以在选定父级Nav.Item时添加"selected"类。并且添加了onMouseLeave={handleItemMouseLeave}事件处理程序到Nav.Item上,以触发selectedItemId的重置。

英文:

To achieve the desired behavior of adding the "selected" class to the Nav.Item elements on hover and keeping it when a child menu appears, you can modify the code as below:

import Nav from &#39;react-bootstrap/Nav&#39;;
import Link from &#39;next/link&#39;;
import { getMenu } from &#39;@/lib/APIs/menu&#39;;
import { usePathname } from &#39;next/navigation&#39;;
import { Button, NavDropdown } from &#39;react-bootstrap&#39;;
const dataPromise = getMenu();
const GlobalNav = () =&gt; {
const MENU = use(dataPromise);
const [selectedItemId, setSelectedItemId] = useState&lt;string | null&gt;(null);
function handleItemHover(itemId: string) {
setSelectedItemId(itemId);
}
function handleItemMouseLeave() {
setSelectedItemId(null);
}
function renderMenuItems(menuItems: any[]) {
return menuItems.map((item: any) =&gt; (
&lt;Nav.Item
as=&#39;li&#39;
key={item.id}
onMouseOver={() =&gt; handleItemHover(item.id)}
onMouseLeave={handleItemMouseLeave}
className={selectedItemId === item.id ? &#39;selected&#39; : &#39;&#39;}
&gt;
&lt;Nav.Link as={Link} href={item.url}&gt;
&lt;span&gt;{item.title}&lt;/span&gt;
&lt;/Nav.Link&gt;
{item.children &amp;&amp; item.children.length &gt; 0 &amp;&amp; (
&lt;Nav as=&#39;ul&#39; className={selectedItemId === item.id ? &#39;selected&#39; : &#39;&#39;}&gt;
{renderMenuItems(item.children)}
&lt;/Nav&gt;
)}
&lt;/Nav.Item&gt;
));
}
return (
&lt;Nav as=&#39;ul&#39;&gt;
{renderMenuItems(MENU)}
&lt;/Nav&gt;
);
}
export default GlobalNav;

Adding the className prop to the nested Nav component to add the "selected" class when the parent Nav.Item is selected. And adding the onMouseLeave={handleItemMouseLeave} event handler to the Nav.Item to trigger the reset of selectedItemId

huangapple
  • 本文由 发表于 2023年7月13日 18:22:34
  • 转载请务必保留本文链接:https://go.coder-hub.com/76678321.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定