【问题标题】:Referencing child component from styled component从样式化组件引用子组件
【发布时间】:2021-08-15 22:45:13
【问题描述】:

我正在尝试创建一个可悬停的帮助图标,但似乎无法通过帮助图标的悬停事件来控制帮助框的状态。

可以在此处找到该问题的完整工作示例https://codesandbox.io/s/info-drop-down-th88d?file=/src/HelpIcon.tsx

根据样式组件docs,我需要将我的反应组件包装在styled()

const StyledInfoIcon = styled(InfoIcon)``;
const StyledInfoBox = styled(InfoBox)``;

这确实允许我控制子组件的行为,例如:

const HelpWrapper = styled.div`
  ${StyledInfoIcon}:hover {
    display: none;
  }
`;

按预期工作,当我将鼠标悬停在图标上时,它会消失,但不幸的是为文本框添加了额外的选择器:

const HelpWrapper = styled.div`
  ${StyledInfoIcon}:hover ${StyledInfoBox} {
    display: none;
  }
`;

没有。

感谢任何帮助,我想知道是否有更好的方法来解决我不知道的这个问题。提前致谢。

【问题讨论】:

    标签: html css reactjs typescript styled-components


    【解决方案1】:

    简介

    组件是相邻的,因此您不会使用 CSS 选择器来控制一个组件与另一个组件的可见性;此外,您将无法保持相同的悬停触发器高度/宽度。相反,我建议使用带有 onMouseEnteronMouseLeave 事件侦听器的 React 状态。

    问题

    虽然您可以利用 HelperWrapper 和样式选择器,但您会遇到 CSS 问题:

    import styled from "styled-components";
    
    import InfoIcon from "./InfoIcon";
    import InfoBox from "./InfoBox";
    
    const StyledInfoIcon = styled(InfoIcon)``;
    // initially set tooltip to not be displayed
    const StyledInfoBox = styled(InfoBox)`display: none;`;
    
    // when the HelpWrapper is hovered, change the StyledInfoBox display to a block
    const HelpWrapper = styled.div`
      :hover > ${StyledInfoBox} {
        display: block;
      }
    `;
    
    const HelpIcon = () => {
      return (
        <HelpWrapper>
          <StyledInfoIcon />
          <StyledInfoBox text="We are having waffles for breakfast" />
        </HelpWrapper>
      );
    };
    
    export default HelpIcon;
    

    由于HelpWrapper 包装了两个组件,它会调整其高度/宽度以包含两个组件:

    悬停前:

    悬停后:

    这并没有真正实现仅将鼠标悬停在信息图标上以显示工具提示的预期效果。

    解决方案

    在这种情况下,我建议使用带有鼠标事件侦听器的 React 状态。

    演示

    代码

    HelpIcon.tsx

    import * as React from "react";
    import InfoIcon from "./InfoIcon";
    import InfoBox from "./InfoBox";
    
    const HelpIcon = (): React.ReactElement => {
      const [showTooltip, setTooltipVisiblity] = React.useState(false);
    
      const handleOnEnter = (): void => {
        setTooltipVisiblity(true);
      };
    
      const handleOnLeave = (): void => {
        setTooltipVisiblity(false);
      };
    
      return (
        <>
          <InfoIcon onMouseEnter={handleOnEnter} onMouseLeave={handleOnLeave} />
          <InfoBox
            showTooltip={showTooltip}
            text="We are having waffles for breakfast"
          />
        </>
      );
    };
    
    export default HelpIcon;
    

    InfoBox.tsx

    import styled from "styled-components";
    import type { ReactElement } from "react";
    
    type InfoBoxProps = {
      className?: string;
      text: string;
      maxWidth?: string;
      showTooltip?: boolean;
    };
    
    const InfoBoxComponent = (props: InfoBoxProps): ReactElement => (
      <div className={props.className}>
        <div className="arrow" />
        <p>{props.text}</p>
      </div>
    );
    
    const InfoBox = styled(InfoBoxComponent)<InfoBoxProps>`
      position: relative;
      display: ${(props) => (props.showTooltip ? "block" : "none")};
    
      width: fit-content;
      max-width: ${(props) => (props.maxWidth ? props.maxWidth : "200px")};
    
      white-space: normal;
    
      background-color: #475f6a;
      color: white;
    
      box-shadow: 0px 3px 6px #00000029;
      border-radius: 5px;
    
      font-family: "Arial";
      font-size: 0.875rem;
    
      p {
        padding: 1rem;
      }
    
      .arrow {
        position: absolute;
        right: 15%;
        top: -16px;
    
        width: 0;
        height: 0;
        border-left: 9px solid transparent;
        border-right: 9px solid transparent;
    
        border-bottom: 16px solid #475f6a;
      }
    `;
    
    export default InfoBox;
    

    InfoIcon.tsx

    import styled from "styled-components";
    import type { ReactElement } from "react";
    
    type InfoIconProps = {
      className?: string;
      onMouseEnter: () => void;
      onMouseLeave: () => void;
    };
    
    const InfoIconComponent = (props: InfoIconProps): ReactElement => (
      <svg
        className={props.className}
        onMouseEnter={props.onMouseEnter}
        onMouseLeave={props.onMouseLeave}
        viewBox="0 0 100 100"
        xmlns="http://www.w3.org/2000/svg"
      >
        <circle cx="50%" cy="50%" r="50%" />
        <text x="50%" y="50%" dominantBaseline="central" textAnchor="middle">
          i
        </text>
      </svg>
    );
    
    const InfoIcon = styled(InfoIconComponent)`
      height: 20px;
      user-select: none;
    
      circle {
        fill: #83add0;
    
        :hover {
          fill: #475f6a;
        }
      }
    
      text {
        fill: white;
        font-family: Arial, Helvetica, sans-serif;
        font-size: 4rem;
        font-weight: 700;
      }
    `;
    
    export default InfoIcon;
    

    index.tsx

    import * as React from "react";
    import ReactDOM from "react-dom";
    import HelpIcon from "./HelpIcon";
    
    ReactDOM.render(
      <React.StrictMode>
        <HelpIcon />
      </React.StrictMode>,
      document.getElementById("root")
    );
    

    【讨论】:

    • 这太棒了!感谢您花时间写出如此详尽的答案,我学到了很多!我注意到你写了你的 React 组件,然后是 styled-components,我以前没见过,这是推荐的做法还是个人喜好?谢谢
    • 使合成更容易。例如,如果您想使用组合样式的选择器(例如您的 HelperWrapper 示例),那么您需要做的就是导入组件!
    • 啊,是的!完全有道理,再次感谢您:)
    猜你喜欢
    • 2021-10-31
    • 2021-12-14
    • 2020-09-26
    • 2020-09-22
    • 2023-03-26
    • 2020-10-27
    • 2020-06-03
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多