phx_component_helpers
Extensible Phoenix liveview components, without boilerplate.
Demonstration & code samples.
set_attributes & extend_class
This is the basic example showing how you can pass attributes to your
components with required values and providing defaults.
It also illustrates how extend_class
makes it easy to adjust
your component styling from the markup.
<.badge label="This is a badge"/>
<.badge label="Badge with a dot" dot={true} class="ml-4"/>
<.badge label="Badge with red dot" dot={true} class="ml-4" dot_class="!text* text-red-400"/>
defmodule PhxComponentHelpersDemoWeb.Components.Badge do
use PhxComponentHelpersDemoWeb, :component
import PhxComponentHelpers
@class "inline-flex items-center px-3 py-0.5 rounded-full\
text-sm font-medium bg-blue-100 text-blue-800"
@dot_class "-ml-0.5 mr-1.5 h-2.5 w-2 text-blue-400"
def badge(assigns) do
assigns
|> set_attributes([:label, dot: false], required: [:label])
|> extend_class(@class, prefix_replace: false)
|> extend_class(@dot_class, attribute: :dot_class, prefix_replace: false)
|> render()
end
defp render(assigns) do
~H"""
<span {@heex_class}>
<%= if @dot do %>
<svg {@heex_dot_class} fill="currentColor" viewBox="0 0 7 7">
<circle cx="4" cy="4" r="3" />
</svg>
<% end %>
<%= @label %>
</span>
"""
end
end
live_component & phx_attributes
phx_component_helpers are also working for live_components. It provides
a useful feature to pass all phx-* attributes at once to your component markup.
In this sample, we also show how extend_class
can be used with
a function.
<.live_component module={Button} id="button-1" label="I do nothing"/>
<.live_component module={Button} id="button-2"
color={:purple}
label="I change my color"
phx-click="change_color"
/>
defmodule PhxComponentHelpersDemoWeb.Components.Button do
use PhxComponentHelpersDemoWeb, :live_component
import PhxComponentHelpers
def update(assigns, socket) do
assigns =
assigns
|> set_phx_attributes()
|> set_attributes([:label, color: :indigo], required: [:label])
|> extend_class(&button_class/1, prefix_replace: false)
{:ok, assign(socket, assigns)}
end
def render(assigns) do
~H"""
<button {@heex_class} {@heex_phx_attributes} phx-target={@myself}>
<%= @label %>
</button>
"""
end
def handle_event("change_color", _, socket) do
assigns = extend_class(%{color: random_color()}, &button_class/1, prefix_replace: false)
{:noreply, assign(socket, assigns)}
end
defp button_class(assigns) do
"inline-flex items-center px-4 py-2 border border-transparent text-sm\
font-medium rounded-md shadow-sm text-white #{color(assigns)}"
end
defp random_color, do: Enum.random([:indigo, :yellow, :red, :purple, :emerald])
defp color(%{color: :indigo}), do: "bg-indigo-600 hover:bg-indigo-700"
defp color(%{color: :yellow}), do: "bg-yellow-400 hover:bg-yellow-500"
defp color(%{color: :red}), do: "bg-red-600 hover:bg-red-700"
defp color(%{color: :purple}), do: "bg-purple-600 hover:bg-purple-700"
defp color(%{color: :emerald}), do: "bg-emerald-600 hover:bg-emerald-700"
end
set_prefixed_attributes
Using set_prefixed_attributes
you can forward multiple assigns
at once. For example it's very convenient to detect and merge a whole set of x-
attributes when using alpinejs.
<.dropdown
label="I'm a dropdown"
x-data="{open: false}"
x-bind:class="open ? '' : 'hidden'"
x-on:click="open = !open"
/>
defmodule PhxComponentHelpersDemoWeb.Components.Dropdown do
use PhxComponentHelpersDemoWeb, :component
import PhxComponentHelpers
@class "relative inline-block text-left"
@button_class "inline-flex justify-center w-full rounded-md border border-gray-300\
shadow-sm px-4 py-2 bg-white text-sm font-medium text-gray-700 hover:bg-gray-50"
@dropdown_class "origin-top-left absolute left-0 mt-2 w-56 rounded-md shadow-lg bg-white"
@dropdown_entry_class "text-gray-700 block px-4 py-2 text-sm"
def dropdown(assigns) do
assigns
|> set_attributes(label: "Dropdown")
|> extend_class(@class, prefix_replace: false)
|> extend_class(@button_class, attribute: :button_class, prefix_replace: false)
|> extend_class(@dropdown_class, attribute: :dropdown_class, prefix_replace: false)
|> extend_class(@dropdown_entry_class, attribute: :dropdown_entry_class, prefix_replace: false)
|> set_prefixed_attributes(["x-data"], into: :init_alpine)
|> set_prefixed_attributes(["x-bind", "x-on"])
|> render()
end
defp render(assigns) do
~H"""
<div {@heex_class} {@heex_init_alpine}>
<div>
<button type="button" {@heex_button_class} {assigns[:"heex_x-on:click"]}>
<%= @label %>
</button>
</div>
<div {@heex_dropdown_class} {assigns[:"heex_x-bind:class"]}>
<div class="py-1">
<a {@heex_dropdown_entry_class}>Account settings</a>
<a {@heex_dropdown_entry_class}>Support</a>
<a {@heex_dropdown_entry_class}>License</a>
</div>
</div>
</div>
"""
end
end
forward_assigns
This code sample shows how nested components than still be customized from template
markup: by using forward_assigns
all assigns prefixed by :button_
are forwarded to the button sub component.
Beware!
With a button color
<.alert title="Beware!" button_id="alert-btn-1"/>
<.alert title="With a button color" button_id="alert-btn-2" button_color={:yellow}/>
defmodule PhxComponentHelpersDemoWeb.Components.Alert do
use PhxComponentHelpersDemoWeb, :component
import PhxComponentHelpers
alias PhxComponentHelpersDemoWeb.Components.Button
def alert(assigns) do
assigns
|> set_attributes([:title, :message], required: [:title])
|> extend_class("rounded-md bg-yellow-50 p-4 mb-4", prefix_replace: false)
|> extend_class("text-sm font-medium text-yellow-800",
attribute: :title_class,
prefix_replace: false
)
|> render()
end
defp render(assigns) do
~H"""
<div {@heex_class}>
<div class="flex w-full justify-between">
<h3 {@heex_title_class}>
<i class="fa fa-exclamation-circle text-yellow-400 mr-2 fa-lg"/>
<%= @title %>
</h3>
<.live_component module={Button} label="Click me!"
{forward_assigns(assigns, prefix: :button)}
/>
</div>
</div>
"""
end
end