如何在子菜单打开时保持悬停处于启用状态

how keep the hover enabled while the submenu is open

提问人:Paul 提问时间:10/28/2023 最后编辑:Paul 更新时间:11/4/2023 访问量:271

问:

我的网站上有一个简单的表格,列出了设备及其特性(在下面链接的示例中,将有表格的缩短版本)。

import "./styles.css";
import { SubMenu } from "./SubMenu";

const subMenuSlice = <SubMenu />;

const nodes = [
  {
    id: "0",
    name: "Samsung Galaxy",
    subMenu: subMenuSlice
  },
  {
    id: "0",
    name: "Iphone",
    subMenu: subMenuSlice
  }
];

export default function App() {
  return (
    <table>
      <tbody>
        {nodes.map((val, key) => (
          <tr key={key}>
            <td>{val.name}</td>
            <td>{val.subMenu}</td>
          </tr>
        ))}
      </tbody>
    </table>
  );
}

子菜单.tsx

import { useState } from "react";
import AppsIcon from "@mui/icons-material/Apps";
import * as DropdownMenu from "@radix-ui/react-dropdown-menu";
import "./styles.css";

export const SubMenu = () => {
  const [isOpen, setIsOpen] = useState(false);

  return (
    <DropdownMenu.Root open={isOpen} onOpenChange={setIsOpen}>
      <DropdownMenu.Trigger>
        <AppsIcon className="sss" />
      </DropdownMenu.Trigger>
      <DropdownMenu.Portal>
        <DropdownMenu.Content
          side="bottom"
          sideOffset={-30}
          align="start"
          alignOffset={80}
        >
          <button className="style-button">Edit </button>
          <button className="style-button">Make </button>
          <button className="style-button">Delete </button>
        </DropdownMenu.Content>
      </DropdownMenu.Portal>
    </DropdownMenu.Root>
  );
};

样式.css

    .sss {
  visibility: hidden;
}

tr:hover .sss {
  background: gray;
  visibility: visible;
}

tr:hover {
  background: gray;
  visibility: visible;
  pointer-events: initial !important;
}

.style-button:hover {
  background-color: aqua;
}

https://codesandbox.io/s/romantic-rgb-5t7xkq

如您所见,当您将鼠标悬停在任何一条线上时,整条线都会变成灰色,并出现一个附加按钮。通过单击此按钮,用户将获得一个子菜单。

问题描述:问题是当用户将光标移动到子菜单时,悬停(灰色)从表格行中消失。请告诉我如何在子菜单处于活动状态(打开)时保持悬停处于启用状态

javascript css reactjs 悬停 子菜单

评论

1赞 Zak 11/1/2023
向我们提供您的CSS代码,以便尽快进入解决方案
0赞 Paul 11/1/2023
@Zak您可以在我问题的链接中看到完整的代码
2赞 Heretic Monkey 11/1/2023
请阅读如何提问,尤其是标题为“帮助他人重现问题”的部分,其中说:“如果可以创建可以链接到的问题的实时示例(例如,在 sqlfiddle.comjsbin.com 上),那么这样做 - 但也要将代码复制到问题本身中。不是每个人都可以访问外部网站,链接可能会随着时间的推移而中断。使用 Stack Snippets 制作内联 JavaScript/HTML/CSS 的现场演示。
0赞 Moob 11/1/2023
我认为这是一个简单的解决方案,但如果没有 MVCE,我无法测试或演示解决方案。
0赞 Paul 11/1/2023
@HereticMonkey我了解您的要求。谢谢

答:

1赞 Dirk J. Faber 11/1/2023 #1

我会跟踪某些状态,例如正在“悬停”的项的电流。然后,对于该项目,添加一个额外的类,并根据该类设置其样式,在您的示例中:id

应用程序.tsx

import "./styles.css";
import { SubMenu } from "./SubMenu";
import { useState } from "react";

const subMenuSlice = <SubMenu />;

const nodes = [
  {
    id: "0",
    name: "Samsung Galaxy",
    subMenu: subMenuSlice
  },
  {
    id: "1",
    name: "Iphone",
    subMenu: subMenuSlice
  }
];

export default function App() {
  const [isHovered, setIsHovered] = useState(null);

  const handleMouseEnter = (id) => {
    setIsHovered(id);
  };

  const handleMouseLeave = () => {
    setIsHovered(null);
  };

  return (
    <table>
      <tbody>
        {nodes.map((val, key) => (
          <tr
            key={key}
            onMouseEnter={() => handleMouseEnter(val.id)}
            onMouseLeave={handleMouseLeave}
            className={val.id === isHovered ? "hovered" : ""} // here you set the class if the id matches. 
          >
            <td>{val.name}</td>
            <td>{val.subMenu}</td>
          </tr>
        ))}
      </tbody>
    </table>
  );
}

