mirror of
https://github.com/maybe-finance/maybe.git
synced 2025-08-06 05:55:21 +02:00
Initial tooltip component
This commit is contained in:
parent
40ae3126ac
commit
d932ac84a7
5 changed files with 147 additions and 0 deletions
9
app/components/DS/tooltip.html.erb
Normal file
9
app/components/DS/tooltip.html.erb
Normal file
|
@ -0,0 +1,9 @@
|
|||
<div data-controller="ds-tooltip" data-ds-tooltip-placement-value="<%= placement %>" data-ds-tooltip-offset-value="<%= offset %>" data-ds-tooltip-cross-axis-value="<%= cross_axis %>">
|
||||
<%= helpers.icon icon_name, size: size, color: color %>
|
||||
|
||||
<div role="tooltip" data-ds-tooltip-target="tooltip" class="hidden absolute z-50 bg-gray-700 text-sm px-1.5 py-1 rounded-md">
|
||||
<div class="fg-inverse font-normal max-w-[200px]">
|
||||
<%= tooltip_content %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
17
app/components/DS/tooltip.rb
Normal file
17
app/components/DS/tooltip.rb
Normal file
|
@ -0,0 +1,17 @@
|
|||
class DS::Tooltip < ApplicationComponent
|
||||
attr_reader :placement, :offset, :cross_axis, :icon_name, :size, :color
|
||||
|
||||
def initialize(text: nil, placement: "top", offset: 10, cross_axis: 0, icon: "info", size: "sm", color: "default")
|
||||
@text = text
|
||||
@placement = placement
|
||||
@offset = offset
|
||||
@cross_axis = cross_axis
|
||||
@icon_name = icon
|
||||
@size = size
|
||||
@color = color
|
||||
end
|
||||
|
||||
def tooltip_content
|
||||
content? ? content : @text
|
||||
end
|
||||
end
|
|
@ -16,6 +16,10 @@
|
|||
</div>
|
||||
|
||||
<div class="flex items-center gap-4">
|
||||
<div class="flex items-center gap-1">
|
||||
<span class="font-medium"><%= balance_trend.current.format %></span>
|
||||
<%= render DS::Tooltip.new(text: "The end of day balance, after all transactions and adjustments") %>
|
||||
</div>
|
||||
<%= helpers.icon "chevron-down", class: "group-open:rotate-180" %>
|
||||
</div>
|
||||
</div>
|
||||
|
|
85
app/javascript/controllers/ds_tooltip_controller.js
Normal file
85
app/javascript/controllers/ds_tooltip_controller.js
Normal file
|
@ -0,0 +1,85 @@
|
|||
import {
|
||||
autoUpdate,
|
||||
computePosition,
|
||||
flip,
|
||||
offset,
|
||||
shift,
|
||||
} from "@floating-ui/dom";
|
||||
import { Controller } from "@hotwired/stimulus";
|
||||
|
||||
export default class extends Controller {
|
||||
static targets = ["tooltip"];
|
||||
static values = {
|
||||
placement: { type: String, default: "top" },
|
||||
offset: { type: Number, default: 10 },
|
||||
crossAxis: { type: Number, default: 0 },
|
||||
};
|
||||
|
||||
connect() {
|
||||
this._cleanup = null;
|
||||
this.boundUpdate = this.update.bind(this);
|
||||
this.addEventListeners();
|
||||
}
|
||||
|
||||
disconnect() {
|
||||
this.removeEventListeners();
|
||||
this.stopAutoUpdate();
|
||||
}
|
||||
|
||||
addEventListeners() {
|
||||
this.element.addEventListener("mouseenter", this.show);
|
||||
this.element.addEventListener("mouseleave", this.hide);
|
||||
}
|
||||
|
||||
removeEventListeners() {
|
||||
this.element.removeEventListener("mouseenter", this.show);
|
||||
this.element.removeEventListener("mouseleave", this.hide);
|
||||
}
|
||||
|
||||
show = () => {
|
||||
this.tooltipTarget.style.display = "block";
|
||||
this.startAutoUpdate();
|
||||
this.update();
|
||||
};
|
||||
|
||||
hide = () => {
|
||||
this.tooltipTarget.style.display = "none";
|
||||
this.stopAutoUpdate();
|
||||
};
|
||||
|
||||
startAutoUpdate() {
|
||||
if (!this._cleanup) {
|
||||
this._cleanup = autoUpdate(
|
||||
this.element.firstElementChild, // Use the icon as the reference element
|
||||
this.tooltipTarget,
|
||||
this.boundUpdate
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
stopAutoUpdate() {
|
||||
if (this._cleanup) {
|
||||
this._cleanup();
|
||||
this._cleanup = null;
|
||||
}
|
||||
}
|
||||
|
||||
update() {
|
||||
computePosition(this.element.firstElementChild, this.tooltipTarget, {
|
||||
placement: this.placementValue,
|
||||
middleware: [
|
||||
offset({
|
||||
mainAxis: this.offsetValue,
|
||||
crossAxis: this.crossAxisValue,
|
||||
}),
|
||||
flip(),
|
||||
shift({ padding: 5 }),
|
||||
],
|
||||
}).then(({ x, y }) => {
|
||||
Object.assign(this.tooltipTarget.style, {
|
||||
left: `${x}px`,
|
||||
top: `${y}px`,
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
32
test/components/previews/tooltip_component_preview.rb
Normal file
32
test/components/previews/tooltip_component_preview.rb
Normal file
|
@ -0,0 +1,32 @@
|
|||
class TooltipComponentPreview < ViewComponent::Preview
|
||||
# @param text text
|
||||
# @param placement select [top, right, bottom, left]
|
||||
# @param offset number
|
||||
# @param cross_axis number
|
||||
# @param icon text
|
||||
# @param size select [xs, sm, md, lg, xl, 2xl]
|
||||
# @param color select [default, white, success, warning, destructive, current]
|
||||
def default(text: "This is helpful information", placement: "top", offset: 10, cross_axis: 0, icon: "info", size: "sm", color: "default")
|
||||
render DS::Tooltip.new(
|
||||
text: text,
|
||||
placement: placement,
|
||||
offset: offset,
|
||||
cross_axis: cross_axis,
|
||||
icon: icon,
|
||||
size: size,
|
||||
color: color
|
||||
)
|
||||
end
|
||||
|
||||
def with_block_content
|
||||
render DS::Tooltip.new(icon: "help-circle", color: "warning") do
|
||||
tag.div do
|
||||
tag.p("Custom content with formatting:", class: "font-medium mb-1") +
|
||||
tag.ul(class: "list-disc list-inside text-xs") do
|
||||
tag.li("First item") +
|
||||
tag.li("Second item")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
Loading…
Add table
Add a link
Reference in a new issue