howdy/ui/toast

Toasts: short notifications in a corner of the screen that go away on their own.

toast.region(words.english(), [], [
  toast.toast(toast.Info, [], [
    toast.title([text("Saved")]),
    toast.description([text("Your changes are live.")]),
    toast.close([attribute.aria_label("Dismiss")]),
  ]),
])

Render the region once, on every page or in the live view, and put toasts in it. It is a live region, so a screen reader reads a toast when it appears. A toast fades out after five seconds, or the time set with duration; hovering over it or focusing inside it pauses the countdown. persistent keeps it until it is closed.

The countdown is a CSS animation, so it works the same for a flash message rendered with a page and a toast a live view adds to its model. When a toast fades, its close button is pressed for it, so a live view hears it the same way as a click and can drop the toast from its model; queue and view do this for you.

words name the region and, in view, the close buttons; see howdy/ui/words.

Types

pub type Item {
  Item(
    id: Int,
    variant: Variant,
    title: String,
    description: String,
    shown_at: Int,
    lasts: option.Option(Int),
    round: Int,
    held_since: option.Option(Int),
    held_for: Int,
  )
}

Constructors

  • Item(
      id: Int,
      variant: Variant,
      title: String,
      description: String,
      shown_at: Int,
      lasts: option.Option(Int),
      round: Int,
      held_since: option.Option(Int),
      held_for: Int,
    )

    Arguments

    round

    How many times it has been updated; each update restarts its countdown.

    held_since

    When the reader started holding it open, if they still are.

    held_for

    How long it was held open before that, since it was last shown.

The toasts a live view is showing, by id, for keeping in its model. Push a toast when something happens; update it when a loading toast finishes, so it turns into a success or an error in place; and expire the queue now and then, so toasts that have faded are dropped.

let #(toasts, id) = toast.push(model.toasts, Loading, "Saving…", "", now)
// later, when the save is done:
let toasts = toast.update(toasts, id, Success, "Saved", "", now)

The browser decides when a toast has gone, since it pauses the countdown while the toast is hovered or focused: when one fades, its close button is pressed for it, so on_close in view hears it and the toast can be dismissed. The browser also says when a toast is held open and let go, through on_hold; pass that to hold, so the queue’s clock pauses with the browser’s.

expire is a fallback for a fade the browser never reported, such as a message lost in transit. It drops a toast a minute after its countdown should have ended, counting only the time it was not held, so a toast someone is reading is never taken away however long they read it.

Times are milliseconds from any clock that only moves forward, such as erlang:monotonic_time(millisecond).

pub opaque type Queue
pub type Variant {
  Info
  Success
  Danger
  Loading
}

Constructors

  • Info
  • Success

    Something finished well, with a tick.

  • Danger
  • Loading

    Something is under way, with a spinner. It stays until it is updated or closed.

Values

pub fn classes() -> List(css.Class)
pub fn close(
  attributes: List(attribute.Attribute(msg)),
) -> element.Element(msg)

A button in the top corner that hides the toast. Give it an aria-label; in a live view, add a click handler to drop the toast from the model too.

pub fn close_class() -> css.Class
pub fn description(
  children: List(element.Element(msg)),
) -> element.Element(msg)
pub fn description_class() -> css.Class
pub fn dismiss(queue: Queue, id: Int) -> Queue

Take a toast away, such as when its close button is clicked.

pub fn duration(milliseconds: Int) -> attribute.Attribute(msg)

How long the toast stays before fading, in milliseconds.

pub fn expire(queue: Queue, now: Int) -> Queue

Drop toasts whose countdown should have ended more than a minute before now but were never closed by the browser. Time held open does not count, and a toast being held now is always kept.

pub fn flashed(
  words: words.Words,
  messages: List(flash.Message),
) -> List(element.Element(msg))

A toast for each flash message, to put in a region. They fade like any other toast, and each has a close button, labelled with the words’ dismiss.

use messages <- flash.take(ctx, flash)
let w = words.english()
page.new("Settings")
|> page.body([toast.region(w, [], toast.flashed(w, messages)), ..])
pub fn hold(queue: Queue, id: Int, held: Bool, now: Int) -> Queue

Note that the reader has started (True) or stopped (False) holding a toast open, as view’s on_hold reports.

pub fn icon_class() -> css.Class
pub fn items(queue: Queue) -> List(Item)
pub fn persistent() -> attribute.Attribute(msg)

Keep the toast until it is closed.

pub fn push(
  queue: Queue,
  variant: Variant,
  title: String,
  description: String,
  now: Int,
) -> #(Queue, Int)

Show a toast; returns its id. A loading toast stays until updated; the others fade after five seconds.

pub fn push_flashed(
  queue: Queue,
  messages: List(flash.Message),
  now: Int,
) -> Queue

Push each flash message onto a live view’s queue, oldest first.

pub fn queue() -> Queue
pub fn region(
  words: words.Words,
  attributes: List(attribute.Attribute(msg)),
  toasts: List(element.Element(msg)),
) -> element.Element(msg)

The corner toasts appear in. Keep it in the page even when it is empty, so a screen reader is already listening when a toast arrives.

pub fn region_class() -> css.Class
pub fn rules() -> List(#(String, String))

Every class this module uses, for howdy/ui/export. The CSS this module registers with style.rule, for howdy/ui/export.

pub fn title(
  children: List(element.Element(msg)),
) -> element.Element(msg)
pub fn title_class() -> css.Class
pub fn toast(
  variant: Variant,
  attributes: List(attribute.Attribute(msg)),
  children: List(element.Element(msg)),
) -> element.Element(msg)
pub fn toast_class(variant: Variant) -> css.Class
pub fn update(
  queue: Queue,
  id: Int,
  variant: Variant,
  title: String,
  description: String,
  now: Int,
) -> Queue

Change a toast in place, such as a loading one that has finished. Its countdown starts again.

pub fn view(
  words: words.Words,
  queue: Queue,
  on_close on_close: fn(Int) -> List(attribute.Attribute(msg)),
  on_hold on_hold: fn(Int, Bool) -> msg,
) -> element.Element(msg)

The queue’s toasts in a region. on_close gives the attributes of each toast’s close button, such as a click handler that dismisses it; its aria-label is the words’ dismiss. It is pressed for you when a toast fades. on_hold hears a toast’s id when the reader starts (True) or stops (False) holding it open by pointing at it or focusing inside it; pass it on to hold. howdy/ui/behaviour presses a faded toast’s close button and reports holds, so a live view keeping toasts in its model hears both.

✨ Search Document