简介
组件是相邻的,因此您不会使用 CSS 选择器来控制一个组件与另一个组件的可见性;此外,您将无法保持相同的悬停触发器高度/宽度。相反,我建议使用带有 onMouseEnter 和 onMouseLeave 事件侦听器的 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")
);