All you need to create, append and render toast stacks in the Elm architecture.
persistent : content -> Toast content
Create a new toast that won't be automatically removed, it will stay visible until you explicitly remove it.
Toast.persistent
{ message = "hello, world"
, color = "#7f7"
}
expireIn : Basics.Int -> content -> Toast content
Create a new toast with a fixed expiry. Toast's lifespan is expressed in milliseconds.
Toast.expireIn 5000 "I'll disappear in five seconds"
expireOnBlur : Basics.Int -> content -> Toast content
This kind of toast has an interaction-based expiration. It'll be removed automatically in given time if user doesn't interact with the toast, but it'll stay visible if receives focus or mouse over.
When the interaction has ended and the toast lost both focus and mouse over the given amount of milliseconds is awaited before it's removed.
Toast.expireOnBlur 5000 "I won't go away while I'm focused!"
withExitTransition : Basics.Int -> Toast content -> Toast content
Add a delay between toast exit phase and actual removal.
Toast.persistent { message = "hello, world", color = "#7f7" }
|> Toast.withExitTransition 1000
Private (Toast_ content)
Toast.Toast
is something you'll need if you have to reference
the output type of persistent, expireIn, expireOnBlur,
this is one of those things you'll know when you need it, so don't worry about this.
Private (Tray_ content)
Toast.Tray
represents the stack where toasts are stored.
You probably want to use this opaque type in your model:
type alias MyToast =
{ message : String
, color : String
}
type alias Model =
{ tray : Toast.Tray MyToast
-- model fields...
}
tray : Tray content
An empty tray, it's a thing you can put in an init
.
init : anything -> ( Model, Cmd msg )
init _ =
( { tray = Toast.tray }, Cmd.none )
add : Tray content -> Toast content -> ( Tray content, Platform.Cmd.Cmd Msg )
Add a toast to a tray, produces an updated tray and a Cmd Toast.Msg
.
updateTuple :
( Toast.Tray { message : String, color : String }
, Cmd Toast.Msg
)
updateTuple =
Toast.persistent { message = "hello, world", color = "#7f7" }
|> Toast.withExitTransition 1000
|> Toast.add currentTray
addUnique : Tray content -> Toast content -> ( Tray content, Platform.Cmd.Cmd Msg )
Add a toast only if its content is not already in the tray.
Toast contents are compared with structural equality.
-- if currentTray already contains a toast with the same
-- message and color it won't be added again
Toast.persistent { message = "hello, world", color = "#7f7" }
|> Toast.addUnique currentTray
addUniqueBy : (content -> a) -> Tray content -> Toast content -> ( Tray content, Platform.Cmd.Cmd Msg )
This is what you need if, for example, you want to have toast
with unique content.message
.
-- no two "hello, world" in the same tray
Toast.persistent { message = "hello, world", color = "#7f7" }
|> Toast.addUniqueBy .message currentTray
addUniqueWith : (content -> content -> Basics.Bool) -> Tray content -> Toast content -> ( Tray content, Platform.Cmd.Cmd Msg )
Most powerful addUnique
version: it takes a function that
compares two toast contents.
type alias MyToast =
{ message : String
, color : String
}
sameMessageLength : MyToast -> MyToast -> Bool
sameMessageLength t1 t2 =
String.length t1.message == String.length t2.message
-- we can't have two toast with same message length
-- for some reason...
Toast.persistent { message = "hello, world", color = "#7f7" }
|> Toast.addUniqueWith sameMessageLength currentTray
Internal message, you probably want to do something like
type Msg
= ToastMsg Toast.Msg
-- other stuff...
| AddToast MyToastContent
in your app Msg
.
update : Msg -> Tray content -> ( Tray content, Platform.Cmd.Cmd Msg )
Nothing fancy here: given a Toast.Msg and a Toast.Tray
updates tray's state and produces a Cmd
.
tuple : (Msg -> msg) -> { model | tray : Tray content } -> ( Tray content, Platform.Cmd.Cmd Msg ) -> ( { model | tray : Tray content }, Platform.Cmd.Cmd msg )
Helps in conversion between
( Toast.Tray, Cmd Toast.Msg )
and ( Model, Cmd Msg )
.
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 model
Private (Config_ msg)
Toast.Config
is something that holds information about
rendering stuff.
config : (Msg -> msg) -> Config msg
To create an empty Toast.Config you have to provide
a Toast.Msg -> msg
function, this probably should look like
type Msg
= ToastMsg Toast.Msg
-- other stuff...
| AddToast MyToastContent
toastConfig : Toast.Config Msg
toastConfig =
Toast.config ToastMsg
render : (List (Html.Attribute msg) -> Info content -> Html msg) -> Tray content -> Config msg -> Html msg
This function is where our money are: all our data shrunk down
to a beautiful Html msg
, ready to be served.
The first thing needed to make this magic is a viewToast
function
that I'll try to explain how it works:
elm-toast
which you need to remember to attach to some nodecontent
and other stuffHtml msg
that represent your toast,
or something that contains your toast like a wrapper
or whateverSecondly you have to provide a Toast.Tray and last but not least your Toast.Config.
viewToast : List (Html.Attribute Msg) -> Toast.Info Toast -> Html Msg
viewToast attrs toast =
Html.div
attrs -- do not forget this little friend!
[ Html.text toast.content.message ]
Toast.render viewToast model.tray toastConfig
A toast go through three phases:
Toast.Enter
when it's just been added to a trayToast.In
during the next render, immediately after enter phaseToast.Exit
when it's about to be removedYou can control how much time a toast is kept in
Toast.Exit
phase through withExitTransition.
Both Toast.Enter
and Toast.Exit
are considered transition phases.
Can be Toast.Focus
or Toast.Blur
, just like Toast.Phase
you'll have this information while rendering a toast through Toast.Info.
viewToast :
List (Html.Attribute Msg)
-> Toast.Info Toast
-> Html Msg
viewToast attributes toast =
Html.div
(if toast.interaction == Toast.Focus then
class "toast-active" :: attributes
else
attributes
)
[ Html.text toast.content.message ]
view : Model -> Html Msg
view model =
Toast.config ToastMsg
|> Toast.render viewToast model.tray
{ id : Private Id
, phase : Phase
, interaction : Interaction
, content : content
}
Toast.Info
represent data publicly exposed about a toast.
You already know Toast.Phase and Toast.Interaction,
of course you also know content
since this is your own data.
Meet id
, this little field contains a unique value for each toast
that you need to pass to Toast.remove and Toast.exit.
withAttributes : List (Html.Attribute msg) -> Config msg -> Config msg
Add custom attributes to every toasts.
Toast.config ToastMsg
|> Toast.withAttributes [ style "background" "#4a90e2" ]
|> Toast.render viewToast model.tray
withFocusAttributes : List (Html.Attribute msg) -> Config msg -> Config msg
Add custom attributes to toasts when they're focused.
Toast.config ToastMsg
|> Toast.withFocusAttributes [ style "box-shadow" "2px 3px 7px 2px #c8cdd0" ]
|> Toast.render viewToast model.tray
withEnterAttributes : List (Html.Attribute msg) -> Config msg -> Config msg
Add custom attributes to toasts only during their Toast.Enter phase.
import Html.Attributes exposing (style)
Toast.config ToastMsg
|> Toast.withEnterAttributes [ style "opacity" "0" ]
|> Toast.render viewToast model.tray
withExitAttributes : List (Html.Attribute msg) -> Config msg -> Config msg
Add custom attributes to toasts only during their Toast.Exit phase.
Toast.config ToastMsg
|> Toast.withExitAttributes [ style "transform" "translateX(20em)" ]
|> Toast.render viewToast model.tray
withTransitionAttributes : List (Html.Attribute msg) -> Config msg -> Config msg
Shortcut for withEnterAttributes plus withExitAttributes.
Toast.config ToastMsg
|> Toast.withEnterAttributes [ class "toast-fade-out" ]
|> Toast.withExitAttributes [ class "toast-fade-out" ]
|> Toast.render viewToast model.tray
Toast.config ToastMsg
|> Toast.withTransitionAttributes [ class "toast-fade-out" ]
|> Toast.render viewToast model.tray
withTrayAttributes : List (Html.Attribute msg) -> Config msg -> Config msg
Add custom attributes to rendered Toast.Tray.
Toast.config ToastMsg
|> Toast.withTrayAttributes [ class "nice-tray" ]
|> Toast.render viewToast model.tray
withTrayNode : String -> Config msg -> Config msg
Set nodeName of rendered Toast.Tray.
By default this is "div"
. I know, as a String
,
but hey that's what you get from Html.Keyed.node
remove : Private Id -> Msg
Inside your viewToast you may want to remove your toast programmatically. Remove means that the toast is deleted right away. If you want to go through the exit transition use exit.
closeButton : Toast.Info Toast -> Html Msg
closeButton toast =
Html.div
[ onClick <| ToastMsg (Toast.exit toast.id) ]
[ Html.text "✘" ]
viewToast : List (Html.Attribute Msg) -> Toast.Info Toast -> Html Msg
viewToast attrs toast =
Html.div
attrs
[ Html.text toast.content.message
, closeButton toast
]
exit : Private Id -> Msg
Same as remove, but the toast goes through its exit transition phase. If you have a fade-out animation it'll be showed.