Skip to content

🔮[New Component] Toasts #674

@emplums

Description

@emplums

Acceptance Criteria

  • Allows users to add an "action" which is a button styled like a bolded link.
  • Handles keyboard shortcut for focusing Toast action
  • If keyboard shortcut is used, it should not auto-dismiss
  • Has prop for removing the auto-dismiss on the toast
  • Has prop for configuring auto-dismiss timing
  • Has proper animation
  • Only allow one Toast to appear on the page at a time
  • Exposes an onToastDismiss function
  • Fully documented
  • Fully tested
  • TS exports written
  • TS exports tested in codesandbox
  • TS export tested in another project

Component Structure

Internal Components (will not be publicly exposed by PRC)

useToastInternal

  • handles all the functionality of toasts: adding a toast, removing a toast, setting up auto dismiss, and generating the props needed by ToastContainer
  • I've also housed all the TS interfaces that are reused in a few different places in this file. In PRC these will all be moved to the index.d.ts file so didn't worry too much about making sure these were in the right place for now.

Toast

  • Used by ToastContainer
  • Not meant to be used on it's own publicly
  • Handles the ctrl + t keyboard shortcut (this is handled in the component instead of the hook so that I have easy access to the call to action ref)
  • Calls the action click handler

Public Components

ToastContainer

  • Applies the useToastsInternal hook passing along the autoDismiss and timeout configuration options passed to ToastContainer through it's props.
  • useToastsInternal returns the addToast function and the getToastProps function
  • Sets up a context provider, with the addToast function as it's value
  • Maps through all toasts and applies the props from getToastProps to each individual toast (these props include the cancelAutoDismiss and startRemovingToast functions that we need to use for the keyboard shortcut.

useToasts & addToast

  • useToasts is a very simple hook that just returns the addToast function from the context object. This makes it so that users don't have to call useContext themselves and we don't have to publicly export the context object from ToastContainer to the user. It also will throw a console error if you try to use the hook somewhere that's not wrapped by ToastContainer. A little silly but feels nice for DX.

addToast arguments:

Name Type Description
message string Message to display inside of toast
type string One of success, default, warning or error
onDismiss callback Callback to run when the toast leaves the page
action Object Used to create an action link inside the Toast. See the below table for configuration options.

action objects:

Name Type Description
text string Action button text
handleOnClick function Function to call after the user clicks the action.
export const MyComponent = () => {
    const { addToast } = useToasts();
    
... 

    const submitAndStopEditing = () => {

        addToast({
            message: "Changes saved",
            type: "success",
            onToastDismiss: () => window.alert("toast has left"),
            action: { text: "Undo", handleOnClick: () => window.alert("hi")}
        });
  }

What Toasts do not do

  • Toasts do not manage focus when the toast is dismissed. Users should manage this themselves and Primer should provide guidelines on the best way to do this from an accessibility standpoint.

  • We don't currently allow more than one Toast on the page at a time. I've written the code so that if we do decide to add this functionality in the future, it shouldn't be a breaking change. But I think we need to spend more time thinking about the UX impact of having multiple Toasts on a page as well as the accessibility impact. Right now we are getting around the accessibility issues of having disappearing content by providing a keyboard shortcut users can use to jump to the action item in the Toast - if there are more than one Toasts on the page which Toast would we jump to? Would we need to "freeze" the other Toasts while the user is on the first one, and how would we allow users to jump between Toasts to check them all out? What would happen if something like an undo action in one toast effected the state of the table/data in such a way that clicking undo on another Toast on the page had unexpected effects? For all these reasons I think we should stick with the constraint of only having one Toast on the page at a time.

  • Ability to use a link for the action item. Navigating to new pages from within a Toast is not currently supported and needs UX/design thought before being considered.

Open Questions

  • Should we add a note to the alert about the keyboard shortcut?
  • Are there any other handlers/hooks into when a toast is show/hidden that we anticipate needing?
  • Should we try to handle focus management after the toast leaves ourselves? My gut was that we should leave this up to the consuming application but we could add a returnFocusRef property to the addToast args that we use to return focus when the user either presses enter on the Toast or closes it with their mouse.

Needs design support

  • What should happen to the toast when longer text is added? Should it wrap to two lines? What should the min/max width of a Toast be?

  • Should we allow folks to put navigational links as action items inside of the Toast?

  • Since the Toast component is one that could be abused in ways that are annoying/inaccessible, it will be important to have really solid usage guidelines written up, ideally before we ship this in PRC. Some of the items I anticipate us needing to address:

    • Timeout settings: I've read that 500ms per word is standard. The default in Toasts is 5000ms, but we should guide users to adjust this if there's more text in their Toast
    • Maximum text length: we should guide folks on the best length for Toast text. We don't want people adding paragraphs to a Toast
    • When Toast usage is appropriate / not appropriate
    • Guidelines on having actions inside of Toasts - generally I think we should guide people to use actions inside of a Toast only when totally necessary.
    • When to use a modal vs when to use a Toast
    • How to manage focus when the Toast is closed: applications should return the focus back to the item that triggered the toast after a click or enter on the Toast

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions