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 command.ex.eex
Raw

priv/templates/components/command.ex.eex

defmodule <%= @module_name %>.Components.UI.Command do
@moduledoc """
Command palette component with PhiaCommand vanilla JS hook.
Ejected from PhiaUI — you own this copy. Customise freely.
## Example
<.command id="global-cmd">
<.command_input id="global-cmd-input" on_change="search" />
<.command_list id="global-cmd-list">
<%= if @results == [] do %>
<.command_empty>No results found.</.command_empty>
<% else %>
<.command_group label="Pages">
<.command_item :for={item <- @results} on_click="navigate" value={item.path}>
<%= item.label %>
</.command_item>
</.command_group>
<% end %>
</.command_list>
</.command>
## Hook setup
import PhiaCommand from "./hooks/command"
let liveSocket = new LiveSocket("/live", Socket, {
hooks: { PhiaCommand }
})
"""
use Phoenix.Component
import <%= @module_name %>.ClassMerger, only: [cn: 1]
attr :id, :string, required: true
attr :class, :string, default: nil
attr :rest, :global
slot :inner_block, required: true
def command(assigns) do
~H"""
<div
id={@id}
phx-hook="PhiaCommand"
role="dialog"
aria-modal="true"
data-command-modal
class={cn(["fixed inset-0 z-50 hidden", @class])}
{@rest}
>
<div class="fixed inset-0 bg-black/50" data-command-backdrop></div>
<div class="fixed left-1/2 top-[20%] -translate-x-1/2 w-full max-w-lg px-4">
<div class="overflow-hidden rounded-lg border border-border bg-popover shadow-lg">
<%%= render_slot(@inner_block) %>
</div>
</div>
</div>
"""
end
attr :id, :string, required: true
attr :placeholder, :string, default: "Type a command or search..."
attr :on_change, :string, required: true
attr :class, :string, default: nil
attr :rest, :global
def command_input(assigns) do
~H"""
<input
id={@id}
type="text"
role="combobox"
aria-expanded="false"
aria-autocomplete="list"
autocomplete="off"
spellcheck="false"
placeholder={@placeholder}
phx-change={@on_change}
data-command-input
class={cn([
"flex h-11 w-full border-0 border-b border-border bg-transparent",
"px-4 py-3 text-sm outline-none placeholder:text-muted-foreground",
"disabled:cursor-not-allowed disabled:opacity-50",
@class
])}
{@rest}
/>
"""
end
attr :id, :string, required: true
attr :class, :string, default: nil
attr :rest, :global
slot :inner_block, required: true
def command_list(assigns) do
~H"""
<div id={@id} role="listbox" data-command-list
class={cn(["max-h-72 overflow-y-auto overflow-x-hidden py-1", @class])} {@rest}>
<%%= render_slot(@inner_block) %>
</div>
"""
end
attr :class, :string, default: nil
attr :rest, :global
slot :inner_block, required: true
def command_empty(assigns) do
~H"""
<div class={cn(["py-6 text-center text-sm text-muted-foreground", @class])} {@rest}>
<%%= render_slot(@inner_block) %>
</div>
"""
end
attr :label, :string, required: true
attr :class, :string, default: nil
attr :rest, :global
slot :inner_block, required: true
def command_group(assigns) do
~H"""
<div role="group" class={cn([@class])} {@rest}>
<div class="px-2 py-1.5 text-xs font-medium text-muted-foreground">
<%%= @label %>
</div>
<%%= render_slot(@inner_block) %>
</div>
"""
end
attr :on_click, :string, required: true
attr :value, :string, required: true
attr :class, :string, default: nil
attr :rest, :global
slot :inner_block, required: true
def command_item(assigns) do
~H"""
<div role="option" aria-selected="false" phx-click={@on_click} phx-value-value={@value}
data-command-item
class={cn([
"relative flex cursor-pointer select-none items-center justify-between",
"rounded-sm px-2 py-1.5 text-sm outline-none",
"hover:bg-accent hover:text-accent-foreground",
"data-[selected]:bg-accent data-[selected]:text-accent-foreground",
@class
])} {@rest}>
<%%= render_slot(@inner_block) %>
</div>
"""
end
attr :class, :string, default: nil
attr :rest, :global
def command_separator(assigns) do
~H"""
<div data-command-separator class={cn(["-mx-1 my-1 h-px bg-border", @class])} {@rest} />
"""
end
attr :class, :string, default: nil
attr :rest, :global
slot :inner_block, required: true
def command_shortcut(assigns) do
~H"""
<span class={cn(["ml-auto text-xs tracking-widest text-muted-foreground", @class])} {@rest}>
<%%= render_slot(@inner_block) %>
</span>
"""
end
end