Hover Card
Rich preview card shown on hover or focus of a trigger, rendered through a portal.
Installation
bin/rails g wabi:add hover_cardExample
The Rails component library you copy into your app.
render Components::UI::HoverCard.new do
render Components::UI::HoverCardTrigger.new { "@wabi" }
render Components::UI::HoverCardContent.new do
plain "The Rails component library you copy into your app."
end
end
Custom delays
Tune open_delay: and close_delay: (milliseconds) to make the card appear or dismiss faster. Defaults are 700 / 300.
Opens and closes almost instantly.
render Components::UI::HoverCard.new(open_delay: 100, close_delay: 100) do
render Components::UI::HoverCardTrigger.new { "@wabi (snappy)" }
render Components::UI::HoverCardContent.new do
plain "Opens and closes almost instantly."
end
end
Source
app/components/ui/hover_card.rb
# frozen_string_literal: true
require "date" # Phlex 2.4 references Date/Time constants lazily when rendering data:{} hashes
module Components
module UI
class HoverCard < Wabi::Base
def initialize(id: nil, open_delay: 700, close_delay: 300, portal: true, **attrs)
@id = id
@open_delay = open_delay
@close_delay = close_delay
@portal = portal
@attrs = attrs
end
def view_template(&block)
div(
id: @id,
class: "inline-block",
data: {
controller: "wabi--hover-card",
"wabi--hover-card-open-delay-value": @open_delay.to_s,
"wabi--hover-card-close-delay-value": @close_delay.to_s,
"wabi--hover-card-portal-value": @portal.to_s,
}
) do
yield if block_given?
end
end
end
end
end
app/components/ui/hover_card_trigger.rb
# frozen_string_literal: true
require "date" # Phlex 2.4 references Date/Time constants lazily when rendering data:{} hashes
module Components
module UI
class HoverCardTrigger < Wabi::Base
def initialize(**attrs)
@attrs = attrs
end
def view_template(&block)
user_class = @attrs.delete(:class)
button(
type: "button",
# aria-haspopup signals to AT that this trigger opens a popup panel.
# Zag never supplies this attribute via getTriggerProps(), so we set
# it statically here. Callers may override via attrs if needed.
"aria-haspopup": "true",
**@attrs,
data: { "wabi--hover-card-target": "trigger" },
class: merge_class("inline-flex items-center underline-offset-4 hover:underline cursor-pointer", user_class)
) do
yield if block_given?
end
end
end
end
end
app/components/ui/hover_card_content.rb
# frozen_string_literal: true
require "date" # Phlex 2.4 references Date/Time constants lazily when rendering data:{} hashes
module Components
module UI
class HoverCardContent < Wabi::Base
variants do
base "z-50 w-64 rounded-md border border-input bg-popover p-4 text-popover-foreground shadow-md outline-none " \
"transition-opacity duration-200 ease-out motion-reduce:transition-none " \
"data-[state=open]:opacity-100 data-[state=closed]:opacity-0 " \
"data-[state=closed]:pointer-events-none"
end
def initialize(**attrs)
@attrs = attrs
end
def view_template(&block)
user_class = @attrs.delete(:class)
div(
data: { "wabi--hover-card-target": "positioner" },
class: "z-50 pointer-events-none"
) do
div(
data: { "wabi--hover-card-target": "content" },
"data-state": "closed",
inert: true,
class: merge_class(tokens, user_class)
) do
yield if block_given?
end
end
end
end
end
end
Accessibility
- Opens on pointer hover and on keyboard focus of the trigger.
- Content is inert and aria-hidden while closed; not a focus trap (non-modal).
- Respects prefers-reduced-motion (fade transition is disabled).