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
@spec clear_toasts() :: Phoenix.LiveView.JS.t()
Removes every toast: from the server with a socket, in the browser as a %JS{}.
@spec clear_toasts(Phoenix.LiveView.Socket.t() | Phoenix.LiveView.JS.t()) :: Phoenix.LiveView.Socket.t() | Phoenix.LiveView.JS.t()
@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")}).
@spec dismiss_toast(Phoenix.LiveView.Socket.t() | Phoenix.LiveView.JS.t(), String.t()) :: Phoenix.LiveView.Socket.t() | Phoenix.LiveView.JS.t()
@spec put_toast(Plug.Conn.t() | Phoenix.LiveView.Socket.t(), String.t(), keyword()) :: Plug.Conn.t() | Phoenix.LiveView.Socket.t()
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.
@spec send_toast(Phoenix.LiveView.Socket.t(), String.t() | keyword()) :: Phoenix.LiveView.Socket.t()
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:.
@spec send_toast(Phoenix.LiveView.Socket.t(), String.t() | nil, keyword()) :: Phoenix.LiveView.Socket.t()
@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.
@spec show_toast( Phoenix.LiveView.JS.t() | String.t() | nil, String.t() | keyword() | nil ) :: Phoenix.LiveView.JS.t()
@spec show_toast(Phoenix.LiveView.JS.t(), String.t() | nil, keyword()) :: Phoenix.LiveView.JS.t()
@spec toast_colors() :: [String.t()]
The colors a toast takes.
@spec toast_kinds() :: [String.t()]
The kinds a toast takes.
@spec toast_positions() :: [String.t()]
The positions a toast takes.
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, whereput_toast/3leaves 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 to5000.stack(:boolean) - Pile the toasts of a corner behind the newest and open the pile under the pointer. Defaults totrue.persist_errors(:boolean) - Toasts ofkind: :errorwithout adurationstay until closed. Defaults tofalse.close_label(:string) - Defaults to"Close".loading_label(:string) - Defaults to"Loading".- Global attributes are accepted.
@spec update_toast(Phoenix.LiveView.Socket.t(), String.t(), keyword()) :: Phoenix.LiveView.Socket.t()
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.