样式.css

.sss {
  visibility: hidden;
}

tr.hovered .sss {
  background: gray;
  visibility: visible;
}

tr.hovered {
  background: gray;
  visibility: visible;
  pointer-events: initial !important;
}

.style-button:hover {
  background-color: aqua;
}

在此示例中,您必须确保所有 ID 都是唯一的。如果无法做到这一点,请使用另一个唯一值。

更新

如果您不仅希望在组件悬停时保持这种状态,而且通常在处于活动状态时保持这种状态,我会执行以下操作:

将状态重命名为“isActive”,并将子组件中的值传递给父组件,以便可以在 className 上使用此值。此外,在悬停组件时重新添加原始样式,在这种情况下,您将在悬停组件和悬停时都具有样式。这是你如何做到的:

应用程序.tsx

import "./styles.css";
import { SubMenu } from "./SubMenu";
import { useState } from "react";

const subMenuSlice = <SubMenu />;

const nodes = [
  {
    id: "0",
    name: "Samsung Galaxy",
    subMenu: subMenuSlice
  },
  {
    id: "1",
    name: "Iphone",
    subMenu: subMenuSlice
  }
];

export default function App() {
  const [isActive, setIsActive] = useState<string | null>(null);

  return (
    <table>
      <tbody>
        {nodes.map((val, key) => (
          <tr key={key} className={isActive === val.id ? "active" : ""}>
            <td>{val.name}</td>
            <td>
              {/* Check if the callback returns true, if so set isActive to the id */}
              <SubMenu
                openCallback={(boolValue) =>
                  boolValue ? setIsActive(val.id) : setIsActive(null)
                }
              />
            </td>
          </tr>
        ))}
      </tbody>
    </table>
  );
}

样式.css

.sss {
  visibility: hidden;
}

tr.active .sss, tr:hover .sss {
  background: gray;
  visibility: visible;
}

tr.active, tr:hover {
  background: gray;
  visibility: visible;
  pointer-events: initial !important;
}

.style-button:hover {
  background-color: aqua;
}

子菜单.tsx

import { useState } from "react";
import AppsIcon from "@mui/icons-material/Apps";
import * as DropdownMenu from "@radix-ui/react-dropdown-menu";
import "./styles.css";

export const SubMenu = ({ openCallback }: { openCallback?: (arg: boolean) => void }) => {
  const [isOpen, setIsOpen] = useState(false);

  const handleOpen = () => {
    setIsOpen((prevState) => !prevState);
    if (openCallback) {
      openCallback(!isOpen); // call the function with the boolean value !isOpen
    }
  };

  return (
    <DropdownMenu.Root open={isOpen} onOpenChange={handleOpen}>
      <DropdownMenu.Trigger>
        <AppsIcon className="sss" />
      </DropdownMenu.Trigger>
      <DropdownMenu.Portal>
        <DropdownMenu.Content
          side="bottom"
          sideOffset={-30}
          align="start"
          alignOffset={80}
        >
          <button className="style-button">Edit </button>
          <button className="style-button">Make </button>
          <button className="style-button">Delete </button>
        </DropdownMenu.Content>
      </DropdownMenu.Portal>
    </DropdownMenu.Root>
  );
};

评论

0赞 Paul 11/1/2023
感谢您关注我的问题。是的,你的答案几乎是正确的。但有一个细微差别:在我打开子菜单并将光标移动到那里后,悬停仍然处于活动状态。但是,如果我将光标移到子菜单之外,悬停就会消失,尽管子菜单处于活动状态。是否可以在菜单下处于活动状态时使悬停处于活动状态?
0赞 Dirk J. Faber 11/1/2023
在这种情况下,我会使用回调函数将状态从子菜单传递到父组件。让我更新一下我的答案。
0赞 Paul 11/1/2023
是的,效果很好,谢谢。但让我澄清一下:我渲染 {val.name} 和 {val.subMenu},以便每个设备 ID 都有自己的设置。如果我们在第二个选项中使用 <td> <SubMenu....../> </td>,这不是问题吗?
0赞 Dirk J. Faber 11/1/2023
您在 <SubMenu .../> 中需要的任何数据都可以来自您的列表(或其他任何地方),因此这无关紧要。只需确保将所需的设置传递到组件中即可。notes
0赞 Paul 11/11/2023
也许你可以帮助我。我目前正在尝试将我要删除的 ID 准确地传输到 SubMenu,但它不起作用。告诉我如何准确传输所需的 ID 以进行进一步的工作
0赞 Moob 11/1/2023 #2

