defmodule Liliform.Components do use Phoenix.Component, global_prefixes: ["is-", "has-", "flex-", "justify-", "align-"] alias Phoenix.LiveView.JS import FreediveWeb.Gettext import PhxComponentHelpers require Logger @doc """ Translates an error message using gettext. """ def translate_error({msg, opts}) do # When using gettext, we typically pass the strings we want # to translate as a static argument: # # # Translate the number of files with plural rules # dngettext("errors", "1 file", "%{count} files", count) # # However the error messages in our forms and APIs are generated # dynamically, so we need to translate them by calling Gettext # with our gettext backend as first argument. Translations are # available in the errors.po file (as we use the "errors" domain). if count = opts[:count] do Gettext.dngettext(FreediveWeb.Gettext, "errors", msg, msg, count, opts) else Gettext.dgettext(FreediveWeb.Gettext, "errors", msg, opts) end end @doc """ Translates the errors for a field from a keyword list of errors. """ def translate_errors(errors, field) when is_list(errors) do for {^field, {msg, opts}} <- errors, do: translate_error({msg, opts}) end @doc """ Renders a label. """ attr :for, :string, default: nil, doc: "the for attribute of the label" attr :class, :string, default: nil, doc: "the optional class of the label" attr :rest, :global, doc: "the arbitrary HTML attributes to add to the label" slot :inner_block, required: true, doc: "the inner block that renders the label content" def label(assigns) do assigns = assigns |> extend_class("label") |> set_attributes([:for]) |> set_phx_attributes() |> set_bulma_classes() ~H""" """ end @doc """ Renders an input with label and error messages. A `Phoenix.HTML.FormField` may be passed as argument, which is used to retrieve the input name, id, and values. Otherwise all attributes may be passed explicitly. ## Types This function accepts all HTML input types, considering that: * You may also set `type="select"` to render a ` <%!-- 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 %> <.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""" """ 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""" """ 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""" """ 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""" """ 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""" #
#
#

# <%= render_slot(@inner_block) %> #

#

# <%= render_slot(@subtitle) %> #

#
#
<%= render_slot(@actions) %>
#
# """ # 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