Reputation: 6461
This is my current tooltip.
I am using react-power-tooltip
When I click the button, I can close the tooltip.
But I want to close the tooltip when I click outside the tooltip.
How am I supposed to do it?
App.js
import "./styles.css";
import MoreHorizIcon from "@material-ui/icons/MoreHoriz";
import TooltipList from "./TooltipList";
import { useState } from "react";
export default function App() {
const [showTooltip, setShowTooltip] = useState(true);
return (
<div className="App">
<button
className="post-section__body__list__item__right__menu-btn"
onClick={() => {
setShowTooltip((x) => !x);
}}
style={{ position: "relative" }}
>
<MoreHorizIcon />
<TooltipList show={showTooltip} />
</button>
</div>
);
}
TooltipList
import React from "react";
import Tooltip from "react-power-tooltip";
const options = [
{
id: "edit",
label: "Edit"
},
{
id: "view",
label: "View"
}
];
function Tooptip(props) {
const { show } = props;
return (
<Tooltip
show={show}
position="top center"
arrowAlign="end"
textBoxWidth="180px"
fontSize="0.875rem"
fontWeight="400"
padding="0.5rem 1rem"
>
{options.map((option) => {
return (
<div
className="tooltop__option d-flex align-items-center w-100"
key={option.id}
>
{option.icon}
<span style={{ fontSize: "1rem" }}>{option.label}</span>
</div>
);
})}
</Tooltip>
);
}
export default Tooptip;
CodeSandbox:
https://codesandbox.io/s/optimistic-morning-m9eq3?file=/src/App.js
Update 1:
I update the code based on the answer.
It can now click outside to close, but if I click the button to close the tooltip, it's not working.
App.js
import "./styles.css";
import MoreHorizIcon from "@material-ui/icons/MoreHoriz";
import TooltipList from "./TooltipList";
import { useState } from "react";
export default function App() {
const [showTooltip, setShowTooltip] = useState(true);
return (
<div className="App">
<button
className="post-section__body__list__item__right__menu-btn"
onClick={() => {
setShowTooltip((x) => !x);
}}
style={{ position: "relative" }}
>
<MoreHorizIcon />
<TooltipList
show={showTooltip}
onClose={() => {
setShowTooltip();
}}
/>
</button>
</div>
);
}
TooltipList.js
import React, { useEffect, useRef } from "react";
import Tooltip from "react-power-tooltip";
const options = [
{
id: "edit",
label: "Edit"
},
{
id: "view",
label: "View"
}
];
function Tooptip(props) {
const { show, onClose } = props;
const containerRef = useRef();
useEffect(() => {
if (show) {
containerRef.current.focus();
}
}, [show]);
return (
<div
style={{ display: "inline-flex" }}
ref={containerRef}
tabIndex={0}
onBlur={(e) => {
onClose();
}}
>
<Tooltip
show={show}
position="top center"
arrowAlign="end"
textBoxWidth="180px"
fontSize="0.875rem"
fontWeight="400"
padding="0.5rem 1rem"
>
{options.map((option) => {
return (
<div
className="tooltop__option d-flex align-items-center w-100"
key={option.id}
>
{option.icon}
<span style={{ fontSize: "1rem" }}>{option.label}</span>
</div>
);
})}
</Tooltip>
</div>
);
}
export default Tooptip;
Codesandbox
https://codesandbox.io/s/optimistic-morning-m9eq3?file=/src/App.js:572-579
Upvotes: 1
Views: 8131
Reputation: 2256
Here is a crazy little idea. I wrapped your component in an inline-flex
div and gave it focus on load. Then added an onBlur event which will hide the menu if you click anywhere else. This can be used if you don't want to give focus on any other element on the page.
https://codesandbox.io/s/epic-kapitsa-yh7si?file=/src/App.js:0-940
import "./styles.css";
import MoreHorizIcon from "@material-ui/icons/MoreHoriz";
import TooltipList from "./TooltipList";
import { useRef, useState, useEffect } from "react";
export default function App() {
const [showTooltip, setShowTooltip] = useState(true);
const containerRef = useRef();
useEffect(() => {
containerRef.current.focus();
}, []);
return (
<div className="App">
<div
style={{ display: "inline-flex" }}
ref={containerRef}
tabIndex={0}
onBlur={(e) => {
debugger;
setShowTooltip(false);
}}
>
<button
className="post-section__body__list__item__right__menu-btn"
onClick={() => {
setShowTooltip((x) => !x);
}}
style={{ position: "relative" }}
>
<MoreHorizIcon />
<TooltipList show={showTooltip} />
</button>
</div>
</div>
);
}
Update 1:
The problem was your button click was called every time you select an item that toggles your state. I have updated the code to prevent that using a useRef
that holds a value.
ToolTip:
import React from "react";
import Tooltip from "react-power-tooltip";
const options = [
{
id: "edit",
label: "Edit"
},
{
id: "view",
label: "View"
}
];
function Tooptip(props) {
const { show, onChange } = props;
return (
<>
<Tooltip
show={show}
position="top center"
arrowAlign="end"
textBoxWidth="180px"
fontSize="0.875rem"
fontWeight="400"
padding="0.5rem 1rem"
>
{options.map((option) => {
return (
<div
onClick={onChange}
className="tooltop__option d-flex align-items-center w-100"
key={option.id}
>
{option.icon}
<span style={{ fontSize: "1rem" }}>{option.label}</span>
</div>
);
})}
</Tooltip>
</>
);
}
export default Tooptip;
App
import "./styles.css";
import MoreHorizIcon from "@material-ui/icons/MoreHoriz";
import TooltipList from "./TooltipList";
import { useRef, useState, useEffect, useCallback } from "react";
export default function App() {
const [showTooltip, setShowTooltip] = useState(true);
const [onChangeTriggered, setonChangeTriggered] = useState(false);
const containerRef = useRef();
const itemClicked = useRef(false);
useEffect(() => {
containerRef.current.focus();
}, []);
return (
<div className="App">
<div
style={{ display: "inline-flex" }}
ref={containerRef}
tabIndex={0}
onBlur={(e) => {
debugger;
if (!onChangeTriggered) setShowTooltip(false);
}}
// onFocus={() => {
// setShowTooltip(true);
// }}
>
<button
className="post-section__body__list__item__right__menu-btn"
onClick={() => {
if (!itemClicked.current) setShowTooltip((x) => !x);
itemClicked.current = false;
}}
style={{ position: "relative" }}
>
<MoreHorizIcon />
<TooltipList
show={showTooltip}
onChange={useCallback(() => {
itemClicked.current = true;
}, [])}
/>
</button>
</div>
</div>
);
}
https://codesandbox.io/s/epic-kapitsa-yh7si
Enjoy !!
Upvotes: 0
Reputation: 12181
As I can see, you are using material-ui for icons, so there is an option known as ClickAwayListner within material-ui
App.js
import "./styles.css";
import MoreHorizIcon from "@material-ui/icons/MoreHoriz";
import ClickAwayListener from '@material-ui/core/ClickAwayListener';
import TooltipList from "./TooltipList";
import { useState } from "react";
export default function App() {
const [showTooltip, setShowTooltip] = useState(true);
const handleClickAway = () => {
setShowTooltip(false);
}
return (
<div className="App">
<ClickAwayListener onClickAway={handleClickAway}>
<button
className="post-section__body__list__item__right__menu-btn"
onClick={e => {
e.stopPropagation();
setShowTooltip((x) => !x);
}}
style={{ position: "relative" }}
>
<MoreHorizIcon />
<TooltipList show={showTooltip} />
</button>
</ClickAwayListener>
</div>
);
}
Wrap your container with ClickAwayListener
Upvotes: 5
Reputation: 1
You should add a wrapper element to detect if the click is outside a component then the showTooltip to false at your code: codeSanbox
import "./styles.css";
import MoreHorizIcon from "@material-ui/icons/MoreHoriz";
import TooltipList from "./TooltipList";
import { useState, useEffect, useRef } from "react";
export default function App() {
const [showTooltip, setShowTooltip] = useState(true);
const outsideClick = (ref) => {
useEffect(() => {
const handleOutsideClick = (event) => {
if (ref.current && !ref.current.contains(event.target)) {
setShowTooltip(false);
}
};
// add the event listener
document.addEventListener("mousedown", handleOutsideClick);
}, [ref]);
};
const wrapperRef = useRef(null);
outsideClick(wrapperRef);
return (
<div className="App" ref={wrapperRef}>
<button
className="post-section__body__list__item__right__menu-btn"
onClick={() => {
setShowTooltip((x) => !x);
}}
style={{ position: "relative" }}
>
<MoreHorizIcon />
<TooltipList show={showTooltip} />
</button>
</div>
);
}
Upvotes: 0