Skip to content

Files

Latest commit

 

History

History
152 lines (111 loc) · 7.84 KB

Toast.md

File metadata and controls

152 lines (111 loc) · 7.84 KB

Introduction

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.

Toast API

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;

ToastContainer API

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.
  • 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'...>)
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;

Use Cases

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.

Feature requests from the react-spectrum slack channel

  • 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.

Examples of React Spectrum Toast usage in Adobe

Research

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.

Open Questions

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?

Additional Links

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/