Crispy toasts served just as you like in the Elm architecture.
elm-toast is a highly customizable library that handles a toast stack for you.
We serve three kind of toasts:
import Toast
first = Toast.persistent "Hello, I'm a persistent toast"
second = Toast.expireIn 5000 "I'm going to expire in five seconds"
third = Toast.expireOnBlur 5000 "I'll expire only if not focused"Persistent toasts will stay there until you take explicit action, they won't fade out automatically.
The second kind of toast, instead, will be removed after a fixed amount of time: in our example five seconds.
Lastly we have toasts that will only expire if the user is not interacting with them, if they receive focus or have mouse over, they have to wait the end of user's interaction and then five more seconds to fade out.
Toasts have to be served on a tray, and get an empty tray is as simple as:
import Toast
emptyTray : Toast.Tray String
emptyTray =
Toast.trayYou may have noticed that Toast.Tray is parametric and we are using the String type there. This is just the type of our own toast, it can be anything from a plain string, or a record to a whole new type.
type Color
= Red
| Blue
| Green
type alias Toast =
{ message : String
, color : Color
}
emptyTray : Toast.Tray Toast
emptyTray =
Toast.trayNow that we have both toast and tray we are almost done, we just need to plug some wires:
We'll declare our Toast type, then application's Model and Msg.
import Toast
type Color
= Red
| Green
| Blue
type alias Toast =
{ message : String
, color : Color
}
{- Let's store our tray here -}
type alias Model =
{ tray : Toast.Tray Toast }
{- We need a variant that contains Toast.Msg -}
type Msg
= ToastMsg Toast.Msg
| AddToast ToastWe'll create a model with an empty tray and schedule toast insertion using Task.perform and Process.sleep.
import Process
import Task
delay : Int -> msg -> Cmd msg
delay ms msg =
Task.perform (always msg) (Process.sleep <| toFloat ms)
{- Create a model with an empty tray and schedule toast insertions -}
init : () -> ( Model, Cmd Msg )
init () =
( { tray = Toast.tray }
, Cmd.batch
[ delay 0 (AddToast { message = "hello, world", color = Green })
, delay 500 (AddToast { message = "I'm red", color = Red })
, delay 1000 (AddToast { message = "...and I'm blue", color = Blue })
]
)We have two messages right now: for AddToast we'll create a persistent toast and add it to app tray, for ToastMsg we have to forward its content to Toast.update and update our app accordingly.
update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
case msg of
AddToast content ->
let
( tray, tmesg ) =
Toast.add model.tray (Toast.persistent content)
in
( { model | tray = tray }, Cmd.map ToastMsg tmesg )
ToastMsg tmsg ->
let
( tray, newTmesg ) =
Toast.update tmsg model.tray
in
( { model | tray = tray }, Cmd.map ToastMsg newTmesg )The main actor here is call to Toast.render, it receives a viewToast function, our toast tray and a Toast.Config Msg.
First thing you'll notice in following snippet is that toast view is completely delegated to the user, elm-toast makes almost no assumption on how it should be done.
import Html exposing (Html)
import Html.Attributes exposing (style)
view : Model -> { title : String, body : List (Html Msg) }
view model =
{ title = "Yay! elm-toast"
, body = [ Toast.render viewToast model.tray (Toast.config ToastMsg) ]
}
viewToast : List (Html.Attribute Msg) -> Toast.Info Toast -> Html Msg
viewToast attributes toast =
Html.div
(toastStyles toast ++ attributes)
[ Html.text toast.content.message ]
toastStyles : Toast.Info Toast -> List (Html.Attribute msg)
toastStyles toast =
let
background : Html.Attribute msg
background =
case toast.content.color of
Red ->
style "background" "#f77"
Green ->
style "background" "#7f7"
Blue ->
style "background" "#77f"
in
[ background
, style "width" "110px"
, style "font-size" "18px"
, style "padding" "10px"
, style "margin" "10px"
]Export a Browser.document at this point is trivial.
module MyApp exposing (main)
import Browser
main : Program () Model Msg
main =
Browser.document
{ init = init
, view = view
, update = update
, subscriptions = \_ -> Sub.none
}It's worth noting that we could use Toast.tuple to refactor our update function:
update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
case msg of
AddToast content ->
Toast.persistent content
|> Toast.add model.tray
|> Toast.tuple ToastMsg model
ToastMsg tmsg ->
Toast.update tmsg model.tray
|> Toast.tuple ToastMsg modelYou can also find working toy applications on /examples, they can be ran simply by
git clone [email protected]:emilianobovetti/elm-toast.git
cd elm-toast/examples
yarn
# run trivial example
yarn start src/Basic.elm
# run full-fledged thing
yarn start src/Complete.elmOf course, you can use withExitTransition passing the number of milliseconds between the moment the toast is exiting and the moment the toast is removed.
Toast.persistent content
|> Toast.withExitTransition 1000
|> Toast.add model.tray
|> Toast.tuple ToastMsg modelThere are three functions to achieve that: addUnique, addUniqueBy and addUniqueWith.
One of these two functions: remove or exit.
E.g.:
type alias Toast = { message : String }
viewToast : List (Html.Attribute Msg) -> Toast.Info Toast -> Html Msg
viewToast attributes toast =
Html.div
attributes
[ Html.text toast.content.message
, Html.div
[ onClick (ToastMsg <| Toast.exit toast.id) ]
[ Html.text "close" ]
]The difference between those two is pretty simple: if you remove a toast it'll be deleted right away, if exit is used the toast will go through its fade-out cycle, so exitTransition milliseconds are waited before the toast is removed.