如果你不急于支持旧浏览器,你可以使用 :has() CSS 关系伪类来设置它包含 having .需要注意的是,Firefox 尚不支持此功能。然而,在撰写本文时,它确实有~87.53%的全球支持。trbuttondata-state="open"

因此,如果这适合您,方法如下:

  1. 为打开按钮的后代提供灰色 bg:.sss

    tr td button[data-state="open"] .sss {...}

  2. 给包含 (/'has') 打开按钮的灰色 bg:tr

    tr:has(button[data-state="open"]) {...}

您的完整样式 .css 如下所示:

.sss {
  visibility: hidden;
}

tr:hover .sss,
tr td button[data-state="open"] .sss {
  background: gray;
  visibility: visible;
}

tr:hover,
tr:has(button[data-state="open"]) {
  background: gray;
  visibility: visible;
  pointer-events: initial !important;
}

.style-button:hover {
  background-color: aqua;
}

演示:https://codesandbox.io/s/sweet-tess-zcf23k

评论

0赞 Paul 11/2/2023
非常感谢您的建议。您的代码可以正常工作,但存在一个问题。下面我将尝试描述这个问题:如您所知,我的真实表有更多的列,其中一列包含一个类似于 SubMenu 按钮的按钮(更改颜色),也就是说,此按钮仅在悬停时出现,并且具有 className=“sss”。出于某种原因,在应用解决方案时,当您打开子菜单时,悬停仍然存在,但此按钮 (ChangeColor) 会消失(尽管“子菜单”按钮不会消失)。也许您知道如何消除此问题
0赞 Moob 11/2/2023
沙盒中的@Paul是 的 CHILD。您上面的描述表明您的“更改颜色”按钮本身具有类 .您能否提供一个更新的示例来说明问题?.sssbutton.sss
0赞 imhvost 11/4/2023 #3

删除,以便内容不会粘贴到门户
中,我还要补充一点,单元格之间没有空格。
下面是一个示例:codesandbox
<DropdownMenu.Portal>bodytable { border-spacing: 0; }

0赞 J.Porter 11/4/2023 #4

有很多方法,我介绍一种。

-App.tsx

import "./styles.css";
import { SubMenu } from "./SubMenu";
import { useState } from "react";

const nodes = [
  {
    id: "0",
    name: "Samsung Galaxy"
  },
  {
    id: "1",
    name: "Iphone"
  }
];

export default function App() {
  const [isActive, setIsActive] = useState<string | null>(null);

  const subMenuSlice = (id) => (
    <SubMenu openCallback={(boolValue) => boolValue ? setIsActive(id) : setIsActive(null)} />
  );

  const addSubMenuNodes = nodes.map((node) => ({
    ...node,
    subMenu: subMenuSlice(node.id)
  }));
  //you can modify submenu for each node in nodes.

  return (
    <table>
      <tbody>
        {addSubMenuNodes.map((val, key) => (
          <tr key={key} className={isActive === val.id ? "active" : ""}>
            <td>{val.name}</td>
            <td>{val.subMenu}</td>
          </tr>
        ))}
      </tbody>
    </table>
  );
}

-SubMenu.tsx

import { useState } from "react";
import AppsIcon from "@mui/icons-material/Apps";
import * as DropdownMenu from "@radix-ui/react-dropdown-menu";
import "./styles.css";

export const SubMenu = ({ openCallback }) => {
  const [isOpen, setIsOpen] = useState(false);

  const handleOpen = () => {
    setIsOpen((prevState) => !prevState);
    if (openCallback) {
      openCallback(!isOpen); // call the function with the boolean value !isOpen
    }
  };

  return (
    <DropdownMenu.Root open={isOpen} onOpenChange={handleOpen}>
      <DropdownMenu.Trigger>
        <AppsIcon className="sss" />
      </DropdownMenu.Trigger>
      <DropdownMenu.Portal>
        <DropdownMenu.Content
          side="bottom"
          sideOffset={-30}
          align="start"
          alignOffset={80}
        >
          <button className="style-button">Edit </button>
          <button className="style-button">Make </button>
          <button className="style-button">Delete </button>
        </DropdownMenu.Content>
      </DropdownMenu.Portal>
    </DropdownMenu.Root>
  );
};

-样式.css

table {
  border-spacing: 0;
}

.sss {
  visibility: hidden;
}

tr.active .sss,
tr:hover .sss {
  background: gray;
  visibility: visible;
}

tr.active,
tr:hover {
  background: gray;
  visibility: visible;
  pointer-events: initial !important;
}

.style-button:hover {
  background-color: aqua;
}

它将正常工作。

我希望我的回答能帮到你。

祝你好运。

哦,如果这没问题,请不要忘记投票给我的答案