Packages

shadcn/ui-inspired component library for Phoenix LiveView with eject-based distribution. CSS-first theme system, 829 components across 20+ categories — Calendar, Chart, Animation, DnD, Editor, Collaboration, Typography, Navigation, Background, Surface, and more.

Current section

Files

Jump to
phia_ui priv templates components popover.ex.eex
Raw

priv/templates/components/popover.ex.eex

defmodule <%= @module_name %>.Components.UI.Popover do
@moduledoc """
Popover component with PhiaPopover vanilla JS hook.
Ejected from PhiaUI — you own this copy. Customise freely.
## Example
<.popover id="settings-pop">
<.popover_trigger popover_id="settings-pop">
Settings
</.popover_trigger>
<.popover_content popover_id="settings-pop" position={:bottom}>
<input type="text" name="name" placeholder="Your name" />
<button type="submit">Save</button>
</.popover_content>
</.popover>
## Hook setup
import PhiaPopover from "./hooks/popover"
let liveSocket = new LiveSocket("/live", Socket, {
hooks: { PhiaPopover }
})
"""
use Phoenix.Component
import <%= @module_name %>.ClassMerger, only: [cn: 1]
attr :id, :string, required: true, doc: "Unique ID for aria coordination"
attr :class, :string, default: nil, doc: "Additional CSS classes"
attr :rest, :global
slot :inner_block, required: true
def popover(assigns) do
~H"""
<div
id={@id}
phx-hook="PhiaPopover"
class={cn(["relative inline-flex", @class])}
{@rest}
>
<%%= render_slot(@inner_block) %>
</div>
"""
end
attr :popover_id, :string, required: true, doc: "ID of the parent popover wrapper"
attr :class, :string, default: nil, doc: "Additional CSS classes"
attr :rest, :global
slot :inner_block, required: true
def popover_trigger(assigns) do
~H"""
<button
type="button"
aria-controls={"#{@popover_id}-content"}
aria-expanded="false"
data-popover-trigger
class={cn([@class])}
{@rest}
>
<%%= render_slot(@inner_block) %>
</button>
"""
end
attr :popover_id, :string, required: true, doc: "ID of the parent popover wrapper"
attr :position, :atom,
default: :bottom,
values: [:top, :bottom, :left, :right],
doc: "Preferred position of the floating panel"
attr :class, :string, default: nil, doc: "Additional CSS classes"
attr :rest, :global
slot :inner_block, required: true
def popover_content(assigns) do
~H"""
<div
id={"#{@popover_id}-content"}
role="dialog"
aria-modal="true"
data-position={@position}
data-popover-content
class={cn([
"absolute z-50 rounded-md border border-border bg-popover text-popover-foreground",
"p-4 shadow-md outline-none",
"hidden",
@class
])}
{@rest}
>
<%%= render_slot(@inner_block) %>
</div>
"""
end
end