This document outlines the API for the Toast component to be implemented in React Spectrum v3. A major update from previous iterations is the concept of a Toast queue. Additionally, an effort has been made to create an API that meshes with the React Hooks concept, allowing us to provide aria and stately hooks for outside use in custom Toast components.
interface ToastProps {
// Variant of the toast, default is 'neutral'.
variant?: 'neutral' | 'informative' | 'positive' | 'negative',
// Whether or not the toast is closable via a 'x' button. Note: this is a carry over from previous versions.
isDismissible?: boolean,
// Callback when toast is dismissed.
onDismiss? () => void,
// Sets how long the toast remains on screen before being automatically dismissed. If undefined, the toast remains indefinitely.
autoDismissDuration? number,
// The contents of the toast.
children: ReactNode,
// The label for the optional action button.
actionLabel?: ReactNode,
// Callback triggered when toast action button is pressed.
onAction? () => void,
// Unique identifier for the toast, autogenerated if not provided by the user.
key?: string,
// Callback triggered when the toast has fully transitioned out
onExit?: () => void
}
interface ToastAria {
// Props for the Toast div (e.g. role="alert").
toastProps: HTMLAttributes<HTMLDivElement>,
// Props for close button (e.g. aria-label = "close", press handlers that call props.onDismiss).
closeButtonProps: ButtonHTMLAttributes<HTMLButtonElement>,
// Props for action button (e.g. press handlers that call props.onAction)
actionButtonProps: ButtonHTMLAttributes<HTMLButtonElement>
}
function useToast(props: ToastProps, state: ToastState): ToastAria;
The core approach to handle queuing is as follows:
- Setup a global state that tracks what toast is visible, what toasts are in queue, and contains functions for adding/removing toasts.
- This state would be provided by a
useToastContainerState
hook and made available to child components via context.
- This state would be provided by a
- A spectrum component called
ToastContainer
would be made available for@react-spectrum
package consumers. It would sets up a context to provide this global state to all children- This component would need to be high up in the component tree (i.e. Provider level) so that the context would be available to components that may want to add a Toast to the queue.
- Sample usage:
state.addToast(<Toast variant='warning'...>)
- Sample usage:
- This component would need to be high up in the component tree (i.e. Provider level) so that the context would be available to components that may want to add a Toast to the queue.
interface ToastContainerProps {
/**
Some prop that allow the user to specify where the container should be located on the page. Open questions are as follows:
- Where to position the ToastContainer? Would the toast appear at the bottom of the container it was placed in or portalled out so it is at the bottom of the screen?
- Should there be an option to portal?
- How would this affect theming? Would we get the theme from the nearest container or should it receive the theming from the top most Provider?
*/
positioning: ?
}
interface ToastContainerState {
// Returns what toast is currently displayed.
visibleToast: ToastElement,
// Setter for what toast to display. Returned here in case user wants to ignore priority logic and set what toast should be visible.
setVisibleToastProps: (ToastElement) => void,
// Flat array that contains the current toast queue.
toastQueue: Array<ToastElement>,
// Setter to modify toast queue. Returned here if the user want to modify the queue directly (wipe the queue, etc).
setToastQueue: (Array<ToastElement>) => void,
// Adds a toast to the queue. Needs a unique identifier so toast look up can happen via removeToast.
addToast: (ToastElement, id/key) => void,
// Removes a toast regardless if it is visible or in queue. Uses the provided id/key to find what toast to remove.
removeToast: (id/key) => void,
// Function that overrides the default priority logic. Consumes toast props and returns a priority number.
determinePriorityFn: (props) => number
}
function useToastContainerState(): ToastContainerState;
interface ToastContainerAria {
// TODO: Accesibility review
role: 'region',
aria-label: 'Notifications'
}
function useToastContainer(): ToastContainerAria;
Toasts are used to display temporary notifications to an end user. Common causes for notification are when an operation finishes (e.g. file upload completes) and error messaging when said operation doesn't complete successfully.
- Have toast last an indefinite amount of time to track progress, then automatic dismissal when operation finishes.
- Flexible placement of toasts, specifically inside other elements.
- https://git.corp.adobe.com/io-sdk/willow/blob/492f02a527c1de111f9ea4246c262360f70684a1/src/components/AppsPage/AppsPageLayout.js
- Toast appears to notify user of current iOS support and CLI information.
- https://git.corp.adobe.com/adobe-platform/ethos-homepage/blob/4327571c748e17f34af603246e8ec88e82b73397/comps/featurecomps/AddNewFlag.tsx
- Notification toast shown for feature flag creation success and failure.
- https://git.corp.adobe.com/di-services-3d/dncr-gltf-viewer/blob/5344620e96e4e1d14587cbb749df58130bca1418/src/AbuseForm.js
- Notification toast shown for abuse report submission success or failure.
- https://git.corp.adobe.com/aem-eng-ops/aem-headcount/blob/14275b2f4c4ca9ccb7c4f5eeea45dfdad1668b95/ui/src/views/login/Login.js
- Error toast shown if authentication fails during login.
Looked at MaterialUI, Ant Design, and React Bootstrap for API inspiration. They had similar looking APIs for the most part, each with some form of toast variant, toast timeout, onClose handler, positioning handling, transition customization, and message customization. Ant Design differed from the other two libraries by having users render a toast via notification.[variant](config)
instead of the typical <Toast />
format. Ant Design also didn't have a controlled open/show
prop on its toast, instead featuring a notification.open/close
api.
None of the libraries handled queuing out of the box. The end user was made responsible for Toast visibility and ordering.
Where is the line drawn for spectrum behavior here?
- Do we consider the queue logic as something to exclude from the hooks entirely? Or is the existence of a queue and only allowing a single toast to be displayed at a time also something we would want to exclude from the hooks?
- How much end user customization would we want to allow for the priority logic? Would we want to allow a user to define their own priority logic?
How flexible do we want toast customization to be?
- Positioning? Should it always be placed at the bottom or should we give users the ability to customize positioning freely?
- What about Toast transitions, should we allow customization?
https://w3c.github.io/aria-practices/examples/alert/alert.html https://github.com/adobe/react-spectrum/blob/main/specs/accessibility/Toast.mdx https://spectrum.adobe.com/page/toast/