user3343396
user3343396

Reputation: 765

Render tooltop element on specific Ref

I want to render my TooltipComponent on a button only when the user click on this button and specific condition holds.

I'm familiar with the react-tooltip library, but I don't want to use it, my opinion is not positive about this lib. In this library there is a ReactTooltop.show function.

I want to be able to do something like (again, without using this lib:

const onBtnClick = () => {
    if(...condition...){
       MyTooltip.show(btnRef, ...); // maybe pass some arguments like tooltip text...
    }
}

Any idea how can i implement this?

Thanks!

Upvotes: 0

Views: 1846

Answers (1)

Quentin Grisel
Quentin Grisel

Reputation: 4987

What you need is a popover, here is an example. You can find some on the internet but it's also easy to recreate :

.ts:

import React, { Component } from "react";
import { render } from "react-dom";
import "./style.css";

const App = () => {
  const [isPopoverVisible, setPopoverVisibility] = React.useState(false);
  const togglePopover = () => {
    if (!isPopoverVisible) {
      setPopoverVisibility(!isPopoverVisible);
    } else {
      setTimeout(() => setPopoverVisibility(!isPopoverVisible), 20000);
    }
  };

  return (
    <>
      <div>
        Content Content Content Content Content Content Content Content Content
        Content Content Content Content Content Content Content Content Content
        Content Content Content Content Content Content Content Content Content
        Content Content Content Content Content Content Content Content Content
        Content Content Content Content
      </div>
      <span>Some content</span>
      <Popover
        isVisible={isPopoverVisible}
        resetVisibility={togglePopover}
      >
        <button onClick={() => togglePopover()}>coucou</button>
      </Popover>
      <div>other content</div>
    </>
  );
};

const Popover = ({ children, isVisible, resetVisibility, position }) => {
  const [classes, setClasses] = React.useState("popover");
  const [style, setStyle] = React.useState({});
  const childrenRef = React.createRef();
  const popoverMessageRef = React.createRef();

  React.useEffect(() => {
    const messagePos = popoverMessageRef.current.getBoundingClientRect();
    const childrenPos = childrenRef.current.getBoundingClientRect();

    // x and y from getBoundingClientRect() dos not take into account the padding, we need to add it to our calculs
    const messagePaddingFull = window
      .getComputedStyle(popoverMessageRef.current)
      .getPropertyValue("padding");
    const messagePadding = 
      messagePaddingFull.substring(0, messagePaddingFull.length - 2);

  if(position){
     if (position.toLowerCase() === "top") {
      setStyle({
        top: `${childrenPos.y - messagePos.height - 10}px`,
        left: `${(childrenPos.x - messagePadding) - childrenPos.width / 2}px`
      });
    } else if(position.toLowerCase() === "left"){
      setStyle({
        top: `${(+childrenPos.y + +messagePadding/2) - childrenPos.height / 2}px`,
        left: `${(childrenPos.x) - messagePos.width - 10}px`
      });
    } else if(position.toLowerCase() === "right"){
      setStyle({
        top: `${(+childrenPos.y + +messagePadding/2) - childrenPos.height / 2}px`,
        left: `${+childrenPos.x + +childrenPos.width + 10}px`
      });
    } else {
      // default bottom
      setStyle({
        top: `${(childrenPos.y) + childrenPos.height + 10}px`,
        left: `${(childrenPos.x  - messagePadding/2) - childrenPos.width / 2}px`
      });
    }
  } else {
      // default bottom position
      setStyle({
        top: `${(childrenPos.y) + childrenPos.height + 10}px`,
        left: `${(childrenPos.x - messagePadding/2) - childrenPos.width / 2}px`
      });
    }

    console.log("messagePos = ", messagePos);
    console.log("messagePos = ", messagePadding);
    console.log("childrenPos = ", childrenPos);
  }, [classes]);

  React.useEffect(() => {
    setClasses(`popover ${isVisible ? "pop" : ""}`);

    if (isVisible) {
      resetVisibility();
    }
  }, [isVisible]);

  return (
    <div className="popover-component">
      <span ref={childrenRef}>{children}</span>
      <div className={classes} ref={popoverMessageRef} style={style}>
        This is the popover
      </div>
    </div>
  );
};

render(<App />, document.getElementById("root"));

.css:

.popover-component{
  display: inline-block;
  position: relative;
}
.popover{
  display: none;
  position: fixed; /* You can play with absolute position as well */
  background-color: #797979;
  border-radius: 4px;
  padding: 5px;
}

.pop {
  display: block; /* You can play with inline-block as well */
}

Here is the repro on stackblitz. You can then adjust the popover by getting the children's width and playing with it (like recreating the options "top", "bottom", "left", "right" by changing your algo).

Upvotes: 1

Related Questions