概要
今回も備忘録的な記事内容ですが、ReactでTooltipを作る際に調べて自作したので、その記録として記事にしました。
下のテーブルは、Tooltipを表示させるにあたって、任意の場所に表示させるためのイメージです。
テキストから見て、「左上」「真上」「右上」「左横」「右横」「左下」「真下」「右下」の8箇所のいずれかに表示させたいと思います。
◯ | ◯ | ◯ |
---|---|---|
◯ | テキスト | ◯ |
◯ | ◯ | ◯ |
以下、実際に書いたコードです。
CSSはemotionを使用しています。
Tooltip.js
/** @jsxImportSource @emotion/react */
import {css} from "@emotion/react"
import {useState} from "react"
export const Tooltip = ({message, children, offset, vertical, horizontal}) => {
const [isShow, setIsShow] = useState(false)
const handleShow = () => {
setIsShow(true)
}
const handleHide = () => {
setIsShow(false)
}
return (
<div
css={TooltipWrap}
onMouseEnter={handleShow}
onMouseLeave={handleHide}
onFocus={handleShow}
onBlur={handleHide}
>
<div css={TooltipContent(isShow, vertical, horizontal, offset)}>
{message}
</div>
{children}
</div>
)
}
const TooltipWrap = css({
position: "relative"
})
const TooltipContent = (
isShow,
vertical = "top",
horizontal = "center",
offset = 0
) =>
css({
position: "absolute",
color: "#fff",
background: "#000",
padding: "4px 8px",
borderRadius: 4,
visibility: isShow ? "visible" : "hidden",
maxWidth: 280,
width: "max-content",
...(vertical === "top" &&
horizontal === "center" && {
bottom: `calc(100% + ${offset}px + 4px)`,
left: "50%",
transform: "translateX(-50%)",
"&::after": {
content: "''",
width: 8,
height: 4,
position: "absolute",
backgroundColor: "#000",
top: "100%",
left: "50%",
transform: "translateX(-50%)",
clipPath: "polygon(0% 0%, 100% 0%, 50% 100%, 50% 100%)"
}
}),
...(vertical === "top" &&
horizontal === "left" && {
bottom: `calc(100% + ${offset}px + 4px)`,
right: "calc(50% - 12px)",
"&::after": {
content: "''",
width: 8,
height: 4,
position: "absolute",
backgroundColor: "#000",
top: "100%",
right: 8,
clipPath: "polygon(0% 0%, 100% 0%, 50% 100%, 50% 100%)"
}
}),
...(vertical === "top" &&
horizontal === "right" && {
bottom: `calc(100% + ${offset}px + 4px)`,
left: "calc(50% - 12px)",
"&::after": {
content: "''",
width: 8,
height: 4,
position: "absolute",
backgroundColor: "#000",
top: "100%",
left: 8,
clipPath: "polygon(0% 0%, 100% 0%, 50% 100%, 50% 100%)"
}
}),
...(vertical === "middle" &&
horizontal === "left" && {
top: "50%",
transform: "translateY(-50%)",
right: `calc(100% + ${offset}px + 4px)`,
"&::after": {
content: "''",
width: 4,
height: 8,
position: "absolute",
backgroundColor: "#000",
top: "50%",
transform: "translateY(-50%)",
left: "100%",
clipPath: "polygon(0% 0%, 100% 50%, 100% 50%, 0% 100%)"
}
}),
...(vertical === "middle" &&
horizontal === "right" && {
top: "50%",
transform: "translateY(-50%)",
left: `calc(100% + ${offset}px + 4px)`,
"&::after": {
content: "''",
width: 4,
height: 8,
position: "absolute",
backgroundColor: "#000",
top: "50%",
transform: "translateY(-50%)",
right: "100%",
clipPath: "polygon(0% 50%, 100% 0%, 100% 100%, 0% 50%)"
}
}),
...(vertical === "bottom" &&
horizontal === "left" && {
top: `calc(100% + ${offset}px + 4px)`,
right: "calc(50% - 12px)",
"&::after": {
content: "''",
width: 8,
height: 4,
position: "absolute",
backgroundColor: "#000",
bottom: "100%",
right: 8,
clipPath: "polygon(50% 0%, 50% 0%, 100% 100%, 0% 100%)"
}
}),
...(vertical === "bottom" &&
horizontal === "center" && {
top: `calc(100% + ${offset}px + 4px)`,
left: "50%",
transform: "translateX(-50%)",
"&::after": {
content: "''",
width: 8,
height: 4,
position: "absolute",
backgroundColor: "#000",
bottom: "100%",
left: "50%",
transform: "translateX(-50%)",
clipPath: "polygon(50% 0%, 50% 0%, 100% 100%, 0% 100%)"
}
}),
...(vertical === "bottom" &&
horizontal === "right" && {
top: `calc(100% + ${offset}px + 4px)`,
left: "calc(50% - 12px)",
"&::after": {
content: "''",
width: 8,
height: 4,
position: "absolute",
backgroundColor: "#000",
bottom: "100%",
left: 8,
clipPath: "polygon(50% 0%, 50% 0%, 100% 100%, 0% 100%)"
}
})
})
App.js
/** @jsxImportSource @emotion/react */
import "./styles.css"
import {css} from "@emotion/react"
import {Tooltip} from "./Tootip"
export default function App() {
return (
<div className="App">
<h1>Tooltip TEST</h1>
<ul css={toolTipList}>
<li>
<div css={hoverText}>
<Tooltip horizontal="left" message="Tooltip Text">
左上に表示されます。
</Tooltip>
</div>
</li>
<li>
<div css={hoverText}>
<Tooltip message="Tooltip Text">真上に表示されます。</Tooltip>
</div>
</li>
<li>
<div css={hoverText}>
<Tooltip horizontal="right" message="Tooltip Text">
右上に表示されます。
</Tooltip>
</div>
</li>
<li>
<div css={hoverText}>
<Tooltip horizontal="left" vertical="middle" message="Tooltip Text">
左横に表示されます。
</Tooltip>
</div>
</li>
<li>
<div css={hoverText}>
<Tooltip
horizontal="right"
vertical="middle"
message="Tooltip Text"
>
右横に表示されます。
</Tooltip>
</div>
</li>
<li>
<div css={hoverText}>
<Tooltip horizontal="left" vertical="bottom" message="Tooltip Text">
左下に表示されます。
</Tooltip>
</div>
</li>
<li>
<div css={hoverText}>
<Tooltip vertical="bottom" message="Tooltip Text">
真下に表示されます。
</Tooltip>
</div>
</li>
<li>
<div css={hoverText}>
<Tooltip
horizontal="right"
vertical="bottom"
message="Tooltip Text"
>
右下に表示されます。
</Tooltip>
</div>
</li>
</ul>
</div>
)
}
const toolTipList = css({
listStyleType: "none",
display: "grid",
gap: "32px 0"
})
const hoverText = css({
textDecoration: "underline dotted",
display: "inline-block"
})
DEMO
簡単な解説
vertical
(縦の位置:初期値はtop)、horizontal
(横の位置:初期値はcenter)、offset
(対象とツールチップの距離:初期値は0)をPropsで渡しています。
初期値にvertical="top"
、horizontal="center"
を設定しているので、何も指定がなければTooltipは真上に表示されます。
また、hoverする対象とTooltipを少し離したい場合はoffset
に数値をいれることにより、離れて表示させることができます。
TypeScriptで書くことによって、verticalやhorizontalのルールを厳格にすることができますが、今回のコードではそこまで厳密に書いておりません。