Toast
The toast component is used to give feedback to users after an action has taken place.
Features
Install
Install the component from your command line.
Anatomy
Import all parts and piece them together.
<script setup lang="ts">import { normalizeProps, useMachine } from '@destyler/vue'import { computed, useId } from 'vue'import * as toast from '@destyler/toast'
const [groupState, groupSend] = useMachine( toast.group.machine({ id: useId(), }),)
const groupApi = computed(() => toast.group.connect(groupState.value as any, groupSend, normalizeProps))
const [state, send] = useActor(placement)
const api = computed(() => toast.connect(state.value, send, normalizeProps))</script>
<template> <Teleport to="body"> <div v-for="placement in groupApi.getPlacements()" :key="placement" v-bind="groupApi.getGroupProps({ placement })"> <div v-bind="api.getRootProps()"> <div v-bind="api.getGhostBeforeProps()" /> <p v-bind="api.getTitleProps()"> {{ api.title }} </p> <p v-bind="api.getDescriptionProps()"> {{ api.description }} </p> <button v-bind="api.getCloseTriggerProps()" /> <div v-bind="api.getGhostAfterProps()" /> </div> </div> </Teleport></template>import { normalizeProps, Portal, useMachine } from '@destyler/react'import * as toast from '@destyler/toast'import { useId } from 'react'
export default function Toast() { const [groupState, groupSend] = useMachine( toast.group.machine({ id: useId(), }), )
const groupApi = toast.group.connect(groupState as any, groupSend, normalizeProps)
const [state, send] = useActor(toast)
const api = computed(() => toast.connect(state.value, send, normalizeProps))
return ( <Portal> {groupApi.getPlacements().map(placement => ( <div data-layout="sinppets" key={placement} {...groupApi.getGroupProps({ placement })}> {groupApi.getToastsByPlacement(placement).map(toast => ( <div {...api.getRootProps()}> <div {...api.getGhostBeforeProps()}></div> <p {...api.getTitleProps()}> {api.title} </p> <p {...api.getDescriptionProps()}> {api.description} </p> <button {...api.getCloseTriggerProps()} /> <div {...api.getGhostAfterProps()} /> </div> ))} </div> ))} </Portal> )}<script lang="ts"> import * as toast from '@destyler/toast' import {normalizeProps, useMachine, portal} from '@destyler/svelte' import ToastItem from './Item.svelte'; import './style.css'
const id = $props.id()
const [groupState, groupSend] = useMachine( toast.group.machine({ id: id, placement: 'bottom-end', overlap: true, removeDelay: 200, }), )
const groupApi = $derived(toast.group.connect(groupState as any, groupSend, normalizeProps))
const [state, send] = useActor(placement)
const api = computed(() => toast.connect(state.value, send, normalizeProps))
</script>
<div use:portal> {#each groupApi.getPlacements() as placement} <div key={placement} data-layout="sinppets" {...groupApi.getGroupProps({ placement })}> {#each groupApi.getToastsByPlacement(placement) as toast (toast.id)} <div {...api.getRootProps()}> <div {...api.getGhostBeforeProps()}></div> <p {...api.getTitleProps()}>{api.title}</p> <p {...api.getDescriptionProps()}>{api.description}</p> <button {...api.getCloseTriggerProps()} /> <div {...api.getGhostAfterProps()}></div> </div> {/each} </div> {/each}</div>import { normalizeProps, useMachine } from '@destyler/solid'import * as toast from '@destyler/toast'import { createMemo, createUniqueId, For } from 'solid-js'import { Portal } from 'solid-js/web'
export default function Toast() { const [groupState, groupSend] = useMachine( toast.group.machine({ id: createUniqueId(), }), )
const groupApi = createMemo(() => toast.group.connect(groupState as any, groupSend, normalizeProps))
const [state, send] = useActor(placement)
const api = computed(() => toast.connect(state.value, send, normalizeProps))
return ( <Portal> <For each={groupApi().getPlacements()}> {placement => ( <div {...groupApi().getGroupProps({ placement })}> <For each={groupApi().getToastsByPlacement(placement)}> {toast => ( <div {...api().getRootProps()}> <div {...api().getGhostBeforeProps()}></div> <p {...api().getTitleProps()}> {api().title} </p> <p {...api().getDescriptionProps()}> {api().description} </p> <button {...api().getCloseTriggerProps()}> <div class="i-ph-x-bold"></div> </button> <div {...api().getGhostAfterProps()} /> </div> )} </For> </div> )} </For> </Portal> )}Usage
Toast Group
toast.group.machine
The state machine representation of a group of toasts. It is responsible for spawning, updating and removing toasts.
toast.group.connect
function gives you access to methods you can use to add, update, and remove a toast.
Toast Item
toast.machine
The state machine representation of a single toast.
toast.connect
The function that takes the toast machine and returns methods and JSX properties.
Creating a toast
There are five toast types that can be created with the toast machine. info, success, loading, custom and error.
To create a toast, use the toast.create(...) method.
toast.create({ title: "Hello World", description: "This is a toast", type: "info",})The options you can pass in are:
title
string
The title of the toast
description
string
The description of the toast
type
string | "success" | "error" | "loading" | "info"
The type of the toast. Can be either error, success , info, loading
duration
number
The duration of the toast. The default duration is computed based on the specified type.
onStatusChange
(details: StatusChangeDetails) => void
A callback that listens for the status changes across the toast lifecycle.
placement
"top-start" | "top" | "top-end" | "bottom-start" | "bottom" | "bottom-end"
The placement of the toast.
removeDelay
number
The delay before unmounting the toast from the DOM. Useful for transition
Changing the placement
Use the placement property when you call the toast.create(...) to change the position of the toast.
toast.info({ title: "Hello World", description: "This is a toast", placement: "top-start",})Overlapping toasts
When multiple toasts are created, they are rendered in a stack.
To make the toasts overlap, set the overlap property to true.
const [state, send] = useMachine( toast.group.machine({ overlap: true, }),)When using overlap, the toast’s placement must match the placement of the toast group (which is bottom by default).
::: tip Be sure to set up the required styles to make the toasts overlap correctly. :::
Changing the duration
Every toast has a default visible duration depending on the type set.
Here’s the following toast types and matching default durations:
info- 5000mssuccess- 5000mserror- 2000msloading- indefinite (until updated)
You can override the duration of the toast by passing the duration property to the toast.create(...) function.
toast.create({ title: "Hello World", description: "This is a toast", type: "info", duration: 6000,})You can also use the toast.upsert(...) function which creates or updates a toast.
Programmatic control
To update a toast programmatically, you need access to the unique identifier of the toast.
This identifier can be either:
-
the
idpassed intotoast.create(...)or, -
the returned random
idwhen thetoast.create(...)is called.
You can use any of the following methods to control a toast:
-
toast.upsert(...)— Creates or updates a toast. -
toast.update(...)— Updates a toast. -
toast.remove(...)— Removes a toast instantly without delay. -
toast.dismiss(...)— Removes a toast with delay. -
toast.pause(...)— Pauses a toast. -
toast.resume(...)— Resumes a toast.
// grab the id from the created toastconst id = toast.create({ title: "Hello World", description: "This is a toast", type: "info", duration: 6000, placement: "top-start",})
// update the toasttoast.update(id, { title: "Hello World", description: "This is a toast", type: "success",})
// remove the toasttoast.remove(id)
// dismiss the toasttoast.dismiss(id)Handling promises
The toast group API exposes a toast.promise() function to allow you update the toast when it resolves or rejects.
With the promise API, you can pass the toast options for each promise lifecycle.
toast.promise(promise, { loading: { title: "Loading", description: "Please wait...", }, success: (data) => ({ title: "Success", description: "An request has success", }), error: (err) => ({ title: "Error", description: "An request has error", }),})Pausing the toasts
There are three scenarios we provide to pause a toast from timing out:
-
When the document loses focus or the page is idle (e.g. switching to a new browser tab), controlled via the
pauseOnPageIdlecontext property. -
When the
toast.pause(id)provided by thetoast.group.connect(...)is called.
// Global pause optionsconst [state, send] = useMachine( toast.group.machine({ pauseOnPageIdle: true, }),)
// Programmatically pause a toast (by `id`)// `id` is the return value of `api.create(...)`toast.pause(id)Limiting the number of toasts
Toasts are great but displaying too many of them can sometimes hamper the user experience.
To limit the number of visible toasts, pass the max property to the group machine’s context.
const [state, send] = useMachine( toast.group.machine({ max: 10, }),)Focus Hotkey for toasts
When a toast is created, you can focus the toast region by pressing the alt + T. This is useful for screen readers and keyboard navigation.
Set the hotkey context property to change the underlying hotkey.
const [state, send] = useMachine( toast.group.machine({ hotkey: ["F6"], }),)Listening for toast lifecycle
When a toast is created, you can listen for the status changes across its lifecycle using the onStatusChange callback when you call toast.create(...).
The status values are:
-
visible- The toast is mounted and rendered -
dismissed- The toast is visually invisible but still mounted -
unmounted- The toast has been completely unmounted and no longer exists
toast.info({ title: "Hello World", description: "This is a toast", type: "info", onStatusChange: (details) => { // details => { status: "visible" | "dismissed" | "unmounted" } console.log("Toast status:", details) },})Changing the gap between toasts
When multiple toasts are rendered, a gap of 16px is applied between each toast. To change this value, set the gap context property.
const [state, send] = useMachine( toast.group.machine({ gap: 24, }),)Changing the offset
The toast region has a default 16px offset from the viewport. Use the offset context property to change the offset.
const [state, send] = useMachine( toast.group.machine({ offsets: "24px", }),)Styling guide
Earlier, we mentioned that each Tabs part has a
data-partattribute added to them to select and style them in the DOM.
Requirement
The toast machine injects a bunch of css variables that are required for it to work. You need to connect these variables in your styles.
[data-part="root"] { translate: var(--x) var(--y); scale: var(--scale); z-index: var(--z-index); height: var(--height); opacity: var(--opacity); will-change: translate, opacity, scale;}To make it transition smoothly, you should includes transition properties.
[data-part="root"] { transition: translate 400ms, scale 400ms, opacity 400ms; transition-timing-function: cubic-bezier(0.21, 1.02, 0.73, 1);}
[data-part="root"][data-state="closed"] { transition: translate 400ms, scale 400ms, opacity 200ms; transition-timing-function: cubic-bezier(0.06, 0.71, 0.55, 1);}Toast styling
When a toast is created and the api.getRootProps() from the toast.connect is used,
the toast will have a data-type that matches the specified type at its creation.
You can use this property to style the toast.
[data-part="root"][data-type="info"] { /* Styles for the specific toast type */}
[data-part="root"][data-type="error"] { /* Styles for the error toast type */}
[data-part="root"][data-type="success"] { /* Styles for the success toast type */}
[data-part="root"][data-type="loading"] { /* Styles for the loading toast type */}Methods and Properties
Machine Context
The toast machine exposes the following context properties:
booleannumbernumberstring | Record<"left" | "right" | "bottom" | "top", string>string[]booleanPlacementnumbernumber"ltr" | "rtl"string() => ShadowRoot | Node | DocumentMachine API
The switch api exposes the following methods:
() => number() => Placement[](placement: Placement) => Service<O>[](id: string) => boolean(options: Options<O>) => string(options: Options<O>) => string(id: string, options: Options<O>) => void(options: Options<O>) => string(options: Options<O>) => string(options: Options<O>) => string(id?: string) => void(id?: string) => void(id?: string) => void(placement: Placement) => void(id?: string) => void<T>(promise: Promise<T> | (() => Promise<T>), options: Options<O>>) => string(callback: (toasts: Options<O>[]) => void) => VoidFunctionData Attributes
Root
data-scopedata-partdata-statedata-typedata-placementdata-aligndata-sidedata-mounteddata-pauseddata-firstdata-siblingdata-stackdata-overlapGhost Before
data-scopedata-partdata-ghostGhost After
data-scopedata-partdata-ghost