aws-amplify / amplify-ui

Amplify UI is a collection of accessible, themeable, performant React (and more!) components that can connect directly to the cloud.
https://ui.docs.amplify.aws
Apache License 2.0
931 stars 297 forks source link

Toast component #2292

Open dreamorosi opened 2 years ago

dreamorosi commented 2 years ago

On which framework/platform would you like to see this feature implemented?

React

Which UI component is this feature-request for?

Other

Please describe your feature-request in detail.

I would like to propose a new Toast feature that would allow customers to display a notification (a.k.a. toast) on screen. This would not be a connected component and would be used for purely frontend-related notifications (i.e. An error occurred, message sent successfully).

With this component being provided by Amplify UI customers could avoid to have to implement their own component / system and thus reduce boilerplate code. This component would leverage the existing Hub utility to allow user to send toasts from any component in their app.

Having this component would allow customers to make their applications more engaging as displaying notifications that provide context to end users as result of an action is a common pattern in modern apps.

Please describe a solution you'd like.

Below an high-level and non-exhaustive example of how the feature could be implemented in Amplify UI:

import React, { useEffect, useState } from "react";
import { Hub } from "aws-amplify";
import type { HubCallback } from "@aws-amplify/core";

// Toast component (the actual notification/toast)

type ToastPlacement = "top-left" | "top-center" | "top-right" | "bottom-left" | "bottom-center" | "bottom-left"; // etc.

type ToastStatus = "success" | "error" | "warning" | "info";

type ToastProps = {
  children?: React.ReactNode;
  title?: string;
  description: string;
  duration?: number;
  status: ToastStatus;
  isClosable: boolean;
  placement: ToastPlacement;
}

const Toast: React.FC<ToastProps> = ({ title, description, duration, status, isClosable, placement }) => {
  return (
    // Markup for the single toast that takes in account the props passed + any default value (i.e. default duration 2500ms)
  );
};

// Toast context (name TBD) - the container of the toasts that also listens for new toasts being sent

type ToastContextProps = {
  children?: React.ReactNode;
};

const ToastContext: React.FC<ToastContextProps> = ({ children }) => {
  const [toastList, setToastList] = useState([]);

  const handleNotifications: HubCallback = ({ payload: { data } }) => {
    // Creates a timeout equal to the duration passed (or default duration). This timeout will be used to remove the toast when duration reaches 0

    // Adds the toast to the list
    setToastList([...toastList, {
      title: data?.title || null,
      description: data?.description || null,
      status: data?.status || "success",
      duration: data?.duration || 5000,
      isClosable: data?.isCloseable || true,
      placement: data?.placement || 
    })];
  };

  useEffect(() => {
    Hub.listen("notifications", handleNotifications);

    // Clean up subscriptions when the component unmounts
    return () => {
      Hub.remove("notifications", handleNotifications);
    };
  });

  return (
    <>
      {children}
     // The toasts would be displayed vertically in descending order of arrival
      <div>
         {toastList.map((toast, idx) => <Toast title={toast.title} ...  key={idx} />}
      <div>
    </>
  )
}

Below instead an example of how customers could use the feature to send a notification, there are two pieces to it:

import React from "react";
import { Hub } from "aws-amplify";
import { ToastContext } from "@aws-amplify/ui";

type MyComponentProps = {
  children?: React.ReactNode;
};

const MyComponent = () => {

  const handleSomeEvent = () => {
     // Do something, then show a notification

    Hub.dispatch("notifications", {
      event: "newNotification",
      data: {
        title: "something happened",
        status: "success",
        duration: 3000
      }
    });
  }

  return (
    <ToastContext>
      // Other components & stuff
    </ToastContext>
  )

};

We love contributors! Is this something you'd be interested in working on?

hbuchel commented 2 years ago

Thanks @dreamorosi for the detailed request! Yes, like you mentioned there is a lot of cross over between the pinpoint functionality so we might find that at least the UI portion is re-usable to achieve a lot of this. I will definitely bring this up with the team.

OperationalFallacy commented 1 year ago

https://github.com/aws-amplify/amplify-ui/issues/4727