Module: Layered::Ui::PopoverHelper

Defined in:
app/helpers/layered/ui/popover_helper.rb

Defined Under Namespace

Classes: PopoverBuilder

Instance Method Summary collapse

Instance Method Details

#l_ui_popover(id: nil, placement: :bottom, align: :start, container: {}, &block) ⇒ Object

Renders a floating panel anchored to a trigger button, built on the native HTML popover attribute. Showing, hiding, light-dismiss, and Escape-to-close are all handled by the browser; the l-ui--popover Stimulus controller only computes placement (with auto-flip near viewport edges).

<%= l_ui_popover(placement: :bottom) do |p| %>
<% p.trigger(class: "l-ui-button l-ui-button--outline") do %>
  Options
<% end %>
<p>Popover content.</p>
<% end %>

Note: use <% p.trigger %> (without the equals sign) so its content is captured by the builder rather than written to the body buffer.

Options:

id:         (String)  DOM id for the popover element; defaults to an auto-generated id.
placement:  (Symbol)  :top, :bottom (default), :left, or :right. Flips automatically if it would overflow the viewport.
align:      (Symbol)  :start (default) flushes the popover's leading edge with the trigger's; :end flushes its trailing edge instead
                    (e.g. placement: :bottom, align: :end hangs the popover down and to the left of a trigger at the right end of a row).
container:  (Hash)    Extra HTML attributes for the wrapping <div>.


26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
# File 'app/helpers/layered/ui/popover_helper.rb', line 26

def l_ui_popover(id: nil, placement: :bottom, align: :start, container: {}, &block)
  id ||= "l-ui-popover-#{SecureRandom.hex(4)}"
  builder = PopoverBuilder.new(self, id: id)
  body_content = capture { block.call(builder) }

  container_attrs = container.deep_dup
  container_data = container_attrs[:data] || {}
  existing_controller = container_data.delete(:controller) || container_data.delete("controller")
  container_data[:controller] = [existing_controller, "l-ui--popover"].compact.reject(&:empty?).join(" ")
  container_data[:"l-ui--popover-placement-value"] = placement
  container_data[:"l-ui--popover-align-value"] = align
  container_attrs[:data] = container_data

  tag.div(**container_attrs) do
    safe_join([
      builder.trigger_html || ActiveSupport::SafeBuffer.new,
      render_popover(builder, body_content)
    ])
  end
end