Popper is a javascript positioning engine to speed up the development of popovers and tooltips.
More information about it can be found here
Project setup:
- Rails 7
- Stimulus 2
- esbuild / jsbuild
- Tailwind CSS
rails new project_name --css=tailwind --javascript=esbuild
Step 1: add popper to the project
yarn add @popperjs/core
Step2: create a stimulus controller
rails g stimulus popper
Open the popper_controller.js
and perform the following edits:
Add at the top of the file:
import { createPopper } from "@popperjs/core";
Before creating an instance of popper in the controller, let's add "target" and "values" to make this controller more reusable.
Popper instance is instantiated in the connect()
method
connect() {
// Create a new Popper instance
this.popperInstance = createPopper(this.elementTarget, this.tooltipTarget, {
placement: this.placementValue,
modifiers: [
{
name: "offset",
options: {
offset: this.offsetValue,
},
},
],
});
}
Note that the event listeners are not added to the target or element from within the stimulus controller. As written in Better Stimulus and the official stimulus documentation, event management should be handled by the Stimulus framework. We will attached the event management to the element using the data-action
tag as explained below.
Let's create the show
and hide
methods as well as the disconnect
one that us used to remove the popper instance.
show(event) {
this.tooltipTarget.setAttribute("data-show", "");
// We need to tell Popper to update the tooltip position
// after we show the tooltip, otherwise it will be incorrect
this.popperInstance.update();
}
hide(event) {
this.tooltipTarget.removeAttribute("data-show");
}
// Destroy the Popper instance
disconnect() {
if (this.popperInstance) {
this.popperInstance.destroy();
}
}
So far we are pretty much sticking to popper's documentation. The main difference is that the event listeners are not attached to the element programmatically within the stimulus controller.
At this point, the popper_controller.js
file should look like this:
import { Controller } from "@hotwired/stimulus";
import { createPopper } from "@popperjs/core";
// Connects to data-controller="popper"
export default class extends Controller {
static targets = ["element", "tooltip"];
static values = {
placement: { type: String, default: "top" },
offset: { type: Array, default: [0, 8] },
};
connect() {
// Create a new Popper instance
this.popperInstance = createPopper(this.elementTarget, this.tooltipTarget, {
placement: this.placementValue,
modifiers: [
{
name: "offset",
options: {
offset: this.offsetValue,
},
},
],
});
}
show(event) {
this.tooltipTarget.setAttribute("data-show", "");
// We need to tell Popper to update the tooltip position
// after we show the tooltip, otherwise it will be incorrect
this.popperInstance.update();
}
hide(event) {
this.tooltipTarget.removeAttribute("data-show");
}
// Destroy the Popper instance
disconnect() {
if (this.popperInstance) {
this.popperInstance.destroy();
}
}
}
Step 3: Let's get stylish!
We can use the one provided by the popper team as an example on their website
#tooltip {
background: #333;
color: white;
font-weight: bold;
padding: 4px 8px;
font-size: 13px;
border-radius: 4px;
display: none;
}
#arrow,
#arrow::before {
position: absolute;
width: 8px;
height: 8px;
background: inherit;
}
#arrow {
visibility: hidden;
}
#arrow::before {
visibility: visible;
content: "";
transform: rotate(45deg);
}
#tooltip[data-popper-placement^="top"] > #arrow {
bottom: -4px;
}
#tooltip[data-popper-placement^="bottom"] > #arrow {
top: -4px;
}
#tooltip[data-popper-placement^="left"] > #arrow {
right: -4px;
}
#tooltip[data-popper-placement^="right"] > #arrow {
left: -4px;
}
#tooltip[data-show] {
display: block;
}
Feel free to use tailwind CSS styles and animation or any other CSS trick that is required or will make your popover/tooltip look better.
Saved it in app/assets/stylesheets/popper.css
and import it at the top of app/assets/stylesheets/application.tailwind.css
@import "popper.css";
/* Tailwind CSS */
@tailwind base;
@tailwind components;
@tailwind utilities;
It's now time to work on the frontend of the project!
Step 4: Let's create a button
<div data-controller="popper">
<button id="button" data-popper-target="element" data-action="mouseenter->popper#show mouseleave->popper#hide" class="bg-blue-500 text-blue-100 px-3 py-2 rounded-xl">
Click me
<div id="tooltip" role="tooltip" data-popper-target="tooltip">
My tooltip
<div id="arrow" data-popper-arrow></div>
</div>
</button>
</div>
As stated above, the event listeners are added using the data-action
parameter. in this case we direct the mouseenter
event, which is triggered when the mouse is over the button, to the show
method defined in the controller. When the mouse is no longer over the button element, mouseleave
is triggered and the hide
method is called to hide the tooltip.
You can add more actions or adapt to your needs. For example, you can have data-action="click->popper#show"
to open a popover when a user click on a certain element.
Top comments (1)
That's great @spaquet ! Thanks for sharing this, it's very helpful!
One question tho, if I want to change the position of the tooltip, how can I do that? I saw that we have the css to show the arrow in different places, but I could not figure out where to set the position.