Skip to content
Dimitrios

Auto-hide Phoenix flash messages

Enhancing the core Flash component with a simple JavaScript hook

The main idea is to enhance the core Flash component that we get by running the Phoenix generator, with a simple JavaScript hook.

Untitled-1
 
```js
let Hooks = {};
 
Hooks.AutoHideFlash = {
  mounted() {
    let duration = 4_000;
 
    setTimeout(() => {
      this.el.style.opacity = 0;
      this.el.style.transition = "opacity 0.2s ease-out";
      setTimeout(() => {
        this.el.remove();
      }, 500);
    }, duration);
  },
};
 
export default Hooks;
flash_components.ex
defmodule HorionosWeb.FlashComponents do
  @moduledoc """
  Flash messages components
  """
  use Phoenix.Component
 
  alias Phoenix.LiveView.JS
  import HorionosWeb.JSHelpers
  import HorionosWeb.CoreComponents
  import HorionosWeb.Gettext
 
  @doc """
  Renders flash notices.
 
  ## Examples
 
      <.flash kind={:info} flash={@flash} />
      <.flash kind={:info} phx-mounted={show("#flash")}>Welcome Back!</.flash>
  """
  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 :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"""
    <div
      :if={msg = render_slot(@inner_block) || Phoenix.Flash.get(@flash, @kind)}
      id={@id}
      phx-hook="AutoHideFlash"
      phx-click={JS.push("lv:clear-flash", value: %{key: @kind}) |> hide("##{@id}")}
      role="alert"
      class={[
        "fixed top-4 right-4 z-50 mr-2 w-80 rounded-xl p-4 sm:w-80",
        @kind == :info && "border-t-2 border-emerald-500 bg-white shadow-md",
        @kind == :error && "border-t-2 border-red-500 bg-white shadow-md"
      ]}
      {@rest}
    >
      <div class="flex items-start space-x-4">
        <.icon
          :if={@kind == :info}
          name="hero-information-circle"
          class="size-6 flex-shrink-0 text-emerald-500"
        />
        <.icon
          :if={@kind == :error}
          name="hero-exclamation-circle"
          class="size-6 flex-shrink-0 text-red-500"
        />
        <div class="space-y-1">
          <div :if={@title} class="text-md font-semibold">
            <%= @title %>
          </div>
          <div class="text-sm leading-5"><%= msg %></div>
        </div>
      </div>
      <button type="button" class="group absolute top-1 right-1 p-2" aria-label={gettext("close")}>
        <.icon name="hero-x-mark-solid" class="h-5 w-5 opacity-40 group-hover:opacity-70" />
      </button>
    </div>
    """
  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"""
    <div id={@id}>
      <.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")}
        phx-disconnected={show(".phx-client-error #client-error")}
        phx-connected={hide("#client-error")}
        hidden
      >
        <%= gettext("Attempting to reconnect") %>
        <.icon name="hero-arrow-path" class="ml-1 h-3 w-3 animate-spin" />
      </.flash>
 
      <.flash
        id="server-error"
        kind={:error}
        title={gettext("Something went wrong!")}
        phx-disconnected={show(".phx-server-error #server-error")}
        phx-connected={hide("#server-error")}
        hidden
      >
        <%= gettext("Hang in there while we get back on track") %>
        <.icon name="hero-arrow-path" class="ml-1 h-3 w-3 animate-spin" />
      </.flash>
    </div>
    """
  end
end