` tag
* `type="checkbox"` is used exclusively to render boolean values
* For live file uploads, see `Phoenix.Component.live_file_input/1`
See https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input
for more information.
## Examples
<.input field={@form[:email]} type="email" />
<.input name="my-input" errors={["oh no!"]} />
"""
attr :id, :any, default: nil
attr :name, :any
attr :label, :string, default: nil
attr :value, :any
attr :type, :string,
default: "text",
values: ~w(checkbox color date datetime-local email file hidden month number password
range radio search select tel text textarea time url week)
attr :field, Phoenix.HTML.FormField,
doc: "a form field struct retrieved from the form, for example: @form[:email]"
attr :errors, :list, default: []
attr :checked, :boolean, doc: "the checked flag for checkbox inputs"
attr :prompt, :string, default: nil, doc: "the prompt for select inputs"
attr :options, :list, doc: "the options to pass to Phoenix.HTML.Form.options_for_select/2"
attr :multiple, :boolean, default: false, doc: "the multiple flag for select inputs"
attr :rest, :global,
include: ~w(accept autocomplete capture cols disabled form list max maxlength min minlength
multiple pattern placeholder readonly required rows size step)
slot :inner_block
def input(%{field: %Phoenix.HTML.FormField{} = field} = assigns) do
assigns
|> assign(field: nil, id: assigns.id || field.id)
|> assign(:errors, Enum.map(field.errors, &translate_error(&1)))
|> assign_new(:name, fn -> if assigns.multiple, do: field.name <> "[]", else: field.name end)
|> assign_new(:value, fn -> field.value end)
|> input()
end
def input(%{type: "checkbox"} = assigns) do
assigns =
assign_new(assigns, :checked, fn ->
Phoenix.HTML.Form.normalize_value("checkbox", assigns[:value])
end)
~H"""
<%!-- todo: is focus:ring-0 part of tailwind? if yes, find alternative/remove --%>
<%= @label %>
<.error :for={msg <- @errors}><%= msg %>
"""
end
def input(%{type: "select"} = assigns) do
~H"""
<.label for={@id}><%= @label %>
<%= @prompt %>
<%= Phoenix.HTML.Form.options_for_select(@options, @value) %>
<.error :for={msg <- @errors}><%= msg %>
"""
end
def input(%{type: "textarea"} = assigns) do
~H"""
<.label for={@id}><%= @label %>
<.error :for={msg <- @errors}><%= msg %>
"""
end
# All other inputs text, datetime-local, url, password, etc. are handled here...
def input(assigns) do
~H"""
<.label for={@id}><%= @label %>
<.error :for={msg <- @errors}><%= msg %>
"""
end
@doc """
Renders section.
"""
attr :class, :string, default: nil, doc: "the optional class of the section"
attr :rest, :global, doc: "the arbitrary HTML attributes to add to the section"
slot :inner_block, required: true, doc: "the inner block that renders the section content"
def section(assigns) do
assigns =
assigns
|> extend_class("section")
|> set_phx_attributes()
|> set_bulma_classes()
~H"""
<%= render_slot(@inner_block) %>
"""
end
@doc """
Renders box.
"""
attr :class, :string, default: nil, doc: "the optional class of the box"
attr :rest, :global, doc: "the arbitrary HTML attributes to add to the box"
slot :inner_block, required: true, doc: "the inner block that renders the box content"
def box(assigns) do
assigns =
assigns
|> extend_class("box")
|> set_phx_attributes()
|> set_bulma_classes()
~H"""
<%= render_slot(@inner_block) %>
"""
end
@doc """
Renders container.
"""
attr :class, :string, default: nil, doc: "the optional class of the container"
attr :rest, :global, doc: "the arbitrary HTML attributes to add to the container"
slot :inner_block, required: true, doc: "the inner block that renders the container content"
def container(assigns) do
assigns =
assigns
|> extend_class("container")
|> set_phx_attributes()
|> set_bulma_classes()
~H"""
<%= render_slot(@inner_block) %>
"""
end
@doc """
Renders footer.
"""
attr :class, :string, default: nil, doc: "the optional class of the footer"
attr :rest, :global, doc: "the arbitrary HTML attributes to add to the footer"
slot :inner_block, required: true, doc: "the inner block that renders the footer content"
def footer(assigns) do
assigns =
assigns
|> extend_class("footer")
|> set_phx_attributes()
|> set_bulma_classes()
~H"""
<%= render_slot(@inner_block) %>
"""
end
@doc """
Renders media object.
"""
attr :class, :string, default: nil, doc: "the optional class of the media object"
attr :rest, :global, doc: "the arbitrary HTML attributes to add to the media object"
slot :inner_block, required: true, doc: "the inner block that renders the media object content"
def media(assigns) do
assigns =
assigns
|> extend_class("media")
|> set_phx_attributes()
|> set_bulma_classes()
~H"""
<%= render_slot(@inner_block) %>
"""
end
@doc """
Renders media-left.
"""
attr :class, :string, default: nil, doc: "the optional class of the media-left"
attr :rest, :global, doc: "the arbitrary HTML attributes to add to the media-left"
slot :inner_block, required: true, doc: "the inner block that renders the media-left content"
def media_left(assigns) do
assigns =
assigns
|> extend_class("media-left")
|> set_phx_attributes()
|> set_bulma_classes()
~H"""
<%= render_slot(@inner_block) %>
"""
end
@doc """
Renders media-content.
"""
attr :class, :string, default: nil, doc: "the optional class of the media-content"
attr :rest, :global, doc: "the arbitrary HTML attributes to add to the media-content"
slot :inner_block, required: true, doc: "the inner block that renders the media-content content"
def media_content(assigns) do
assigns =
assigns
|> extend_class("media-content")
|> set_phx_attributes()
|> set_bulma_classes()
~H"""
<%= render_slot(@inner_block) %>
"""
end
@doc """
Renders media-right.
"""
attr :class, :string, default: nil, doc: "the optional class of the media-right"
attr :rest, :global, doc: "the arbitrary HTML attributes to add to the media-right"
slot :inner_block, required: true, doc: "the inner block that renders the media-right content"
def media_right(assigns) do
assigns =
assigns
|> extend_class("media-right")
|> set_phx_attributes()
|> set_bulma_classes()
~H"""
<%= render_slot(@inner_block) %>
"""
end
@doc """
Renders content.
"""
attr :class, :string, default: nil, doc: "the optional class of the content"
attr :rest, :global, doc: "the arbitrary HTML attributes to add to the content"
slot :inner_block, required: true, doc: "the inner block that renders the content"
def content(assigns) do
assigns =
assigns
|> extend_class("content")
|> set_phx_attributes()
|> set_bulma_classes()
~H"""
<%= render_slot(@inner_block) %>
"""
end
@doc """
Renders a level.
"""
attr :class, :string, default: nil, doc: "the optional class of the level"
attr :rest, :global, doc: "the arbitrary HTML attributes to add to the level"
slot :inner_block, required: true, doc: "the inner block that renders the level content"
def level(assigns) do
assigns =
assigns
|> extend_class("level")
|> set_phx_attributes()
|> set_bulma_classes()
~H"""
<%= render_slot(@inner_block) %>
"""
end
@doc """
Renders level-left.
"""
attr :class, :string, default: nil, doc: "the optional class of the level-left"
attr :rest, :global, doc: "the arbitrary HTML attributes to add to the level-left"
slot :inner_block, required: true, doc: "the inner block that renders the level-left content"
def level_left(assigns) do
assigns =
assigns
|> extend_class("level-left")
|> set_phx_attributes()
|> set_bulma_classes()
~H"""
<%= render_slot(@inner_block) %>
"""
end
@doc """
Renders level-right.
"""
attr :class, :string, default: nil, doc: "the optional class of the level-right"
attr :rest, :global, doc: "the arbitrary HTML attributes to add to the level-right"
slot :inner_block, required: true, doc: "the inner block that renders the level-right content"
def level_right(assigns) do
assigns =
assigns
|> extend_class("level-right")
|> set_phx_attributes()
|> set_bulma_classes()
~H"""
<%= render_slot(@inner_block) %>
"""
end
@doc """
Renders level-item.
"""
attr :class, :string, default: nil, doc: "the optional class of the level-item"
attr :rest, :global, doc: "the arbitrary HTML attributes to add to the level-item"
slot :inner_block, required: true, doc: "the inner block that renders the level-item content"
def level_item(assigns) do
assigns =
assigns
|> extend_class("level-item")
|> set_phx_attributes()
|> set_bulma_classes()
~H"""
<%= render_slot(@inner_block) %>
"""
end
@doc """
Renders columns.
"""
attr :class, :string, default: nil, doc: "the optional class of the columns"
attr :rest, :global, doc: "the arbitrary HTML attributes to add to the column"
slot :inner_block, required: true, doc: "the inner block that renders the columns content"
def columns(assigns) do
assigns =
assigns
|> extend_class("columns")
|> set_phx_attributes()
|> set_bulma_classes()
~H"""
<%= render_slot(@inner_block) %>
"""
end
@doc """
Renders column.
"""
attr :class, :string, default: nil, doc: "the optional class of the column"
attr :rest, :global, doc: "the arbitrary HTML attributes to add to the column"
slot :inner_block, required: true, doc: "the inner block that renders the column content"
def column(assigns) do
assigns =
assigns
|> extend_class("column")
|> set_phx_attributes()
|> set_bulma_classes()
~H"""
<%= render_slot(@inner_block) %>
"""
end
@doc """
Renders button.
"""
attr :id, :string, required: false, doc: "the id of the button"
attr :type, :string, default: "button", doc: "the type of the button"
attr :rest, :global, doc: "the arbitrary HTML attributes to add to the button"
slot :inner_block, required: true, doc: "the inner block that renders the button content"
def button(assigns) do
assigns =
assigns
|> extend_class("button")
|> set_attributes([:type, :id])
|> set_phx_attributes()
|> set_bulma_classes()
~H"""
<%= render_slot(@inner_block) %>
"""
end
@doc """
Renders hero section.
"""
attr :id, :string, required: false, doc: "the id of the hero section"
attr :class, :string, default: nil, doc: "the optional class of the hero section"
attr :rest, :global, doc: "the arbitrary HTML attributes to add to the hero section"
slot :inner_block, required: true, doc: "the inner block that renders the hero content"
def hero(assigns) do
assigns =
assigns
|> extend_class("hero")
|> set_attributes([:id])
|> set_phx_attributes()
|> set_bulma_classes()
~H"""
<%= render_slot(@inner_block) %>
"""
end
@doc """
Renders title.
"""
attr :rest, :global, doc: "the arbitrary HTML attributes to add to the button"
slot :inner_block, required: true, doc: "the inner block that renders the title content"
def title(assigns) do
assigns
|> extend_class("title")
|> set_phx_attributes()
|> set_bulma_classes()
|> render_bulma_heading()
end
@doc """
Renders subtitle.
"""
attr :rest, :global, doc: "the arbitrary HTML attributes to add to the button"
slot :inner_block, required: true, doc: "the inner block that renders the subtitle content"
def subtitle(assigns) do
assigns
|> extend_class("subtitle")
|> set_phx_attributes()
|> set_bulma_classes()
|> render_bulma_heading()
end
@doc """
Renders navbar.
"""
attr :class, :string, default: nil, doc: "the optional class of the navbar"
attr :rest, :global, doc: "the arbitrary HTML attributes to add to the navbar"
slot :inner_block, required: true, doc: "the inner block that renders the navbar content"
def navbar(assigns) do
assigns =
assigns
|> extend_class("navbar")
|> set_phx_attributes()
|> set_bulma_classes()
~H"""
<%= render_slot(@inner_block) %>
"""
end
@doc """
Renders navbar-brand.
"""
attr :class, :string, default: nil, doc: "the optional class of the navbar-brand"
attr :rest, :global, doc: "the arbitrary HTML attributes to add to the navbar-brand"
slot :inner_block, required: true, doc: "the inner block that renders the navbar-brand content"
def navbar_brand(assigns) do
assigns =
assigns
|> extend_class("navbar-brand")
|> set_phx_attributes()
|> set_bulma_classes()
~H"""
<%= render_slot(@inner_block) %>
"""
end
@doc """
Renders navbar-menu.
"""
attr :id, :string, required: true, doc: "the id of the navbar-menu"
attr :class, :string, default: nil, doc: "the optional class of the navbar-menu"
attr :rest, :global, doc: "the arbitrary HTML attributes to add to the navbar-menu"
slot :inner_block, required: true, doc: "the inner block that renders the navbar-menu content"
def navbar_menu(assigns) do
assigns =
assigns
|> extend_class("navbar-menu")
|> set_attributes([:id])
|> set_phx_attributes()
|> set_bulma_classes()
~H"""
<%= render_slot(@inner_block) %>
"""
end
@doc """
Renders navbar-start.
"""
attr :class, :string, default: nil, doc: "the optional class of the navbar-start"
attr :rest, :global, doc: "the arbitrary HTML attributes to add to the navbar-start"
slot :inner_block, required: true, doc: "the inner block that renders the navbar-start content"
def navbar_start(assigns) do
assigns =
assigns
|> extend_class("navbar-start")
|> set_phx_attributes()
|> set_bulma_classes()
~H"""
<%= render_slot(@inner_block) %>
"""
end
@doc """
Renders navbar-end.
"""
attr :class, :string, default: nil, doc: "the optional class of the navbar-end"
attr :rest, :global, doc: "the arbitrary HTML attributes to add to the navbar-end"
slot :inner_block, required: true, doc: "the inner block that renders the navbar-end content"
def navbar_end(assigns) do
assigns =
assigns
|> extend_class("navbar-end")
|> set_phx_attributes()
|> set_bulma_classes()
~H"""
<%= render_slot(@inner_block) %>
"""
end
@doc """
Renders navbar-item.
"""
attr :class, :string, default: nil, doc: "the optional class of the navbar-item"
attr :rest, :global, doc: "the arbitrary HTML attributes to add to the navbar-item"
slot :inner_block, required: true, doc: "the inner block that renders the navbar-item content"
def navbar_item(assigns) do
assigns =
assigns
|> extend_class("navbar-item")
|> set_phx_attributes()
|> set_bulma_classes()
~H"""
<%= render_slot(@inner_block) %>
"""
end
@doc """
Renders navbar-dropdown.
"""
attr :class, :string, default: nil, doc: "the optional class of the navbar-dropdown"
attr :rest, :global, doc: "the arbitrary HTML attributes to add to the navbar-dropdown"
slot :inner_block,
required: true,
doc: "the inner block that renders the navbar-dropdown content"
def navbar_dropdown(assigns) do
assigns =
assigns
|> extend_class("navbar-dropdown")
|> set_phx_attributes()
|> set_bulma_classes()
~H"""
<%= render_slot(@inner_block) %>
"""
end
@doc """
Renders navbar-link.
"""
attr :class, :string, default: nil, doc: "the optional class of the navbar-link"
attr :rest, :global, doc: "the arbitrary HTML attributes to add to the navbar-link"
slot :inner_block, required: true, doc: "the inner block that renders the navbar-link content"
def navbar_link(assigns) do
assigns =
assigns
|> extend_class("navbar-link")
|> set_phx_attributes()
|> set_bulma_classes()
~H"""
<%= render_slot(@inner_block) %>
"""
end
@doc """
Renders navbar-divider.
"""
attr :class, :string, default: nil, doc: "the optional class of the navbar-divider"
attr :rest, :global, doc: "the arbitrary HTML attributes to add to the navbar-divider"
def navbar_divider(assigns) do
assigns =
assigns
|> extend_class("navbar-divider")
|> set_phx_attributes()
|> set_bulma_classes()
~H"""
"""
end
@doc """
Renders navbar-burger.
"""
attr :target, :string, required: true, doc: "the target of the navbar-burger"
attr :size, :string, default: "1.6rem", doc: "the size of the navbar-burger"
attr :class, :string, default: nil, doc: "the optional class of the navbar-burger"
attr :rest, :global, doc: "the arbitrary HTML attributes to add to the navbar-burger"
def navbar_burger(assigns) do
assigns =
assigns
|> extend_class("navbar-burger")
|> set_phx_attributes()
|> set_bulma_classes()
~H"""
"""
end
@doc """
Renders search input.
"""
attr :id, :string, required: true, doc: "the id of the search input"
attr :name, :string, required: true, doc: "the name of the search input"
attr :value, :string, default: nil, doc: "the value of the search input"
attr :placeholder, :string, default: nil, doc: "the placeholder of the search input"
attr :class, :string, default: nil, doc: "the optional class of the search input"
attr :rest, :global, doc: "the arbitrary HTML attributes to add to the search input"
slot :inner_block, required: true, doc: "the inner block that renders the search input content"
slot :icon_left, doc: "the slot for the left icon"
slot :icon_right, doc: "the slot for the right icon"
def search_input(assigns) do
assigns =
assigns
|> extend_class("control has-icons-left has-icons-right")
|> set_phx_attributes()
|> set_bulma_classes()
~H"""
<%= if @icon_left != [] do %>
<%= render_slot(@icon_left) %>
<% end %>
<%= if @icon_right != [] do %>
<%= render_slot(@icon_right) %>
<% end %>
"""
end
# @doc """
# Renders a header with title.
# """
# attr :class, :string, default: nil
# slot :inner_block, required: true
# slot :subtitle
# slot :actions
# def header(assigns) do
# ~H"""
#
# """
# end
@doc """
Generates a generic error message.
"""
slot :inner_block, required: true
def error(assigns) do
~H"""
<%!-- <.icon name="hero-exclamation-circle-mini" class="mt-0.5 h-5 w-5 flex-none" /> --%>
<%= render_slot(@inner_block) %>
"""
end
@doc """
Renders a simple form.
## Examples
<.simple_form for={@form} phx-change="validate" phx-submit="save">
<.input field={@form[:email]} label="Email"/>
<.input field={@form[:username]} label="Username" />
<:actions>
<.button>Save
"""
attr :for, :any, required: true, doc: "the datastructure for the form"
attr :as, :any, default: nil, doc: "the server side parameter to collect all input under"
attr :rest, :global,
include: ~w(autocomplete name rel action enctype method novalidate target multipart),
doc: "the arbitrary HTML attributes to apply to the form tag"
slot :inner_block, required: true
slot :actions, doc: "the slot for form actions, such as a submit button"
def simple_form(assigns) do
~H"""
<.form :let={f} for={@for} as={@as} {@rest}>
<%= render_slot(@inner_block, f) %>
<%= render_slot(action, f) %>
"""
end
@doc """
Renders flash notices.
## Examples
<.flash kind={:info} flash={@flash} />
<.flash kind={:info} phx-mounted={show("#flash")}>Welcome Back!
"""
attr :id, :string, doc: "the optional id of flash container"
attr :kind, :atom, values: [:info, :error], doc: "used for styling and flash lookup"
attr :title, :string, default: nil, doc: "optional title for flash message"
attr :flash, :map, default: %{}, doc: "the map of flash messages to display"
attr :rest, :global, doc: "the arbitrary HTML attributes to add to the flash container"
slot :inner_block, doc: "the optional inner block that renders the flash message"
def flash(assigns) do
assigns = assign_new(assigns, :id, fn -> "flash-#{assigns.kind}" end)
# IO.inspect(assigns.rest)
~H"""
<.hero
:if={msg = render_slot(@inner_block) || Phoenix.Flash.get(@flash, @kind)}
id={@id}
phx-click={JS.push("lv:clear-flash", value: %{key: @kind}) |> hide("##{@id}")}
role="alert"
class={
Enum.join(
[
"is-small",
@kind == :info && "is-info",
@kind == :error && "is-warning"
],
" "
)
}
{@rest}
>
<.media>
<.media_left>
<.media_content>
<.content>
<.title :if={@title} is-4>
<%= @title %>
<.subtitle is-6>
<%= msg %>
<.media_right is-hidden-touch>
<.button aria-label={gettext("close")} phx-click={hide("##{@id}")}>
"""
end
@doc """
Shows the flash group with standard titles and content.
## Examples
<.flash_group flash={@flash} />
"""
attr :flash, :map, required: true, doc: "the map of flash messages"
attr :id, :string, default: "flash-group", doc: "the optional id of flash container"
def flash_group(assigns) do
~H"""
<.flash kind={:info} title={gettext("Info")} flash={@flash} is-info />
<.flash kind={:error} title={gettext("Error")} flash={@flash} is-danger />
<.flash
id="client-error"
kind={:error}
title={gettext("We can't find the internet")}
phx-disconnected={show("#client-error")}
phx-connected={hide("#client-error")}
is-hidden
is-warning
>
<%= gettext("Attempting to reconnect") %>
<.flash
id="server-error"
kind={:error}
title={gettext("Something went wrong!")}
phx-disconnected={show("#server-error")}
phx-connected={hide("#server-error")}
is-hidden
is-warning
>
<%= gettext("Hang in there while we get back on track") %>
"""
end
## JS Commands
def show(js \\ %JS{}, selector) do
JS.remove_class(js, "is-hidden",
to: selector,
time: 100,
transition:
{"transition-all transform ease-out duration-300",
"opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95",
"opacity-100 translate-y-0 sm:scale-100"}
)
end
def hide(js \\ %JS{}, selector) do
JS.add_class(js, "is-hidden",
to: selector,
time: 200,
transition:
{"transition-all transform ease-in duration-200",
"opacity-100 translate-y-0 sm:scale-100",
"opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"}
)
end
## Private functions
defp set_bulma_classes(assigns, opts \\ []) do
opts = Keyword.put_new(opts, :from, :rest)
assigns =
assigns
|> set_prefixed_attributes(["is-"], Keyword.put_new(opts, :into, :bulma_is))
|> set_prefixed_attributes(["has-"], Keyword.put_new(opts, :into, :bulma_has))
|> set_prefixed_attributes(["flex-"], Keyword.put_new(opts, :into, :bulma_flex))
|> set_prefixed_attributes(["justify-"], Keyword.put_new(opts, :into, :bulma_justify))
|> set_prefixed_attributes(["align-"], Keyword.put_new(opts, :into, :bulma_align))
bulma_classes =
[]
|> Enum.concat(for({is, _} <- assigns.heex_bulma_is, do: to_string(is)))
|> Enum.concat(for({has, _} <- assigns.heex_bulma_has, do: to_string(has)))
|> Enum.concat(for({flex, _} <- assigns.heex_bulma_flex, do: to_string(flex)))
|> Enum.concat(for({justify, _} <- assigns.heex_bulma_justify, do: to_string(justify)))
|> Enum.concat(for({align, _} <- assigns.heex_bulma_align, do: to_string(align)))
if assigns.heex_class do
class =
assigns.heex_class[:class]
|> String.split(" ")
|> Enum.concat(bulma_classes)
# todo: where is this coming from?
|> Enum.reject(&(&1 == "false"))
|> Enum.join(" ")
assigns |> Map.put(:heex_class, class: class)
else
assigns |> Map.put(:heex_class, class: bulma_classes |> Enum.join(" "))
end
end
slot :inner_block, required: true
defp render_bulma_heading(assigns) do
level =
assigns
|> Map.filter(fn assign ->
case assign do
{:"is-1", _} -> true
{:"is-2", _} -> true
{:"is-3", _} -> true
{:"is-4", _} -> true
{:"is-5", _} -> true
{:"is-6", _} -> true
_ -> false
end
end)
|> Map.keys()
|> List.first()
|> Atom.to_string()
|> String.split("-")
|> List.last()
level =
case level do
# todo: where is this coming from?
"nil" -> 3
_ -> String.to_integer(level)
end
case level do
1 ->
~H"<%= render_slot(@inner_block) %> "
2 ->
~H"<%= render_slot(@inner_block) %> "
3 ->
~H"<%= render_slot(@inner_block) %> "
4 ->
~H"<%= render_slot(@inner_block) %> "
5 ->
~H"<%= render_slot(@inner_block) %> "
6 ->
~H"<%= render_slot(@inner_block) %> "
end
end
end