defmodule TrackerWeb.CoreComponents do @moduledoc """ Provides core UI components. """ use Phoenix.Component use Gettext, backend: TrackerWeb.Gettext alias Phoenix.LiveView.JS alias TrackerWeb.Lens @doc """ Renders a modal. ## Examples <.modal id="confirm-modal"> This is a modal. JS commands may be passed to the `:on_cancel` to configure the closing/cancel event, for example: <.modal id="confirm" on_cancel={JS.navigate(~p"/posts")}> This is another modal. """ attr :id, :string, required: true attr :show, :boolean, default: false attr :on_cancel, JS, default: %JS{} slot :inner_block, required: true def modal(assigns) do ~H"""
{render_slot(@inner_block)}
""" 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 :flash, :map, default: %{}, doc: "the map of flash messages to display" attr :title, :string, default: nil attr :kind, :atom, values: [:info, :error], doc: "used for styling and flash lookup" attr :auto_dismiss, :boolean, default: true, doc: "whether the flash should auto-dismiss" 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) ~H"""
hide("##{@id}")} role="alert" class={[ "flash", @kind == :info && "flash-info", @kind == :error && "flash-error", @auto_dismiss && "flash-auto-dismiss" ]} {@rest} >
{@title}

{msg}

""" 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("Success!")} flash={@flash} /> <.flash kind={:error} title={gettext("Error!")} flash={@flash} /> <.flash id="client-error" kind={:error} title={gettext("We can't find the internet")} auto_dismiss={false} phx-disconnected={show(".phx-client-error #client-error")} phx-connected={hide("#client-error")} hidden > {gettext("Attempting to reconnect")} ↻ <.flash id="server-error" kind={:error} title={gettext("Something went wrong!")} auto_dismiss={false} phx-disconnected={show(".phx-server-error #server-error")} phx-connected={hide("#server-error")} hidden > {gettext("Hang in there while we get back on track")} ↻
""" 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 data structure 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 a button. ## Examples <.button>Send! <.button phx-click="go" class="ml-2">Send! """ attr :type, :string, default: nil attr :class, :string, default: nil attr :rest, :global, include: ~w(disabled form name value) slot :inner_block, required: true def button(assigns) do ~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 ` {@label} <.error :for={msg <- @errors}>{msg} """ end def input(%{type: "select"} = assigns) do ~H""" <.error :for={msg <- @errors}>{msg} """ end def input(%{type: "textarea"} = assigns) do ~H""" <.error :for={msg <- @errors}>{msg} """ end def input(assigns) do ~H""" <.error :for={msg <- @errors}>{msg} """ end @doc """ Generates a generic error message. """ slot :inner_block, required: true def error(assigns) do ~H""" {render_slot(@inner_block)} """ 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 """ Renders an inline status badge. ## Examples <.badge variant={:danger}>Build problem <.badge variant={:ok}>Healthy """ attr :variant, :atom, default: :neutral, values: [:danger, :warn, :ok, :neutral] attr :rest, :global slot :inner_block, required: true def badge(assigns) do ~H""" {render_slot(@inner_block)} """ end @doc """ `Phoenix.Component.link/1`, with the channel lens on every internal target. This shadows the imported `link/1` (see `TrackerWeb`) so a link cannot forget the lens and silently hand the next page a different channel than the one the visitor is looking at. External targets and links that state their own channel are left alone; see `TrackerWeb.Lens.decorate/1`. """ attr :navigate, :string, default: nil attr :patch, :string, default: nil attr :href, :any, default: nil attr :replace, :boolean, default: false attr :method, :string, default: "get" attr :csrf_token, :any, default: true attr :rest, :global, include: ~w(download hreflang referrerpolicy rel target type) slot :inner_block, required: true def link(assigns) do assigns |> assign(:navigate, Lens.decorate(assigns.navigate)) |> assign(:patch, Lens.decorate(assigns.patch)) |> assign(:href, Lens.decorate(assigns.href)) |> Phoenix.Component.link() end @doc """ Renders a data list. ## Examples <.list> <:item title="Title">{@post.title} <:item title="Views">{@post.views} """ slot :item, required: true do attr :title, :string, required: true end def list(assigns) do ~H"""
{item.title}
{render_slot(item)}
""" end ## JS Commands def show(js \\ %JS{}, selector) do JS.show(js, to: selector, time: 300) end def hide(js \\ %JS{}, selector) do JS.hide(js, to: selector, time: 200) end def show_modal(js \\ %JS{}, id) when is_binary(id) do js |> JS.show(to: "##{id}") |> JS.set_attribute({"open", ""}, to: "##{id}") |> JS.focus_first(to: "##{id}-content") end def hide_modal(js \\ %JS{}, id) do js |> JS.remove_attribute("open", to: "##{id}") |> JS.hide(to: "##{id}") |> JS.pop_focus() end @doc """ Translates an error message using gettext. """ def translate_error({msg, opts}) do if count = opts[:count] do Gettext.dngettext(TrackerWeb.Gettext, "errors", msg, msg, count, opts) else Gettext.dgettext(TrackerWeb.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 end