LineaUI.Components.Toast (linea_ui v0.3.0)

Copy Markdown View Source

LineOne's notifications: a small colored note at a corner or an edge of the screen that leaves by itself after a few seconds.

Mount the toaster once, in the layout every LiveView renders (it reads the flash):

<.toaster flash={@flash} />

Then, from the server:

conn |> put_toast("Saved", color: "success") |> redirect(to: ~p"/orders")
socket |> put_toast("Saved", color: "success") |> push_navigate(to: ~p"/orders")
socket |> send_toast("Copied", color: "info", position: "center-top")

put_toast/3 goes through the flash, so it survives a redirect; send_toast/3 pushes the toast to the page at once. From the browser, without a round trip:

<.button phx-click={show_toast("This is a simple notification", color: "primary")}>Primary</.button>

Options: color (neutral primary secondary info success warning error light dark), position (left-top left-bottom center-top center-bottom right-top right-bottom), duration in milliseconds (0 or :infinity keeps it until closed), close (a close button) and template, the id of a <template> on the page whose first element is shown instead of the message (an element with data-toast-dismiss inside it closes the toast; this is the way to put buttons of your own in a toast).

Kinds, descriptions and actions

kind (neutral info success warning error loading) draws the toast with an icon and a title, in the color of the kind unless color says otherwise; description adds a second line and actions a row of buttons:

send_toast(socket, "Invoice sent",
  kind: :success,
  description: "NF-e 1234 was authorized.",
  actions: [
    [label: "Open", event: "open-invoice", value: %{id: 1234}],
    [label: "Undo", js: JS.push("undo")],
    [label: "See all", href: "/invoices"]
  ]
)

An action takes label and one of event (pushed to the LiveView with value merged with %{"toast_id" => id}, to target when given), js (a %JS{} run in the browser) or href (a link). It closes the toast unless dismiss: false; [label: "Close", dismiss: true] alone only closes it.

Loading, then the answer

A toast with a fixed id is updated in place when another one comes with the same id:

socket = send_toast(socket, "Sending the invoice…", id: "nfe-42", kind: :loading)
# later, in handle_info/2
send_toast(socket, "Invoice authorized", id: "nfe-42", kind: :success)

A loading toast never leaves by itself and has no close button; once it becomes another kind it takes its duration again. update_toast/3 changes only the options it is given, keeping the rest (update_toast(socket, "nfe-42", description: "Waiting for SEFAZ…")); dismiss_toast/2 and clear_toasts/1 remove toasts. send_toast/2 also takes the message as title: (send_toast(socket, id: "x", kind: :loading, title: "Working…")). Errors stay until closed when the toaster has persist_errors.

The toasts of a corner are stacked: the newest is shown in full and the older ones sit behind it, smaller, as slivers (three at most). With the pointer over the stack it opens, every toast in its own size, and their timers wait; when the pointer leaves they go on with the time they had left. Set stack={false} on the toaster to keep them always open, one above the other.

The toasts sit on the CSS layer --linea-z-toast (see the README).

Summary

Functions

Removes every toast: from the server with a socket, in the browser as a %JS{}.

Removes the toast id: from the server with a socket, in the browser as a %JS{} (phx-click={dismiss_toast("nfe-42")}).

Leaves a toast in the flash of a Plug.Conn (after fetch_flash) or a LiveView socket. The toaster shows it on the next page, so it survives redirect/2, push_navigate/2 and push_patch/2. Several calls pile up.

Shows a toast now, from handle_event/3 or handle_info/2: it is pushed to the page as the linea:toast event, which the toaster's hook handles. A toast with the id of one on the screen replaces it in place. The message can also go in the options, as title:.

A %JS{} that shows a toast in the browser, without a round trip. Takes the same options as put_toast/3.

The colors a toast takes.

The kinds a toast takes.

The positions a toast takes.

The place the toasts are drawn. Mount it once per page, inside the LiveView (the layout that receives @flash).

Changes the toast id in place, from the server: only the options given change (title, description, kind, color, duration, close, actions), the rest stay. Nothing happens when no toast has that id.

Functions

clear_toasts()

@spec clear_toasts() :: Phoenix.LiveView.JS.t()

Removes every toast: from the server with a socket, in the browser as a %JS{}.

clear_toasts(socket)

dismiss_toast(id)

@spec dismiss_toast(String.t()) :: Phoenix.LiveView.JS.t()

Removes the toast id: from the server with a socket, in the browser as a %JS{} (phx-click={dismiss_toast("nfe-42")}).

dismiss_toast(socket, id)

put_toast(conn_or_socket, message, opts \\ [])

Leaves a toast in the flash of a Plug.Conn (after fetch_flash) or a LiveView socket. The toaster shows it on the next page, so it survives redirect/2, push_navigate/2 and push_patch/2. Several calls pile up.

send_toast(socket, message_or_opts)

Shows a toast now, from handle_event/3 or handle_info/2: it is pushed to the page as the linea:toast event, which the toaster's hook handles. A toast with the id of one on the screen replaces it in place. The message can also go in the options, as title:.

send_toast(socket, message, opts)

show_toast(message)

@spec show_toast(String.t() | nil) :: Phoenix.LiveView.JS.t()

A %JS{} that shows a toast in the browser, without a round trip. Takes the same options as put_toast/3.

show_toast(js, message)

@spec show_toast(
  Phoenix.LiveView.JS.t() | String.t() | nil,
  String.t() | keyword() | nil
) ::
  Phoenix.LiveView.JS.t()

show_toast(js, message, opts)

@spec show_toast(Phoenix.LiveView.JS.t(), String.t() | nil, keyword()) ::
  Phoenix.LiveView.JS.t()

toast_colors()

@spec toast_colors() :: [String.t()]

The colors a toast takes.

toast_kinds()

@spec toast_kinds() :: [String.t()]

The kinds a toast takes.

toast_positions()

@spec toast_positions() :: [String.t()]

The positions a toast takes.

toaster(assigns)

The place the toasts are drawn. Mount it once per page, inside the LiveView (the layout that receives @flash).

Events: an action with event pushes it with its value and %{"toast_id" => id}.

Attributes

  • id (:string) - Defaults to "linea-toaster".
  • flash (:map) - The @flash, where put_toast/3 leaves its toasts. Defaults to %{}.
  • position (:string) - The default position. Defaults to "right-bottom". Must be one of "left-top", "left-bottom", "center-top", "center-bottom", "right-top", or "right-bottom".
  • duration (:integer) - The default duration, in milliseconds. Defaults to 5000.
  • stack (:boolean) - Pile the toasts of a corner behind the newest and open the pile under the pointer. Defaults to true.
  • persist_errors (:boolean) - Toasts of kind: :error without a duration stay until closed. Defaults to false.
  • close_label (:string) - Defaults to "Close".
  • loading_label (:string) - Defaults to "Loading".
  • Global attributes are accepted.

update_toast(socket, id, opts)

Changes the toast id in place, from the server: only the options given change (title, description, kind, color, duration, close, actions), the rest stay. Nothing happens when no toast has that id.